]> git.immae.eu Git - perso/Immae/Projets/packagist/ludivine-ckeditor-component.git/blob - sources/core/tools.js
Update to 4.7.3
[perso/Immae/Projets/packagist/ludivine-ckeditor-component.git] / sources / core / tools.js
1 /**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6 /**
7 * @fileOverview Defines the {@link CKEDITOR.tools} object that contains
8 * utility functions.
9 */
10
11 ( function() {
12 var functions = [],
13 cssVendorPrefix =
14 CKEDITOR.env.gecko ? '-moz-' :
15 CKEDITOR.env.webkit ? '-webkit-' :
16 CKEDITOR.env.ie ? '-ms-' :
17 '',
18 ampRegex = /&/g,
19 gtRegex = />/g,
20 ltRegex = /</g,
21 quoteRegex = /"/g,
22 tokenCharset = 'abcdefghijklmnopqrstuvwxyz0123456789',
23 TOKEN_COOKIE_NAME = 'ckCsrfToken',
24 TOKEN_LENGTH = 40,
25
26 allEscRegex = /&(lt|gt|amp|quot|nbsp|shy|#\d{1,5});/g,
27 namedEntities = {
28 lt: '<',
29 gt: '>',
30 amp: '&',
31 quot: '"',
32 nbsp: '\u00a0',
33 shy: '\u00ad'
34 },
35 allEscDecode = function( match, code ) {
36 if ( code[ 0 ] == '#' ) {
37 return String.fromCharCode( parseInt( code.slice( 1 ), 10 ) );
38 } else {
39 return namedEntities[ code ];
40 }
41 };
42
43 CKEDITOR.on( 'reset', function() {
44 functions = [];
45 } );
46
47 /**
48 * Utility functions.
49 *
50 * @class
51 * @singleton
52 */
53 CKEDITOR.tools = {
54 /**
55 * Compares the elements of two arrays.
56 *
57 * var a = [ 1, 'a', 3 ];
58 * var b = [ 1, 3, 'a' ];
59 * var c = [ 1, 'a', 3 ];
60 * var d = [ 1, 'a', 3, 4 ];
61 *
62 * alert( CKEDITOR.tools.arrayCompare( a, b ) ); // false
63 * alert( CKEDITOR.tools.arrayCompare( a, c ) ); // true
64 * alert( CKEDITOR.tools.arrayCompare( a, d ) ); // false
65 *
66 * @param {Array} arrayA An array to be compared.
67 * @param {Array} arrayB The other array to be compared.
68 * @returns {Boolean} `true` if the arrays have the same length and
69 * their elements match.
70 */
71 arrayCompare: function( arrayA, arrayB ) {
72 if ( !arrayA && !arrayB )
73 return true;
74
75 if ( !arrayA || !arrayB || arrayA.length != arrayB.length )
76 return false;
77
78 for ( var i = 0; i < arrayA.length; i++ ) {
79 if ( arrayA[ i ] != arrayB[ i ] )
80 return false;
81 }
82
83 return true;
84 },
85
86 /**
87 * Finds the index of the first element in an array for which the `compareFunction` returns `true`.
88 *
89 * CKEDITOR.tools.getIndex( [ 1, 2, 4, 3, 5 ], function( el ) {
90 * return el >= 3;
91 * } ); // 2
92 *
93 * @since 4.5
94 * @param {Array} array Array to search in.
95 * @param {Function} compareFunction Compare function.
96 * @returns {Number} The index of the first matching element or `-1` if none matches.
97 */
98 getIndex: function( arr, compareFunction ) {
99 for ( var i = 0; i < arr.length; ++i ) {
100 if ( compareFunction( arr[ i ] ) )
101 return i;
102 }
103 return -1;
104 },
105
106 /**
107 * Creates a deep copy of an object.
108 *
109 * **Note**: Recursive references are not supported.
110 *
111 * var obj = {
112 * name: 'John',
113 * cars: {
114 * Mercedes: { color: 'blue' },
115 * Porsche: { color: 'red' }
116 * }
117 * };
118 * var clone = CKEDITOR.tools.clone( obj );
119 * clone.name = 'Paul';
120 * clone.cars.Porsche.color = 'silver';
121 *
122 * alert( obj.name ); // 'John'
123 * alert( clone.name ); // 'Paul'
124 * alert( obj.cars.Porsche.color ); // 'red'
125 * alert( clone.cars.Porsche.color ); // 'silver'
126 *
127 * @param {Object} object The object to be cloned.
128 * @returns {Object} The object clone.
129 */
130 clone: function( obj ) {
131 var clone;
132
133 // Array.
134 if ( obj && ( obj instanceof Array ) ) {
135 clone = [];
136
137 for ( var i = 0; i < obj.length; i++ )
138 clone[ i ] = CKEDITOR.tools.clone( obj[ i ] );
139
140 return clone;
141 }
142
143 // "Static" types.
144 if ( obj === null || ( typeof obj != 'object' ) || ( obj instanceof String ) || ( obj instanceof Number ) || ( obj instanceof Boolean ) || ( obj instanceof Date ) || ( obj instanceof RegExp ) )
145 return obj;
146
147 // DOM objects and window.
148 if ( obj.nodeType || obj.window === obj )
149 return obj;
150
151 // Objects.
152 clone = new obj.constructor();
153
154 for ( var propertyName in obj ) {
155 var property = obj[ propertyName ];
156 clone[ propertyName ] = CKEDITOR.tools.clone( property );
157 }
158
159 return clone;
160 },
161
162 /**
163 * Turns the first letter of a string to upper-case.
164 *
165 * @param {String} str
166 * @param {Boolean} [keepCase] Keep the case of 2nd to last letter.
167 * @returns {String}
168 */
169 capitalize: function( str, keepCase ) {
170 return str.charAt( 0 ).toUpperCase() + ( keepCase ? str.slice( 1 ) : str.slice( 1 ).toLowerCase() );
171 },
172
173 /**
174 * Copies the properties from one object to another. By default, properties
175 * already present in the target object **are not** overwritten.
176 *
177 * // Create the sample object.
178 * var myObject = {
179 * prop1: true
180 * };
181 *
182 * // Extend the above object with two properties.
183 * CKEDITOR.tools.extend( myObject, {
184 * prop2: true,
185 * prop3: true
186 * } );
187 *
188 * // Alert 'prop1', 'prop2' and 'prop3'.
189 * for ( var p in myObject )
190 * alert( p );
191 *
192 * @param {Object} target The object to be extended.
193 * @param {Object...} source The object(s) from properties will be
194 * copied. Any number of objects can be passed to this function.
195 * @param {Boolean} [overwrite] If `true` is specified, it indicates that
196 * properties already present in the target object could be
197 * overwritten by subsequent objects.
198 * @param {Object} [properties] Only properties within the specified names
199 * list will be received from the source object.
200 * @returns {Object} The extended object (target).
201 */
202 extend: function( target ) {
203 var argsLength = arguments.length,
204 overwrite, propertiesList;
205
206 if ( typeof ( overwrite = arguments[ argsLength - 1 ] ) == 'boolean' )
207 argsLength--;
208 else if ( typeof ( overwrite = arguments[ argsLength - 2 ] ) == 'boolean' ) {
209 propertiesList = arguments[ argsLength - 1 ];
210 argsLength -= 2;
211 }
212 for ( var i = 1; i < argsLength; i++ ) {
213 var source = arguments[ i ];
214 for ( var propertyName in source ) {
215 // Only copy existed fields if in overwrite mode.
216 if ( overwrite === true || target[ propertyName ] == null ) {
217 // Only copy specified fields if list is provided.
218 if ( !propertiesList || ( propertyName in propertiesList ) )
219 target[ propertyName ] = source[ propertyName ];
220
221 }
222 }
223 }
224
225 return target;
226 },
227
228 /**
229 * Creates an object which is an instance of a class whose prototype is a
230 * predefined object. All properties defined in the source object are
231 * automatically inherited by the resulting object, including future
232 * changes to it.
233 *
234 * @param {Object} source The source object to be used as the prototype for
235 * the final object.
236 * @returns {Object} The resulting copy.
237 */
238 prototypedCopy: function( source ) {
239 var copy = function() {};
240 copy.prototype = source;
241 return new copy();
242 },
243
244 /**
245 * Makes fast (shallow) copy of an object.
246 * This method is faster than {@link #clone} which does
247 * a deep copy of an object (including arrays).
248 *
249 * @since 4.1
250 * @param {Object} source The object to be copied.
251 * @returns {Object} Copy of `source`.
252 */
253 copy: function( source ) {
254 var obj = {},
255 name;
256
257 for ( name in source )
258 obj[ name ] = source[ name ];
259
260 return obj;
261 },
262
263 /**
264 * Checks if an object is an Array.
265 *
266 * alert( CKEDITOR.tools.isArray( [] ) ); // true
267 * alert( CKEDITOR.tools.isArray( 'Test' ) ); // false
268 *
269 * @param {Object} object The object to be checked.
270 * @returns {Boolean} `true` if the object is an Array, otherwise `false`.
271 */
272 isArray: function( object ) {
273 return Object.prototype.toString.call( object ) == '[object Array]';
274 },
275
276 /**
277 * Whether the object contains no properties of its own.
278 *
279 * @param object
280 * @returns {Boolean}
281 */
282 isEmpty: function( object ) {
283 for ( var i in object ) {
284 if ( object.hasOwnProperty( i ) )
285 return false;
286 }
287 return true;
288 },
289
290 /**
291 * Generates an object or a string containing vendor-specific and vendor-free CSS properties.
292 *
293 * CKEDITOR.tools.cssVendorPrefix( 'border-radius', '0', true );
294 * // On Firefox: '-moz-border-radius:0;border-radius:0'
295 * // On Chrome: '-webkit-border-radius:0;border-radius:0'
296 *
297 * @param {String} property The CSS property name.
298 * @param {String} value The CSS value.
299 * @param {Boolean} [asString=false] If `true`, then the returned value will be a CSS string.
300 * @returns {Object/String} The object containing CSS properties or its stringified version.
301 */
302 cssVendorPrefix: function( property, value, asString ) {
303 if ( asString )
304 return cssVendorPrefix + property + ':' + value + ';' + property + ':' + value;
305
306 var ret = {};
307 ret[ property ] = value;
308 ret[ cssVendorPrefix + property ] = value;
309
310 return ret;
311 },
312
313 /**
314 * Transforms a CSS property name to its relative DOM style name.
315 *
316 * alert( CKEDITOR.tools.cssStyleToDomStyle( 'background-color' ) ); // 'backgroundColor'
317 * alert( CKEDITOR.tools.cssStyleToDomStyle( 'float' ) ); // 'cssFloat'
318 *
319 * @method
320 * @param {String} cssName The CSS property name.
321 * @returns {String} The transformed name.
322 */
323 cssStyleToDomStyle: ( function() {
324 var test = document.createElement( 'div' ).style;
325
326 var cssFloat = ( typeof test.cssFloat != 'undefined' ) ? 'cssFloat' : ( typeof test.styleFloat != 'undefined' ) ? 'styleFloat' : 'float';
327
328 return function( cssName ) {
329 if ( cssName == 'float' )
330 return cssFloat;
331 else {
332 return cssName.replace( /-./g, function( match ) {
333 return match.substr( 1 ).toUpperCase();
334 } );
335 }
336 };
337 } )(),
338
339 /**
340 * Builds a HTML snippet from a set of `<style>/<link>`.
341 *
342 * @param {String/Array} css Each of which are URLs (absolute) of a CSS file or
343 * a trunk of style text.
344 * @returns {String}
345 */
346 buildStyleHtml: function( css ) {
347 css = [].concat( css );
348 var item,
349 retval = [];
350 for ( var i = 0; i < css.length; i++ ) {
351 if ( ( item = css[ i ] ) ) {
352 // Is CSS style text ?
353 if ( /@import|[{}]/.test( item ) )
354 retval.push( '<style>' + item + '</style>' );
355 else
356 retval.push( '<link type="text/css" rel=stylesheet href="' + item + '">' );
357 }
358 }
359 return retval.join( '' );
360 },
361
362 /**
363 * Replaces special HTML characters in a string with their relative HTML
364 * entity values.
365 *
366 * alert( CKEDITOR.tools.htmlEncode( 'A > B & C < D' ) ); // 'A &gt; B &amp; C &lt; D'
367 *
368 * @param {String} text The string to be encoded.
369 * @returns {String} The encoded string.
370 */
371 htmlEncode: function( text ) {
372 // Backwards compatibility - accept also non-string values (casting is done below).
373 // Since 4.4.8 we return empty string for null and undefined because these values make no sense.
374 if ( text === undefined || text === null ) {
375 return '';
376 }
377
378 return String( text ).replace( ampRegex, '&amp;' ).replace( gtRegex, '&gt;' ).replace( ltRegex, '&lt;' );
379 },
380
381 /**
382 * Decodes HTML entities that browsers tend to encode when used in text nodes.
383 *
384 * alert( CKEDITOR.tools.htmlDecode( '&lt;a &amp; b &gt;' ) ); // '<a & b >'
385 *
386 * Read more about chosen entities in the [research](http://dev.ckeditor.com/ticket/13105#comment:8).
387 *
388 * @param {String} The string to be decoded.
389 * @returns {String} The decoded string.
390 */
391 htmlDecode: function( text ) {
392 // See:
393 // * http://dev.ckeditor.com/ticket/13105#comment:8 and comment:9,
394 // * http://jsperf.com/wth-is-going-on-with-jsperf JSPerf has some serious problems, but you can observe
395 // that combined regexp tends to be quicker (except on V8). It will also not be prone to fail on '&amp;lt;'
396 // (see http://dev.ckeditor.com/ticket/13105#DXWTF:CKEDITOR.tools.htmlEnDecodeAttr).
397 return text.replace( allEscRegex, allEscDecode );
398 },
399
400 /**
401 * Replaces special HTML characters in HTMLElement attribute with their relative HTML entity values.
402 *
403 * alert( CKEDITOR.tools.htmlEncodeAttr( '<a " b >' ) ); // '&lt;a &quot; b &gt;'
404 *
405 * @param {String} The attribute value to be encoded.
406 * @returns {String} The encoded value.
407 */
408 htmlEncodeAttr: function( text ) {
409 return CKEDITOR.tools.htmlEncode( text ).replace( quoteRegex, '&quot;' );
410 },
411
412 /**
413 * Decodes HTML entities that browsers tend to encode when used in attributes.
414 *
415 * alert( CKEDITOR.tools.htmlDecodeAttr( '&lt;a &quot; b&gt;' ) ); // '<a " b>'
416 *
417 * Since CKEditor 4.5 this method simply executes {@link #htmlDecode} which covers
418 * all necessary entities.
419 *
420 * @param {String} text The text to be decoded.
421 * @returns {String} The decoded text.
422 */
423 htmlDecodeAttr: function( text ) {
424 return CKEDITOR.tools.htmlDecode( text );
425 },
426
427 /**
428 * Transforms text to valid HTML: creates paragraphs, replaces tabs with non-breaking spaces etc.
429 *
430 * @since 4.5
431 * @param {String} text Text to transform.
432 * @param {Number} enterMode Editor {@link CKEDITOR.config#enterMode Enter mode}.
433 * @returns {String} HTML generated from the text.
434 */
435 transformPlainTextToHtml: function( text, enterMode ) {
436 var isEnterBrMode = enterMode == CKEDITOR.ENTER_BR,
437 // CRLF -> LF
438 html = this.htmlEncode( text.replace( /\r\n/g, '\n' ) );
439
440 // Tab -> &nbsp x 4;
441 html = html.replace( /\t/g, '&nbsp;&nbsp; &nbsp;' );
442
443 var paragraphTag = enterMode == CKEDITOR.ENTER_P ? 'p' : 'div';
444
445 // Two line-breaks create one paragraphing block.
446 if ( !isEnterBrMode ) {
447 var duoLF = /\n{2}/g;
448 if ( duoLF.test( html ) ) {
449 var openTag = '<' + paragraphTag + '>', endTag = '</' + paragraphTag + '>';
450 html = openTag + html.replace( duoLF, function() {
451 return endTag + openTag;
452 } ) + endTag;
453 }
454 }
455
456 // One <br> per line-break.
457 html = html.replace( /\n/g, '<br>' );
458
459 // Compensate padding <br> at the end of block, avoid loosing them during insertion.
460 if ( !isEnterBrMode ) {
461 html = html.replace( new RegExp( '<br>(?=</' + paragraphTag + '>)' ), function( match ) {
462 return CKEDITOR.tools.repeat( match, 2 );
463 } );
464 }
465
466 // Preserve spaces at the ends, so they won't be lost after insertion (merged with adjacent ones).
467 html = html.replace( /^ | $/g, '&nbsp;' );
468
469 // Finally, preserve whitespaces that are to be lost.
470 html = html.replace( /(>|\s) /g, function( match, before ) {
471 return before + '&nbsp;';
472 } ).replace( / (?=<)/g, '&nbsp;' );
473
474 return html;
475 },
476
477 /**
478 * Gets a unique number for this CKEDITOR execution session. It returns
479 * consecutive numbers starting from 1.
480 *
481 * alert( CKEDITOR.tools.getNextNumber() ); // (e.g.) 1
482 * alert( CKEDITOR.tools.getNextNumber() ); // 2
483 *
484 * @method
485 * @returns {Number} A unique number.
486 */
487 getNextNumber: ( function() {
488 var last = 0;
489 return function() {
490 return ++last;
491 };
492 } )(),
493
494 /**
495 * Gets a unique ID for CKEditor interface elements. It returns a
496 * string with the "cke_" prefix and a consecutive number.
497 *
498 * alert( CKEDITOR.tools.getNextId() ); // (e.g.) 'cke_1'
499 * alert( CKEDITOR.tools.getNextId() ); // 'cke_2'
500 *
501 * @returns {String} A unique ID.
502 */
503 getNextId: function() {
504 return 'cke_' + this.getNextNumber();
505 },
506
507 /**
508 * Gets a universally unique ID. It returns a random string
509 * compliant with ISO/IEC 11578:1996, without dashes, with the "e" prefix to
510 * make sure that the ID does not start with a number.
511 *
512 * @returns {String} A global unique ID.
513 */
514 getUniqueId: function() {
515 var uuid = 'e'; // Make sure that id does not start with number.
516 for ( var i = 0; i < 8; i++ ) {
517 uuid += Math.floor( ( 1 + Math.random() ) * 0x10000 ).toString( 16 ).substring( 1 );
518 }
519 return uuid;
520 },
521
522 /**
523 * Creates a function override.
524 *
525 * var obj = {
526 * myFunction: function( name ) {
527 * alert( 'Name: ' + name );
528 * }
529 * };
530 *
531 * obj.myFunction = CKEDITOR.tools.override( obj.myFunction, function( myFunctionOriginal ) {
532 * return function( name ) {
533 * alert( 'Overriden name: ' + name );
534 * myFunctionOriginal.call( this, name );
535 * };
536 * } );
537 *
538 * @param {Function} originalFunction The function to be overridden.
539 * @param {Function} functionBuilder A function that returns the new
540 * function. The original function reference will be passed to this function.
541 * @returns {Function} The new function.
542 */
543 override: function( originalFunction, functionBuilder ) {
544 var newFn = functionBuilder( originalFunction );
545 newFn.prototype = originalFunction.prototype;
546 return newFn;
547 },
548
549 /**
550 * Executes a function after a specified delay.
551 *
552 * CKEDITOR.tools.setTimeout( function() {
553 * alert( 'Executed after 2 seconds' );
554 * }, 2000 );
555 *
556 * @param {Function} func The function to be executed.
557 * @param {Number} [milliseconds=0] The amount of time (in milliseconds) to wait
558 * to fire the function execution.
559 * @param {Object} [scope=window] The object to store the function execution scope
560 * (the `this` object).
561 * @param {Object/Array} [args] A single object, or an array of objects, to
562 * pass as argument to the function.
563 * @param {Object} [ownerWindow=window] The window that will be used to set the
564 * timeout.
565 * @returns {Object} A value that can be used to cancel the function execution.
566 */
567 setTimeout: function( func, milliseconds, scope, args, ownerWindow ) {
568 if ( !ownerWindow )
569 ownerWindow = window;
570
571 if ( !scope )
572 scope = ownerWindow;
573
574 return ownerWindow.setTimeout( function() {
575 if ( args )
576 func.apply( scope, [].concat( args ) );
577 else
578 func.apply( scope );
579 }, milliseconds || 0 );
580 },
581
582 /**
583 * Removes spaces from the start and the end of a string. The following
584 * characters are removed: space, tab, line break, line feed.
585 *
586 * alert( CKEDITOR.tools.trim( ' example ' ); // 'example'
587 *
588 * @method
589 * @param {String} str The text from which the spaces will be removed.
590 * @returns {String} The modified string without the boundary spaces.
591 */
592 trim: ( function() {
593 // We are not using \s because we don't want "non-breaking spaces" to be caught.
594 var trimRegex = /(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g;
595 return function( str ) {
596 return str.replace( trimRegex, '' );
597 };
598 } )(),
599
600 /**
601 * Removes spaces from the start (left) of a string. The following
602 * characters are removed: space, tab, line break, line feed.
603 *
604 * alert( CKEDITOR.tools.ltrim( ' example ' ); // 'example '
605 *
606 * @method
607 * @param {String} str The text from which the spaces will be removed.
608 * @returns {String} The modified string excluding the removed spaces.
609 */
610 ltrim: ( function() {
611 // We are not using \s because we don't want "non-breaking spaces" to be caught.
612 var trimRegex = /^[ \t\n\r]+/g;
613 return function( str ) {
614 return str.replace( trimRegex, '' );
615 };
616 } )(),
617
618 /**
619 * Removes spaces from the end (right) of a string. The following
620 * characters are removed: space, tab, line break, line feed.
621 *
622 * alert( CKEDITOR.tools.ltrim( ' example ' ); // ' example'
623 *
624 * @method
625 * @param {String} str The text from which spaces will be removed.
626 * @returns {String} The modified string excluding the removed spaces.
627 */
628 rtrim: ( function() {
629 // We are not using \s because we don't want "non-breaking spaces" to be caught.
630 var trimRegex = /[ \t\n\r]+$/g;
631 return function( str ) {
632 return str.replace( trimRegex, '' );
633 };
634 } )(),
635
636 /**
637 * Returns the index of an element in an array.
638 *
639 * var letters = [ 'a', 'b', 0, 'c', false ];
640 * alert( CKEDITOR.tools.indexOf( letters, '0' ) ); // -1 because 0 !== '0'
641 * alert( CKEDITOR.tools.indexOf( letters, false ) ); // 4 because 0 !== false
642 *
643 * @param {Array} array The array to be searched.
644 * @param {Object/Function} value The element to be found. This can be an
645 * evaluation function which receives a single parameter call for
646 * each entry in the array, returning `true` if the entry matches.
647 * @returns {Number} The (zero-based) index of the first entry that matches
648 * the entry, or `-1` if not found.
649 */
650 indexOf: function( array, value ) {
651 if ( typeof value == 'function' ) {
652 for ( var i = 0, len = array.length; i < len; i++ ) {
653 if ( value( array[ i ] ) )
654 return i;
655 }
656 } else if ( array.indexOf )
657 return array.indexOf( value );
658 else {
659 for ( i = 0, len = array.length; i < len; i++ ) {
660 if ( array[ i ] === value )
661 return i;
662 }
663 }
664 return -1;
665 },
666
667 /**
668 * Returns the index of an element in an array.
669 *
670 * var obj = { prop: true };
671 * var letters = [ 'a', 'b', 0, obj, false ];
672 *
673 * alert( CKEDITOR.tools.indexOf( letters, '0' ) ); // null
674 * alert( CKEDITOR.tools.indexOf( letters, function( value ) {
675 * // Return true when passed value has property 'prop'.
676 * return value && 'prop' in value;
677 * } ) ); // obj
678 *
679 * @param {Array} array The array to be searched.
680 * @param {Object/Function} value The element to be found. Can be an
681 * evaluation function which receives a single parameter call for
682 * each entry in the array, returning `true` if the entry matches.
683 * @returns Object The value that was found in an array.
684 */
685 search: function( array, value ) {
686 var index = CKEDITOR.tools.indexOf( array, value );
687 return index >= 0 ? array[ index ] : null;
688 },
689
690 /**
691 * Creates a function that will always execute in the context of a
692 * specified object.
693 *
694 * var obj = { text: 'My Object' };
695 *
696 * function alertText() {
697 * alert( this.text );
698 * }
699 *
700 * var newFunc = CKEDITOR.tools.bind( alertText, obj );
701 * newFunc(); // Alerts 'My Object'.
702 *
703 * @param {Function} func The function to be executed.
704 * @param {Object} obj The object to which the execution context will be bound.
705 * @returns {Function} The function that can be used to execute the
706 * `func` function in the context of `obj`.
707 */
708 bind: function( func, obj ) {
709 return function() {
710 return func.apply( obj, arguments );
711 };
712 },
713
714 /**
715 * Class creation based on prototype inheritance which supports of the
716 * following features:
717 *
718 * * Static fields
719 * * Private fields
720 * * Public (prototype) fields
721 * * Chainable base class constructor
722 *
723 * @param {Object} definition The class definition object.
724 * @returns {Function} A class-like JavaScript function.
725 */
726 createClass: function( definition ) {
727 var $ = definition.$,
728 baseClass = definition.base,
729 privates = definition.privates || definition._,
730 proto = definition.proto,
731 statics = definition.statics;
732
733 // Create the constructor, if not present in the definition.
734 !$ && ( $ = function() {
735 baseClass && this.base.apply( this, arguments );
736 } );
737
738 if ( privates ) {
739 var originalConstructor = $;
740 $ = function() {
741 // Create (and get) the private namespace.
742 var _ = this._ || ( this._ = {} );
743
744 // Make some magic so "this" will refer to the main
745 // instance when coding private functions.
746 for ( var privateName in privates ) {
747 var priv = privates[ privateName ];
748
749 _[ privateName ] = ( typeof priv == 'function' ) ? CKEDITOR.tools.bind( priv, this ) : priv;
750 }
751
752 originalConstructor.apply( this, arguments );
753 };
754 }
755
756 if ( baseClass ) {
757 $.prototype = this.prototypedCopy( baseClass.prototype );
758 $.prototype.constructor = $;
759 // Super references.
760 $.base = baseClass;
761 $.baseProto = baseClass.prototype;
762 // Super constructor.
763 $.prototype.base = function() {
764 this.base = baseClass.prototype.base;
765 baseClass.apply( this, arguments );
766 this.base = arguments.callee;
767 };
768 }
769
770 if ( proto )
771 this.extend( $.prototype, proto, true );
772
773 if ( statics )
774 this.extend( $, statics, true );
775
776 return $;
777 },
778
779 /**
780 * Creates a function reference that can be called later using
781 * {@link #callFunction}. This approach is especially useful to
782 * make DOM attribute function calls to JavaScript-defined functions.
783 *
784 * var ref = CKEDITOR.tools.addFunction( function() {
785 * alert( 'Hello!');
786 * } );
787 * CKEDITOR.tools.callFunction( ref ); // 'Hello!'
788 *
789 * @param {Function} fn The function to be executed on call.
790 * @param {Object} [scope] The object to have the context on `fn` execution.
791 * @returns {Number} A unique reference to be used in conjuction with
792 * {@link #callFunction}.
793 */
794 addFunction: function( fn, scope ) {
795 return functions.push( function() {
796 return fn.apply( scope || this, arguments );
797 } ) - 1;
798 },
799
800 /**
801 * Removes the function reference created with {@link #addFunction}.
802 *
803 * @param {Number} ref The function reference created with
804 * {@link #addFunction}.
805 */
806 removeFunction: function( ref ) {
807 functions[ ref ] = null;
808 },
809
810 /**
811 * Executes a function based on the reference created with {@link #addFunction}.
812 *
813 * var ref = CKEDITOR.tools.addFunction( function() {
814 * alert( 'Hello!');
815 * } );
816 * CKEDITOR.tools.callFunction( ref ); // 'Hello!'
817 *
818 * @param {Number} ref The function reference created with {@link #addFunction}.
819 * @param {Mixed} params Any number of parameters to be passed to the executed function.
820 * @returns {Mixed} The return value of the function.
821 */
822 callFunction: function( ref ) {
823 var fn = functions[ ref ];
824 return fn && fn.apply( window, Array.prototype.slice.call( arguments, 1 ) );
825 },
826
827 /**
828 * Appends the `px` length unit to the size value if it is missing.
829 *
830 * var cssLength = CKEDITOR.tools.cssLength;
831 * cssLength( 42 ); // '42px'
832 * cssLength( '42' ); // '42px'
833 * cssLength( '42px' ); // '42px'
834 * cssLength( '42%' ); // '42%'
835 * cssLength( 'bold' ); // 'bold'
836 * cssLength( false ); // ''
837 * cssLength( NaN ); // ''
838 *
839 * @method
840 * @param {Number/String/Boolean} length
841 */
842 cssLength: ( function() {
843 var pixelRegex = /^-?\d+\.?\d*px$/,
844 lengthTrimmed;
845
846 return function( length ) {
847 lengthTrimmed = CKEDITOR.tools.trim( length + '' ) + 'px';
848
849 if ( pixelRegex.test( lengthTrimmed ) )
850 return lengthTrimmed;
851 else
852 return length || '';
853 };
854 } )(),
855
856 /**
857 * Converts the specified CSS length value to the calculated pixel length inside this page.
858 *
859 * **Note:** Percentage-based value is left intact.
860 *
861 * @method
862 * @param {String} cssLength CSS length value.
863 */
864 convertToPx: ( function() {
865 var calculator;
866
867 return function( cssLength ) {
868 if ( !calculator ) {
869 calculator = CKEDITOR.dom.element.createFromHtml( '<div style="position:absolute;left:-9999px;' +
870 'top:-9999px;margin:0px;padding:0px;border:0px;"' +
871 '></div>', CKEDITOR.document );
872 CKEDITOR.document.getBody().append( calculator );
873 }
874
875 if ( !( /%$/ ).test( cssLength ) ) {
876 calculator.setStyle( 'width', cssLength );
877 return calculator.$.clientWidth;
878 }
879
880 return cssLength;
881 };
882 } )(),
883
884 /**
885 * String specified by `str` repeats `times` times.
886 *
887 * @param {String} str
888 * @param {Number} times
889 * @returns {String}
890 */
891 repeat: function( str, times ) {
892 return new Array( times + 1 ).join( str );
893 },
894
895 /**
896 * Returns the first successfully executed return value of a function that
897 * does not throw any exception.
898 *
899 * @param {Function...} fn
900 * @returns {Mixed}
901 */
902 tryThese: function() {
903 var returnValue;
904 for ( var i = 0, length = arguments.length; i < length; i++ ) {
905 var lambda = arguments[ i ];
906 try {
907 returnValue = lambda();
908 break;
909 } catch ( e ) {}
910 }
911 return returnValue;
912 },
913
914 /**
915 * Generates a combined key from a series of params.
916 *
917 * var key = CKEDITOR.tools.genKey( 'key1', 'key2', 'key3' );
918 * alert( key ); // 'key1-key2-key3'.
919 *
920 * @param {String} subKey One or more strings used as subkeys.
921 * @returns {String}
922 */
923 genKey: function() {
924 return Array.prototype.slice.call( arguments ).join( '-' );
925 },
926
927 /**
928 * Creates a "deferred" function which will not run immediately,
929 * but rather runs as soon as the interpreter’s call stack is empty.
930 * Behaves much like `window.setTimeout` with a delay.
931 *
932 * **Note:** The return value of the original function will be lost.
933 *
934 * @param {Function} fn The callee function.
935 * @returns {Function} The new deferred function.
936 */
937 defer: function( fn ) {
938 return function() {
939 var args = arguments,
940 self = this;
941 window.setTimeout( function() {
942 fn.apply( self, args );
943 }, 0 );
944 };
945 },
946
947 /**
948 * Normalizes CSS data in order to avoid differences in the style attribute.
949 *
950 * @param {String} styleText The style data to be normalized.
951 * @param {Boolean} [nativeNormalize=false] Parse the data using the browser.
952 * @returns {String} The normalized value.
953 */
954 normalizeCssText: function( styleText, nativeNormalize ) {
955 var props = [],
956 name,
957 parsedProps = CKEDITOR.tools.parseCssText( styleText, true, nativeNormalize );
958
959 for ( name in parsedProps )
960 props.push( name + ':' + parsedProps[ name ] );
961
962 props.sort();
963
964 return props.length ? ( props.join( ';' ) + ';' ) : '';
965 },
966
967 /**
968 * Finds and converts `rgb(x,x,x)` color definition to hexadecimal notation.
969 *
970 * @param {String} styleText The style data (or just a string containing RGB colors) to be converted.
971 * @returns {String} The style data with RGB colors converted to hexadecimal equivalents.
972 */
973 convertRgbToHex: function( styleText ) {
974 return styleText.replace( /(?:rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\))/gi, function( match, red, green, blue ) {
975 var color = [ red, green, blue ];
976 // Add padding zeros if the hex value is less than 0x10.
977 for ( var i = 0; i < 3; i++ )
978 color[ i ] = ( '0' + parseInt( color[ i ], 10 ).toString( 16 ) ).slice( -2 );
979 return '#' + color.join( '' );
980 } );
981 },
982
983 /**
984 * Normalizes hexadecimal notation so that the color string is always 6 characters long and lowercase.
985 *
986 * @param {String} styleText The style data (or just a string containing hex colors) to be converted.
987 * @returns {String} The style data with hex colors normalized.
988 */
989 normalizeHex: function( styleText ) {
990 return styleText.replace( /#(([0-9a-f]{3}){1,2})($|;|\s+)/gi, function( match, hexColor, hexColorPart, separator ) {
991 var normalizedHexColor = hexColor.toLowerCase();
992 if ( normalizedHexColor.length == 3 ) {
993 var parts = normalizedHexColor.split( '' );
994 normalizedHexColor = [ parts[ 0 ], parts[ 0 ], parts[ 1 ], parts[ 1 ], parts[ 2 ], parts[ 2 ] ].join( '' );
995 }
996 return '#' + normalizedHexColor + separator;
997 } );
998 },
999
1000 /**
1001 * Turns inline style text properties into one hash.
1002 *
1003 * @param {String} styleText The style data to be parsed.
1004 * @param {Boolean} [normalize=false] Normalize properties and values
1005 * (e.g. trim spaces, convert to lower case).
1006 * @param {Boolean} [nativeNormalize=false] Parse the data using the browser.
1007 * @returns {Object} The object containing parsed properties.
1008 */
1009 parseCssText: function( styleText, normalize, nativeNormalize ) {
1010 var retval = {};
1011
1012 if ( nativeNormalize ) {
1013 // Injects the style in a temporary span object, so the browser parses it,
1014 // retrieving its final format.
1015 var temp = new CKEDITOR.dom.element( 'span' );
1016 styleText = temp.setAttribute( 'style', styleText ).getAttribute( 'style' ) || '';
1017 }
1018
1019 // Normalize colors.
1020 if ( styleText ) {
1021 styleText = CKEDITOR.tools.normalizeHex( CKEDITOR.tools.convertRgbToHex( styleText ) );
1022 }
1023
1024 // IE will leave a single semicolon when failed to parse the style text. (http://dev.ckeditor.com/ticket/3891)
1025 if ( !styleText || styleText == ';' )
1026 return retval;
1027
1028 styleText.replace( /&quot;/g, '"' ).replace( /\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) {
1029 if ( normalize ) {
1030 name = name.toLowerCase();
1031 // Drop extra whitespacing from font-family.
1032 if ( name == 'font-family' )
1033 value = value.replace( /\s*,\s*/g, ',' );
1034 value = CKEDITOR.tools.trim( value );
1035 }
1036
1037 retval[ name ] = value;
1038 } );
1039 return retval;
1040 },
1041
1042 /**
1043 * Serializes the `style name => value` hash to a style text.
1044 *
1045 * var styleObj = CKEDITOR.tools.parseCssText( 'color: red; border: none' );
1046 * console.log( styleObj.color ); // -> 'red'
1047 * CKEDITOR.tools.writeCssText( styleObj ); // -> 'color:red; border:none'
1048 * CKEDITOR.tools.writeCssText( styleObj, true ); // -> 'border:none; color:red'
1049 *
1050 * @since 4.1
1051 * @param {Object} styles The object contaning style properties.
1052 * @param {Boolean} [sort] Whether to sort CSS properties.
1053 * @returns {String} The serialized style text.
1054 */
1055 writeCssText: function( styles, sort ) {
1056 var name,
1057 stylesArr = [];
1058
1059 for ( name in styles )
1060 stylesArr.push( name + ':' + styles[ name ] );
1061
1062 if ( sort )
1063 stylesArr.sort();
1064
1065 return stylesArr.join( '; ' );
1066 },
1067
1068 /**
1069 * Compares two objects.
1070 *
1071 * **Note:** This method performs shallow, non-strict comparison.
1072 *
1073 * @since 4.1
1074 * @param {Object} left
1075 * @param {Object} right
1076 * @param {Boolean} [onlyLeft] Check only the properties that are present in the `left` object.
1077 * @returns {Boolean} Whether objects are identical.
1078 */
1079 objectCompare: function( left, right, onlyLeft ) {
1080 var name;
1081
1082 if ( !left && !right )
1083 return true;
1084 if ( !left || !right )
1085 return false;
1086
1087 for ( name in left ) {
1088 if ( left[ name ] != right[ name ] )
1089 return false;
1090
1091 }
1092
1093 if ( !onlyLeft ) {
1094 for ( name in right ) {
1095 if ( left[ name ] != right[ name ] )
1096 return false;
1097 }
1098 }
1099
1100 return true;
1101 },
1102
1103 /**
1104 * Returns an array of passed object's keys.
1105 *
1106 * console.log( CKEDITOR.tools.objectKeys( { foo: 1, bar: false } );
1107 * // -> [ 'foo', 'bar' ]
1108 *
1109 * @since 4.1
1110 * @param {Object} obj
1111 * @returns {Array} Object's keys.
1112 */
1113 objectKeys: function( obj ) {
1114 var keys = [];
1115 for ( var i in obj )
1116 keys.push( i );
1117
1118 return keys;
1119 },
1120
1121 /**
1122 * Converts an array to an object by rewriting array items
1123 * to object properties.
1124 *
1125 * var arr = [ 'foo', 'bar', 'foo' ];
1126 * console.log( CKEDITOR.tools.convertArrayToObject( arr ) );
1127 * // -> { foo: true, bar: true }
1128 * console.log( CKEDITOR.tools.convertArrayToObject( arr, 1 ) );
1129 * // -> { foo: 1, bar: 1 }
1130 *
1131 * @since 4.1
1132 * @param {Array} arr The array to be converted to an object.
1133 * @param [fillWith=true] Set each property of an object to `fillWith` value.
1134 */
1135 convertArrayToObject: function( arr, fillWith ) {
1136 var obj = {};
1137
1138 if ( arguments.length == 1 )
1139 fillWith = true;
1140
1141 for ( var i = 0, l = arr.length; i < l; ++i )
1142 obj[ arr[ i ] ] = fillWith;
1143
1144 return obj;
1145 },
1146
1147 /**
1148 * Tries to fix the `document.domain` of the current document to match the
1149 * parent window domain, avoiding "Same Origin" policy issues.
1150 * This is an Internet Explorer only requirement.
1151 *
1152 * @since 4.1.2
1153 * @returns {Boolean} `true` if the current domain is already good or if
1154 * it has been fixed successfully.
1155 */
1156 fixDomain: function() {
1157 var domain;
1158
1159 while ( 1 ) {
1160 try {
1161 // Try to access the parent document. It throws
1162 // "access denied" if restricted by the "Same Origin" policy.
1163 domain = window.parent.document.domain;
1164 break;
1165 } catch ( e ) {
1166 // Calculate the value to set to document.domain.
1167 domain = domain ?
1168
1169 // If it is not the first pass, strip one part of the
1170 // name. E.g. "test.example.com" => "example.com"
1171 domain.replace( /.+?(?:\.|$)/, '' ) :
1172
1173 // In the first pass, we'll handle the
1174 // "document.domain = document.domain" case.
1175 document.domain;
1176
1177 // Stop here if there is no more domain parts available.
1178 if ( !domain )
1179 break;
1180
1181 document.domain = domain;
1182 }
1183 }
1184
1185 return !!domain;
1186 },
1187
1188 /**
1189 * Buffers `input` events (or any `input` calls)
1190 * and triggers `output` not more often than once per `minInterval`.
1191 *
1192 * var buffer = CKEDITOR.tools.eventsBuffer( 200, function() {
1193 * console.log( 'foo!' );
1194 * } );
1195 *
1196 * buffer.input();
1197 * // 'foo!' logged immediately.
1198 * buffer.input();
1199 * // Nothing logged.
1200 * buffer.input();
1201 * // Nothing logged.
1202 * // ... after 200ms a single 'foo!' will be logged.
1203 *
1204 * Can be easily used with events:
1205 *
1206 * var buffer = CKEDITOR.tools.eventsBuffer( 200, function() {
1207 * console.log( 'foo!' );
1208 * } );
1209 *
1210 * editor.on( 'key', buffer.input );
1211 * // Note: There is no need to bind buffer as a context.
1212 *
1213 * @since 4.2.1
1214 * @param {Number} minInterval Minimum interval between `output` calls in milliseconds.
1215 * @param {Function} output Function that will be executed as `output`.
1216 * @param {Object} [scopeObj] The object used to scope the listener call (the `this` object).
1217 * @returns {Object}
1218 * @returns {Function} return.input Buffer's input method.
1219 * @returns {Function} return.reset Resets buffered events &mdash; `output` will not be executed
1220 * until next `input` is triggered.
1221 */
1222 eventsBuffer: function( minInterval, output, scopeObj ) {
1223 var scheduled,
1224 lastOutput = 0;
1225
1226 function triggerOutput() {
1227 lastOutput = ( new Date() ).getTime();
1228 scheduled = false;
1229 if ( scopeObj ) {
1230 output.call( scopeObj );
1231 } else {
1232 output();
1233 }
1234 }
1235
1236 return {
1237 input: function() {
1238 if ( scheduled )
1239 return;
1240
1241 var diff = ( new Date() ).getTime() - lastOutput;
1242
1243 // If less than minInterval passed after last check,
1244 // schedule next for minInterval after previous one.
1245 if ( diff < minInterval )
1246 scheduled = setTimeout( triggerOutput, minInterval - diff );
1247 else
1248 triggerOutput();
1249 },
1250
1251 reset: function() {
1252 if ( scheduled )
1253 clearTimeout( scheduled );
1254
1255 scheduled = lastOutput = 0;
1256 }
1257 };
1258 },
1259
1260 /**
1261 * Enables HTML5 elements for older browsers (IE8) in the passed document.
1262 *
1263 * In IE8 this method can also be executed on a document fragment.
1264 *
1265 * **Note:** This method has to be used in the `<head>` section of the document.
1266 *
1267 * @since 4.3
1268 * @param {Object} doc Native `Document` or `DocumentFragment` in which the elements will be enabled.
1269 * @param {Boolean} [withAppend] Whether to append created elements to the `doc`.
1270 */
1271 enableHtml5Elements: function( doc, withAppend ) {
1272 var els = 'abbr,article,aside,audio,bdi,canvas,data,datalist,details,figcaption,figure,footer,header,hgroup,main,mark,meter,nav,output,progress,section,summary,time,video'.split( ',' ),
1273 i = els.length,
1274 el;
1275
1276 while ( i-- ) {
1277 el = doc.createElement( els[ i ] );
1278 if ( withAppend )
1279 doc.appendChild( el );
1280 }
1281 },
1282
1283 /**
1284 * Checks if any of the `arr` items match the provided regular expression.
1285 *
1286 * @param {Array} arr The array whose items will be checked.
1287 * @param {RegExp} regexp The regular expression.
1288 * @returns {Boolean} Returns `true` for the first occurrence of the search pattern.
1289 * @since 4.4
1290 */
1291 checkIfAnyArrayItemMatches: function( arr, regexp ) {
1292 for ( var i = 0, l = arr.length; i < l; ++i ) {
1293 if ( arr[ i ].match( regexp ) )
1294 return true;
1295 }
1296 return false;
1297 },
1298
1299 /**
1300 * Checks if any of the `obj` properties match the provided regular expression.
1301 *
1302 * @param obj The object whose properties will be checked.
1303 * @param {RegExp} regexp The regular expression.
1304 * @returns {Boolean} Returns `true` for the first occurrence of the search pattern.
1305 * @since 4.4
1306 */
1307 checkIfAnyObjectPropertyMatches: function( obj, regexp ) {
1308 for ( var i in obj ) {
1309 if ( i.match( regexp ) )
1310 return true;
1311 }
1312 return false;
1313 },
1314
1315 /**
1316 * Converts a keystroke to its string representation. Returns an object with two fields:
1317 *
1318 * * `display` &ndash; A string that should be used for visible labels.
1319 * For Mac devices it uses `⌥` for `ALT`, `⇧` for `SHIFT` and `⌘` for `COMMAND`.
1320 * * `aria` &ndash; A string that should be used for ARIA descriptions.
1321 * It does not use special characters such as `⌥`, `⇧` or `⌘`.
1322 *
1323 * var lang = editor.lang.common.keyboard;
1324 * var shortcut = CKEDITOR.tools.keystrokeToString( lang, CKEDITOR.CTRL + 88 );
1325 * console.log( shortcut.display ); // 'CTRL + X', on Mac '⌘ + X'.
1326 * console.log( shortcut.aria ); // 'CTRL + X', on Mac 'COMMAND + X'.
1327 *
1328 * @since 4.6.0
1329 * @param {Object} lang A language object with the key name translation.
1330 * @param {Number} keystroke The keystroke to convert.
1331 * @returns {{display: String, aria: String}}
1332 */
1333 keystrokeToString: function( lang, keystroke ) {
1334 var special = keystroke & 0xFF0000,
1335 key = keystroke & 0x00FFFF,
1336 isMac = CKEDITOR.env.mac,
1337 CTRL = 17,
1338 CMD = 224,
1339 ALT = 18,
1340 SHIFT = 16,
1341 display = [],
1342 aria = [];
1343
1344
1345 if ( special & CKEDITOR.CTRL ) {
1346 display.push( isMac ? '⌘' : lang[ CTRL ] );
1347 aria.push( isMac ? lang[ CMD ] : lang[ CTRL ] );
1348 }
1349
1350 if ( special & CKEDITOR.ALT ) {
1351 display.push( isMac ? '⌥' : lang[ ALT ] );
1352 aria.push( lang[ ALT ] );
1353 }
1354
1355 if ( special & CKEDITOR.SHIFT ) {
1356 display.push( isMac ? '⇧' : lang[ SHIFT ] );
1357 aria.push( lang[ SHIFT ] );
1358 }
1359
1360 if ( key ) {
1361 if ( lang[ key ] ) {
1362 display.push( lang[ key ] );
1363 aria.push( lang[ key ] );
1364 } else {
1365 display.push( String.fromCharCode( key ) );
1366 aria.push( String.fromCharCode( key ) );
1367 }
1368 }
1369
1370 return {
1371 display: display.join( '+' ),
1372 aria: aria.join( '+' )
1373 };
1374 },
1375
1376 /**
1377 * The data URI of a transparent image. May be used e.g. in HTML as an image source or in CSS in `url()`.
1378 *
1379 * @since 4.4
1380 * @readonly
1381 */
1382 transparentImageData: 'data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==',
1383
1384
1385 /**
1386 * Returns the value of the cookie with a given name or `null` if the cookie is not found.
1387 *
1388 * @since 4.5.6
1389 * @param {String} name
1390 * @returns {String}
1391 */
1392 getCookie: function( name ) {
1393 name = name.toLowerCase();
1394 var parts = document.cookie.split( ';' );
1395 var pair, key;
1396
1397 for ( var i = 0; i < parts.length; i++ ) {
1398 pair = parts[ i ].split( '=' );
1399 key = decodeURIComponent( CKEDITOR.tools.trim( pair[ 0 ] ).toLowerCase() );
1400
1401 if ( key === name ) {
1402 return decodeURIComponent( pair.length > 1 ? pair[ 1 ] : '' );
1403 }
1404 }
1405
1406 return null;
1407 },
1408
1409 /**
1410 * Sets the value of the cookie with a given name.
1411 *
1412 * @since 4.5.6
1413 * @param {String} name
1414 * @param {String} value
1415 */
1416 setCookie: function( name, value ) {
1417 document.cookie = encodeURIComponent( name ) + '=' + encodeURIComponent( value ) + ';path=/';
1418 },
1419
1420 /**
1421 * Returns the CSRF token value. The value is a hash stored in `document.cookie`
1422 * under the `ckCsrfToken` key. The CSRF token can be used to secure the communication
1423 * between the web browser and the server, i.e. for the file upload feature in the editor.
1424 *
1425 * @since 4.5.6
1426 * @returns {String}
1427 */
1428 getCsrfToken: function() {
1429 var token = CKEDITOR.tools.getCookie( TOKEN_COOKIE_NAME );
1430
1431 if ( !token || token.length != TOKEN_LENGTH ) {
1432 token = generateToken( TOKEN_LENGTH );
1433 CKEDITOR.tools.setCookie( TOKEN_COOKIE_NAME, token );
1434 }
1435
1436 return token;
1437 },
1438
1439 /**
1440 * Returns an escaped CSS selector. `CSS.escape()` is used if defined, leading digit is escaped otherwise.
1441 *
1442 * @since 4.5.10
1443 * @param {String} selector A CSS selector to escape.
1444 * @returns {String} An escaped selector.
1445 */
1446 escapeCss: function( selector ) {
1447 // Invalid input.
1448 if ( !selector ) {
1449 return '';
1450 }
1451
1452 // CSS.escape() can be used.
1453 if ( window.CSS && CSS.escape ) {
1454 return CSS.escape( selector );
1455 }
1456
1457 // Simple leading digit escape.
1458 if ( !isNaN( parseInt( selector.charAt( 0 ), 10 ) ) ) {
1459 return '\\3' + selector.charAt( 0 ) + ' ' + selector.substring( 1, selector.length );
1460 }
1461
1462 return selector;
1463 },
1464
1465 /**
1466 * Detects which mouse button generated a given DOM event.
1467 *
1468 * @since 4.7.3
1469 * @param {CKEDITOR.dom.event} evt DOM event.
1470 * @returns {Number|Boolean} Returns a number indicating the mouse button or `false`
1471 * if the mouse button cannot be determined.
1472 */
1473 getMouseButton: function( evt ) {
1474 var evtData = evt.data,
1475 domEvent = evtData && evtData.$;
1476
1477 if ( !( evtData && domEvent ) ) {
1478 // Added in case when there's no data available. That's the case in some unit test in built version which
1479 // mock event but doesn't put data object.
1480 return false;
1481 }
1482
1483 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
1484 if ( domEvent.button === 4 ) {
1485 return CKEDITOR.MOUSE_BUTTON_MIDDLE;
1486 } else if ( domEvent.button === 1 ) {
1487 return CKEDITOR.MOUSE_BUTTON_LEFT;
1488 } else {
1489 return CKEDITOR.MOUSE_BUTTON_RIGHT;
1490 }
1491 }
1492
1493 return domEvent.button;
1494 },
1495
1496 /**
1497 * A set of functions for operations on styles.
1498 *
1499 * @property {CKEDITOR.tools.style}
1500 */
1501 style: {
1502 /**
1503 * Methods to parse miscellaneous CSS properties.
1504 *
1505 * @property {CKEDITOR.tools.style.parse}
1506 * @member CKEDITOR.tools.style
1507 */
1508 parse: {
1509 // Color list based on https://www.w3.org/TR/css-color-4/#named-colors.
1510 _colors: {
1511 aliceblue: '#F0F8FF',
1512 antiquewhite: '#FAEBD7',
1513 aqua: '#00FFFF',
1514 aquamarine: '#7FFFD4',
1515 azure: '#F0FFFF',
1516 beige: '#F5F5DC',
1517 bisque: '#FFE4C4',
1518 black: '#000000',
1519 blanchedalmond: '#FFEBCD',
1520 blue: '#0000FF',
1521 blueviolet: '#8A2BE2',
1522 brown: '#A52A2A',
1523 burlywood: '#DEB887',
1524 cadetblue: '#5F9EA0',
1525 chartreuse: '#7FFF00',
1526 chocolate: '#D2691E',
1527 coral: '#FF7F50',
1528 cornflowerblue: '#6495ED',
1529 cornsilk: '#FFF8DC',
1530 crimson: '#DC143C',
1531 cyan: '#00FFFF',
1532 darkblue: '#00008B',
1533 darkcyan: '#008B8B',
1534 darkgoldenrod: '#B8860B',
1535 darkgray: '#A9A9A9',
1536 darkgreen: '#006400',
1537 darkgrey: '#A9A9A9',
1538 darkkhaki: '#BDB76B',
1539 darkmagenta: '#8B008B',
1540 darkolivegreen: '#556B2F',
1541 darkorange: '#FF8C00',
1542 darkorchid: '#9932CC',
1543 darkred: '#8B0000',
1544 darksalmon: '#E9967A',
1545 darkseagreen: '#8FBC8F',
1546 darkslateblue: '#483D8B',
1547 darkslategray: '#2F4F4F',
1548 darkslategrey: '#2F4F4F',
1549 darkturquoise: '#00CED1',
1550 darkviolet: '#9400D3',
1551 deeppink: '#FF1493',
1552 deepskyblue: '#00BFFF',
1553 dimgray: '#696969',
1554 dimgrey: '#696969',
1555 dodgerblue: '#1E90FF',
1556 firebrick: '#B22222',
1557 floralwhite: '#FFFAF0',
1558 forestgreen: '#228B22',
1559 fuchsia: '#FF00FF',
1560 gainsboro: '#DCDCDC',
1561 ghostwhite: '#F8F8FF',
1562 gold: '#FFD700',
1563 goldenrod: '#DAA520',
1564 gray: '#808080',
1565 green: '#008000',
1566 greenyellow: '#ADFF2F',
1567 grey: '#808080',
1568 honeydew: '#F0FFF0',
1569 hotpink: '#FF69B4',
1570 indianred: '#CD5C5C',
1571 indigo: '#4B0082',
1572 ivory: '#FFFFF0',
1573 khaki: '#F0E68C',
1574 lavender: '#E6E6FA',
1575 lavenderblush: '#FFF0F5',
1576 lawngreen: '#7CFC00',
1577 lemonchiffon: '#FFFACD',
1578 lightblue: '#ADD8E6',
1579 lightcoral: '#F08080',
1580 lightcyan: '#E0FFFF',
1581 lightgoldenrodyellow: '#FAFAD2',
1582 lightgray: '#D3D3D3',
1583 lightgreen: '#90EE90',
1584 lightgrey: '#D3D3D3',
1585 lightpink: '#FFB6C1',
1586 lightsalmon: '#FFA07A',
1587 lightseagreen: '#20B2AA',
1588 lightskyblue: '#87CEFA',
1589 lightslategray: '#778899',
1590 lightslategrey: '#778899',
1591 lightsteelblue: '#B0C4DE',
1592 lightyellow: '#FFFFE0',
1593 lime: '#00FF00',
1594 limegreen: '#32CD32',
1595 linen: '#FAF0E6',
1596 magenta: '#FF00FF',
1597 maroon: '#800000',
1598 mediumaquamarine: '#66CDAA',
1599 mediumblue: '#0000CD',
1600 mediumorchid: '#BA55D3',
1601 mediumpurple: '#9370DB',
1602 mediumseagreen: '#3CB371',
1603 mediumslateblue: '#7B68EE',
1604 mediumspringgreen: '#00FA9A',
1605 mediumturquoise: '#48D1CC',
1606 mediumvioletred: '#C71585',
1607 midnightblue: '#191970',
1608 mintcream: '#F5FFFA',
1609 mistyrose: '#FFE4E1',
1610 moccasin: '#FFE4B5',
1611 navajowhite: '#FFDEAD',
1612 navy: '#000080',
1613 oldlace: '#FDF5E6',
1614 olive: '#808000',
1615 olivedrab: '#6B8E23',
1616 orange: '#FFA500',
1617 orangered: '#FF4500',
1618 orchid: '#DA70D6',
1619 palegoldenrod: '#EEE8AA',
1620 palegreen: '#98FB98',
1621 paleturquoise: '#AFEEEE',
1622 palevioletred: '#DB7093',
1623 papayawhip: '#FFEFD5',
1624 peachpuff: '#FFDAB9',
1625 peru: '#CD853F',
1626 pink: '#FFC0CB',
1627 plum: '#DDA0DD',
1628 powderblue: '#B0E0E6',
1629 purple: '#800080',
1630 rebeccapurple: '#663399',
1631 red: '#FF0000',
1632 rosybrown: '#BC8F8F',
1633 royalblue: '#4169E1',
1634 saddlebrown: '#8B4513',
1635 salmon: '#FA8072',
1636 sandybrown: '#F4A460',
1637 seagreen: '#2E8B57',
1638 seashell: '#FFF5EE',
1639 sienna: '#A0522D',
1640 silver: '#C0C0C0',
1641 skyblue: '#87CEEB',
1642 slateblue: '#6A5ACD',
1643 slategray: '#708090',
1644 slategrey: '#708090',
1645 snow: '#FFFAFA',
1646 springgreen: '#00FF7F',
1647 steelblue: '#4682B4',
1648 tan: '#D2B48C',
1649 teal: '#008080',
1650 thistle: '#D8BFD8',
1651 tomato: '#FF6347',
1652 turquoise: '#40E0D0',
1653 violet: '#EE82EE',
1654 wheat: '#F5DEB3',
1655 white: '#FFFFFF',
1656 whitesmoke: '#F5F5F5',
1657 yellow: '#FFFF00',
1658 yellowgreen: '#9ACD32'
1659 },
1660
1661 _borderStyle: [
1662 'none',
1663 'hidden',
1664 'dotted',
1665 'dashed',
1666 'solid',
1667 'double',
1668 'groove',
1669 'ridge',
1670 'inset',
1671 'outset'
1672 ],
1673
1674 _widthRegExp: /^(thin|medium|thick|[\+-]?\d+(\.\d+)?[a-z%]+|[\+-]?0+(\.0+)?|\.\d+[a-z%]+)$/,
1675
1676 _rgbaRegExp: /rgba?\(\s*\d+%?\s*,\s*\d+%?\s*,\s*\d+%?\s*(?:,\s*[0-9.]+\s*)?\)/gi,
1677
1678 _hslaRegExp: /hsla?\(\s*[0-9.]+\s*,\s*\d+%\s*,\s*\d+%\s*(?:,\s*[0-9.]+\s*)?\)/gi,
1679
1680 /**
1681 * Parses the `value` used as a `background` property shorthand and returns information as an object.
1682 *
1683 * **Note:** Currently only the `color` property is extracted. Any other parts will go into the `unprocessed` property.
1684 *
1685 * var background = CKEDITOR.tools.style.parse.background( '#0C0 url(foo.png)' );
1686 * console.log( background );
1687 * // Logs: { color: '#0C0', unprocessed: 'url(foo.png)' }
1688 *
1689 * @param {String} value The value of the `background` property.
1690 * @returns {Object} An object with information extracted from the background.
1691 * @returns {String} return.color The **first** color value found. The color format remains the same as in input.
1692 * @returns {String} return.unprocessed The remaining part of the `value` that has not been processed.
1693 * @member CKEDITOR.tools.style.parse
1694 */
1695 background: function( value ) {
1696 var ret = {},
1697 colors = this._findColor( value );
1698
1699 if ( colors.length ) {
1700 ret.color = colors[ 0 ];
1701
1702 CKEDITOR.tools.array.forEach( colors, function( colorToken ) {
1703 value = value.replace( colorToken, '' );
1704 } );
1705 }
1706
1707 value = CKEDITOR.tools.trim( value );
1708
1709 if ( value ) {
1710 // If anything was left unprocessed include it as unprocessed part.
1711 ret.unprocessed = value;
1712 }
1713
1714 return ret;
1715 },
1716
1717 /**
1718 * Parses the `margin` CSS property shorthand format.
1719 *
1720 * console.log( CKEDITOR.tools.parse.margin( '3px 0 2' ) );
1721 * // Logs: { top: "3px", right: "0", bottom: "2", left: "0" }
1722 *
1723 * @param {String} value The `margin` property value.
1724 * @returns {Object}
1725 * @returns {Number} return.top Top margin.
1726 * @returns {Number} return.right Right margin.
1727 * @returns {Number} return.bottom Bottom margin.
1728 * @returns {Number} return.left Left margin.
1729 * @member CKEDITOR.tools.style.parse
1730 */
1731 margin: function( value ) {
1732 var ret = {};
1733
1734 var widths = value.match( /(?:\-?[\.\d]+(?:%|\w*)|auto|inherit|initial|unset)/g ) || [ '0px' ];
1735
1736 switch ( widths.length ) {
1737 case 1:
1738 mapStyles( [ 0, 0, 0, 0 ] );
1739 break;
1740 case 2:
1741 mapStyles( [ 0, 1, 0, 1 ] );
1742 break;
1743 case 3:
1744 mapStyles( [ 0, 1, 2, 1 ] );
1745 break;
1746 case 4:
1747 mapStyles( [ 0, 1, 2, 3 ] );
1748 break;
1749 }
1750
1751 function mapStyles( map ) {
1752 ret.top = widths[ map[ 0 ] ];
1753 ret.right = widths[ map[ 1 ] ];
1754 ret.bottom = widths[ map[ 2 ] ];
1755 ret.left = widths[ map[ 3 ] ];
1756 }
1757
1758 return ret;
1759 },
1760
1761 /**
1762 * Parses the `border` CSS property shorthand format.
1763 * This CSS property does not support inheritance (https://www.w3.org/TR/css3-background/#the-border-shorthands).
1764 *
1765 * console.log( CKEDITOR.tools.style.parse.border( '3px solid #ffeedd' ) );
1766 * // Logs: { width: "3px", style: "solid", color: "#ffeedd" }
1767 *
1768 * @param {String} value The `border` property value.
1769 * @returns {Object}
1770 * @returns {String} return.width The border-width attribute.
1771 * @returns {String} return.style The border-style attribute.
1772 * @returns {String} return.color The border-color attribute.
1773 * @member CKEDITOR.tools.style.parse
1774 */
1775 border: function( value ) {
1776 var ret = {},
1777 input = value.split( /\s+/ );
1778
1779 CKEDITOR.tools.array.forEach( input, function( val ) {
1780 if ( !ret.color ) {
1781 var parseColor = CKEDITOR.tools.style.parse._findColor( val );
1782 if ( parseColor.length ) {
1783 ret.color = parseColor[ 0 ];
1784 return;
1785 }
1786 }
1787
1788 if ( !ret.style ) {
1789 if ( CKEDITOR.tools.indexOf( CKEDITOR.tools.style.parse._borderStyle, val ) !== -1 ) {
1790 ret.style = val;
1791 return;
1792 }
1793 }
1794
1795 if ( !ret.width ) {
1796 if ( CKEDITOR.tools.style.parse._widthRegExp.test( val ) ) {
1797 ret.width = val;
1798 return;
1799 }
1800 }
1801
1802 } );
1803 return ret;
1804 },
1805
1806 /**
1807 * Searches the `value` for any CSS color occurrences and returns it.
1808 *
1809 * @private
1810 * @param {String} value
1811 * @returns {String[]} An array of matched results.
1812 * @member CKEDITOR.tools.style.parse
1813 */
1814 _findColor: function( value ) {
1815 var ret = [],
1816 arrayTools = CKEDITOR.tools.array;
1817
1818
1819 // Check for rgb(a).
1820 ret = ret.concat( value.match( this._rgbaRegExp ) || [] );
1821
1822 // Check for hsl(a).
1823 ret = ret.concat( value.match( this._hslaRegExp ) || [] );
1824
1825 ret = ret.concat( arrayTools.filter( value.split( /\s+/ ), function( colorEntry ) {
1826 // Check for hex format.
1827 if ( colorEntry.match( /^\#[a-f0-9]{3}(?:[a-f0-9]{3})?$/gi ) ) {
1828 return true;
1829 }
1830
1831 // Check for preset names.
1832 return colorEntry.toLowerCase() in CKEDITOR.tools.style.parse._colors;
1833 } ) );
1834
1835 return ret;
1836 }
1837 }
1838 },
1839
1840 /**
1841 * A set of array helpers.
1842 *
1843 * @property {CKEDITOR.tools.array}
1844 * @member CKEDITOR.tools
1845 */
1846 array: {
1847 /**
1848 * Returns a copy of `array` filtered using the `fn` function. Any elements that the `fn` will return `false` for
1849 * will get removed from the returned array.
1850 *
1851 * var filtered = this.array.filter( [ 0, 1, 2, 3 ], function( value ) {
1852 * // Leave only values equal or greater than 2.
1853 * return value >= 2;
1854 * } );
1855 * console.log( filtered );
1856 * // Logs: [ 2, 3 ]
1857 *
1858 * @param {Array} array
1859 * @param {Function} fn A function that gets called with each `array` item. Any item that `fn`
1860 * returned a `false`-alike value for will be filtered out of the `array`.
1861 * @param {Mixed} fn.value The currently iterated array value.
1862 * @param {Number} fn.index The index of the currently iterated value in an array.
1863 * @param {Array} fn.array The original array passed as the `array` variable.
1864 * @param {Mixed} [thisArg=undefined] A context object for `fn`.
1865 * @returns {Array} The filtered array.
1866 * @member CKEDITOR.tools.array
1867 */
1868 filter: function( array, fn, thisArg ) {
1869 var ret = [];
1870
1871 this.forEach( array, function( val, i ) {
1872 if ( fn.call( thisArg, val, i, array ) ) {
1873 ret.push( val );
1874 }
1875 } );
1876
1877 return ret;
1878 },
1879
1880 /**
1881 * Iterates over every element in the `array`.
1882 *
1883 * @param {Array} array An array to be iterated over.
1884 * @param {Function} fn The function called for every `array` element.
1885 * @param {Mixed} fn.value The currently iterated array value.
1886 * @param {Number} fn.index The index of the currently iterated value in an array.
1887 * @param {Array} fn.array The original array passed as an `array` variable.
1888 * @param {Mixed} [thisArg=undefined] The context object for `fn`.
1889 * @member CKEDITOR.tools.array
1890 */
1891 forEach: function( array, fn, thisArg ) {
1892 var len = array.length,
1893 i;
1894
1895 for ( i = 0; i < len; i++ ) {
1896 fn.call( thisArg, array[ i ], i, array );
1897 }
1898 },
1899
1900 /**
1901 * Applies a function to each element of an array and returns the array of results in the same order.
1902 * Note the order of the parameters.
1903 *
1904 * @param {Array} array An array of elements that `fn` is applied on.
1905 * @param {Function} fn A function with the signature `a -> b`.
1906 * @param {Mixed} [thisArg=undefined] The context object for `fn`.
1907 * @returns {Array} An array of mapped elements.
1908 * @member CKEDITOR.tools.array
1909 * @since 4.6.2
1910 */
1911 map: function( array, fn, thisArg ) {
1912 var result = [];
1913 for ( var i = 0; i < array.length; i++ ) {
1914 result.push( fn.call( thisArg, array[ i ], i, array ) );
1915 }
1916 return result;
1917 },
1918
1919 /**
1920 * Applies a function against each value in an array storing the result in an accumulator passed to the next iteration.
1921 * Note the order of the parameters.
1922 *
1923 * @param {Array} array An array of elements that `fn` is applied on.
1924 * @param {Function} fn A function with the signature `(accumulator, a, index, array) -> b`.
1925 * @param {Mixed} initial Initial value of the accumulator.
1926 * @param {Mixed} [thisArg=undefined] The context object for `fn`.
1927 * @returns {Mixed} The final value of the accumulator.
1928 * @member CKEDITOR.tools.array
1929 * @since 4.6.2
1930 */
1931 reduce: function( array, fn, initial, thisArg ) {
1932 var acc = initial;
1933 for ( var i = 0; i < array.length; i++ ) {
1934 acc = fn.call( thisArg, acc, array[ i ], i, array );
1935 }
1936 return acc;
1937 }
1938 },
1939
1940 /**
1941 * A set of object helpers.
1942 *
1943 * @property {CKEDITOR.tools.object}
1944 * @member CKEDITOR.tools
1945 */
1946 object: {
1947 /**
1948 * Returns the first key from `obj` which has a given `value`.
1949 *
1950 * @param {Object} obj An object whose `key` is looked for.
1951 * @param {Mixed} value An object's `value` to be looked for.
1952 * @returns {String/null} Matched `key` or `null` if not found.
1953 * @member CKEDITOR.tools.object
1954 */
1955
1956 findKey: function( obj, value ) {
1957 if ( typeof obj !== 'object' ) {
1958 return null;
1959 }
1960
1961 var key;
1962
1963 for ( key in obj ) {
1964 if ( obj[ key ] === value ) {
1965 return key;
1966 }
1967 }
1968
1969 return null;
1970 }
1971 }
1972 };
1973
1974 // Generates a CSRF token with a given length.
1975 //
1976 // @since 4.5.6
1977 // @param {Number} length
1978 // @returns {string}
1979 function generateToken( length ) {
1980 var randValues = [];
1981 var result = '';
1982
1983 if ( window.crypto && window.crypto.getRandomValues ) {
1984 randValues = new Uint8Array( length );
1985 window.crypto.getRandomValues( randValues );
1986 } else {
1987 for ( var i = 0; i < length; i++ ) {
1988 randValues.push( Math.floor( Math.random() * 256 ) );
1989 }
1990 }
1991
1992 for ( var j = 0; j < randValues.length; j++ ) {
1993 var character = tokenCharset.charAt( randValues[ j ] % tokenCharset.length );
1994 result += Math.random() > 0.5 ? character.toUpperCase() : character;
1995 }
1996
1997 return result;
1998 }
1999
2000 /**
2001 * @member CKEDITOR.tools.array
2002 * @method indexOf
2003 * @inheritdoc CKEDITOR.tools#indexOf
2004 */
2005 CKEDITOR.tools.array.indexOf = CKEDITOR.tools.indexOf;
2006
2007 /**
2008 * @member CKEDITOR.tools.array
2009 * @method isArray
2010 * @inheritdoc CKEDITOR.tools#isArray
2011 */
2012 CKEDITOR.tools.array.isArray = CKEDITOR.tools.isArray;
2013
2014 /**
2015 * Left mouse button.
2016 *
2017 * @since 4.7.3
2018 * @readonly
2019 * @property {Number} [=0]
2020 * @member CKEDITOR
2021 */
2022 CKEDITOR.MOUSE_BUTTON_LEFT = 0;
2023
2024 /**
2025 * Middle mouse button.
2026 *
2027 * @since 4.7.3
2028 * @readonly
2029 * @property {Number} [=1]
2030 * @member CKEDITOR
2031 */
2032 CKEDITOR.MOUSE_BUTTON_MIDDLE = 1;
2033
2034 /**
2035 * Right mouse button.
2036 *
2037 * @since 4.7.3
2038 * @readonly
2039 * @property {Number} [=2]
2040 * @member CKEDITOR
2041 */
2042 CKEDITOR.MOUSE_BUTTON_RIGHT = 2;
2043
2044 /**
2045 * The namespace containing functions to work on CSS properties.
2046 *
2047 * @since 4.6.1
2048 * @class CKEDITOR.tools.style
2049 */
2050
2051 /**
2052 * The namespace with helper functions to parse some common CSS properties.
2053 *
2054 * @since 4.6.1
2055 * @class CKEDITOR.tools.style.parse
2056 */
2057
2058 /**
2059 * The namespace with helper functions and polyfills for arrays.
2060 *
2061 * @since 4.6.1
2062 * @class CKEDITOR.tools.array
2063 */
2064
2065 /**
2066 * The namespace with helper functions and polyfills for objects.
2067 *
2068 * @since 4.7.1
2069 * @class CKEDITOR.tools.object
2070 */
2071 } )();
2072
2073 // PACKAGER_RENAME( CKEDITOR.tools )