]> git.sur5r.net Git - bacula/bacula/blob - gui/baculum/debian/missing-sources/framework/Web/Javascripts/source/tinymce-405/classes/util/Quirks.js
baculum: Add missing-sources directory in debian metadata structure
[bacula/bacula] / gui / baculum / debian / missing-sources / framework / Web / Javascripts / source / tinymce-405 / classes / util / Quirks.js
1 /**
2  * Quirks.js
3  *
4  * Copyright, Moxiecode Systems AB
5  * Released under LGPL License.
6  *
7  * License: http://www.tinymce.com/license
8  * Contributing: http://www.tinymce.com/contributing
9  *
10  * @ignore-file
11  */
12
13 /**
14  * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes.
15  *
16  * @class tinymce.util.Quirks
17  */
18 define("tinymce/util/Quirks", [
19         "tinymce/util/VK",
20         "tinymce/dom/RangeUtils",
21         "tinymce/html/Node",
22         "tinymce/html/Entities",
23         "tinymce/Env",
24         "tinymce/util/Tools"
25 ], function(VK, RangeUtils, Node, Entities, Env, Tools) {
26         return function(editor) {
27                 var each = Tools.each;
28                 var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
29                         settings = editor.settings, parser = editor.parser, serializer = editor.serializer;
30                 var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit;
31
32                 /**
33                  * Executes a command with a specific state this can be to enable/disable browser editing features.
34                  */
35                 function setEditorCommandState(cmd, state) {
36                         try {
37                                 editor.getDoc().execCommand(cmd, false, state);
38                         } catch (ex) {
39                                 // Ignore
40                         }
41                 }
42
43                 /**
44                  * Returns current IE document mode.
45                  */
46                 function getDocumentMode() {
47                         var documentMode = editor.getDoc().documentMode;
48
49                         return documentMode ? documentMode : 6;
50                 }
51
52                 /**
53                  * Returns true/false if the event is prevented or not.
54                  *
55                  * @private
56                  * @param {Event} e Event object.
57                  * @return {Boolean} true/false if the event is prevented or not.
58                  */
59                 function isDefaultPrevented(e) {
60                         return e.isDefaultPrevented();
61                 }
62
63                 /**
64                  * Fixes a WebKit bug when deleting contents using backspace or delete key.
65                  * WebKit will produce a span element if you delete across two block elements.
66                  *
67                  * Example:
68                  * <h1>a</h1><p>|b</p>
69                  *
70                  * Will produce this on backspace:
71                  * <h1>a<span class="Apple-style-span" style="<all runtime styles>">b</span></p>
72                  *
73                  * This fixes the backspace to produce:
74                  * <h1>a|b</p>
75                  *
76                  * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784
77                  *
78                  * This code is a bit of a hack and hopefully it will be fixed soon in WebKit.
79                  */
80                 function cleanupStylesWhenDeleting() {
81                         function removeMergedFormatSpans(isDelete) {
82                                 var rng, blockElm, wrapperElm, bookmark, container, offset, elm;
83
84                                 function isAtStartOrEndOfElm() {
85                                         if (container.nodeType == 3) {
86                                                 if (isDelete && offset == container.length) {
87                                                         return true;
88                                                 }
89
90                                                 if (!isDelete && offset === 0) {
91                                                         return true;
92                                                 }
93                                         }
94                                 }
95
96                                 rng = selection.getRng();
97                                 var tmpRng = [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset];
98
99                                 if (!rng.collapsed) {
100                                         isDelete = true;
101                                 }
102
103                                 container = rng[(isDelete ? 'start' : 'end') + 'Container'];
104                                 offset = rng[(isDelete ? 'start' : 'end') + 'Offset'];
105
106                                 if (container.nodeType == 3) {
107                                         blockElm = dom.getParent(rng.startContainer, dom.isBlock);
108
109                                         // On delete clone the root span of the next block element
110                                         if (isDelete) {
111                                                 blockElm = dom.getNext(blockElm, dom.isBlock);
112                                         }
113
114                                         if (blockElm && (isAtStartOrEndOfElm() || !rng.collapsed)) {
115                                                 // Wrap children of block in a EM and let WebKit stick is
116                                                 // runtime styles junk into that EM
117                                                 wrapperElm = dom.create('em', {'id': '__mceDel'});
118
119                                                 each(Tools.grep(blockElm.childNodes), function(node) {
120                                                         wrapperElm.appendChild(node);
121                                                 });
122
123                                                 blockElm.appendChild(wrapperElm);
124                                         }
125                                 }
126
127                                 // Do the backspace/delete action
128                                 rng = dom.createRng();
129                                 rng.setStart(tmpRng[0], tmpRng[1]);
130                                 rng.setEnd(tmpRng[2], tmpRng[3]);
131                                 selection.setRng(rng);
132                                 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
133
134                                 // Remove temp wrapper element
135                                 if (wrapperElm) {
136                                         bookmark = selection.getBookmark();
137
138                                         while ((elm = dom.get('__mceDel'))) {
139                                                 dom.remove(elm, true);
140                                         }
141
142                                         selection.moveToBookmark(bookmark);
143                                 }
144                         }
145
146                         editor.on('keydown', function(e) {
147                                 var isDelete;
148
149                                 isDelete = e.keyCode == DELETE;
150                                 if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
151                                         e.preventDefault();
152                                         removeMergedFormatSpans(isDelete);
153                                 }
154                         });
155
156                         editor.addCommand('Delete', function() {removeMergedFormatSpans();});
157                 }
158
159                 /**
160                  * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors.
161                  *
162                  * For example:
163                  * <p><b>|</b></p>
164                  *
165                  * Or:
166                  * <h1>|</h1>
167                  *
168                  * Or:
169                  * [<h1></h1>]
170                  */
171                 function emptyEditorWhenDeleting() {
172                         function serializeRng(rng) {
173                                 var body = dom.create("body");
174                                 var contents = rng.cloneContents();
175                                 body.appendChild(contents);
176                                 return selection.serializer.serialize(body, {format: 'html'});
177                         }
178
179                         function allContentsSelected(rng) {
180                                 var selection = serializeRng(rng);
181
182                                 var allRng = dom.createRng();
183                                 allRng.selectNode(editor.getBody());
184
185                                 var allSelection = serializeRng(allRng);
186                                 return selection === allSelection;
187                         }
188
189                         editor.on('keydown', function(e) {
190                                 var keyCode = e.keyCode, isCollapsed;
191
192                                 // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
193                                 if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
194                                         isCollapsed = editor.selection.isCollapsed();
195
196                                         // Selection is collapsed but the editor isn't empty
197                                         if (isCollapsed && !dom.isEmpty(editor.getBody())) {
198                                                 return;
199                                         }
200
201                                         // IE deletes all contents correctly when everything is selected
202                                         if (isIE && !isCollapsed) {
203                                                 return;
204                                         }
205
206                                         // Selection isn't collapsed but not all the contents is selected
207                                         if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
208                                                 return;
209                                         }
210
211                                         // Manually empty the editor
212                                         e.preventDefault();
213                                         editor.setContent('');
214                                         editor.selection.setCursorLocation(editor.getBody(), 0);
215                                         editor.nodeChanged();
216                                 }
217                         });
218                 }
219
220                 /**
221                  * WebKit doesn't select all the nodes in the body when you press Ctrl+A.
222                  * This selects the whole body so that backspace/delete logic will delete everything
223                  */
224                 function selectAll() {
225                         editor.on('keydown', function(e) {
226                                 if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) {
227                                         e.preventDefault();
228                                         editor.execCommand('SelectAll');
229                                 }
230                         });
231                 }
232
233                 /**
234                  * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes.
235                  * The IME on Mac doesn't initialize when it doesn't fire a proper focus event.
236                  *
237                  * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until
238                  * you enter a character into the editor.
239                  *
240                  * It also happens when the first focus in made to the body.
241                  *
242                  * See: https://bugs.webkit.org/show_bug.cgi?id=83566
243                  */
244                 function inputMethodFocus() {
245                         if (!editor.settings.content_editable) {
246                                 // Case 1 IME doesn't initialize if you focus the document
247                                 dom.bind(editor.getDoc(), 'focusin', function() {
248                                         selection.setRng(selection.getRng());
249                                 });
250
251                                 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
252                                 dom.bind(editor.getDoc(), 'mousedown', function(e) {
253                                         if (e.target == editor.getDoc().documentElement) {
254                                                 editor.getWin().focus();
255                                                 selection.setRng(selection.getRng());
256                                         }
257                                 });
258                         }
259                 }
260
261                 /**
262                  * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the
263                  * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is
264                  * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js
265                  * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other
266                  * browsers
267                  */
268                 function removeHrOnBackspace() {
269                         editor.on('keydown', function(e) {
270                                 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
271                                         if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
272                                                 var node = selection.getNode();
273                                                 var previousSibling = node.previousSibling;
274
275                                                 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
276                                                         dom.remove(previousSibling);
277                                                         e.preventDefault();
278                                                 }
279                                         }
280                                 }
281                         });
282                 }
283
284                 /**
285                  * Firefox 3.x has an issue where the body element won't get proper focus if you click out
286                  * side it's rectangle.
287                  */
288                 function focusBody() {
289                         // Fix for a focus bug in FF 3.x where the body element
290                         // wouldn't get proper focus if the user clicked on the HTML element
291                         if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
292                                 editor.on('mousedown', function(e) {
293                                         if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
294                                                 var body = editor.getBody();
295
296                                                 // Blur the body it's focused but not correctly focused
297                                                 body.blur();
298
299                                                 // Refocus the body after a little while
300                                                 setTimeout(function() {
301                                                         body.focus();
302                                                 }, 0);
303                                         }
304                                 });
305                         }
306                 }
307
308                 /**
309                  * WebKit has a bug where it isn't possible to select image, hr or anchor elements
310                  * by clicking on them so we need to fake that.
311                  */
312                 function selectControlElements() {
313                         editor.on('click', function(e) {
314                                 e = e.target;
315
316                                 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
317                                 // WebKit can't even do simple things like selecting an image
318                                 // Needs tobe the setBaseAndExtend or it will fail to select floated images
319                                 if (/^(IMG|HR)$/.test(e.nodeName)) {
320                                         selection.getSel().setBaseAndExtent(e, 0, e, 1);
321                                 }
322
323                                 if (e.nodeName == 'A' && dom.hasClass(e, 'mce-item-anchor')) {
324                                         selection.select(e);
325                                 }
326
327                                 editor.nodeChanged();
328                         });
329                 }
330
331                 /**
332                  * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements.
333                  *
334                  * Fixes do backspace/delete on this:
335                  * <p>bla[ck</p><p style="color:red">r]ed</p>
336                  *
337                  * Would become:
338                  * <p>bla|ed</p>
339                  *
340                  * Instead of:
341                  * <p style="color:red">bla|ed</p>
342                  */
343                 function removeStylesWhenDeletingAcrossBlockElements() {
344                         function getAttributeApplyFunction() {
345                                 var template = dom.getAttribs(selection.getStart().cloneNode(false));
346
347                                 return function() {
348                                         var target = selection.getStart();
349
350                                         if (target !== editor.getBody()) {
351                                                 dom.setAttrib(target, "style", null);
352
353                                                 each(template, function(attr) {
354                                                         target.setAttributeNode(attr.cloneNode(true));
355                                                 });
356                                         }
357                                 };
358                         }
359
360                         function isSelectionAcrossElements() {
361                                 return !selection.isCollapsed() &&
362                                         dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
363                         }
364
365                         editor.on('keypress', function(e) {
366                                 var applyAttributes;
367
368                                 if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
369                                         applyAttributes = getAttributeApplyFunction();
370                                         editor.getDoc().execCommand('delete', false, null);
371                                         applyAttributes();
372                                         e.preventDefault();
373                                         return false;
374                                 }
375                         });
376
377                         dom.bind(editor.getDoc(), 'cut', function(e) {
378                                 var applyAttributes;
379
380                                 if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
381                                         applyAttributes = getAttributeApplyFunction();
382
383                                         setTimeout(function() {
384                                                 applyAttributes();
385                                         }, 0);
386                                 }
387                         });
388                 }
389
390                 /**
391                  * Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5. It only fires the nodeChange
392                  * event every 50ms since it would other wise update the UI when you type and it hogs the CPU.
393                  */
394                 function selectionChangeNodeChanged() {
395                         var lastRng, selectionTimer;
396
397                         editor.on('selectionchange', function() {
398                                 if (selectionTimer) {
399                                         clearTimeout(selectionTimer);
400                                         selectionTimer = 0;
401                                 }
402
403                                 selectionTimer = window.setTimeout(function() {
404                                         var rng = selection.getRng();
405
406                                         // Compare the ranges to see if it was a real change or not
407                                         if (!lastRng || !RangeUtils.compareRanges(rng, lastRng)) {
408                                                 editor.nodeChanged();
409                                                 lastRng = rng;
410                                         }
411                                 }, 50);
412                         });
413                 }
414
415                 /**
416                  * Screen readers on IE needs to have the role application set on the body.
417                  */
418                 function ensureBodyHasRoleApplication() {
419                         document.body.setAttribute("role", "application");
420                 }
421
422                 /**
423                  * Backspacing into a table behaves differently depending upon browser type.
424                  * Therefore, disable Backspace when cursor immediately follows a table.
425                  */
426                 function disableBackspaceIntoATable() {
427                         editor.on('keydown', function(e) {
428                                 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
429                                         if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
430                                                 var previousSibling = selection.getNode().previousSibling;
431                                                 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
432                                                         e.preventDefault();
433                                                         return false;
434                                                 }
435                                         }
436                                 }
437                         });
438                 }
439
440                 /**
441                  * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this
442                  * logic adds a \n before the BR so that it will get rendered.
443                  */
444                 function addNewLinesBeforeBrInPre() {
445                         // IE8+ rendering mode does the right thing with BR in PRE
446                         if (getDocumentMode() > 7) {
447                                 return;
448                         }
449
450                          // Enable display: none in area and add a specific class that hides all BR elements in PRE to
451                          // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
452                         setEditorCommandState('RespectVisibilityInDesign', true);
453                         editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
454                         dom.addClass(editor.getBody(), 'mceHideBrInPre');
455
456                         // Adds a \n before all BR elements in PRE to get them visual
457                         parser.addNodeFilter('pre', function(nodes) {
458                                 var i = nodes.length, brNodes, j, brElm, sibling;
459
460                                 while (i--) {
461                                         brNodes = nodes[i].getAll('br');
462                                         j = brNodes.length;
463                                         while (j--) {
464                                                 brElm = brNodes[j];
465
466                                                 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered
467                                                 sibling = brElm.prev;
468                                                 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
469                                                         sibling.value += '\n';
470                                                 } else {
471                                                         brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n';
472                                                 }
473                                         }
474                                 }
475                         });
476
477                         // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
478                         serializer.addNodeFilter('pre', function(nodes) {
479                                 var i = nodes.length, brNodes, j, brElm, sibling;
480
481                                 while (i--) {
482                                         brNodes = nodes[i].getAll('br');
483                                         j = brNodes.length;
484                                         while (j--) {
485                                                 brElm = brNodes[j];
486                                                 sibling = brElm.prev;
487                                                 if (sibling && sibling.type == 3) {
488                                                         sibling.value = sibling.value.replace(/\r?\n$/, '');
489                                                 }
490                                         }
491                                 }
492                         });
493                 }
494
495                 /**
496                  * Moves style width/height to attribute width/height when the user resizes an image on IE.
497                  */
498                 function removePreSerializedStylesWhenSelectingControls() {
499                         dom.bind(editor.getBody(), 'mouseup', function() {
500                                 var value, node = selection.getNode();
501
502                                 // Moved styles to attributes on IMG eements
503                                 if (node.nodeName == 'IMG') {
504                                         // Convert style width to width attribute
505                                         if ((value = dom.getStyle(node, 'width'))) {
506                                                 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
507                                                 dom.setStyle(node, 'width', '');
508                                         }
509
510                                         // Convert style height to height attribute
511                                         if ((value = dom.getStyle(node, 'height'))) {
512                                                 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
513                                                 dom.setStyle(node, 'height', '');
514                                         }
515                                 }
516                         });
517                 }
518
519                 /**
520                  * Backspace or delete on WebKit will combine all visual styles in a span if the last character is deleted.
521                  *
522                  * For example backspace on:
523                  * <p><b>x|</b></p>
524                  *
525                  * Will produce:
526                  * <p><span style="font-weight: bold">|<br></span></p>
527                  *
528                  * When it should produce:
529                  * <p><b>|<br></b></p>
530                  *
531                  * See: https://bugs.webkit.org/show_bug.cgi?id=81656
532                  */
533                 function keepInlineElementOnDeleteBackspace() {
534                         editor.on('keydown', function(e) {
535                                 var isDelete, rng, container, offset, brElm, sibling, collapsed, nonEmptyElements;
536
537                                 isDelete = e.keyCode == DELETE;
538                                 if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
539                                         rng = selection.getRng();
540                                         container = rng.startContainer;
541                                         offset = rng.startOffset;
542                                         collapsed = rng.collapsed;
543
544                                         // Override delete if the start container is a text node and is at the beginning of text or
545                                         // just before/after the last character to be deleted in collapsed mode
546                                         if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) ||
547                                                 (collapsed && offset === (isDelete ? 0 : 1)))) {
548                                                 // Edge case when deleting <p><b><img> |x</b></p>
549                                                 sibling = container.previousSibling;
550                                                 if (sibling && sibling.nodeName == "IMG") {
551                                                         return;
552                                                 }
553
554                                                 nonEmptyElements = editor.schema.getNonEmptyElements();
555
556                                                 // Prevent default logic since it's broken
557                                                 e.preventDefault();
558
559                                                 // Insert a BR before the text node this will prevent the containing element from being deleted/converted
560                                                 brElm = dom.create('br', {id: '__tmp'});
561                                                 container.parentNode.insertBefore(brElm, container);
562
563                                                 // Do the browser delete
564                                                 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
565
566                                                 // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
567                                                 container = selection.getRng().startContainer;
568                                                 sibling = container.previousSibling;
569                                                 if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) &&
570                                                         !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
571                                                         dom.remove(sibling);
572                                                 }
573
574                                                 // Remove the temp element we inserted
575                                                 dom.remove('__tmp');
576                                         }
577                                 }
578                         });
579                 }
580
581                 /**
582                  * Removes a blockquote when backspace is pressed at the beginning of it.
583                  *
584                  * For example:
585                  * <blockquote><p>|x</p></blockquote>
586                  *
587                  * Becomes:
588                  * <p>|x</p>
589                  */
590                 function removeBlockQuoteOnBackSpace() {
591                         // Add block quote deletion handler
592                         editor.on('keydown', function(e) {
593                                 var rng, container, offset, root, parent;
594
595                                 if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
596                                         return;
597                                 }
598
599                                 rng = selection.getRng();
600                                 container = rng.startContainer;
601                                 offset = rng.startOffset;
602                                 root = dom.getRoot();
603                                 parent = container;
604
605                                 if (!rng.collapsed || offset !== 0) {
606                                         return;
607                                 }
608
609                                 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
610                                         parent = parent.parentNode;
611                                 }
612
613                                 // Is the cursor at the beginning of a blockquote?
614                                 if (parent.tagName === 'BLOCKQUOTE') {
615                                         // Remove the blockquote
616                                         editor.formatter.toggle('blockquote', null, parent);
617
618                                         // Move the caret to the beginning of container
619                                         rng = dom.createRng();
620                                         rng.setStart(container, 0);
621                                         rng.setEnd(container, 0);
622                                         selection.setRng(rng);
623                                 }
624                         });
625                 }
626
627                 /**
628                  * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc.
629                  */
630                 function setGeckoEditingOptions() {
631                         function setOpts() {
632                                 editor._refreshContentEditable();
633
634                                 setEditorCommandState("StyleWithCSS", false);
635                                 setEditorCommandState("enableInlineTableEditing", false);
636
637                                 if (!settings.object_resizing) {
638                                         setEditorCommandState("enableObjectResizing", false);
639                                 }
640                         }
641
642                         if (!settings.readonly) {
643                                 editor.on('BeforeExecCommand MouseDown', setOpts);
644                         }
645                 }
646
647                 /**
648                  * Fixes a gecko link bug, when a link is placed at the end of block elements there is
649                  * no way to move the caret behind the link. This fix adds a bogus br element after the link.
650                  *
651                  * For example this:
652                  * <p><b><a href="#">x</a></b></p>
653                  *
654                  * Becomes this:
655                  * <p><b><a href="#">x</a></b><br></p>
656                  */
657                 function addBrAfterLastLinks() {
658                         function fixLinks() {
659                                 each(dom.select('a'), function(node) {
660                                         var parentNode = node.parentNode, root = dom.getRoot();
661
662                                         if (parentNode.lastChild === node) {
663                                                 while (parentNode && !dom.isBlock(parentNode)) {
664                                                         if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
665                                                                 return;
666                                                         }
667
668                                                         parentNode = parentNode.parentNode;
669                                                 }
670
671                                                 dom.add(parentNode, 'br', {'data-mce-bogus': 1});
672                                         }
673                                 });
674                         }
675
676                         editor.on('SetContent ExecCommand', function(e) {
677                                 if (e.type == "setcontent" || e.command === 'mceInsertLink') {
678                                         fixLinks();
679                                 }
680                         });
681                 }
682
683                 /**
684                  * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by
685                  * default we want to change that behavior.
686                  */
687                 function setDefaultBlockType() {
688                         if (settings.forced_root_block) {
689                                 editor.on('init', function() {
690                                         setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
691                                 });
692                         }
693                 }
694
695                 /**
696                  * Removes ghost selections from images/tables on Gecko.
697                  */
698                 function removeGhostSelection() {
699                         editor.on('Undo Redo SetContent', function(e) {
700                                 if (!e.initial) {
701                                         editor.execCommand('mceRepaint');
702                                 }
703                         });
704                 }
705
706                 /**
707                  * Deletes the selected image on IE instead of navigating to previous page.
708                  */
709                 function deleteControlItemOnBackSpace() {
710                         editor.on('keydown', function(e) {
711                                 var rng;
712
713                                 if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
714                                         rng = editor.getDoc().selection.createRange();
715                                         if (rng && rng.item) {
716                                                 e.preventDefault();
717                                                 editor.undoManager.beforeChange();
718                                                 dom.remove(rng.item(0));
719                                                 editor.undoManager.add();
720                                         }
721                                 }
722                         });
723                 }
724
725                 /**
726                  * IE10 doesn't properly render block elements with the right height until you add contents to them.
727                  * This fixes that by adding a padding-right to all empty text block elements.
728                  * See: https://connect.microsoft.com/IE/feedback/details/743881
729                  */
730                 function renderEmptyBlocksFix() {
731                         var emptyBlocksCSS;
732
733                         // IE10+
734                         if (getDocumentMode() >= 10) {
735                                 emptyBlocksCSS = '';
736                                 each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
737                                         emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
738                                 });
739
740                                 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
741                         }
742                 }
743
744                 /**
745                  * Old IE versions can't retain contents within noscript elements so this logic will store the contents
746                  * as a attribute and the insert that value as it's raw text when the DOM is serialized.
747                  */
748                 function keepNoScriptContents() {
749                         if (getDocumentMode() < 9) {
750                                 parser.addNodeFilter('noscript', function(nodes) {
751                                         var i = nodes.length, node, textNode;
752
753                                         while (i--) {
754                                                 node = nodes[i];
755                                                 textNode = node.firstChild;
756
757                                                 if (textNode) {
758                                                         node.attr('data-mce-innertext', textNode.value);
759                                                 }
760                                         }
761                                 });
762
763                                 serializer.addNodeFilter('noscript', function(nodes) {
764                                         var i = nodes.length, node, textNode, value;
765
766                                         while (i--) {
767                                                 node = nodes[i];
768                                                 textNode = nodes[i].firstChild;
769
770                                                 if (textNode) {
771                                                         textNode.value = Entities.decode(textNode.value);
772                                                 } else {
773                                                         // Old IE can't retain noscript value so an attribute is used to store it
774                                                         value = node.attributes.map['data-mce-innertext'];
775                                                         if (value) {
776                                                                 node.attr('data-mce-innertext', null);
777                                                                 textNode = new Node('#text', 3);
778                                                                 textNode.value = value;
779                                                                 textNode.raw = true;
780                                                                 node.append(textNode);
781                                                         }
782                                                 }
783                                         }
784                                 });
785                         }
786                 }
787
788                 /**
789                  * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode.
790                  */
791                 function fixCaretSelectionOfDocumentElementOnIe() {
792                         var doc = dom.doc, body = doc.body, started, startRng, htmlElm;
793
794                         // Return range from point or null if it failed
795                         function rngFromPoint(x, y) {
796                                 var rng = body.createTextRange();
797
798                                 try {
799                                         rng.moveToPoint(x, y);
800                                 } catch (ex) {
801                                         // IE sometimes throws and exception, so lets just ignore it
802                                         rng = null;
803                                 }
804
805                                 return rng;
806                         }
807
808                         // Fires while the selection is changing
809                         function selectionChange(e) {
810                                 var pointRng;
811
812                                 // Check if the button is down or not
813                                 if (e.button) {
814                                         // Create range from mouse position
815                                         pointRng = rngFromPoint(e.x, e.y);
816
817                                         if (pointRng) {
818                                                 // Check if pointRange is before/after selection then change the endPoint
819                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) {
820                                                         pointRng.setEndPoint('StartToStart', startRng);
821                                                 } else {
822                                                         pointRng.setEndPoint('EndToEnd', startRng);
823                                                 }
824
825                                                 pointRng.select();
826                                         }
827                                 } else {
828                                         endSelection();
829                                 }
830                         }
831
832                         // Removes listeners
833                         function endSelection() {
834                                 var rng = doc.selection.createRange();
835
836                                 // If the range is collapsed then use the last start range
837                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) {
838                                         startRng.select();
839                                 }
840
841                                 dom.unbind(doc, 'mouseup', endSelection);
842                                 dom.unbind(doc, 'mousemove', selectionChange);
843                                 startRng = started = 0;
844                         }
845
846                         // Make HTML element unselectable since we are going to handle selection by hand
847                         doc.documentElement.unselectable = true;
848
849                         // Detect when user selects outside BODY
850                         dom.bind(doc, 'mousedown contextmenu', function(e) {
851                                 if (e.target.nodeName === 'HTML') {
852                                         if (started) {
853                                                 endSelection();
854                                         }
855
856                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
857                                         htmlElm = doc.documentElement;
858                                         if (htmlElm.scrollHeight > htmlElm.clientHeight) {
859                                                 return;
860                                         }
861
862                                         started = 1;
863                                         // Setup start position
864                                         startRng = rngFromPoint(e.x, e.y);
865                                         if (startRng) {
866                                                 // Listen for selection change events
867                                                 dom.bind(doc, 'mouseup', endSelection);
868                                                 dom.bind(doc, 'mousemove', selectionChange);
869
870                                                 dom.win.focus();
871                                                 startRng.select();
872                                         }
873                                 }
874                         });
875                 }
876
877                 /**
878                  * Fixes selection issues on Gecko where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
879                  * this fix will lean the caret right into the closest inline element.
880                  */
881                 function normalizeSelection() {
882                         // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
883                         editor.on('keyup focusin', function(e) {
884                                 if (e.keyCode != 65 || !VK.metaKeyPressed(e)) {
885                                         selection.normalize();
886                                 }
887                         });
888                 }
889
890                 /**
891                  * Forces Gecko to render a broken image icon if it fails to load an image.
892                  */
893                 function showBrokenImageIcon() {
894                         editor.contentStyles.push(
895                                 'img:-moz-broken {' +
896                                         '-moz-force-broken-image-icon:1;' +
897                                         'min-width:24px;' +
898                                         'min-height:24px' +
899                                 '}'
900                         );
901                 }
902
903                 /**
904                  * iOS has a bug where it's impossible to type if the document has a touchstart event
905                  * bound and the user touches the document while having the on screen keyboard visible.
906                  *
907                  * The touch event moves the focus to the parent document while having the caret inside the iframe
908                  * this fix moves the focus back into the iframe document.
909                  */
910                 function restoreFocusOnKeyDown() {
911                         if (!editor.inline) {
912                                 editor.on('keydown', function() {
913                                         if (document.activeElement == document.body) {
914                                                 editor.getWin().focus();
915                                         }
916                                 });
917                         }
918                 }
919
920                 // All browsers
921                 disableBackspaceIntoATable();
922                 removeBlockQuoteOnBackSpace();
923                 emptyEditorWhenDeleting();
924                 normalizeSelection();
925
926                 // WebKit
927                 if (isWebKit) {
928                         keepInlineElementOnDeleteBackspace();
929                         cleanupStylesWhenDeleting();
930                         inputMethodFocus();
931                         selectControlElements();
932                         setDefaultBlockType();
933
934                         // iOS
935                         if (Env.iOS) {
936                                 selectionChangeNodeChanged();
937                                 restoreFocusOnKeyDown();
938                         } else {
939                                 selectAll();
940                         }
941                 }
942
943                 // IE
944                 if (isIE && Env.ie < 11) {
945                         removeHrOnBackspace();
946                         ensureBodyHasRoleApplication();
947                         addNewLinesBeforeBrInPre();
948                         removePreSerializedStylesWhenSelectingControls();
949                         deleteControlItemOnBackSpace();
950                         renderEmptyBlocksFix();
951                         keepNoScriptContents();
952                         fixCaretSelectionOfDocumentElementOnIe();
953                 }
954
955                 // Gecko
956                 if (isGecko) {
957                         removeHrOnBackspace();
958                         focusBody();
959                         removeStylesWhenDeletingAcrossBlockElements();
960                         setGeckoEditingOptions();
961                         addBrAfterLastLinks();
962                         removeGhostSelection();
963                         showBrokenImageIcon();
964                 }
965         };
966 });