source: gs3-extensions/seaweed-debug/trunk/src/Clipboard.js@ 25160

Last change on this file since 25160 was 25160, checked in by sjm84, 12 years ago

Initial cut at a version of seaweed for debugging purposes. Check it out live into the web/ext folder

File size: 23.6 KB
Line 
1/*
2 * file: Clipboard.js
3 *
4 * @BEGINLICENSE
5 * Copyright 2010 Brook Novak (email : [email protected])
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 * @ENDLICENSE
18 */
19/*
20 * System-Copy pipelines for copy/cut key strokes:
21 *
22 * IE: window.clipboardData -> Fall-through-events
23 * Moz: XUL -> Fall-through-events
24 * WK: Fall-through-events
25 * Op: Fall-through-events
26 *
27 * ZeroClipboard can be used for copying via buttons (outside of package).
28 * Internal System-Copy pipelines for copy/cut buttons:
29 *
30 * WK: ExecCommand
31 * IE: window.clipboardData
32 * Moz: XUL
33 *
34 * System-Paste pipelines for paste buttons:
35 * IE: window.clipboardData
36 * Moz: XUL
37 *
38 */
39
40bootstrap.provides("Clipboard");
41
42(function() {
43
44 $enqueueInit("Clipboard", function() {
45
46 // Setup platform independant event handlers for Accel+C, Accel+V and Accel+X key strokes
47 switch(_engine) {
48 case _Platform.TRIDENT:
49 _addHandler(document, "keydown", onIEKeyDown);
50 break;
51
52 case _Platform.GECKO:
53 _addHandler(document, "keypress", onGeckoKeyPress);
54 break;
55
56 case _Platform.PRESTO:
57 _addHandler(document, "keydown", onPrestoKeyDown);
58 break;
59
60 case _Platform.WEBKIT:
61 _addHandler(document, "copy", onWKCopy);
62 _addHandler(document, "paste", onWKPaste);
63 _addHandler(document, "keydown", onWKKeyDown); // Cutting in all webkit, copy/paste in safari mac
64 break;
65
66 }
67
68 // Create the multi-lined text box for capturing clipboard ketstrokes
69 clipInputEle = $createElement("textarea");
70 _setFullStyle(clipInputEle, "width:1px;height:1px;border-style:none");
71 clipContainer = $createElement("div");
72 _setClassName(clipContainer, _PROTECTED_CLASS);
73 _setFullStyle(clipContainer, "position:absolute;width:1px;height:1px;display:none;z-index:-500");
74
75 clipContainer.appendChild(clipInputEle);
76 docBody.appendChild(clipContainer);
77
78 }, "events.Events");
79
80 /* The internal clipboard text - stored whenever a user copies. */
81 var intClipText,
82
83 /* The internal clipboard DOM - stored whenever a user copies.*/
84 intClipDOM,
85
86 /* True if managed to copy the current internal clip text to the system clipboard */
87 isSysClip,
88
89 /* Used for copy/cut/paste keystroke hijacking. */
90 clipInputEle,
91
92 /* Used for copy/cut/paste keystroke hijacking. */
93 clipContainer,
94
95 /* Used for copy/cut/paste keystroke hijacking. */
96 clipboardTOID = null,
97
98 /* The cursor descriptor to restore to after a native copy. NULL if there is no cursor to restore. */
99 restoreCursor;
100
101 /**
102 * Converts a DOM tree to a textual version.
103 * @param {Node} node A dom tree to convert to text
104 * @return {String} The text equivalent of the given root node of the dom tree.
105 */
106 function domToText(node) {
107
108 var text = "", child;
109
110 if (node.nodeType == Node.TEXT_NODE && _doesTextSupportNonWS(node)) {
111 text = node.nodeValue.replace(/[\t\n\r]/g, " "); // Make all HTML-whitespace symbols actual whitespace
112
113 } else if (node.nodeType == Node.ELEMENT_NODE) {
114 switch (_nodeName(node)) {
115 case "br":
116 text += "\n";
117 break;
118 case "li":
119 text += "\n * ";
120 break;
121 default:
122 if (_isBlockLevel(node))
123 text += "\n";
124 }
125
126 // Recurse
127 child = node.firstChild;
128 while (child) {
129 text += domToText(child);
130 child = child.nextSibling;
131 }
132
133 // Block-elements have line breaks before and after
134 if (_isBlockLevel(node))
135 text += "\n";
136 }
137
138 return text;
139 }
140
141 /**
142 * Copies the documents selection to the internal clipboard.
143 * Sets the locals intClipText, intClipDOM and isSysClip appropriatly if there is something to copy.
144 *
145 * @return {String} The text that is copied to the internal clipboard.
146 * Null if there was nothing to copy (the clipboard state will be unchanged in this case).
147 */
148 function internalCopy() {
149
150 // Get the current document selections dom
151 var selection = de.selection.getHighlightedDOM();
152 if (!selection) return null;
153
154 // Store duplicated dom
155 intClipDOM = selection;
156
157 // Special case: if the selection root is a list element then we need to
158 // get the list element type (ol/ul)
159
160 // TODO
161
162 // Convert DOM into text
163 intClipText = domToText(intClipDOM);
164
165 // Chop off leading and trailing new line if the dom tree's root is block level
166 if (_isBlockLevel(intClipDOM))
167 intClipText = intClipText.replace(/^\n/, "").replace(/\n$/, "");
168
169 // Reset system clip flag
170 isSysClip = false;
171
172 return intClipText;
173 }
174
175 /**
176 * Copies and removes the documents selection to the internal clipboard.
177 *
178 * @return {String} The text that is copied to the internal clipboard.
179 * Null if there was nothing to cut (the clipboard and document state will be unchanged in this case).
180 *
181 * @see internalCopy
182 */
183 function internalCut() {
184
185 var res = internalCopy();
186
187 // If something was copied, remove any selection from the document
188 if (res) de.selection.remove();
189
190 return res;
191 }
192
193 /**
194 * Pastes text, or DOM, into the document, if the cursor is in an editable section.
195 * @param {String} sysClipText The textual contents of the system clipboard if available.
196 */
197 function internalPaste(sysClipText) {
198
199 // Don't try paste if the cursor does not exist
200 if (!de.cursor.exists()) return;
201
202 // Check permissions of cursor position....
203
204 // Remove the current selection if any
205 de.selection.remove();
206
207 var cursorDesc = de.cursor.getCurrentCursorDesc();
208 debug.assert(cursorDesc != null);
209
210 var textToPaste, domToPaste;
211
212 // Has there ever been anything copied internally in this session before?
213 if (intClipText) {
214
215 // If the internal clipboard content was unable to be copied to the system clipboard,
216 // then unfortunatly we will have to use this.
217 if (!isSysClip)
218 domToPaste = intClipDOM;
219
220 // If the internal clipboard text matches the system clipboard text, then use the
221 // DOM content since it is the riches content.
222 else if (intClipText.replace(/\s/g,"") == sysClipText.replace(/\s/g,""))
223 domToPaste = intClipDOM;
224
225 // If the system clipboard text is available, use that
226 else if (sysClipText)
227 textToPaste = sysClipText;
228
229 // If all else fails, use the internal clip text
230 else
231 domToPaste = intClipDOM;
232
233 } else textToPaste = sysClipText; // If available, use the text in the system clipboard
234
235 // TMP - for debugging text pasting etc..
236 // if (domToPaste) {
237 // domToPaste = null;
238 // textToPaste = intClipText;
239 // }
240
241 // Is there anything to paste?
242 if (domToPaste || textToPaste) {
243
244 // Calculate the cursor index
245 var index = cursorDesc.relIndex;
246 if (cursorDesc.isRightOf) index++;
247 if (_nodeName(cursorDesc.domNode) == "br") index = 1;
248
249 var es = de.doc.getEditSectionContainer(cursorDesc.domNode);
250
251 if (es) {
252
253 var esProps = de.doc.getEditProperties(es);
254
255 if (!esProps.singleLine && domToPaste) { // Can we paste DOM content?
256
257 // TODO, HTML validation, DEdit filters... should this be in the insert HTML command?
258 // LOW PRIORITY
259
260 // This will take a lot of thought...
261
262 // TEMP HACK: Just past inline HTML
263 var inlineContentHolder = $createElement("div");
264
265 _visitAllNodes(domToPaste, domToPaste, true, function(node) {
266
267 // Add text nodes to inline content holder
268 if (node.nodeType == Node.TEXT_NODE)
269 inlineContentHolder.appendChild(node.cloneNode(false));
270
271 else if (_isInlineLevel(node) && _isValidRelationship(node, cursorDesc.domNode.parentNode)) {
272
273 // If this node is inline and can be validly inserted into char position,
274 // see if all of its children are inline
275 var isAllInline = 1;
276 _visitAllNodes(node, node, true, function(innerNode) {
277
278 // End search once exits subtree
279 if (!_isAncestor(node,innerNode))
280 return false;
281
282 // Is a node found to be block level?
283 if (_isBlockLevel(innerNode)) {
284 isAllInline = 0;
285 return false;
286 }
287 });
288
289 // If all inline/text then copy this sub tree
290 if (isAllInline && !(esProps.singleLine && _nodeName(node) == "br")) {
291 inlineContentHolder.appendChild(node.cloneNode(true));
292 return 1;
293 }
294
295 }
296
297 });
298
299 if (inlineContentHolder.firstChild)
300 de.UndoMan.execute("InsertHTML", inlineContentHolder.innerHTML, cursorDesc.domNode.parentNode, cursorDesc.domNode, index);
301
302
303 } else if (textToPaste) { // Can we paste text content?
304 // Decide on insertion action and perform it
305 if (!esProps.singleLine && /\n/.test(textToPaste)) // If has newlines then replace with line breaks
306 de.UndoMan.execute("InsertHTML", _escapeTextToHTML(textToPaste, true), cursorDesc.domNode.parentNode, cursorDesc.domNode, index);
307 else
308 de.UndoMan.execute("InsertText", cursorDesc.domNode, textToPaste, index);
309
310 }
311 }
312 }
313
314 }
315
316 /**
317 * IE Only.
318 * Attempts to copy the text to the clipboard.
319 * @param {String} text the text to copy
320 * @return {Boolean} True iff the text was successfully copied to the system clipboard.
321 */
322 function ieClipboardCopy(text) {
323 var didSucceed = window.clipboardData.setData('Text', text);
324 return didSucceed === $undefined || didSucceed;
325 }
326
327 /**
328 * IE Only.
329 * @return {String} The system clipboard's text. Null if unavailable
330 */
331 function ieClipboardRetrieve() {
332 var clipText = window.clipboardData.getData('Text');
333 if (clipText === "") { // Could be empty, or failed
334 // Verify failure
335 if (!window.clipboardData.setData('Text', clipText))
336 clipText = null;
337 }
338 return clipText;
339 }
340
341 /**
342 * IE's On key down event
343 * @param {Event} e The dom event
344 */
345 function onIEKeyDown(e) {
346 e = e || window.event;
347 if (!de.events.Keyboard.isAcceleratorDown(e))
348 return;
349
350 switch(e.keyCode) {
351 case 67: // COPY (C)
352 case 88: // CUT (X)
353
354 // Perform internal copy
355 var textToCopy = e.keyCode == 67 ? internalCopy() : internalCut();
356 if (textToCopy) {
357 // Try to copy the text to the system clipboard the IE way
358 if (ieClipboardCopy(textToCopy))
359 isSysClip = true;
360 else
361 fallThroughCopyEvent(textToCopy);
362
363 }
364 break;
365
366 case 86: // PASTE (V)
367
368 var sysClipContents = ieClipboardRetrieve();
369
370 if (sysClipContents)
371 internalPaste(sysClipContents);
372 else
373 fallThoughPasteEvent();
374
375 break;
376
377 }
378
379 }
380
381 /**
382 * For mozilla platforms only.
383 * @return {Boolean} True iff this session has privileges to access XPConnect resources
384 */
385 function hasXPCPriv() {
386 try {
387 if (netscape.security.PrivilegeManager.enablePrivilege)
388 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
389 else
390 return false;
391 } catch (ex) {
392 return false;
393 }
394 return true;
395 }
396
397 /**
398 * Mozilla Only.
399 * Attempts to copy the text to the clipboard.
400 * @param {String} text the text to copy
401 * @return {Boolean} True iff the text was successfully copied to the system clipboard.
402 */
403 function mozClipboardCopy(text) {
404
405 try {
406
407 if (!hasXPCPriv())
408 return false;
409
410 var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
411 str.data = text;
412
413 var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
414 if (!trans) return false;
415
416 trans.addDataFlavor("text/unicode");
417 trans.setTransferData("text/unicode", str, copytext.length * 2);
418
419 var clipid = Components.interfaces.nsIClipboard;
420 var clip = Components.classes["@mozilla.org/widget/clipboard;1"].getService(clipid);
421 if (!clip) return false;
422
423 clip.setData(trans, null, clipid.kGlobalClipboard);
424
425 }catch(e) {
426 // FF Sometimes throws random errors on blanks lines
427 return false;
428 }
429 }
430
431 /**
432 * Mozilla Only.
433 * @return {String} The system clipboard's text. Null if unavailable
434 */
435 function mozClipboardRetrieve() {
436
437 try {
438
439 if (!hasXPCPriv())
440 return null;
441
442 var clip = Components.classes["@mozilla.org/widget/clipboard;1"].getService(Components.interfaces.nsIClipboard);
443 if (!clip) return null;
444
445 var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
446 if (!trans) return null;
447 trans.addDataFlavor("text/unicode");
448
449 clip.getData(trans, clip.kGlobalClipboard);
450
451 var str = {},
452 strLength = {},
453 pastetext = "";
454
455 trans.getTransferData("text/unicode", str, strLength);
456
457 if (str)
458 str = str.value.QueryInterface(Components.interfaces.nsISupportsString);
459
460 if (str)
461 pastetext = str.data.substring(0, strLength.value / 2);
462
463 return pastetext;
464
465 } catch (e) {
466 // FF Sometimes throws random errors on blanks lines
467 return null;
468 }
469 }
470
471 /**
472 * Mozilla's On key press event
473 * @param {Event} e The dom event
474 */
475 function onGeckoKeyPress(e) {
476
477 if (!de.events.Keyboard.isAcceleratorDown(e))
478 return;
479
480 switch(e.which) {
481 case 99: // COPY (C)
482 case 67:
483 case 120: // CUT (X)
484 case 88:
485
486 // Perform internal copy
487 var textToCopy = (e.which == 67 || e.which == 99) ? internalCopy() : internalCut();
488 if (textToCopy) {
489 // Try to copy the text to the system clipboard the XUL way
490 if (mozClipboardCopy(textToCopy))
491 isSysClip = true;
492 else {
493 fallThroughCopyEvent(textToCopy);
494 }
495 }
496 break;
497
498 case 118: // PASTE (V)
499 case 86:
500 var sysClipContents = mozClipboardRetrieve();
501
502 if (sysClipContents)
503 internalPaste(sysClipContents);
504 else
505 fallThoughPasteEvent();
506
507 break;
508
509 }
510
511 }
512
513 /**
514 * Opera's On key down event
515 * @param {Event} e The dom event
516 */
517 function onPrestoKeyDown(e) {
518 if (!de.events.Keyboard.isAcceleratorDown(e))
519 return;
520 switch(e.keyCode) {
521 case 67: // COPY (C)
522 var textToCopy = internalCopy();
523 if (textToCopy)
524 fallThroughCopyEvent(textToCopy);
525 break;
526 case 88: // CUT (X)
527 var textToCopy = internalCut();
528 if (textToCopy)
529 fallThroughCopyEvent(textToCopy);
530 break;
531 case 86: // PASTE (V)
532 fallThoughPasteEvent();
533 break;
534 }
535 }
536
537 /**
538 * Webkit's on copy event
539 * @param {Event} e The dom event
540 */
541 function onWKCopy(e) {
542 // Webkit has a bug where the clipboard data cannot be set in the clipboard
543 // events, even though the specificatoin states that it can be set. Therefore
544 // must resort to fall-through event capturing for a workaround
545 if (clipboardTOID === null) { // If not currently using fall-through method...
546 var textToCopy = internalCopy();
547 if (textToCopy) fallThroughCopyEvent(textToCopy);
548 }
549 }
550
551 /**
552 * Webkit's on paste event
553 * @param {Event} e The dom event
554 */
555 function onWKPaste(e) {
556
557 // clipboardData is available for access only in this event
558 if (de.cursor.exists() && clipboardTOID === null) { // If not currently using fall-through method... and something is selected
559 internalPaste(e.clipboardData.getData("Text"));
560 e.preventDefault(); // NOTE: Only prevent default if pasting in editable section, other allow pasting in native controls
561 }
562
563 }
564
565 /**
566 * Webkit's on key down event (Cutting only)
567 * @param {Event} e The dom event
568 */
569 function onWKKeyDown(e) {
570 if (de.events.Keyboard.isAcceleratorDown(e)) {
571
572 switch(e.keyCode) {
573 case 88: // X: Cut events in webkit dont work
574 var textToCopy = internalCut();
575 if (textToCopy)
576 fallThroughCopyEvent(textToCopy);
577 break;
578
579 case 67: // C: Copy events via keyboard in safari mac dont work
580 if (_browser == _Platform.SAFARI && _os == _Platform.MAC && de.cursor.exists()) {
581 // Observation: Safari 4 on mac does not allow copy events to occur if
582 // not coping in native text controls.
583
584 // Perform internal copy
585 var textToCopy = internalCopy();
586 if (textToCopy) fallThroughCopyEvent(textToCopy);
587
588 }
589 break;
590
591 case 86: // V: Paste events via keyboard in safari mac dont work
592 if (_browser == _Platform.SAFARI && _os == _Platform.MAC && de.cursor.exists())
593 // Observation: Safari 4 on mac does not allow paste events to occur if
594 // not pasting in native text controls.
595 fallThoughPasteEvent();
596
597 break;
598
599 }
600
601 }
602 }
603
604 /**
605 * Invoked just before the browser is about to execute default/native code
606 * which copies the documents current native selection.
607 *
608 * @param {String} textToCopy The text to copy to the clipboard. Null/undefined if pasting
609 */
610 function fallThroughCopyEvent(textToCopy) {
611 fallThoughClipEventBase(textToCopy);
612 }
613
614 function fallThoughPasteEvent() {
615 fallThoughClipEventBase();
616 }
617
618 function fallThoughClipEventBase(textToCopy) {
619
620 restoreCursor = de.cursor.getCurrentCursorDesc();
621
622 // Avoid race conditions with pending timeout
623 if (clipboardTOID)
624 clearTimeout(clipboardTOID); // Void removing text input event
625
626 // Set/reset the inputbox contents
627 clipInputEle.value = textToCopy ? textToCopy : "";
628
629 // Get the scrollbar state and set the clipboard capturer position in the viewport
630 // to avoid scrolling the document
631 var scrollPos = _getDocumentScrollPos();
632
633 // Position the float (container) at the top left of the viewport,
634 // but if the scroll bars are at zero, then place the float
635 // outside of the document... this will completely conceal the float
636 clipContainer.style.left = (scrollPos.left == 0 ? -50 : scrollPos.left + 10) + "px";
637 clipContainer.style.top = (scrollPos.top == 0 ? -50 : scrollPos.top + 10) + "px";
638
639 // Reveal the container
640 clipContainer.style.display = "";
641
642 // Select the "revealed" input box
643 try {
644
645 clipInputEle.focus();
646 clipInputEle.select();
647
648 } catch (e){} // Mozilla sometimes throws XPConnect security exceptions
649
650 var timeOutFunc = textToCopy ? afterNativeCopyClipInput : afterNativePasteClipInput;
651
652 // Queue input-box removal function directly after native copy/paste executes
653 clipboardTOID = setTimeout(timeOutFunc, 0);
654
655 }
656
657 /**
658 * Safely hides the "temporary" clipboard input control
659 */
660 function hideClipInput() {
661 clipContainer.style.display = "none";
662 }
663
664 /**
665 * Invoked after the browser natively copies the "temporary" clipboard input control's content
666 */
667 function afterNativeCopyClipInput() {
668 clipboardTOID = null;
669 hideClipInput();
670 isSysClip = true;
671 window.focus();
672 }
673
674 /**
675 * Invoked after the browser natively pastes the system clipboard text to the "temporary" clipboard input control.
676 */
677 function afterNativePasteClipInput() {
678 clipboardTOID = null;
679 hideClipInput();
680
681 // Ensure the cursor did not change/clear
682 if (restoreCursor) {
683 var curCursor = de.cursor.getCurrentCursorDesc();
684 if (!curCursor || curCursor.domNode != restoreCursor.domNode || curCursor.relIndex != restoreCursor.relIndex) de.cursor.setCursor(restoreCursor);
685 }
686
687 internalPaste(clipInputEle.value);
688
689 window.focus();
690 }
691
692
693
694})();
Note: See TracBrowser for help on using the repository browser.