Bug 697407 - Another Orion upstream update; r=rcampbell
authorMihai Sucan <mihai.sucan@gmail.com>
Mon, 31 Oct 2011 13:07:51 +0200
changeset 80110 b78d449f0c8b8e77a78a3b7ce8fb859ad7d253c8
parent 80086 cf1a56ba4a68554c15c40fa75803cc8fe2de984e
child 80111 1fb831781a15926b4d4969621f18f0937170b28e
push id506
push userclegnitto@mozilla.com
push dateWed, 09 Nov 2011 02:03:18 +0000
treeherdermozilla-aurora@63587fc7bb93 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrcampbell
bugs697407
milestone10.0a1
Bug 697407 - Another Orion upstream update; r=rcampbell
browser/devtools/sourceeditor/orion/Makefile.dryice.js
browser/devtools/sourceeditor/orion/README
browser/devtools/sourceeditor/orion/mozilla.css
browser/devtools/sourceeditor/orion/orion.css
browser/devtools/sourceeditor/orion/orion.js
browser/devtools/sourceeditor/test/Makefile.in
browser/devtools/sourceeditor/test/browser_bug684862_paste_html.js
browser/devtools/sourceeditor/test/browser_bug687568_pagescroll.js
browser/devtools/sourceeditor/test/browser_bug687573_vscroll.js
browser/devtools/sourceeditor/test/browser_bug687580_drag_and_drop.js
browser/devtools/sourceeditor/test/browser_sourceeditor_initialization.js
--- a/browser/devtools/sourceeditor/orion/Makefile.dryice.js
+++ b/browser/devtools/sourceeditor/orion/Makefile.dryice.js
@@ -44,16 +44,17 @@ const ORION_EDITOR = "org.eclipse.orion.
 var js_src = copy.createDataObject();
 
 copy({
   source: [
     ORION_EDITOR + "/orion/textview/keyBinding.js",
     ORION_EDITOR + "/orion/textview/rulers.js",
     ORION_EDITOR + "/orion/textview/undoStack.js",
     ORION_EDITOR + "/orion/textview/textModel.js",
+    ORION_EDITOR + "/orion/textview/tooltip.js",
     ORION_EDITOR + "/orion/textview/textView.js",
     ORION_EDITOR + "/orion/editor/htmlGrammar.js",
     ORION_EDITOR + "/orion/editor/textMateStyler.js",
     ORION_EDITOR + "/examples/textview/textStyler.js",
   ],
   dest: js_src,
 });
 
--- a/browser/devtools/sourceeditor/orion/README
+++ b/browser/devtools/sourceeditor/orion/README
@@ -3,18 +3,30 @@
 This is the Orion editor packaged for Mozilla.
 
 The Orion editor web site: http://www.eclipse.org/orion
 
 # Upgrade
 
 To upgrade Orion to a newer version see the UPGRADE file.
 
-Orion version: git clone from 2011-10-07
-               commit hash eedba6403b6dff4536bc0469d31126c3485deb56
+Orion version: git clone from 2011-10-26
+               commit hash 0ab295660e1f7d33ca2bfb8558b3b7492d2c5aa5
+  + patch for Eclipse Bug 358623 - Drag and Drop support:
+    https://github.com/mihaisucan/orion.client/tree/bug-358623
+      see https://bugs.eclipse.org/bugs/show_bug.cgi?id=358623
+  + patch for Eclipse Bug 362286 - Monaco font line height:
+    https://github.com/mihaisucan/orion.client/tree/bug-362286
+      see https://bugs.eclipse.org/bugs/show_bug.cgi?id=362286
+  + patch for Eclipse Bug 362107 - Ctrl-Up/Down failure on Linux:
+    https://github.com/mihaisucan/orion.client/tree/bug-362107
+      see https://bugs.eclipse.org/bugs/show_bug.cgi?id=362107
+  + patch for Eclipse Bug 362428 - _getXToOffset() throws:
+    https://github.com/mihaisucan/orion.client/tree/bug-362428
+      see https://bugs.eclipse.org/bugs/show_bug.cgi?id=362428
 
 # License
 
 The following files are licensed according to the contents in the LICENSE
 file:
   orion.js
   orion.css
 
--- a/browser/devtools/sourceeditor/orion/mozilla.css
+++ b/browser/devtools/sourceeditor/orion/mozilla.css
@@ -3,9 +3,11 @@
 
 
 .rulerLines {
   background: -moz-Dialog;
   color: -moz-DialogText;
   min-width: 1.4em;
   padding-left: 4px;
   padding-right: 4px;
+  text-align: end;
 }
+
--- a/browser/devtools/sourceeditor/orion/orion.css
+++ b/browser/devtools/sourceeditor/orion/orion.css
@@ -29,35 +29,16 @@
 }
 
 /* Styles for the line number ruler */
 .rulerLines {
 	background-color: white;
 }
 .rulerLines.even
 .rulerLines.odd {
-}
-
-/* Styles for the ruler tooltips */
-.rulerTooltip {
-	font-family: monospace;
-	font-size: 10pt;
-	background-color: InfoBackground;
-	color: InfoText;
-	padding: 2px;
-	border-radius: 4px;
-	border: 1px solid black;
-	z-index: 100;
-	position: absolute;
-	overflow: hidden;
-	white-space: pre;
-}
-.rulerTooltip em {
-	font-style: normal;
-	font-weight: bold;
 }.token_singleline_comment {
 	color: green;
 }
 
 .token_multiline_comment {
 	color: green;
 }
 
@@ -133,9 +114,9 @@
 .string-quoted {
 	color: #2a00ff;
 	font-style: italic;
 }
 
 .invalid {
 	color: red;
 	font-weight: bold;
-}
+}
\ No newline at end of file
--- a/browser/devtools/sourceeditor/orion/orion.js
+++ b/browser/devtools/sourceeditor/orion/orion.js
@@ -103,17 +103,17 @@ if (typeof window !== "undefined" && typ
  * All rights reserved. This program and the accompanying materials are made 
  * available under the terms of the Eclipse Public License v1.0 
  * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
  * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
  * 
  * Contributors: IBM Corporation - initial API and implementation
  ******************************************************************************/
 
-/*global window define setTimeout clearTimeout setInterval clearInterval */
+/*global window define setTimeout clearTimeout setInterval clearInterval Node */
 
 /**
  * @namespace The global container for Orion APIs.
  */ 
 var orion = orion || {};
 /**
  * @namespace The container for textview APIs.
  */ 
@@ -121,17 +121,17 @@ orion.textview = orion.textview || {};
 
 /**
  * Constructs a new ruler. 
  * <p>
  * The default implementation does not implement all the methods in the interface
  * and is useful only for objects implementing rulers.
  * <p/>
  * 
- * @param {orion.textview.AnnotationModel} [annotationModel] the annotation model for the ruler.
+ * @param {orion.textview.AnnotationModel} annotationModel the annotation model for the ruler.
  * @param {String} [rulerLocation="left"] the location for the ruler.
  * @param {String} [rulerOverview="page"] the overview for the ruler.
  * @param {orion.textview.Style} [rulerStyle] the style for the ruler. 
  * 
  * @class This interface represents a ruler for the text view.
  * <p>
  * A Ruler is a graphical element that is placed either on the left or on the right side of 
  * the view. It can be used to provide the view with per line decoration such as line numbering,
@@ -169,29 +169,33 @@ orion.textview.Ruler = (function() {
 		/**
 		 * Adds an annotation type to the ruler.
 		 * <p>
 		 * Only annotations of the specified types will be shown by
 		 * this ruler.
 		 * </p>
 		 *
 		 * @param type {Object} the annotation type to be shown
+		 * 
+		 * @see #removeAnnotationType
+		 * @see #isAnnotationTypeVisible
 		 */
 		addAnnotationType: function(type) {
 			this._types.push(type);
 		},
 		/**
-		 * Returns the annotations for a given line range.
+		 * Returns the annotations for a given line range merging multiple
+		 * annotations when necessary.
 		 * <p>
-		 * This method is called the the text view when the ruler is redrawn.
+		 * This method is called by the text view when the ruler is redrawn.
 		 * </p>
 		 *
-		 * @param {Number} startLine the line index
-		 * @param {Number} endLine the line index
-		 * @return {orion.textview.LineAnnotation} the annotations for the line range.
+		 * @param {Number} startLine the start line index
+		 * @param {Number} endLine the end line index
+		 * @return {orion.textview.Annotation[]} the annotations for the line range. The array might be sparse.
 		 */
 		getAnnotations: function(startLine, endLine) {
 			var annotationModel = this._annotationModel;
 			if (!annotationModel) { return []; }
 			var model = this._view.getModel();
 			var start = model.getLineStart(startLine);
 			var end = model.getLineEnd(endLine - 1);
 			var baseModel = model;
@@ -220,28 +224,28 @@ orion.textview.Ruler = (function() {
 					if (rulerAnnotation) {
 						result[visualLineIndex] = rulerAnnotation;
 					}
 				}
 			}
 			if (!this._multiAnnotation && this._multiAnnotationOverlay) {
 				for (var k in result) {
 					if (result[k]._multiple) {
-						result[k].html = result[k].html + this._multiAnnotationOverlay.rulerHTML;
+						result[k].html = result[k].html + this._multiAnnotationOverlay.html;
 					}
 				}
 			}
 			return result;
 		},
 		/**
-		 * Returns the ruler annotation model.
+		 * Returns the annotation model.
 		 *
 		 * @returns {orion.textview.AnnotationModel} the ruler annotation model.
 		 *
-		 * @see #getOverview
+		 * @see #setAnnotationModel
 		 */
 		getAnnotationModel: function() {
 			return this._annotationModel;
 		},
 		/**
 		 * Returns the ruler location.
 		 *
 		 * @returns {String} the ruler location, which is either "left" or "right".
@@ -257,233 +261,238 @@ orion.textview.Ruler = (function() {
 		 * @returns {String} the overview type, which is either "page" or "document".
 		 *
 		 * @see #getLocation
 		 */
 		getOverview: function() {
 			return this._overview;
 		},
 		/**
-		 * Returns the CSS styling information for the ruler.
-		 *
-		 * @returns {orion.textview.Style} the CSS styling for ruler.
+		 * Returns the style information for the ruler.
+		 *
+		 * @returns {orion.textview.Style} the style information.
 		 */
 		getRulerStyle: function() {
 			return this._rulerStyle;
 		},
 		/**
 		 * Returns the widest annotation which determines the width of the ruler.
 		 * <p>
 		 * If the ruler does not have a fixed width it should provide the widest
 		 * annotation to avoid the ruler from changing size as the view scrolls.
 		 * </p>
 		 * <p>
-		 * This method is called the the text view when the ruler is redrawn.
+		 * This method is called by the text view when the ruler is redrawn.
 		 * </p>
 		 *
-		 * @returns {orion.textview.Annotation} the annotation for the generic line.
+		 * @returns {orion.textview.Annotation} the widest annotation.
 		 *
 		 * @see #getAnnotations
 		 */
 		getWidestAnnotation: function() {
 			return null;
 		},
 		/**
 		 * Returns whether the ruler shows annotations of the specified type.
 		 *
-		 * @param {Object} the annotation type 
-		 * @returns {Boolean} whether the specified is shown
+		 * @param {Object} type the annotation type 
+		 * @returns {Boolean} whether the specified annotation type is shown
+		 * 
+		 * @see #addAnnotationType
+		 * @see #removeAnnotationType
 		 */
 		isAnnotationTypeVisible: function(type) {
 			for (var i = 0; i < this._types.length; i++) {
 				if (this._types[i] === type) {
 					return true;
 				}
 			}
 			return false;
 		},
 		/**
 		 * Removes an annotation type from the ruler.
 		 *
-		 * @param type {Object} the annotation type to be shown
+		 * @param {Object} type the annotation type to be removed
+		 * 
+		 * @see #addAnnotationType
+		 * @see #isAnnotationTypeVisible
 		 */
 		removeAnnotationType: function(type) {
 			for (var i = 0; i < this._types.length; i++) {
 				if (this._types[i] === type) {
 					this._types.splice(i, 1);
 					break;
 				}
 			}
 		},
 		/**
 		 * Sets the annotation model for the ruler.
 		 *
 		 * @param {orion.textview.AnnotationModel} annotationModel the annotation model.
+		 *
+		 * @see #getAnnotationModel
 		 */
 		setAnnotationModel: function (annotationModel) {
 			if (this._annotationModel) {
 				this._annotationModel.removeListener(this._annotationModelListener); 
 			}
 			this._annotationModel = annotationModel;
 			if (this._annotationModel) {
 				this._annotationModel.addListener(this._annotationModelListener); 
 			}
 		},
 		/**
 		 * Sets the annotation that is displayed when a given line contains multiple
-		 * annotations.
-		 *
-		 * @param {orion.textview.Annotation} the annotation for lines with multiple annotations.
+		 * annotations.  This annotation is used when there are different types of
+		 * annotations in a given line.
+		 *
+		 * @param {orion.textview.Annotation} annotation the annotation for lines with multiple annotations.
+		 * 
+		 * @see #setMultiAnnotationOverlay
 		 */
 		setMultiAnnotation: function(annotation) {
 			this._multiAnnotation = annotation;
 		},
 		/**
-		 * Sets the annotation that overlays a line with multiple  annotations.
-		 *
-		 * @param {orion.textview.Annotation} the annotation overlay for lines with multiple annotations.
+		 * Sets the annotation that overlays a line with multiple annotations.  This annotation is displayed on
+		 * top of the computed annotation for a given line when there are multiple annotations of the same type
+		 * in the line. It is also used when the multiple annotation is not set.
+		 *
+		 * @param {orion.textview.Annotation} annotation the annotation overlay for lines with multiple annotations.
+		 * 
+		 * @see #setMultiAnnotation
 		 */
 		setMultiAnnotationOverlay: function(annotation) {
 			this._multiAnnotationOverlay = annotation;
 		},
 		/**
 		 * Sets the view for the ruler.
 		 * <p>
-		 * This method is called the the text view when the ruler
+		 * This method is called by the text view when the ruler
 		 * is added to the view.
 		 * </p>
 		 *
 		 * @param {orion.textview.TextView} view the text view.
 		 */
 		setView: function (view) {
 			if (this._onTextModelChanged && this._view) {
 				this._view.removeEventListener("ModelChanged", this, this._onTextModelChanged); 
 			}
 			this._view = view;
 			if (this._onTextModelChanged && this._view) {
 				this._view.addEventListener("ModelChanged", this, this._onTextModelChanged);
 			}
 		},
 		/**
-		 * This event is sent when the user clicks a line decoration.
+		 * This event is sent when the user clicks a line annotation.
 		 *
 		 * @event
-		 * @param {Number} lineIndex the line index of the clicked decoration.
+		 * @param {Number} lineIndex the line index of the annotation under the pointer.
 		 * @param {DOMEvent} e the click event.
 		 */
 		onClick: function(lineIndex, e) {
 		},
 		/**
-		 * This event is sent when the user double clicks a line decoration.
+		 * This event is sent when the user double clicks a line annotation.
 		 *
 		 * @event
-		 * @param {Number} lineIndex the line index of the double clicked decoration.
+		 * @param {Number} lineIndex the line index of the annotation under the pointer.
 		 * @param {DOMEvent} e the double click event.
 		 */
 		onDblClick: function(lineIndex, e) {
 		},
+		/**
+		 * This event is sent when the user moves the mouse over a line annotation.
+		 *
+		 * @event
+		 * @param {Number} lineIndex the line index of the annotation under the pointer.
+		 * @param {DOMEvent} e the mouse move event.
+		 */
 		onMouseMove: function(lineIndex, e) {
-			if (this._tooltip && this._tooltipLineIndex === lineIndex) { return; }
+			var tooltip = orion.textview.Tooltip.getTooltip(this._view);
+			if (!tooltip) { return; }
+			if (tooltip.isVisible() && this._tooltipLineIndex === lineIndex) { return; }
+			this._tooltipLineIndex = lineIndex;
 			var self = this;
-			self._hideTooltip();
-			self._tooltipLineIndex = lineIndex;
-			self._tooltipClientY = e.clientY;
-			self._tooltipShowTimeout = setTimeout(function() {
-				self._showTooltip();
-				if (self._tooltip) {
-					self._tooltipHideTimeout = setTimeout(function() {
-						var opacity = parseFloat(self._getNodeStyle(self._tooltip, "opacity", "1"));
-						self._tooltipFadeTimeout = setInterval(function() {
-							if (self._tooltip && opacity > 0) {
-								opacity -= 0.1;
-								self._tooltip.style.opacity = opacity;
-								return;
-							}
-							self._hideTooltip();
-						}, 50);
-					}, 5000);
-				}
-			}, 1000);
-		},
+			tooltip.setTarget({
+				y: e.clientY,
+				getTooltipInfo: function() {
+					return self._getTooltipInfo(self._tooltipLineIndex, this.y);
+				}
+			});
+		},
+		/**
+		 * This event is sent when the mouse pointer enters a line annotation.
+		 *
+		 * @event
+		 * @param {Number} lineIndex the line index of the annotation under the pointer.
+		 * @param {DOMEvent} e the mouse over event.
+		 */
 		onMouseOver: this._onMouseMove,
+		/**
+		 * This event is sent when the mouse pointer exits a line annotation.
+		 *
+		 * @event
+		 * @param {Number} lineIndex the line index of the annotation under the pointer.
+		 * @param {DOMEvent} e the mouse out event.
+		 */
 		onMouseOut: function(lineIndex, e) {
-			this._hideTooltip();
-		},
-		_getNodeStyle: function(node, prop, defaultValue) {
-			var value;
-			if (node) {
-				value = node.style[prop];
-				if (!value) {
-					if (node.currentStyle) {
-						var index = 0, p = prop;
-						while ((index = p.indexOf("-", index)) !== -1) {
-							p = p.substring(0, index) + p.substring(index + 1, index + 2).toUpperCase() + p.substring(index + 2);
-						}
-						value = node.currentStyle[p];
-					} else {
-						var css = node.ownerDocument.defaultView.getComputedStyle(node, null);
-						value = css ? css.getPropertyValue(prop) : null;
-					}
-				}
-			}
-			return value || defaultValue;
-		},
-		_getTooltip: function(document, lineIndex, annotations) {
-			if (annotations.length === 0) { return null; }
-			var model = this._view.getModel(), annotation;
-			function getText(start, end) {
-				var m = model.getBaseModel ? model.getBaseModel() : model;
-				var textStart = m.getLineStart(m.getLineAtOffset(start));
-				var textEnd = m.getLineEnd(m.getLineAtOffset(end), true);
-				return m.getText(textStart, textEnd);
-			}
-			var title;
-			if (annotations.length === 1) {
-				annotation = annotations[0];
-				if (annotation.rulerTitle) {
-					title = annotation.rulerTitle.replace(/</g, "&lt;").replace(/>/g, "&gt;");
-					return annotation.rulerHTML + "&nbsp;" + title;
-				} else {
-					//TODO show a projection textview to get coloring 
-					return document.createTextNode(getText(annotation.start, annotation.end));
-				}
+			var tooltip = orion.textview.Tooltip.getTooltip(this._view);
+			if (!tooltip) { return; }
+			tooltip.setTarget(null);
+		},
+		/** @ignore */
+		_getTooltipInfo: function(lineIndex, y) {
+			if (lineIndex === undefined) { return; }
+			var view = this._view;
+			var model = view.getModel();
+			var annotationModel = this._annotationModel;
+			var annotations = [];
+			if (annotationModel) {
+				var start = model.getLineStart(lineIndex);
+				var end = model.getLineEnd(lineIndex);
+				if (model.getBaseModel) {
+					start = model.mapOffset(start);
+					end = model.mapOffset(end);
+				}
+				var iter = annotationModel.getAnnotations(start, end);
+				var annotation;
+				while (iter.hasNext()) {
+					annotation = iter.next();
+					if (!this.isAnnotationTypeVisible(annotation.type)) { continue; }
+					annotations.push(annotation);
+				}
+			}
+			var contents = this._getTooltipContents(lineIndex, annotations);
+			if (!contents) { return null; }
+			var info = {
+				contents: contents,
+				anchor: this.getLocation()
+			};
+			var rect = view.getClientArea();
+			if (this.getOverview() === "document") {
+				rect.y = view.convert({y: y}, "view", "document").y;
 			} else {
-				var tooltipHTML = "<em>Multiple annotations:</em><br>";
-				for (var i = 0; i < annotations.length; i++) {
-					annotation = annotations[i];
-					title = annotation.rulerTitle;
-					if (!title) {
-						title = getText(annotation.start, annotation.end);
-					}
-					title = title.replace(/</g, "&lt;").replace(/>/g, "&gt;");
-					tooltipHTML += annotation.rulerHTML + "&nbsp;" + title + "<br>";
-				}
-				return tooltipHTML;
-			}
-		},	
-		_hideTooltip: function() {
-			this._tooltipLineIndex = this._tooltipEvent = undefined;
-			if (this._tooltip) {
-				var parent = this._tooltip.parentNode;
-				if (parent) { parent.removeChild(this._tooltip); }
-				this._tooltip = null;
-			}
-			if (this._tooltipShowTimeout) {
-				clearTimeout(this._tooltipShowTimeout);
-				this._tooltipShowTimeout = null;
-			}
-			if (this._tooltipHideTimeout) {
-				clearTimeout(this._tooltipHideTimeout);
-				this._tooltipHideTimeout = null;
-			}
-			if (this._tooltipFadeTimeout) {
-				clearInterval(this._tooltipFadeTimeout);
-				this._tooltipFadeTimeout = null;
-			}
-		},
+				rect.y = view.getLocationAtOffset(model.getLineStart(lineIndex)).y;
+			}
+			view.convert(rect, "document", "page");
+			info.x = rect.x;
+			info.y = rect.y;
+			if (info.anchor === "right") {
+				info.x += rect.width;
+			}
+			info.maxWidth = rect.width;
+			info.maxHeight = rect.height - (rect.y - view._parent.getBoundingClientRect().top);
+			return info;
+		},
+		/** @ignore */
+		_getTooltipContents: function(lineIndex, annotations) {
+			return annotations;
+		},
+		/** @ignore */
 		_onAnnotationModelChanged: function(e) {
 			var view = this._view;
 			if (!view) { return; }
 			var model = view.getModel(), self = this;
 			var lineCount = model.getLineCount();
 			if (e.textModelChangedEvent) {
 				var start = e.textModelChangedEvent.start;
 				if (model.getBaseModel) { start = model.mapOffset(start, true); }
@@ -504,33 +513,35 @@ orion.textview.Ruler = (function() {
 						view.redrawLines(model.getLineAtOffset(start), model.getLineAtOffset(Math.max(start, end - 1)) + 1, self);
 					}
 				}
 			}
 			redraw(e.added);
 			redraw(e.removed);
 			redraw(e.changed);
 		},
+		/** @ignore */
 		_mergeAnnotation: function(result, annotation, annotationLineIndex, annotationLineCount) {
 			if (!result) { result = {}; }
 			if (annotationLineIndex === 0) {
-				if (result.html && annotation.rulerHTML) {
-					if (annotation.rulerHTML !== result.html) {
+				if (result.html && annotation.html) {
+					if (annotation.html !== result.html) {
 						if (!result._multiple && this._multiAnnotation) {
-							result.html = this._multiAnnotation.rulerHTML;
+							result.html = this._multiAnnotation.html;
 						}
 					} 
 					result._multiple = true;
 				} else {
-					result.html = annotation.rulerHTML;
-				}
-			}
-			result.style = this._mergeStyle(result.style, annotation.rulerStyle);
+					result.html = annotation.html;
+				}
+			}
+			result.style = this._mergeStyle(result.style, annotation.style);
 			return result;
 		},
+		/** @ignore */
 		_mergeStyle: function(result, style) {
 			if (style) {
 				if (!result) { result = {}; }
 				if (result.styleClass && style.styleClass && result.styleClass !== style.styleClass) {
 					result.styleClass += " " + style.styleClass;
 				} else {
 					result.styleClass = style.styleClass;
 				}
@@ -548,74 +559,16 @@ orion.textview.Ruler = (function() {
 					for (prop in style.attributes) {
 						if (!result.attributes[prop]) {
 							result.attributes[prop] = style.attributes[prop];
 						}
 					}
 				}
 			}
 			return result;
-		},
-		_showTooltip: function() {
-			var lineIndex = this._tooltipLineIndex;
-			if (lineIndex === undefined) { return; }
-			var view = this._view;
-			var model = view.getModel();
-			var annotationModel = this._annotationModel;
-			var annotations = [];
-			if (annotationModel) {
-				var start = model.getLineStart(lineIndex);
-				var end = model.getLineEnd(lineIndex);
-				if (model.getBaseModel) {
-					start = model.mapOffset(start);
-					end = model.mapOffset(end);
-				}
-				var iter = annotationModel.getAnnotations(start, end);
-				var annotation;
-				while (iter.hasNext()) {
-					annotation = iter.next();
-					if (!this.isAnnotationTypeVisible(annotation.type)) { continue; }
-					annotations.push(annotation);
-				}
-			}
-			var document = this._view._parentDocument;//TODO bad not API
-			var tooltipContent = this._getTooltip(document, lineIndex, annotations);
-			if (!tooltipContent) { return; }
-			var tooltip = this._tooltip = document.createElement("DIV");
-			tooltip.className = "rulerTooltip";
-			if (typeof tooltipContent === "string") {
-				tooltip.innerHTML = tooltipContent;
-			} else {
-				tooltip.appendChild(tooltipContent);
-			}
-			var rect = view.getClientArea();
-			if (this.getOverview() === "document") {
-				rect.y = view.convert({y: this._tooltipClientY}, "view", "document").y;
-			} else {
-				rect.y = view.getLocationAtOffset(model.getLineStart(lineIndex)).y;
-			}
-			view.convert(rect, "document", "page");
-			tooltip.style.visibility = "hidden";
-			document.body.appendChild(tooltip);
-			var left = parseInt(this._getNodeStyle(tooltip, "padding-left", "0"), 10);
-			left += parseInt(this._getNodeStyle(tooltip, "border-left-width", "0"), 10);
-			var top = parseInt(this._getNodeStyle(tooltip, "padding-top", "0"), 10);
-			top += parseInt(this._getNodeStyle(tooltip, "border-top-width", "0"), 10);
-			rect.y -= top;
-			if (this.getLocation() === "right") {
-				var right = parseInt(this._getNodeStyle(tooltip, "padding-right", "0"), 10);
-				right += parseInt(this._getNodeStyle(tooltip, "border-right-width", "0"), 10);
-				tooltip.style.right = (document.body.getBoundingClientRect().right - (rect.x + rect.width) + left + right) + "px";
-			} else {
-				tooltip.style.left = (rect.x - left) + "px";
-			}
-			tooltip.style.top = rect.y + "px";
-			tooltip.style.maxWidth = rect.width + "px";
-			tooltip.style.maxHeight = (rect.height - (rect.y - view._parent.getBoundingClientRect().top)) + "px";
-			tooltip.style.visibility = "visible";
 		}
 	};
 	return Ruler;
 }());
 
 /**
  * Constructs a new line numbering ruler. 
  *
@@ -751,27 +704,27 @@ orion.textview.OverviewRuler = (function
 		return result;
 	};
 	/** @ignore */	
 	OverviewRuler.prototype.onClick = function(lineIndex, e) {
 		if (lineIndex === undefined) { return; }
 		this._view.setTopIndex(lineIndex);
 	};
 	/** @ignore */
-	OverviewRuler.prototype._getTooltip = function(document, lineIndex, annotations) {
+	OverviewRuler.prototype._getTooltipContents = function(lineIndex, annotations) {
 		if (annotations.length === 0) {
 			var model = this._view.getModel();
 			var mapLine = lineIndex;
 			if (model.getBaseModel) {
 				var lineStart = model.getLineStart(mapLine);
 				mapLine = model.getBaseModel().getLineAtOffset(model.mapOffset(lineStart));
 			}
 			return "Line: " + (mapLine + 1);
 		}
-		return orion.textview.Ruler.prototype._getTooltip.call(this, document, lineIndex, annotations);
+		return orion.textview.Ruler.prototype._getTooltipContents.call(this, lineIndex, annotations);
 	};
 	/** @ignore */
 	OverviewRuler.prototype._mergeAnnotation = function(previousAnnotation, annotation, annotationLineIndex, annotationLineCount) {
 		if (annotationLineIndex !== 0) { return undefined; }
 		var result = previousAnnotation;
 		if (!result) {
 			//TODO annotationLineCount does not work when there are folded lines
 			var height = 3 * annotationLineCount;
@@ -805,33 +758,36 @@ orion.textview.FoldingRuler = (function(
 		}
 		var annotation, iter = annotationModel.getAnnotations(start, end);
 		while (!annotation && iter.hasNext()) {
 			var a = iter.next();
 			if (!this.isAnnotationTypeVisible(a.type)) { continue; }
 			annotation = a;
 		}
 		if (annotation) {
-			this._hideTooltip();
+			var tooltip = orion.textview.Tooltip.getTooltip(this._view);
+			if (tooltip) {
+				tooltip.setTarget(null);
+			}
 			if (annotation.expanded) {
 				annotation.collapse();
 			} else {
 				annotation.expand();
 			}
 			this._annotationModel.modifyAnnotation(annotation);
 		}
 	};
 	/** @ignore */
-	FoldingRuler.prototype._getTooltip = function(document, lineIndex, annotations) {
+	FoldingRuler.prototype._getTooltipContents = function(lineIndex, annotations) {
 		if (annotations.length === 1) {
 			if (annotations[0].expanded) {
 				return null;
 			}
 		}
-		return orion.textview.AnnotationRuler.prototype._getTooltip.call(this, document, lineIndex, annotations);
+		return orion.textview.AnnotationRuler.prototype._getTooltipContents.call(this, lineIndex, annotations);
 	};
 	/** @ignore */
 	FoldingRuler.prototype._onAnnotationModelChanged = function(e) {
 		if (e.textModelChangedEvent) {
 			orion.textview.AnnotationRuler.prototype._onAnnotationModelChanged.call(this, e);
 			return;
 		}
 		var view = this._view;
@@ -858,17 +814,17 @@ orion.textview.FoldingRuler = (function(
 			view.redrawLines(lineIndex, lineCount, rulers[i]);
 		}
 	};
 	
 	return FoldingRuler;
 }());
 
 if (typeof window !== "undefined" && typeof window.define !== "undefined") {
-	define([], function() {
+	define(['orion/textview/tooltip'], function() {
 		return orion.textview;
 	});
 }
 /*******************************************************************************
  * Copyright (c) 2010, 2011 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials are made 
  * available under the terms of the Eclipse Public License v1.0 
  * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
@@ -1010,29 +966,22 @@ orion.textview.UndoStack = (function() {
 		this.reset();
 		var model = view.getModel();
 		if (model.getBaseModel) {
 			model = model.getBaseModel();
 		}
 		this.model = model;
 		var self = this;
 		this._modelListener = {
-			onChanging: function(text, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
-				var e = {
-					text: text,
-					start: start, 
-					removedCharCount: removedCharCount,
-					addedCharCount: addedCharCount,
-					removedLineCount: removedLineCount,
-					addedLineCount: addedLineCount
-				};
+			onChanging: function(e) {
 				self._onModelChanging(e);
 			}
 		};
 		model.addListener(this._modelListener);
+		view._undoStack = this;
 		view.addEventListener("Destroy", this, this._onDestroy);
 	}
 	UndoStack.prototype = /** @lends orion.textview.UndoStack.prototype */ {
 		/**
 		 * Adds a change to the stack.
 		 * 
 		 * @param change the change to add.
 		 * @param {Number} change.offset the offset of the change
@@ -1208,16 +1157,17 @@ orion.textview.UndoStack = (function() {
 				}
 				this._undoStart = undefined;
 				this._undoText = "";
 			}
 		},
 		_onDestroy: function() {
 			this.model.removeListener(this._modelListener);
 			this.view.removeEventListener("Destroy", this, this._onDestroy);
+			this.view._undoStack = null;
 		},
 		_onModelChanging: function(e) {
 			var newText = e.text;
 			var start = e.start;
 			var removedCharCount = e.removedCharCount;
 			var addedCharCount = e.addedCharCount;
 			if (this._ignoreUndo) {
 				return;
@@ -1554,54 +1504,45 @@ orion.textview.TextModel = (function() {
 		 * use {@link orion.textview.TextView#event:onModelChanging}.
 		 * </p>
 		 * <p>
 		 * NOTE: This method is not meant to called directly by application code. It is called internally by the TextModel
 		 * as part of the implementation of {@link #setText}. This method is included in the public API for documentation
 		 * purposes and to allow integration with other toolkit frameworks.
 		 * </p>
 		 *
-		 * @param {String} text the text that is about to be inserted in the model.
-		 * @param {Number} start the character offset in the model where the change will occur.
-		 * @param {Number} removedCharCount the number of characters being removed from the model.
-		 * @param {Number} addedCharCount the number of characters being added to the model.
-		 * @param {Number} removedLineCount the number of lines being removed from the model.
-		 * @param {Number} addedLineCount the number of lines being added to the model.
-		 */
-		onChanging: function(text, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
+		 * @param {orion.textview.ModelChangingEvent} modelChangingEvent the changing event
+		 */
+		onChanging: function(modelChangingEvent) {
 			for (var i = 0; i < this._listeners.length; i++) {
 				var l = this._listeners[i]; 
 				if (l && l.onChanging) { 
-					l.onChanging(text, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount);
+					l.onChanging(modelChangingEvent);
 				}
 			}
 		},
 		/**
 		 * Notifies all listeners that the text has changed.
 		 * <p>
 		 * This notification is intended to be used only by the view. Application clients should
 		 * use {@link orion.textview.TextView#event:onModelChanged}.
 		 * </p>
 		 * <p>
 		 * NOTE: This method is not meant to called directly by application code. It is called internally by the TextModel
 		 * as part of the implementation of {@link #setText}. This method is included in the public API for documentation
 		 * purposes and to allow integration with other toolkit frameworks.
 		 * </p>
 		 *
-		 * @param {Number} start the character offset in the model where the change occurred.
-		 * @param {Number} removedCharCount the number of characters removed from the model.
-		 * @param {Number} addedCharCount the number of characters added to the model.
-		 * @param {Number} removedLineCount the number of lines removed from the model.
-		 * @param {Number} addedLineCount the number of lines added to the model.
-		 */
-		onChanged: function(start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
+		 * @param {orion.textview.ModelChangedEvent} modelChangedEvent the changed event
+		 */
+		onChanged: function(modelChangedEvent) {
 			for (var i = 0; i < this._listeners.length; i++) {
 				var l = this._listeners[i]; 
 				if (l && l.onChanged) { 
-					l.onChanged(start, removedCharCount, addedCharCount, removedLineCount, addedLineCount);
+					l.onChanged(modelChangedEvent);
 				}
 			}
 		},
 		/**
 		 * Sets the line delimiter that is used by the view
 		 * when new lines are inserted in the model due to key
 		 * strokes  and paste operations.
 		 * <p>
@@ -1669,17 +1610,25 @@ orion.textview.TextModel = (function() {
 					index = cr + 1;
 				} else {
 					index = lf + 1;
 				}
 				newLineOffsets.push(start + index);
 				addedLineCount++;
 			}
 		
-			this.onChanging(text, eventStart, removedCharCount, addedCharCount, removedLineCount, addedLineCount);
+			var modelChangingEvent = {
+				text: text,
+				start: eventStart,
+				removedCharCount: removedCharCount,
+				addedCharCount: addedCharCount,
+				removedLineCount: removedLineCount,
+				addedLineCount: addedLineCount
+			};
+			this.onChanging(modelChangingEvent);
 			
 			//TODO this should be done the loops below to avoid getText()
 			if (newLineOffsets.length === 0) {
 				var startLineOffset = this.getLineStart(startLine), endLineOffset;
 				if (endLine + 1 < lineCount) {
 					endLineOffset = this.getLineStart(endLine + 1);
 				} else {
 					endLineOffset = this.getCharCount();
@@ -1724,17 +1673,24 @@ orion.textview.TextModel = (function() {
 			var afterText = lastText.substring(end - lastOffset);
 			var params = [firstChunk, lastChunk - firstChunk + 1];
 			if (beforeText) { params.push(beforeText); }
 			if (text) { params.push(text); }
 			if (afterText) { params.push(afterText); }
 			Array.prototype.splice.apply(this._text, params);
 			if (this._text.length === 0) { this._text = [""]; }
 			
-			this.onChanged(eventStart, removedCharCount, addedCharCount, removedLineCount, addedLineCount);
+			var modelChangedEvent = {
+				start: eventStart,
+				removedCharCount: removedCharCount,
+				addedCharCount: addedCharCount,
+				removedLineCount: removedLineCount,
+				addedLineCount: addedLineCount
+			};
+			this.onChanged(modelChangedEvent);
 		}
 	};
 	
 	return TextModel;
 }());
 
 if (typeof window !== "undefined" && typeof window.define !== "undefined") {
 	define([], function() {
@@ -1743,20 +1699,265 @@ if (typeof window !== "undefined" && typ
 }
 /*******************************************************************************
  * Copyright (c) 2010, 2011 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials are made 
  * available under the terms of the Eclipse Public License v1.0 
  * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
  * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
  * 
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+
+/*global window define setTimeout clearTimeout setInterval clearInterval Node */
+
+/**
+ * @namespace The global container for Orion APIs.
+ */ 
+var orion = orion || {};
+/**
+ * @namespace The container for textview APIs.
+ */ 
+orion.textview = orion.textview || {};
+
+/** @ignore */
+orion.textview.Tooltip = (function() {
+	/** @private */
+	function Tooltip (view) {
+		this._view = view;
+		//TODO add API to get the parent of the view
+		this._create(view._parent.ownerDocument);
+		view.addEventListener("Destroy", this, this.destroy);
+	}
+	Tooltip.getTooltip = function(view) {
+		if (!view._tooltip) {
+			 view._tooltip = new Tooltip(view);
+		}
+		return view._tooltip;
+	};
+	Tooltip.prototype = /** @lends orion.textview.Tooltip.prototype */ {
+		_create: function(document) {
+			if (this._domNode) { return; }
+			this._document = document;
+			var domNode = this._domNode = document.createElement("DIV");
+			domNode.className = "viewTooltip";
+			var viewParent = this._viewParent = document.createElement("DIV");
+			domNode.appendChild(viewParent);
+			var htmlParent = this._htmlParent = document.createElement("DIV");
+			domNode.appendChild(htmlParent);
+			document.body.appendChild(domNode);
+			this.hide();
+		},
+		destroy: function() {
+			if (!this._domNode) { return; }
+			if (this._contentsView) {
+				this._contentsView.destroy();
+				this._contentsView = null;
+				this._emptyModel = null;
+			}
+			var parent = this._domNode.parentNode;
+			if (parent) { parent.removeChild(this._domNode); }
+			this._domNode = null;
+		},
+		hide: function() {
+			if (this._contentsView) {
+				this._contentsView.setModel(this._emptyModel);
+			}
+			if (this._viewParent) {
+				this._viewParent.style.left = "-10000px";
+				this._viewParent.style.position = "fixed";
+				this._viewParent.style.visibility = "hidden";
+			}
+			if (this._htmlParent) {
+				this._htmlParent.style.left = "-10000px";
+				this._htmlParent.style.position = "fixed";
+				this._htmlParent.style.visibility = "hidden";
+				this._htmlParent.innerHTML = "";
+			}
+			if (this._domNode) {
+				this._domNode.style.visibility = "hidden";
+			}
+			if (this._showTimeout) {
+				clearTimeout(this._showTimeout);
+				this._showTimeout = null;
+			}
+			if (this._hideTimeout) {
+				clearTimeout(this._hideTimeout);
+				this._hideTimeout = null;
+			}
+			if (this._fadeTimeout) {
+				clearInterval(this._fadeTimeout);
+				this._fadeTimeout = null;
+			}
+		},
+		isVisible: function() {
+			return this._domNode && this._domNode.style.visibility === "visible";
+		},
+		setTarget: function(target) {
+			if (this.target === target) { return; }
+			this._target = target;
+			this.hide();
+			if (target) {
+				var self = this;
+				self._showTimeout = setTimeout(function() {
+					self.show(true);
+				}, 1000);
+			}
+		},
+		show: function(autoHide) {
+			if (!this._target) { return; }
+			var info = this._target.getTooltipInfo();
+			if (!info) { return; }
+			var domNode = this._domNode;
+			domNode.style.left = domNode.style.right = domNode.style.width = domNode.style.height = "auto";
+			var contents = info.contents, contentsDiv;
+			if (contents instanceof Array) {
+				contents = this._getAnnotationContents(contents);
+			}
+			if (typeof contents === "string") {
+				(contentsDiv = this._htmlParent).innerHTML = contents;
+			} else if (contents instanceof Node) {
+				(contentsDiv = this._htmlParent).appendChild(contents);
+			} else if (contents instanceof orion.textview.ProjectionTextModel) {
+				if (!this._contentsView) {
+					this._emptyModel = new orion.textview.TextModel("");
+					//TODO need hook into setup.js (or editor.js) to create a text view (and styler)
+					var newView = this._contentsView = new orion.textview.TextView({
+						model: this._emptyModel,
+						parent: this._viewParent,
+						tabSize: 4,
+						stylesheet: ["/orion/textview/tooltip.css", "/orion/textview/rulers.css",
+							"/examples/textview/textstyler.css", "/css/default-theme.css"]
+					});
+					//TODO this is need to avoid IE from getting focus
+					newView._clientDiv.contentEditable = false;
+					//TODO need to find a better way of sharing the styler for multiple views
+					var view = this._view;
+					newView.addEventListener("LineStyle", view, view.onLineStyle);
+				}
+				var contentsView = this._contentsView;
+				contentsView.setModel(contents);
+				var size = contentsView.computeSize();
+				contentsDiv = this._viewParent;
+				//TODO always make the width larger than the size of the scrollbar to avoid bug in updatePage
+				contentsDiv.style.width = (size.width + 20) + "px";
+				contentsDiv.style.height = size.height + "px";
+			} else {
+				return;
+			}
+			contentsDiv.style.left = "auto";
+			contentsDiv.style.position = "static";
+			contentsDiv.style.visibility = "visible";
+			var left = parseInt(this._getNodeStyle(domNode, "padding-left", "0"), 10);
+			left += parseInt(this._getNodeStyle(domNode, "border-left-width", "0"), 10);
+			if (info.anchor === "right") {
+				var right = parseInt(this._getNodeStyle(domNode, "padding-right", "0"), 10);
+				right += parseInt(this._getNodeStyle(domNode, "border-right-width", "0"), 10);
+				domNode.style.right = (domNode.ownerDocument.body.getBoundingClientRect().right - info.x + left + right) + "px";
+			} else {
+				domNode.style.left = (info.x - left) + "px";
+			}
+			var top = parseInt(this._getNodeStyle(domNode, "padding-top", "0"), 10);
+			top += parseInt(this._getNodeStyle(domNode, "border-top-width", "0"), 10);
+			domNode.style.top = (info.y - top) + "px";
+			domNode.style.maxWidth = info.maxWidth + "px";
+			domNode.style.maxHeight = info.maxHeight + "px";
+			domNode.style.opacity = "1";
+			domNode.style.visibility = "visible";
+			if (autoHide) {
+				var self = this;
+				self._hideTimeout = setTimeout(function() {
+					var opacity = parseFloat(self._getNodeStyle(domNode, "opacity", "1"));
+					self._fadeTimeout = setInterval(function() {
+						if (domNode.style.visibility === "visible" && opacity > 0) {
+							opacity -= 0.1;
+							domNode.style.opacity = opacity;
+							return;
+						}
+						self.hide();
+					}, 50);
+				}, 5000);
+			}
+		},
+		_getAnnotationContents: function(annotations) {
+			if (annotations.length === 0) {
+				return null;
+			}
+			var model = this._view.getModel(), annotation;
+			var baseModel = model.getBaseModel ? model.getBaseModel() : model;
+			function getText(start, end) {
+				var textStart = baseModel.getLineStart(baseModel.getLineAtOffset(start));
+				var textEnd = baseModel.getLineEnd(baseModel.getLineAtOffset(end), true);
+				return baseModel.getText(textStart, textEnd);
+			}
+			var title;
+			if (annotations.length === 1) {
+				annotation = annotations[0];
+				if (annotation.title) {
+					title = annotation.title.replace(/</g, "&lt;").replace(/>/g, "&gt;");
+					return annotation.html + "&nbsp;" + title;
+				} else {
+					var newModel = new orion.textview.ProjectionTextModel(baseModel);
+					var lineStart = baseModel.getLineStart(baseModel.getLineAtOffset(annotation.start));
+					newModel.addProjection({start: annotation.end, end: newModel.getCharCount()});
+					newModel.addProjection({start: 0, end: lineStart});
+					return newModel;
+				}
+			} else {
+				var tooltipHTML = "<em>Multiple annotations:</em><br>";
+				for (var i = 0; i < annotations.length; i++) {
+					annotation = annotations[i];
+					title = annotation.title;
+					if (!title) {
+						title = getText(annotation.start, annotation.end);
+					}
+					title = title.replace(/</g, "&lt;").replace(/>/g, "&gt;");
+					tooltipHTML += annotation.html + "&nbsp;" + title + "<br>";
+				}
+				return tooltipHTML;
+			}
+		},
+		_getNodeStyle: function(node, prop, defaultValue) {
+			var value;
+			if (node) {
+				value = node.style[prop];
+				if (!value) {
+					if (node.currentStyle) {
+						var index = 0, p = prop;
+						while ((index = p.indexOf("-", index)) !== -1) {
+							p = p.substring(0, index) + p.substring(index + 1, index + 2).toUpperCase() + p.substring(index + 2);
+						}
+						value = node.currentStyle[p];
+					} else {
+						var css = node.ownerDocument.defaultView.getComputedStyle(node, null);
+						value = css ? css.getPropertyValue(prop) : null;
+					}
+				}
+			}
+			return value || defaultValue;
+		}
+	};
+	return Tooltip;
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define([], function() {
+		return orion.textview;
+	});
+}/*******************************************************************************
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ * 
  * Contributors: 
  *		Felipe Heidrich (IBM Corporation) - initial API and implementation
  *		Silenio Quarti (IBM Corporation) - initial API and implementation
- *		Mihai Sucan (Mozilla Foundation) - fix for Bug#334583 Bug#348471 Bug#349485 Bug#350595
+ *		Mihai Sucan (Mozilla Foundation) - fix for Bug#334583 Bug#348471 Bug#349485 Bug#350595 Bug#360726 Bug#361180 Bug#358623 Bug#362286 Bug#362107 Bug#362428
  ******************************************************************************/
 
 /*global window document navigator setTimeout clearTimeout XMLHttpRequest define */
 
 /**
  * @namespace The global container for Orion APIs.
  */ 
 var orion = orion || {};
@@ -1804,16 +2005,17 @@ orion.textview.TextView = (function() {
 	var isSafari = navigator.userAgent.indexOf("Safari") !== -1;
 	var isWebkit = navigator.userAgent.indexOf("WebKit") !== -1;
 	var isPad = navigator.userAgent.indexOf("iPad") !== -1;
 	var isMac = navigator.platform.indexOf("Mac") !== -1;
 	var isWindows = navigator.platform.indexOf("Win") !== -1;
 	var isLinux = navigator.platform.indexOf("Linux") !== -1;
 	var isW3CEvents = typeof window.document.documentElement.addEventListener === "function";
 	var isRangeRects = (!isIE || isIE >= 9) && typeof window.document.createRange().getBoundingClientRect === "function";
+	var isDnD = isFirefox || isWebkit; // drag and drop support
 	var platformDelimiter = isWindows ? "\r\n" : "\n";
 	
 	/** 
 	 * Constructs a new Selection object.
 	 * 
 	 * @class A Selection represents a range of selected text in the view.
 	 * @name orion.textview.Selection
 	 */
@@ -2002,16 +2204,51 @@ orion.textview.TextView = (function() {
 		 * @param {orion.textview.Ruler} ruler the ruler.
 		 */
 		addRuler: function (ruler) {
 			this._rulers.push(ruler);
 			ruler.setView(this);
 			this._createRuler(ruler);
 			this._updatePage();
 		},
+		computeSize: function() {
+			var w = 0, h = 0;
+			var model = this._model, clientDiv = this._clientDiv;
+			var clientWidth = clientDiv.style.width;
+			/*
+			* Feature in WekKit. Webkit limits the width of the lines
+			* computed below to the width of the client div.  This causes
+			* the lines to be wrapped even though "pre" is set.  The fix
+			* is to set the width of the client div to a larger number
+			* before computing the lines width.  Note that this value is
+			* reset to the appropriate value further down.
+			*/
+			if (isWebkit) {
+				clientDiv.style.width = (0x7FFFF).toString() + "px";
+			}
+			var lineCount = model.getLineCount();
+			var document = this._frameDocument;
+			for (var lineIndex=0; lineIndex<lineCount; lineIndex++) {
+				var child = this._getLineNode(lineIndex), dummy = null;
+				if (!child || child.lineChanged) {
+					child = dummy = this._createLine(clientDiv, null, document, lineIndex, model);
+				}
+				var rect = this._getLineBoundingClientRect(child);
+				w = Math.max(w, rect.right - rect.left);
+				h += rect.bottom - rect.top;
+				if (dummy) { clientDiv.removeChild(dummy); }
+			}
+			if (isWebkit) {
+				clientDiv.style.width = clientWidth;
+			}
+			var viewPadding = this._getViewPadding();
+			w += viewPadding.right - viewPadding.left;
+			h += viewPadding.bottom - viewPadding.top;
+			return {width: w, height: h};
+		},
 		/**
 		 * Converts the given rectangle from one coordinate spaces to another.
 		 * <p>The supported coordinate spaces are:
 		 * <ul>
 		 *   <li>"document" - relative to document, the origin is the top-left corner of first line</li>
 		 *   <li>"page" - relative to html page that contains the text view</li>
 		 *   <li>"view" - relative to text view, the origin is the top-left corner of the view container</li>
 		 * </ul>
@@ -2097,16 +2334,18 @@ orion.textview.TextView = (function() {
 			* and the view contents and handlers is released properly by
 			* destroyView().
 			*/
 			this._destroyFrame();
 
 			var e = {};
 			this.onDestroy(e);
 
+			this._dragStartSelection = null;
+			this._dropDestination = null;
 			this._parent = null;
 			this._parentDocument = null;
 			this._model = null;
 			this._selection = null;
 			this._doubleClickSelection = null;
 			this._eventTable = null;
 			this._keyBindings = null;
 			this._actions = null;
@@ -2576,16 +2815,17 @@ orion.textview.TextView = (function() {
 		 * @class This is the event sent when the text view needs the style information for a line.
 		 * <p>
 		 * <b>See:</b><br/>
 		 * {@link orion.textview.TextView}<br/>
 		 * {@link orion.textview.TextView#event:onLineStyle}
 		 * </p>		 
 		 * @name orion.textview.LineStyleEvent
 		 * 
+		 * @property {orion.textview.TextView} textView The text view.		 
 		 * @property {Number} lineIndex The line index.
 		 * @property {String} lineText The line text.
 		 * @property {Number} lineStart The character offset, relative to document, of the first character in the line.
 		 * @property {orion.textview.Style} style The style for the entire line (output argument).
 		 * @property {orion.textview.StyleRange[]} ranges An array of style ranges for the line (output argument).		 
 		 */
 		/**
 		 * This event is sent when the text view needs the style information for a line.
@@ -2611,17 +2851,17 @@ orion.textview.TextView = (function() {
 		 * @property {Number} addedCharCount The number of characters added to the model.
 		 * @property {Number} removedLineCount The number of lines removed from the model.
 		 * @property {Number} addedLineCount The number of lines added to the model.
 		 */
 		/**
 		 * This event is sent when the text in the model has changed.
 		 *
 		 * @event
-		 * @param {orion.textview.ModelChangingEvent} modelChangingEvent the event
+		 * @param {orion.textview.ModelChangedEvent} modelChangedEvent the event
 		 */
 		onModelChanged: function(modelChangedEvent) {
 			this._eventTable.sendEvent("ModelChanged", modelChangedEvent);
 		},
 		/**
 		 * @class This is the event sent when the text in the model is about to change.
 		 * <p>
 		 * <b>See:</b><br/>
@@ -2778,16 +3018,17 @@ orion.textview.TextView = (function() {
 					if (startLine <= lineIndex && lineIndex < endLine) {
 						child.lineChanged = true;
 					}
 					child = child.nextSibling;
 				}
 			}
 			if (!ruler) {
 				if (startLine <= this._maxLineIndex && this._maxLineIndex < endLine) {
+					this._checkMaxLineIndex = this._maxLineIndex;
 					this._maxLineIndex = -1;
 					this._maxLineWidth = 0;
 				}
 			}
 			this._queueUpdatePage();
 		},
 		/**
 		 * Redraws the text in the given range.
@@ -2966,43 +3207,44 @@ orion.textview.TextView = (function() {
 		},
 		/**
 		 * Sets the text model of the text view.
 		 *
 		 * @param {orion.textview.TextModel} model the text model of the view.
 		 */
 		setModel: function(model) {
 			if (!model) { return; }
+			if (model === this._model) { return; }
 			this._model.removeListener(this._modelListener);
 			var oldLineCount = this._model.getLineCount();
 			var oldCharCount = this._model.getCharCount();
 			var newLineCount = model.getLineCount();
 			var newCharCount = model.getCharCount();
 			var newText = model.getText();
 			var e = {
 				text: newText,
 				start: 0,
 				removedCharCount: oldCharCount,
 				addedCharCount: newCharCount,
 				removedLineCount: oldLineCount,
 				addedLineCount: newLineCount
 			};
-			this.onModelChanging(e); 
-			this.redrawRange();
+			this.onModelChanging(e);
 			this._model = model;
 			e = {
 				start: 0,
 				removedCharCount: oldCharCount,
 				addedCharCount: newCharCount,
 				removedLineCount: oldLineCount,
 				addedLineCount: newLineCount
 			};
 			this.onModelChanged(e); 
 			this._model.addListener(this._modelListener);
-			this.redrawRange();
+			this._reset();
+			this._updatePage();
 		},
 		/**
 		 * Sets the text view selection.
 		 * <p>
 		 * The selection is defined by a start and end character offset relative to the
 		 * document. The character at end offset is not included in the selection.
 		 * </p>
 		 * <p>
@@ -3247,29 +3489,139 @@ orion.textview.TextView = (function() {
 			this._lastMouseTime = time;
 			if (this._clickCount !== 2) {
 				this._clickCount = 2;
 				this._handleMouse(e);
 			}
 		},
 		_handleDragStart: function (e) {
 			if (!e) { e = window.event; }
+			if (isDnD) {
+				var sel = this._getSelection();
+				var text = !sel.isEmpty() ? this._getBaseText(sel.start, sel.end) : "";
+				if (text) {
+					e.dataTransfer.effectAllowed = "copyMove";
+					e.dataTransfer.setData("text/plain", text);
+					// TODO: generate a drag image to be a better visual indicatator of the drag operation.
+					this._dragStartSelection = {start: sel.start, end: sel.end};
+					this.focus();
+					return;
+				}
+			}
 			if (e.preventDefault) { e.preventDefault(); }
 			return false;
 		},
+		_handleDragEnd: function (e) {
+			if (!e) { e = window.event; }
+			if (e.preventDefault) { e.preventDefault(); }
+			var startSel = this._dragStartSelection;
+			var drop = this._dropDestination;
+			if (startSel && e.dataTransfer.dropEffect === "move") {
+				var offset = 0;
+				if (drop && drop.offset < Math.min(startSel.start, startSel.end)) {
+					offset = drop.length;
+				}
+				var change = {
+					text: "",
+					start: startSel.start + offset,
+					end: startSel.end + offset
+				};
+				this._modifyContent(change, false);
+			}
+			if (this._undoStack && drop) {
+				this._undoStack.endCompoundChange();
+			}
+			this._dragNode.draggable = false;
+			this._dragStartSelection = null;
+			this._dropDestination = null;
+			return false;
+		},
+		_handleDragEnter: function (e) {
+			if (!e) { e = window.event; }
+			if (e.preventDefault) { e.preventDefault(); }
+			var types = e.dataTransfer.types;
+			var allowed = false;
+			var types = isDnD ? e.dataTransfer.types : null;
+			if (types) {
+				// Firefox gives a .types of type StringList, while Webkit gives us an actual string.
+				allowed = types.contains ? types.contains("text/plain") : types.indexOf("text/plain");
+			}
+			if (allowed) {
+				e.dataTransfer.dropEffect = "copyMove";
+				this.focus();
+				return true;
+			}
+			e.dataTransfer.dropEffect = "none";
+			return false;
+		},
 		_handleDragOver: function (e) {
 			if (!e) { e = window.event; }
-			e.dataTransfer.dropEffect = "none";
 			if (e.preventDefault) { e.preventDefault(); }
-			return false;
+			var allowed = false;
+			var types = isDnD ? e.dataTransfer.types : null;
+			if (types) {
+				allowed = types.contains ? types.contains("text/plain") : types.indexOf("text/plain");
+			}
+			if (!allowed) {
+				e.dataTransfer.dropEffect = "none";
+				return false;
+			}
+
+			var destLine = this._getYToLine(e.clientY);
+			var destOffset = this._getXToOffset(destLine, e.clientX);
+
+			var startSel = this._dragStartSelection;
+			if (startSel && startSel.start <= destOffset && destOffset <= startSel.end) {
+				e.dataTransfer.dropEffect = "none";
+				return false;
+			}
+
+			if (!startSel) {
+				// Hide the selection when the user drags something coming from the outside.
+				// TODO: make sure the cursor is actually visible. It's not visible in Firefox during drag, only in Chrome...
+				this.setSelection(destOffset, destOffset, true);
+			}
+
+			return true;
 		},
 		_handleDrop: function (e) {
 			if (!e) { e = window.event; }
 			if (e.preventDefault) { e.preventDefault(); }
-			return false;
+			var allowed = false;
+			var types = isDnD ? e.dataTransfer.types : null;
+			if (types) {
+				allowed = types.contains ? types.contains("text/plain") : types.indexOf("text/plain");
+			}
+			if (!allowed) {
+				return false;
+			}
+
+			var destLine = this._getYToLine(e.clientY);
+			var destOffset = this._getXToOffset(destLine, e.clientX);
+			var startSel = this._dragStartSelection;
+
+			if (startSel && startSel.start <= destOffset && destOffset <= startSel.end) {
+				return false;
+			}
+
+			var text = e.dataTransfer.getData("text/plain");
+			this.setSelection(destOffset, destOffset, true);
+
+			if (startSel) {
+				this._dropDestination = {offset: destOffset, length: text.length};
+				if (this._undoStack) {
+					this._undoStack.startCompoundChange();
+				}
+			} else {
+				this._dragNode.draggable = false;
+			}
+
+			this._doContent(text);
+			this.focus();
+			return true;
 		},
 		_handleDocFocus: function (e) {
 			if (!e) { e = window.event; }
 			this._clientDiv.focus();
 		},
 		_handleFocus: function (e) {
 			if (!e) { e = window.event; }
 			this._hasFocus = true;
@@ -3314,31 +3666,16 @@ orion.textview.TextView = (function() {
 					if (e.preventDefault) { e.preventDefault(); }
 					return false;
 				}
 				this._startIME();
 			} else {
 				this._commitIME();
 			}
 			/*
-			* Bug in Firefox.  The paste operation on Firefox is done by switching
-			* focus into a textarea, let the user agent paste the text into the
-			* textarea and retrieve the text pasted from it. This works as expected
-			* in Firefox 3.x, but fails in Firefox 4 and greater.  The fix is to
-			* switch focus to the textarea during the key down event that triggers
-			* the paste operation.
-			*/
-			if (isFirefox) {
-				var ctrlKey = isMac ? e.metaKey : e.ctrlKey;
-				if (ctrlKey && e.keyCode === 86 /*Ctrl+v*/) {
-					this._textArea.value = "";
-					this._textArea.focus();
-				}
-			}
-			/*
 			* Feature in Firefox. When a key is held down the browser sends 
 			* right number of keypress events but only one keydown. This is
 			* unexpected and causes the view to only execute an action
 			* just one time. The fix is to ignore the keydown event and 
 			* execute the actions from the keypress handler.
 			* Note: This only happens on the Mac and Linux (Firefox 3.6).
 			*
 			* Feature in Opera.  Opera sends keypress events even for non-printable
@@ -3482,28 +3819,51 @@ orion.textview.TextView = (function() {
 					this._setLinksVisible(false);
 				} else {
 					return;
 				}
 			}
 			var left = e.which ? e.button === 0 : e.button === 1;
 			this._commitIME();
 			if (left) {
-				this._isMouseDown = true;
 				var deltaX = Math.abs(this._lastMouseX - e.clientX);
 				var deltaY = Math.abs(this._lastMouseY - e.clientY);
 				var time = e.timeStamp ? e.timeStamp : new Date().getTime();  
 				if ((time - this._lastMouseTime) <= this._clickTime && deltaX <= this._clickDist && deltaY <= this._clickDist) {
 					this._clickCount++;
 				} else {
 					this._clickCount = 1;
 				}
 				this._lastMouseX = e.clientX;
 				this._lastMouseY = e.clientY;
 				this._lastMouseTime = time;
+
+				// Selection drag support
+				if (isDnD && this._clickCount === 1) {
+					var inSelection = false;
+					var selection = this._getSelection();
+					if (!selection.isEmpty()) {
+						var clickLine = this._getYToLine(e.clientY);
+						var clickOffset = this._getXToOffset(clickLine, e.clientX);
+						inSelection = selection.start < clickOffset && clickOffset < selection.end;
+					}
+
+					// Webkit fails to allow dragging if .draggable is set to true during mousedown.
+					// But Firefox makes it a requirement to set .draggable to true.
+					this._dragNode.draggable = !isWebkit && inSelection;
+
+					if (inSelection) {
+						return; // allow the dragstart event
+					}
+				}
+				if (this._dragNode && this._dragNode.draggable) {
+					this._dragNode.draggable = false;
+				}
+
+				this._isMouseDown = true;
 				this._handleMouse(e);
 				if (isOpera || isChrome) {
 					if (!this._hasFocus) {
 						this.focus();
 					}
 					e.preventDefault();
 				}
 			}
@@ -3586,16 +3946,24 @@ orion.textview.TextView = (function() {
 		},
 		_handleMouseUp: function (e) {
 			if (!e) { e = window.event; }
 			if (this._linksVisible) {
 				return;
 			}
 			var left = e.which ? e.button === 0 : e.button === 1;
 			if (left) {
+				if (this._dragNode && this._dragNode.draggable) {
+					this._dragNode.draggable = false;
+          if (!this._dragStartSelection) {
+            this._setSelectionTo(e.clientX, e.clientY, false);
+          }
+					this.focus();
+				}
+
 				this._isMouseDown = false;
 				this._endAutoScroll();
 				
 				/*
 				* Feature in IE8 and older, the sequence of events in the IE8 event model
 				* for a doule-click is:
 				*
 				*	down
@@ -4114,105 +4482,97 @@ orion.textview.TextView = (function() {
 			return true;
 		},
 		_doLineDown: function (args) {
 			var model = this._model;
 			var selection = this._getSelection();
 			var caret = selection.getCaret();
 			var lineIndex = model.getLineAtOffset(caret);
 			if (lineIndex + 1 < model.getLineCount()) {
+				var scrollX = this._getScroll().x;
 				var x = this._columnX;
-				if (x === -1 || args.select) {
-					x = this._getOffsetToX(caret);
-				}
-				selection.extend(this._getXToOffset(lineIndex + 1, x));
+				if (x === -1 || args.select || args.wholeLine) {
+					var offset = args.wholeLine ? model.getLineEnd(lineIndex + 1) : caret;
+					x = this._getOffsetToX(offset) + scrollX;
+				}
+				selection.extend(this._getXToOffset(lineIndex + 1, x - scrollX));
 				if (!args.select) { selection.collapse(); }
 				this._setSelection(selection, true, true);
-				this._columnX = x;//fix x by scrolling
+				this._columnX = x;
 			}
 			return true;
 		},
 		_doLineUp: function (args) {
 			var model = this._model;
 			var selection = this._getSelection();
 			var caret = selection.getCaret();
 			var lineIndex = model.getLineAtOffset(caret);
 			if (lineIndex > 0) {
+				var scrollX = this._getScroll().x;
 				var x = this._columnX;
-				if (x === -1 || args.select) {
-					x = this._getOffsetToX(caret);
-				}
-				selection.extend(this._getXToOffset(lineIndex - 1, x));
+				if (x === -1 || args.select || args.wholeLine) {
+					var offset = args.wholeLine ? model.getLineStart(lineIndex - 1) : caret;
+					x = this._getOffsetToX(offset) + scrollX;
+				}
+				selection.extend(this._getXToOffset(lineIndex - 1, x - scrollX));
 				if (!args.select) { selection.collapse(); }
 				this._setSelection(selection, true, true);
-				this._columnX = x;//fix x by scrolling
+				this._columnX = x;
 			}
 			return true;
 		},
 		_doPageDown: function (args) {
 			var model = this._model;
 			var selection = this._getSelection();
 			var caret = selection.getCaret();
 			var caretLine = model.getLineAtOffset(caret);
 			var lineCount = model.getLineCount();
 			if (caretLine < lineCount - 1) {
+				var scroll = this._getScroll();
 				var clientHeight = this._getClientHeight();
 				var lineHeight = this._getLineHeight();
 				var lines = Math.floor(clientHeight / lineHeight);
 				var scrollLines = Math.min(lineCount - caretLine - 1, lines);
 				scrollLines = Math.max(1, scrollLines);
 				var x = this._columnX;
 				if (x === -1 || args.select) {
-					x = this._getOffsetToX(caret);
-				}
-				selection.extend(this._getXToOffset(caretLine + scrollLines, x));
+					x = this._getOffsetToX(caret) + scroll.x;
+				}
+				selection.extend(this._getXToOffset(caretLine + scrollLines, x - scroll.x));
 				if (!args.select) { selection.collapse(); }
-				this._setSelection(selection, false, false);
-				
 				var verticalMaximum = lineCount * lineHeight;
-				var verticalScrollOffset = this._getScroll().y;
-				var scrollOffset = verticalScrollOffset + scrollLines * lineHeight;
+				var scrollOffset = scroll.y + scrollLines * lineHeight;
 				if (scrollOffset + clientHeight > verticalMaximum) {
 					scrollOffset = verticalMaximum - clientHeight;
-				} 
-				if (scrollOffset > verticalScrollOffset) {
-					this._scrollView(0, scrollOffset - verticalScrollOffset);
-				} else {
-					this._updateDOMSelection();
-				}
-				this._columnX = x;//fix x by scrolling
+				}
+				this._setSelection(selection, true, true, scrollOffset - scroll.y);
+				this._columnX = x;
 			}
 			return true;
 		},
 		_doPageUp: function (args) {
 			var model = this._model;
 			var selection = this._getSelection();
 			var caret = selection.getCaret();
 			var caretLine = model.getLineAtOffset(caret);
 			if (caretLine > 0) {
+				var scroll = this._getScroll();
 				var clientHeight = this._getClientHeight();
 				var lineHeight = this._getLineHeight();
 				var lines = Math.floor(clientHeight / lineHeight);
 				var scrollLines = Math.max(1, Math.min(caretLine, lines));
 				var x = this._columnX;
 				if (x === -1 || args.select) {
-					x = this._getOffsetToX(caret);
-				}
-				selection.extend(this._getXToOffset(caretLine - scrollLines, x));
+					x = this._getOffsetToX(caret) + scroll.x;
+				}
+				selection.extend(this._getXToOffset(caretLine - scrollLines, x - scroll.x));
 				if (!args.select) { selection.collapse(); }
-				this._setSelection(selection, false, false);
-				
-				var verticalScrollOffset = this._getScroll().y;
-				var scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * lineHeight);
-				if (scrollOffset < verticalScrollOffset) {
-					this._scrollView(0, scrollOffset - verticalScrollOffset);
-				} else {
-					this._updateDOMSelection();
-				}
-				this._columnX = x;//fix x by scrolling
+				var scrollOffset = Math.max(0, scroll.y - scrollLines * lineHeight);
+				this._setSelection(selection, true, true, scrollOffset - scroll.y);
+				this._columnX = x;
 			}
 			return true;
 		},
 		_doPaste: function(e) {
 			var text = this._getClipboardText(e);
 			if (text) {
 				this._doContent(text);
 			}
@@ -4323,36 +4683,34 @@ orion.textview.TextView = (function() {
 			span3.appendChild(document.createTextNode(c));
 			line.appendChild(span3);
 			var span4 = document.createElement("SPAN");
 			span4.style.fontWeight = "bold";
 			span4.style.fontStyle = "italic";
 			span4.appendChild(document.createTextNode(c));
 			line.appendChild(span4);
 			parent.appendChild(line);
+			var lineRect = line.getBoundingClientRect();
 			var spanRect1 = span1.getBoundingClientRect();
 			var spanRect2 = span2.getBoundingClientRect();
 			var spanRect3 = span3.getBoundingClientRect();
 			var spanRect4 = span4.getBoundingClientRect();
 			var h1 = spanRect1.bottom - spanRect1.top;
 			var h2 = spanRect2.bottom - spanRect2.top;
 			var h3 = spanRect3.bottom - spanRect3.top;
 			var h4 = spanRect4.bottom - spanRect4.top;
 			var fontStyle = 0;
-			var lineHeight = h1;
+			var lineHeight = lineRect.bottom - lineRect.top;
 			if (h2 > h1) {
-				lineHeight = h2;
 				fontStyle = 1;
 			}
 			if (h3 > h2) {
-				lineHeight = h3;
 				fontStyle = 2;
 			}
 			if (h4 > h3) {
-				lineHeight = h4;
 				fontStyle = 3;
 			}
 			this._largestFontStyle = fontStyle;
 			parent.removeChild(line);
 			return lineHeight;
 		},
 		_calculatePadding: function() {
 			var document = this._frameDocument;
@@ -4467,16 +4825,20 @@ orion.textview.TextView = (function() {
 				bindings.push({name: "scrollTextEnd",		keyBinding: new KeyBinding(35), predefined: true});
 				bindings.push({name: "textStart",	keyBinding: new KeyBinding(38, true), predefined: true});
 				bindings.push({name: "textEnd",		keyBinding: new KeyBinding(40, true), predefined: true});
 			} else {
 				bindings.push({name: "pageUp",		keyBinding: new KeyBinding(33), predefined: true});
 				bindings.push({name: "pageDown",	keyBinding: new KeyBinding(34), predefined: true});
 				bindings.push({name: "lineStart",	keyBinding: new KeyBinding(36), predefined: true});
 				bindings.push({name: "lineEnd",		keyBinding: new KeyBinding(35), predefined: true});
+				if (isLinux) {
+						bindings.push({name: "lineStartUp",    keyBinding: new KeyBinding(38, true), predefined: true});
+						bindings.push({name: "lineEndDown",    keyBinding: new KeyBinding(40, true), predefined: true});
+				}
 				bindings.push({name: "wordPrevious",	keyBinding: new KeyBinding(37, true), predefined: true});
 				bindings.push({name: "wordNext",	keyBinding: new KeyBinding(39, true), predefined: true});
 				bindings.push({name: "textStart",	keyBinding: new KeyBinding(36, true), predefined: true});
 				bindings.push({name: "textEnd",		keyBinding: new KeyBinding(35, true), predefined: true});
 			}
 
 			// Select Cursor Navigation
 			bindings.push({name: "selectLineUp",		keyBinding: new KeyBinding(38, null, true), predefined: true});
@@ -4490,16 +4852,20 @@ orion.textview.TextView = (function() {
 				bindings.push({name: "selectLineEnd",		keyBinding: new KeyBinding(39, true, true), predefined: true});
 				bindings.push({name: "selectWordPrevious",	keyBinding: new KeyBinding(37, null, true, true), predefined: true});
 				bindings.push({name: "selectWordNext",	keyBinding: new KeyBinding(39, null, true, true), predefined: true});
 				bindings.push({name: "selectTextStart",	keyBinding: new KeyBinding(36, null, true), predefined: true});
 				bindings.push({name: "selectTextEnd",		keyBinding: new KeyBinding(35, null, true), predefined: true});
 				bindings.push({name: "selectTextStart",	keyBinding: new KeyBinding(38, true, true), predefined: true});
 				bindings.push({name: "selectTextEnd",		keyBinding: new KeyBinding(40, true, true), predefined: true});
 			} else {
+				if (isLinux) {
+					bindings.push({name: "selectWholeLineUp",		keyBinding: new KeyBinding(38, true, true), predefined: true});
+					bindings.push({name: "selectWholeLineDown",		keyBinding: new KeyBinding(40, true, true), predefined: true});
+				}
 				bindings.push({name: "selectLineStart",		keyBinding: new KeyBinding(36, null, true), predefined: true});
 				bindings.push({name: "selectLineEnd",		keyBinding: new KeyBinding(35, null, true), predefined: true});
 				bindings.push({name: "selectWordPrevious",	keyBinding: new KeyBinding(37, true, true), predefined: true});
 				bindings.push({name: "selectWordNext",		keyBinding: new KeyBinding(39, true, true), predefined: true});
 				bindings.push({name: "selectTextStart",		keyBinding: new KeyBinding(36, true, true), predefined: true});
 				bindings.push({name: "selectTextEnd",		keyBinding: new KeyBinding(35, true, true), predefined: true});
 			}
 
@@ -4563,32 +4929,36 @@ orion.textview.TextView = (function() {
 
 			//1 to 1, no duplicates
 			var self = this;
 			this._actions = [
 				{name: "lineUp",		defaultHandler: function() {return self._doLineUp({select: false});}},
 				{name: "lineDown",		defaultHandler: function() {return self._doLineDown({select: false});}},
 				{name: "lineStart",		defaultHandler: function() {return self._doHome({select: false, ctrl:false});}},
 				{name: "lineEnd",		defaultHandler: function() {return self._doEnd({select: false, ctrl:false});}},
+				{name: "lineStartUp",    defaultHandler: function() {return self._doLineUp({select: false, wholeLine:true});}},
+				{name: "lineEndDown",    defaultHandler: function() {return self._doLineDown({select: false, wholeLine:true});}},
 				{name: "charPrevious",		defaultHandler: function() {return self._doCursorPrevious({select: false, unit:"character"});}},
 				{name: "charNext",		defaultHandler: function() {return self._doCursorNext({select: false, unit:"character"});}},
 				{name: "pageUp",		defaultHandler: function() {return self._doPageUp({select: false});}},
 				{name: "pageDown",		defaultHandler: function() {return self._doPageDown({select: false});}},
 				{name: "scrollPageUp",		defaultHandler: function() {return self._doScroll({type: "pageUp"});}},
 				{name: "scrollPageDown",		defaultHandler: function() {return self._doScroll({type: "pageDown"});}},
 				{name: "wordPrevious",		defaultHandler: function() {return self._doCursorPrevious({select: false, unit:"word"});}},
 				{name: "wordNext",		defaultHandler: function() {return self._doCursorNext({select: false, unit:"word"});}},
 				{name: "textStart",		defaultHandler: function() {return self._doHome({select: false, ctrl:true});}},
 				{name: "textEnd",		defaultHandler: function() {return self._doEnd({select: false, ctrl:true});}},
 				{name: "scrollTextStart",	defaultHandler: function() {return self._doScroll({type: "textStart"});}},
 				{name: "scrollTextEnd",		defaultHandler: function() {return self._doScroll({type: "textEnd"});}},
 				{name: "centerLine",		defaultHandler: function() {return self._doScroll({type: "centerLine"});}},
 				
 				{name: "selectLineUp",		defaultHandler: function() {return self._doLineUp({select: true});}},
 				{name: "selectLineDown",	defaultHandler: function() {return self._doLineDown({select: true});}},
+				{name: "selectWholeLineUp",		defaultHandler: function() {return self._doLineUp({select: true, wholeLine: true});}},
+				{name: "selectWholeLineDown",	defaultHandler: function() {return self._doLineDown({select: true, wholeLine: true});}},
 				{name: "selectLineStart",	defaultHandler: function() {return self._doHome({select: true, ctrl:false});}},
 				{name: "selectLineEnd",		defaultHandler: function() {return self._doEnd({select: true, ctrl:false});}},
 				{name: "selectCharPrevious",	defaultHandler: function() {return self._doCursorPrevious({select: true, unit:"character"});}},
 				{name: "selectCharNext",	defaultHandler: function() {return self._doCursorNext({select: true, unit:"character"});}},
 				{name: "selectPageUp",		defaultHandler: function() {return self._doPageUp({select: true});}},
 				{name: "selectPageDown",	defaultHandler: function() {return self._doPageDown({select: true});}},
 				{name: "selectWordPrevious",	defaultHandler: function() {return self._doCursorPrevious({select: true, unit:"word"});}},
 				{name: "selectWordNext",	defaultHandler: function() {return self._doCursorNext({select: true, unit:"word"});}},
@@ -4608,17 +4978,17 @@ orion.textview.TextView = (function() {
 				{name: "copy",			defaultHandler: function() {return self._doCopy();}},
 				{name: "cut",			defaultHandler: function() {return self._doCut();}},
 				{name: "paste",			defaultHandler: function() {return self._doPaste();}}
 			];
 		},
 		_createLine: function(parent, sibling, document, lineIndex, model) {
 			var lineText = model.getLine(lineIndex);
 			var lineStart = model.getLineStart(lineIndex);
-			var e = {lineIndex: lineIndex, lineText: lineText, lineStart: lineStart};
+			var e = {textView: this, lineIndex: lineIndex, lineText: lineText, lineStart: lineStart};
 			this.onLineStyle(e);
 			var child = document.createElement("DIV");
 			child.lineIndex = lineIndex;
 			this._applyStyle(e.style, child);
 			if (lineText.length !== 0) {
 				var start = 0;
 				var tabSize = this._customTabSize;
 				if (tabSize && tabSize !== 8) {
@@ -4910,24 +5280,22 @@ orion.textview.TextView = (function() {
 				textArea.style.padding = "0px";
 				textArea.style.margin = "0px";
 				textArea.style.borderRadius = "0px";
 				textArea.style.WebkitAppearance = "none";
 				textArea.style.WebkitTapHighlightColor = "transparent";
 				touchDiv.appendChild(textArea);
 			}
 			if (isFirefox) {
-				textArea = frameDocument.createElement("TEXTAREA");
-				this._textArea = textArea;
-				textArea.id = "textArea";
-				textArea.style.position = "fixed";
-				textArea.style.whiteSpace = "pre";
-				textArea.style.left = "-1000px";
-				textArea.tabIndex = -1;
-				body.appendChild(textArea);
+				var clipboardDiv = frameDocument.createElement("DIV");
+				this._clipboardDiv = clipboardDiv;
+				clipboardDiv.style.position = "fixed";
+				clipboardDiv.style.whiteSpace = "pre";
+				clipboardDiv.style.left = "-1000px";
+				body.appendChild(clipboardDiv);
 			}
 
 			var viewDiv = frameDocument.createElement("DIV");
 			viewDiv.className = "view";
 			this._viewDiv = viewDiv;
 			viewDiv.id = "viewDiv";
 			viewDiv.tabIndex = -1;
 			viewDiv.style.overflow = "auto";
@@ -5042,16 +5410,19 @@ orion.textview.TextView = (function() {
 				overlayDiv.style.padding = clientDiv.style.padding;
 				overlayDiv.style.cursor = "text";
 				overlayDiv.style.zIndex = "1";
 				scrollDiv.appendChild(overlayDiv);
 			}
 			if (!isPad) {
 				clientDiv.contentEditable = "true";
 			}
+			if (isDnD) {
+				this._dragNode = this._overlayDiv || this._clientDiv;
+			}
 			this._lineHeight = this._calculateLineHeight();
 			this._viewPadding = this._calculatePadding();
 			if (isIE) {
 				body.style.lineHeight = this._lineHeight + "px";
 			}
 			if (this._tabSize) {
 				if (isOpera) {
 					clientDiv.style.OTabSize = this._tabSize+"";
@@ -5134,20 +5505,22 @@ orion.textview.TextView = (function() {
 			if (this._touchDiv) {
 				this._parent.removeChild(this._touchDiv);
 				this._touchDiv = null;
 			}
 			this._selDiv1 = null;
 			this._selDiv2 = null;
 			this._selDiv3 = null;
 			this._textArea = null;
+			this._clipboardDiv = null;
 			this._scrollDiv = null;
 			this._viewDiv = null;
 			this._clientDiv = null;
 			this._overlayDiv = null;
+			this._dragNode = null;
 			this._leftDiv = null;
 			this._rightDiv = null;
 		},
 		_doAutoScroll: function (direction, x, y) {
 			this._autoScrollDir = direction;
 			this._autoScrollX = x;
 			this._autoScrollY = y;
 			if (!this._autoScrollTimerID) {
@@ -5278,36 +5651,23 @@ orion.textview.TextView = (function() {
 				//IE
 				clipboadText = [];
 				text = this._frameWindow.clipboardData.getData("Text");
 				this._convertDelimiter(text, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);});
 				return clipboadText.join("");
 			}
 			if (isFirefox) {
 				var document = this._frameDocument;
-				var textArea = this._textArea;
-				textArea.innerHTML = "";
-				textArea.focus();
+				var clipboardDiv = this._clipboardDiv;
+				clipboardDiv.innerHTML = "<pre contenteditable=''></pre>";
+				clipboardDiv.firstChild.focus();
 				var self = this;
 				var _getText = function() {
-					var text;
-					if (textArea.firstChild) {
-						text = "";
-						var child = textArea.firstChild;
-						while (child) {
-							if (child.nodeType === child.TEXT_NODE) {
-								text += child.data;
-							} else if (child.tagName === "BR") {
-								text += delimiter; 
-							} 
-							child = child.nextSibling;
-						}
-					} else {
-						text = textArea.value;
-					}
+					var text = self._getTextFromElement(clipboardDiv);
+					clipboardDiv.innerHTML = "";
 					clipboadText = [];
 					self._convertDelimiter(text, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);});
 					return clipboadText.join("");
 				};
 				
 				/* Try execCommand first. Works on firefox with clipboard permission. */
 				var result = false;
 				this._ignorePaste = true;
@@ -5368,16 +5728,46 @@ orion.textview.TextView = (function() {
 						text += textNode.data;
 					}
 					textNode = textNode.nextSibling;
 				}
 				lineChild = lineChild.nextSibling;
 			}
 			return text;
 		},
+		_getTextFromElement: function(element) {
+			var document = element.ownerDocument;
+			var window = document.defaultView;
+			if (!window.getSelection) {
+				return element.innerText || element.textContent;
+			}
+
+			var newRange = document.createRange();
+			newRange.selectNode(element);
+
+			var selection = window.getSelection();
+			var oldRanges = [];
+			for (var i = 0; i < selection.rangeCount; i++) {
+				oldRanges.push(selection.getRangeAt(i));
+			}
+
+			this._ignoreSelect = true;
+			selection.removeAllRanges();
+			selection.addRange(newRange);
+
+			var text = selection.toString();
+
+			selection.removeAllRanges();
+			for (var i = 0; i < oldRanges.length; i++) {
+				selection.addRange(oldRanges[i]);
+			}
+
+			this._ignoreSelect = false;
+			return text;
+		},
 		_getViewPadding: function() {
 			return this._viewPadding;
 		},
 		_getLineBoundingClientRect: function (child) {
 			var rect = child.getBoundingClientRect();
 			var lastChild = child.lastChild;
 			//Remove any artificial trailing whitespace in the line
 			while (lastChild && lastChild.ignoreChars === lastChild.firstChild.length) {
@@ -5690,17 +6080,17 @@ orion.textview.TextView = (function() {
 								if (found) {
 									high = mid;
 								} else {
 									low = mid;
 								}
 							}
 							offset += high;
 							start = high;
-							end = high === nodeLength - 1 && lineChild.ignoreChars ? textNode.length : high + 1;
+							end = high === nodeLength - 1 && lineChild.ignoreChars ? textNode.length : Math.min(high + 1, textNode.length);
 							if (isRangeRects) {
 								range.setStart(textNode, start);
 								range.setEnd(textNode, end);
 							} else {
 								range.moveToElementText(lineChild);
 								range.move("character", start);
 								range.moveEnd("character", end - start);
 							}
@@ -5809,22 +6199,22 @@ orion.textview.TextView = (function() {
 			right += area;
 			bottom += area;
 			return (left <= x && x <= right && top <= y && y <= bottom);
 		},
 		_hookEvents: function() {
 			var self = this;
 			this._modelListener = {
 				/** @private */
-				onChanging: function(newText, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
-					self._onModelChanging(newText, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount);
+				onChanging: function(modelChangingEvent) {
+					self._onModelChanging(modelChangingEvent);
 				},
 				/** @private */
-				onChanged: function(start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
-					self._onModelChanged(start, removedCharCount, addedCharCount, removedLineCount, addedLineCount);
+				onChanged: function(modelChangedEvent) {
+					self._onModelChanged(modelChangedEvent);
 				}
 			};
 			this._model.addListener(this._modelListener);
 			
 			var clientDiv = this._clientDiv;
 			var viewDiv = this._viewDiv;
 			var body = this._frameDocument.body; 
 			var handlers = this._handlers = [];
@@ -5842,44 +6232,46 @@ orion.textview.TextView = (function() {
 				handlers.push({target: textArea, type: "input", handler: function(e) { return self._handleInput(e); }});
 				handlers.push({target: textArea, type: "textInput", handler: function(e) { return self._handleTextInput(e); }});
 				handlers.push({target: textArea, type: "click", handler: function(e) { return self._handleTextAreaClick(e); }});
 				handlers.push({target: touchDiv, type: "touchstart", handler: function(e) { return self._handleTouchStart(e); }});
 				handlers.push({target: touchDiv, type: "touchmove", handler: function(e) { return self._handleTouchMove(e); }});
 				handlers.push({target: touchDiv, type: "touchend", handler: function(e) { return self._handleTouchEnd(e); }});
 			} else {
 				var topNode = this._overlayDiv || this._clientDiv;
+				var dragNode = this._dragNode || topNode;
 				var grabNode = isIE ? clientDiv : this._frameWindow;
 				handlers.push({target: clientDiv, type: "keydown", handler: function(e) { return self._handleKeyDown(e);}});
 				handlers.push({target: clientDiv, type: "keypress", handler: function(e) { return self._handleKeyPress(e);}});
 				handlers.push({target: clientDiv, type: "keyup", handler: function(e) { return self._handleKeyUp(e);}});
 				handlers.push({target: clientDiv, type: "selectstart", handler: function(e) { return self._handleSelectStart(e);}});
 				handlers.push({target: clientDiv, type: "contextmenu", handler: function(e) { return self._handleContextMenu(e);}});
 				handlers.push({target: clientDiv, type: "copy", handler: function(e) { return self._handleCopy(e);}});
 				handlers.push({target: clientDiv, type: "cut", handler: function(e) { return self._handleCut(e);}});
-				if (!isFirefox) {
-					handlers.push({target: clientDiv, type: "paste", handler: function(e) { return self._handlePaste(e);}});
-				}
+				handlers.push({target: clientDiv, type: "paste", handler: function(e) { return self._handlePaste(e);}});
 				handlers.push({target: clientDiv, type: "mousedown", handler: function(e) { return self._handleMouseDown(e);}});
 				handlers.push({target: grabNode, type: "mouseup", handler: function(e) { return self._handleMouseUp(e);}});
 				handlers.push({target: grabNode, type: "mousemove", handler: function(e) { return self._handleMouseMove(e);}});
 				handlers.push({target: body, type: "mousedown", handler: function(e) { return self._handleBodyMouseDown(e);}});
-				handlers.push({target: topNode, type: "dragstart", handler: function(e) { return self._handleDragStart(e);}});
-				handlers.push({target: topNode, type: "dragover", handler: function(e) { return self._handleDragOver(e);}});
-				handlers.push({target: topNode, type: "drop", handler: function(e) { return self._handleDrop(e);}});
+				handlers.push({target: dragNode, type: "dragstart", handler: function(e) { return self._handleDragStart(e);}});
+				if (isDnD) {
+					handlers.push({target: dragNode, type: "dragend", handler: function(e) { return self._handleDragEnd(e);}});
+					handlers.push({target: dragNode, type: "dragenter", handler: function(e) { return self._handleDragEnter(e);}});
+				}
+				handlers.push({target: dragNode, type: "dragover", handler: function(e) { return self._handleDragOver(e);}});
+				handlers.push({target: dragNode, type: "drop", handler: function(e) { return self._handleDrop(e);}});
 				if (isChrome) {
 					handlers.push({target: this._parentDocument, type: "mousemove", handler: function(e) { return self._handleMouseMove(e);}});
 					handlers.push({target: this._parentDocument, type: "mouseup", handler: function(e) { return self._handleMouseUp(e);}});
 				}
 				if (isIE) {
 					handlers.push({target: this._frameDocument, type: "activate", handler: function(e) { return self._handleDocFocus(e); }});
 				}
 				if (isFirefox) {
 					handlers.push({target: this._frameDocument, type: "focus", handler: function(e) { return self._handleDocFocus(e); }});
-					handlers.push({target: this._textArea, type: "paste", handler: function(e) { return self._handlePaste(e);}});
 				}
 				if (!isIE && !isOpera) {
 					var wheelEvent = isFirefox ? "DOMMouseScroll" : "mousewheel";
 					handlers.push({target: this._viewDiv, type: wheelEvent, handler: function(e) { return self._handleMouseWheel(e); }});
 				}
 				if (isFirefox && !isWindows) {
 					handlers.push({target: this._clientDiv, type: "DOMCharacterDataModified", handler: function (e) { return self._handleDataModified(e); }});
 				}
@@ -5920,16 +6312,19 @@ orion.textview.TextView = (function() {
 			this._selection = new Selection (0, 0, false);
 			this._linksVisible = false;
 			this._eventTable = new EventTable();
 			this._redrawCount = 0;
 			this._maxLineWidth = 0;
 			this._maxLineIndex = -1;
 			this._ignoreSelect = true;
 			this._columnX = -1;
+			
+			this._dragStartSelection = null;
+			this._dropDestination = null;
 
 			/* Auto scroll */
 			this._autoScrollX = null;
 			this._autoScrollY = null;
 			this._autoScrollTimerID = null;
 			this._AUTO_SCROLL_RATE = 50;
 			this._grabControl = null;
 			this._moseMoveClosure  = null;
@@ -5975,26 +6370,23 @@ orion.textview.TextView = (function() {
 			
 			if (updateCaret) {
 				var selection = this._getSelection ();
 				selection.setCaret(e.start + e.text.length);
 				this._setSelection(selection, true);
 			}
 			this.onModify({});
 		},
-		_onModelChanged: function(start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
-			var e = {
-				start: start,
-				removedCharCount: removedCharCount,
-				addedCharCount: addedCharCount,
-				removedLineCount: removedLineCount,
-				addedLineCount: addedLineCount
-			};
-			this.onModelChanged(e);
-			
+		_onModelChanged: function(modelChangedEvent) {
+			this.onModelChanged(modelChangedEvent);
+			var start = modelChangedEvent.start;
+			var addedCharCount = modelChangedEvent.addedCharCount;
+			var removedCharCount = modelChangedEvent.removedCharCount;
+			var addedLineCount = modelChangedEvent.addedLineCount;
+			var removedLineCount = modelChangedEvent.removedLineCount;
 			var selection = this._getSelection();
 			if (selection.end > start) {
 				if (selection.end > start && selection.start < start + removedCharCount) {
 					// selection intersects replaced text. set caret behind text change
 					selection.setCaret(start + addedCharCount);
 				} else {
 					// move selection to keep same text selected
 					selection.start +=  addedCharCount - removedCharCount;
@@ -6012,40 +6404,66 @@ orion.textview.TextView = (function() {
 					child.lineChanged = true;
 				}
 				if (lineIndex > startLine + removedLineCount) {
 					child.lineIndex = lineIndex + addedLineCount - removedLineCount;
 				}
 				child = this._getLineNext(child);
 			}
 			if (startLine <= this._maxLineIndex && this._maxLineIndex <= startLine + removedLineCount) {
+				this._checkMaxLineIndex = this._maxLineIndex;
 				this._maxLineIndex = -1;
 				this._maxLineWidth = 0;
 			}
 			this._updatePage();
 		},
-		_onModelChanging: function(newText, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
-			var e = {
-				text: newText,
-				start: start,
-				removedCharCount: removedCharCount,
-				addedCharCount: addedCharCount,
-				removedLineCount: removedLineCount,
-				addedLineCount: addedLineCount
-			};
-			this.onModelChanging(e);
+		_onModelChanging: function(modelChangingEvent) {
+			this.onModelChanging(modelChangingEvent);
 		},
 		_queueUpdatePage: function() {
 			if (this._updateTimer) { return; }
 			var self = this;
 			this._updateTimer = setTimeout(function() { 
 				self._updateTimer = null;
 				self._updatePage();
 			}, 0);
 		},
+		_reset: function() {
+			this._maxLineIndex = -1;
+			this._maxLineWidth = 0;
+			this._columnX = -1;
+			this._topChild = null;
+			this._bottomChild = null;
+			this._partialY = 0;
+			this._setSelection(new Selection (0, 0, false), false, false);
+			if (this._viewDiv) {
+				this._viewDiv.scrollLeft = 0;
+				this._viewDiv.scrollTop = 0;
+			}
+			var clientDiv = this._clientDiv;
+			if (clientDiv) {
+				var child = clientDiv.firstChild;
+				while (child) {
+					child.lineChanged = true;
+					child = child.nextSibling;
+				}
+				/*
+				* Bug in Firefox.  For some reason, the caret does not show after the
+				* view is refreshed.  The fix is to toggle the contentEditable state and
+				* force the clientDiv to loose and receive focus if the it is focused.
+				*/
+				if (isFirefox) {
+					var hasFocus = this._hasFocus;
+					if (hasFocus) { clientDiv.blur(); }
+					clientDiv.contentEditable = false;
+					clientDiv.contentEditable = true;
+					if (hasFocus) { clientDiv.focus(); }
+				}
+			}
+		},
 		_resizeTouchDiv: function() {
 			var viewRect = this._viewDiv.getBoundingClientRect();
 			var parentRect = this._frame.getBoundingClientRect();
 			var temp = this._frame;
 			while (temp) {
 				if (temp.style && temp.style.top) { break; }
 				temp = temp.parentNode;
 			}
@@ -6121,17 +6539,19 @@ orion.textview.TextView = (function() {
 				range.setStart(child.firstChild, 0);
 				range.setEndBefore(child.lastChild);
 				var sel = window.getSelection();
 				if (sel.rangeCount > 0) { sel.removeAllRanges(); }
 				sel.addRange(range);
 				var self = this;
 				/** @ignore */
 				var cleanup = function() {
-					self._clientDiv.removeChild(child);
+					if (child && child.parentNode === self._clientDiv) {
+						self._clientDiv.removeChild(child);
+					}
 					self._updateDOMSelection();
 				};
 				var result = false;
 				/* 
 				* Try execCommand first, it works on firefox with clipboard permission,
 				* chrome 5, safari 4.
 				*/
 				this._ignoreCopy = true;
@@ -6393,17 +6813,17 @@ orion.textview.TextView = (function() {
 							line.replaceChild(this._createRange(line, document, lineChild.firstChild.data, style), lineChild);
 						}
 						lineChild = next;
 					}
 				}
 				line = this._getLineNext(line);
 			}
 		},
-		_setSelection: function (selection, scroll, update) {
+		_setSelection: function (selection, scroll, update, pageScroll) {
 			if (selection) {
 				this._columnX = -1;
 				if (update === undefined) { update = true; }
 				var oldSelection = this._selection; 
 				if (!oldSelection.equals(selection)) {
 					this._selection = selection;
 					var e = {
 						oldValue: {start:oldSelection.start, end:oldSelection.end},
@@ -6412,17 +6832,17 @@ orion.textview.TextView = (function() {
 					this.onSelection(e);
 				}
 				/* 
 				* Always showCaret(), even when the selection is not changing, to ensure the
 				* caret is visible. Note that some views do not scroll to show the caret during
 				* keyboard navigation when the selection does not chanage. For example, line down
 				* when the caret is already at the last line.
 				*/
-				if (scroll) { update = !this._showCaret(); }
+				if (scroll) { update = !this._showCaret(false, pageScroll); }
 				
 				/* 
 				* Sometimes the browser changes the selection 
 				* as result of method calls or "leaked" events. 
 				* The fix is to set the visual selection even
 				* when the logical selection is not changed.
 				*/
 				if (update) { this._updateDOMSelection(); }
@@ -6468,17 +6888,17 @@ orion.textview.TextView = (function() {
 						end = model.getLineEnd(lineIndex);
 					}
 				}
 				selection.setCaret(start);
 				selection.extend(end);
 			} 
 			this._setSelection(selection, true, true);
 		},
-		_showCaret: function (allSelection) {
+		_showCaret: function (allSelection, pageScroll) {
 			if (!this._clientDiv) { return; }
 			var model = this._model;
 			var selection = this._getSelection();
 			var scroll = this._getScroll();
 			var caret = selection.getCaret();
 			var start = selection.start;
 			var end = selection.end;
 			var startLine = model.getLineAtOffset(start); 
@@ -6528,16 +6948,27 @@ orion.textview.TextView = (function() {
 					if (caret === start && start !== end) {
 						pixelY += Math.min(clientHeight - lineHeight, selectionHeight);
 					}
 				} else {
 					if (caret === end) {
 						pixelY -= Math.min (clientHeight - lineHeight, selectionHeight);
 					}
 				}
+				if (pageScroll) {
+					if (pageScroll > 0) {
+						if (pixelY > 0) {
+							pixelY = Math.max(pixelY, pageScroll);
+						}
+					} else {
+						if (pixelY < 0) {
+							pixelY = Math.min(pixelY, pageScroll);
+						}
+					}
+				}
 			}
 
 			if (pixelX !== 0 || pixelY !== 0) {
 				this._scrollView (pixelX, pixelY);
 				/*
 				* When the view scrolls it is possible that one of the scrollbars can show over the caret.
 				* Depending on the browser scrolling can be synchronous (Safari), in which case the change 
 				* can be detected before showCaret() returns. When scrolling is asynchronous (most browsers), 
@@ -6680,31 +7111,47 @@ orion.textview.TextView = (function() {
 			* is to set the width of the client div to a larger number
 			* before computing the lines width.  Note that this value is
 			* reset to the appropriate value further down.
 			*/ 
 			if (isWebkit) {
 				clientDiv.style.width = (0x7FFFF).toString() + "px";
 			}
 
+			var rect;
 			child = this._getLineNext();
 			while (child) {
 				lineWidth = child.lineWidth;
 				if (lineWidth === undefined) {
-					var rect = this._getLineBoundingClientRect(child);
+					rect = this._getLineBoundingClientRect(child);
 					lineWidth = child.lineWidth = rect.right - rect.left;
 				}
 				if (lineWidth >= this._maxLineWidth) {
 					this._maxLineWidth = lineWidth;
 					this._maxLineIndex = child.lineIndex;
 				}
 				if (child.lineIndex === topIndex) { this._topChild = child; }
 				if (child.lineIndex === bottomIndex) { this._bottomChild = child; }
+				if (this._checkMaxLineIndex === child.lineIndex) { this._checkMaxLineIndex = -1; }
 				child = this._getLineNext(child);
 			}
+			if (this._checkMaxLineIndex !== -1) {
+				lineIndex = this._checkMaxLineIndex;
+				this._checkMaxLineIndex = -1;
+				if (0 <= lineIndex && lineIndex < lineCount) {
+					var dummy = this._createLine(clientDiv, null, document, lineIndex, model);
+					rect = this._getLineBoundingClientRect(dummy);
+					lineWidth = rect.right - rect.left;
+					if (lineWidth >= this._maxLineWidth) {
+						this._maxLineWidth = lineWidth;
+						this._maxLineIndex = lineIndex;
+					}
+					clientDiv.removeChild(dummy);
+				}
+			}
 
 			// Update rulers
 			this._updateRuler(this._leftDiv, topIndex, bottomIndex);
 			this._updateRuler(this._rightDiv, topIndex, bottomIndex);
 			
 			var leftWidth = this._leftDiv ? this._leftDiv.scrollWidth : 0;
 			var rightWidth = this._rightDiv ? this._rightDiv.scrollWidth : 0;
 			viewDiv.style.left = leftWidth + "px";
@@ -6712,16 +7159,19 @@ orion.textview.TextView = (function() {
 			if (this._rightDiv) {
 				this._rightDiv.style.left = (frameWidth - rightWidth) + "px"; 
 			}
 			
 			var scrollDiv = this._scrollDiv;
 			/* Need to set the height first in order for the width to consider the vertical scrollbar */
 			var scrollHeight = lineCount * lineHeight;
 			scrollDiv.style.height = scrollHeight + "px";
+			// TODO if frameHeightWithoutHScrollbar < scrollHeight  < frameHeightWithHScrollbar and the horizontal bar is visible, 
+			// then the clientWidth is wrong because the vertical scrollbar is showing. To correct code should hide both scrollbars 
+			// at this point.
 			var clientWidth = this._getClientWidth();
 			var width = Math.max(this._maxLineWidth, clientWidth);
 			/*
 			* Except by IE 8 and earlier, all other browsers are not allocating enough space for the right padding 
 			* in the scrollbar. It is possible this a bug since all other paddings are considered.
 			*/
 			var scrollWidth = width;
 			if (!isIE || isIE >= 9) { width += viewPad.right; }
@@ -8856,28 +9306,20 @@ examples.textview.TextStyler = (function
 		this.view = view;
 		this.annotationModel = annotationModel;
 		this._currentBracket = undefined; 
 		this._matchingBracket = undefined;
 		
 		view.addEventListener("Selection", this, this._onSelection);
 		var model = view.getModel();
 		if (model.getBaseModel) {
-			//TODO normalize all events to use event objects
 			var self = this;
 			this._baseModelListener = {
-				onChanged: function(start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
-					var e = {
-						start: start, 
-						removedCharCount: removedCharCount,
-						addedCharCount: addedCharCount,
-						removedLineCount: removedLineCount,
-						addedLineCount: addedLineCount
-					};
-					self._onModelChanged(e);
+				onChanged: function(modelChangedEvent) {
+					self._onModelChanged(modelChangedEvent);
 				}
 			};
 			model.getBaseModel().addListener(this._baseModelListener);
 		} else {
 			//TODO still needed to keep the event order correct (styler before view)
 			view.addEventListener("ModelChanged", this, this._onModelChanged);
 		}
 		view.addEventListener("Destroy", this, this._onDestroy);
@@ -8994,19 +9436,19 @@ examples.textview.TextStyler = (function
 					var end = baseModel.getLineEnd(baseModel.getLineAtOffset(tokenStart));
 					if (type !== SINGLELINE_COMMENT) {
 						end = Math.min(end, commentEnd - this.commentEnd.length);
 					}
 					add.push({
 						start: tokenStart,
 						end: end,
 						type: "orion.annotation.task",
-						rulerTitle: baseModel.getText(tokenStart, end),
-						rulerStyle: {styleClass: "annotation task"},
-						rulerHTML: "<div class='annotationHTML task'></div>",
+						title: baseModel.getText(tokenStart, end),
+						style: {styleClass: "annotation task"},
+						html: "<div class='annotationHTML task'></div>",
 						overviewStyle: {styleClass: "annotationOverview task"}
 					});
 				}
 			}
 			annotationModel.replaceAnnotations(remove, add);
 		},
 		_getLineStyle: function(lineIndex) {
 			if (this.highlightCaretLine) {
@@ -9014,18 +9456,17 @@ examples.textview.TextStyler = (function
 				var model = view.getModel();
 				var selection = view.getSelection();
 				if (selection.start === selection.end && model.getLineAtOffset(selection.start) === lineIndex) {
 					return caretLineStyle;
 				}
 			}
 			return null;
 		},
-		_getStyles: function(text, start) {
-			var model = this.view.getModel();
+		_getStyles: function(model, text, start) {
 			if (model.getBaseModel) {
 				start = model.mapOffset(start);
 			}
 			var end = start + text.length;
 			
 			var styles = [];
 			
 			// for any sub range that is not a comment, parse code generating tokens (keywords, numbers, brackets, line comments, etc)
@@ -9347,18 +9788,20 @@ examples.textview.TextStyler = (function
 				}
 			}
 			return result;
 		},
 		_onDestroy: function(e) {
 			this.destroy();
 		},
 		_onLineStyle: function (e) {
-			e.style = this._getLineStyle(e.lineIndex);
-			e.ranges = this._getStyles(e.lineText, e.lineStart);
+			if (e.textView === this.view) {
+				e.style = this._getLineStyle(e.lineIndex);
+			}
+			e.ranges = this._getStyles(e.textView.getModel(), e.lineText, e.lineStart);
 		},
 		_onSelection: function(e) {
 			var oldSelection = e.oldValue;
 			var newSelection = e.newValue;
 			var view = this.view;
 			var model = view.getModel();
 			var lineIndex;
 			var bracket = this._matchingBracket;
--- a/browser/devtools/sourceeditor/test/Makefile.in
+++ b/browser/devtools/sourceeditor/test/Makefile.in
@@ -41,11 +41,15 @@ srcdir    = @srcdir@
 VPATH     = @srcdir@
 relativesrcdir = browser/devtools/sourceeditor/test
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_TEST_FILES = \
 		browser_sourceeditor_initialization.js \
+		browser_bug684862_paste_html.js \
+		browser_bug687573_vscroll.js \
+		browser_bug687568_pagescroll.js \
+		browser_bug687580_drag_and_drop.js \
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_bug684862_paste_html.js
@@ -0,0 +1,114 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource:///modules/source-editor.jsm");
+
+let testWin;
+let editor;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  const pageUrl = "data:text/html,<ul><li>test<li>foobarBug684862";
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+    waitForFocus(pageLoaded, content);
+  }, true);
+
+  content.location = pageUrl;
+}
+
+function pageLoaded()
+{
+  const windowUrl = "data:application/vnd.mozilla.xul+xml,<?xml version='1.0'?>" +
+    "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
+    " title='test for bug 684862 - paste HTML' width='600' height='500'>" +
+    "<script type='application/javascript' src='chrome://global/content/globalOverlay.js'/>" +
+    "<box flex='1'/></window>";
+  const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+  let doCopy = function() {
+    content.focus();
+    EventUtils.synthesizeKey("a", {accelKey: true}, content);
+    EventUtils.synthesizeKey("c", {accelKey: true}, content);
+  };
+
+  let clipboardValidator = function(aData) aData.indexOf("foobarBug684862") > -1;
+
+  let onCopy = function() {
+    testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null);
+    testWin.addEventListener("load", function onWindowLoad() {
+      testWin.removeEventListener("load", onWindowLoad, false);
+      waitForFocus(initEditor, testWin);
+    }, false);
+  };
+
+  waitForClipboard(clipboardValidator, doCopy, onCopy, testEnd);
+}
+
+function initEditor()
+{
+  let box = testWin.document.querySelector("box");
+
+  editor = new SourceEditor();
+  editor.init(box, {}, editorLoaded);
+}
+
+function editorLoaded()
+{
+  editor.focus();
+
+  ok(!editor.getText(), "editor has no content");
+  is(editor.getCaretOffset(), 0, "caret location");
+
+  let onPaste = function() {
+    editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onPaste);
+
+    let text = editor.getText();
+    ok(text, "editor has content after paste");
+
+    isnot(text.indexOf("foobarBug684862"), -1, "editor content is correct");
+
+    executeSoon(function() {
+      editor.setCaretOffset(4);
+      EventUtils.synthesizeKey("a", {}, testWin);
+      EventUtils.synthesizeKey("VK_RIGHT", {}, testWin);
+
+      text = editor.getText();
+
+      isnot(text.indexOf("foobarBug684862"), -1,
+            "editor content is correct after navigation");
+      is(editor.getCaretOffset(), 6, "caret location");
+
+      executeSoon(testEnd);
+    });
+  };
+
+  editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onPaste);
+
+  // Do paste
+  executeSoon(function() {
+    testWin.goDoCommand("cmd_paste");
+  });
+}
+
+function testEnd()
+{
+  if (editor) {
+    editor.destroy();
+  }
+  if (testWin) {
+    testWin.close();
+  }
+  testWin = editor = null;
+  gBrowser.removeCurrentTab();
+
+  waitForFocus(finish, window);
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_bug687568_pagescroll.js
@@ -0,0 +1,87 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource:///modules/source-editor.jsm");
+
+let testWin;
+let editor;
+
+function test()
+{
+  let component = Services.prefs.getCharPref(SourceEditor.PREFS.COMPONENT);
+  if (component != "orion") {
+    ok(true, "skip test for bug 687568: only applicable for Orion");
+    return; // Testing for the fix requires direct Orion API access.
+  }
+
+  waitForExplicitFinish();
+
+  const windowUrl = "data:application/vnd.mozilla.xul+xml,<?xml version='1.0'?>" +
+    "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
+    " title='test for bug 687568 - page scroll' width='600' height='500'>" +
+    "<box flex='1'/></window>";
+  const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+  testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null);
+  testWin.addEventListener("load", function onWindowLoad() {
+    testWin.removeEventListener("load", onWindowLoad, false);
+    waitForFocus(initEditor, testWin);
+  }, false);
+}
+
+function initEditor()
+{
+  let box = testWin.document.querySelector("box");
+
+  editor = new SourceEditor();
+  editor.init(box, { showLineNumbers: true }, editorLoaded);
+}
+
+function editorLoaded()
+{
+  editor.focus();
+
+  let view = editor._view;
+  let model = editor._model;
+
+  let lineHeight = view.getLineHeight();
+  let editorHeight = view.getClientArea().height;
+  let linesPerPage = Math.floor(editorHeight / lineHeight);
+  let totalLines = 3 * linesPerPage;
+
+  let text = "";
+  for (let i = 0; i < totalLines; i++) {
+    text += "l" + i + "\n";
+  }
+
+  editor.setText(text);
+  editor.setCaretOffset(0);
+
+  EventUtils.synthesizeKey("VK_DOWN", {shiftKey: true}, testWin);
+  EventUtils.synthesizeKey("VK_DOWN", {shiftKey: true}, testWin);
+  EventUtils.synthesizeKey("VK_DOWN", {shiftKey: true}, testWin);
+
+  let bottomLine = view.getBottomIndex(true);
+  view.setTopIndex(bottomLine + 1);
+
+  executeSoon(function() {
+    EventUtils.synthesizeKey("VK_PAGE_DOWN", {shiftKey: true}, testWin);
+
+    executeSoon(function() {
+      let topLine = view.getTopIndex(true);
+      let topLineOffset = model.getLineStart(topLine);
+      let selection = editor.getSelection();
+      ok(selection.start < topLineOffset && topLineOffset < selection.end,
+         "top visible line is selected");
+
+      editor.destroy();
+      testWin.close();
+      testWin = editor = null;
+
+      waitForFocus(finish, window);
+    });
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_bug687573_vscroll.js
@@ -0,0 +1,131 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource:///modules/source-editor.jsm");
+
+let testWin;
+let editor;
+
+function test()
+{
+  let component = Services.prefs.getCharPref(SourceEditor.PREFS.COMPONENT);
+  if (component == "textarea") {
+    ok(true, "skip test for bug 687573: not applicable for TEXTAREAs");
+    return; // TEXTAREAs have different behavior
+  }
+
+  waitForExplicitFinish();
+
+  const windowUrl = "data:application/vnd.mozilla.xul+xml,<?xml version='1.0'?>" +
+    "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
+    " title='test for bug 687573 - vertical scroll' width='300' height='500'>" +
+    "<box flex='1'/></window>";
+  const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+  testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null);
+  testWin.addEventListener("load", function onWindowLoad() {
+    testWin.removeEventListener("load", onWindowLoad, false);
+    waitForFocus(initEditor, testWin);
+  }, false);
+}
+
+function initEditor()
+{
+  let box = testWin.document.querySelector("box");
+
+  let text = "abba\n" +
+             "\n" +
+             "abbaabbaabbaabbaabbaabbaabbaabbaabbaabba\n" +
+             "abbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba\n" +
+             "abbaabbaabbaabbaabbaabbaabbaabbaabbaabba\n" +
+             "\n" +
+             "abba\n";
+
+  let config = {
+    showLineNumbers: true,
+    placeholderText: text,
+  };
+
+  editor = new SourceEditor();
+  editor.init(box, config, editorLoaded);
+}
+
+function editorLoaded()
+{
+  let VK_LINE_END = "VK_END";
+  let VK_LINE_END_OPT = {};
+  let OS = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+  if (OS == "Darwin") {
+    VK_LINE_END = "VK_RIGHT";
+    VK_LINE_END_OPT = {accelKey: true};
+  }
+
+  editor.focus();
+
+  editor.setCaretOffset(0);
+  is(editor.getCaretOffset(), 0, "caret location at start");
+
+  EventUtils.synthesizeKey("VK_DOWN", {}, testWin);
+  EventUtils.synthesizeKey("VK_DOWN", {}, testWin);
+
+  // line 3
+  is(editor.getCaretOffset(), 6, "caret location, keypress Down two times, line 3");
+
+  // line 3 end
+  EventUtils.synthesizeKey(VK_LINE_END, VK_LINE_END_OPT, testWin);
+  is(editor.getCaretOffset(), 46, "caret location, keypress End, line 3 end");
+
+  // line 4
+  EventUtils.synthesizeKey("VK_DOWN", {}, testWin);
+  is(editor.getCaretOffset(), 87, "caret location, keypress Down, line 4");
+
+  // line 4 end
+  EventUtils.synthesizeKey(VK_LINE_END, VK_LINE_END_OPT, testWin);
+  is(editor.getCaretOffset(), 135, "caret location, keypress End, line 4 end");
+
+  // line 5 end
+  EventUtils.synthesizeKey("VK_DOWN", {}, testWin);
+  is(editor.getCaretOffset(), 176, "caret location, keypress Down, line 5 end");
+
+  // line 6 end
+  EventUtils.synthesizeKey("VK_DOWN", {}, testWin);
+  is(editor.getCaretOffset(), 177, "caret location, keypress Down, line 6 end");
+
+  // The executeSoon() calls are needed to allow reflows...
+  EventUtils.synthesizeKey("VK_UP", {}, testWin);
+  executeSoon(function() {
+    // line 5 end
+    is(editor.getCaretOffset(), 176, "caret location, keypress Up, line 5 end");
+
+    EventUtils.synthesizeKey("VK_UP", {}, testWin);
+    executeSoon(function() {
+      // line 4 end
+      is(editor.getCaretOffset(), 135, "caret location, keypress Up, line 4 end");
+
+      // line 3 end
+      EventUtils.synthesizeKey("VK_UP", {}, testWin);
+      is(editor.getCaretOffset(), 46, "caret location, keypress Up, line 3 end");
+
+      // line 2 end
+      EventUtils.synthesizeKey("VK_UP", {}, testWin);
+      is(editor.getCaretOffset(), 5, "caret location, keypress Up, line 2 end");
+
+      // line 3 end
+      EventUtils.synthesizeKey("VK_DOWN", {}, testWin);
+      is(editor.getCaretOffset(), 46, "caret location, keypress Down, line 3 end");
+
+      // line 4 end
+      EventUtils.synthesizeKey("VK_DOWN", {}, testWin);
+      is(editor.getCaretOffset(), 135, "caret location, keypress Down, line 4 end");
+
+      editor.destroy();
+      testWin.close();
+      testWin = editor = null;
+
+      waitForFocus(finish, window);
+    });
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_bug687580_drag_and_drop.js
@@ -0,0 +1,159 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource:///modules/source-editor.jsm");
+
+let testWin;
+let editor;
+
+function test()
+{
+  let component = Services.prefs.getCharPref(SourceEditor.PREFS.COMPONENT);
+  if (component != "orion") {
+    ok(true, "skip test for bug 687580: only applicable for Orion");
+    return; // Testing for the fix requires direct Orion API access.
+  }
+
+  waitForExplicitFinish();
+
+  const windowUrl = "data:application/vnd.mozilla.xul+xml,<?xml version='1.0'?>" +
+    "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
+    " title='test for bug 687580 - drag and drop' width='600' height='500'>" +
+    "<box flex='1'/></window>";
+  const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+  testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null);
+  testWin.addEventListener("load", function onWindowLoad() {
+    testWin.removeEventListener("load", onWindowLoad, false);
+    waitForFocus(initEditor, testWin);
+  }, false);
+}
+
+function initEditor()
+{
+  let box = testWin.document.querySelector("box");
+
+  editor = new SourceEditor();
+  editor.init(box, {}, editorLoaded);
+}
+
+function editorLoaded()
+{
+  editor.focus();
+
+  let view = editor._view;
+  let model = editor._model;
+
+  let lineHeight = view.getLineHeight();
+  let editorHeight = view.getClientArea().height;
+  let linesPerPage = Math.floor(editorHeight / lineHeight);
+  let totalLines = 2 * linesPerPage;
+
+  let text = "foobarBug687580-";
+  for (let i = 0; i < totalLines; i++) {
+    text += "l" + i + "\n";
+  }
+
+  editor.setText(text);
+  editor.setCaretOffset(0);
+
+  let bottomPixel = view.getBottomPixel();
+
+  EventUtils.synthesizeKey("VK_DOWN", {shiftKey: true}, testWin);
+  EventUtils.synthesizeKey("VK_DOWN", {shiftKey: true}, testWin);
+  EventUtils.synthesizeKey("VK_DOWN", {shiftKey: true}, testWin);
+
+  let initialSelection = editor.getSelection();
+
+  let ds = Cc["@mozilla.org/widget/dragservice;1"].
+           getService(Ci.nsIDragService);
+
+  let target = view._dragNode;
+  let targetWin = target.ownerDocument.defaultView;
+
+  let dataTransfer = null;
+
+  let onDragStart = function(aEvent) {
+    target.removeEventListener("dragstart", onDragStart, false);
+
+    dataTransfer = aEvent.dataTransfer;
+    ok(dataTransfer, "dragstart event fired");
+    ok(dataTransfer.types.contains("text/plain"),
+       "dataTransfer text/plain available");
+    let text = dataTransfer.getData("text/plain");
+    isnot(text.indexOf("foobarBug687580"), -1, "text/plain data is correct");
+
+    dataTransfer.dropEffect = "move";
+  };
+
+  let onDrop = executeSoon.bind(null, function() {
+    target.removeEventListener("drop", onDrop, false);
+
+    let selection = editor.getSelection();
+    is(selection.start, selection.end, "selection is collapsed");
+    is(editor.getText(0, 2), "l3", "drag and drop worked");
+
+    let offset = editor.getCaretOffset();
+    ok(offset > initialSelection.end, "new caret location");
+
+    let initialLength = initialSelection.end - initialSelection.start;
+    let dropText = editor.getText(offset - initialLength, offset);
+    isnot(dropText.indexOf("foobarBug687580"), -1, "drop text is correct");
+
+    editor.destroy();
+    testWin.close();
+    testWin = editor = null;
+
+    waitForFocus(finish, window);
+  });
+
+  executeSoon(function() {
+    ds.startDragSession();
+
+    target.addEventListener("dragstart", onDragStart, false);
+    target.addEventListener("drop", onDrop, false);
+
+    EventUtils.synthesizeMouse(target, 10, 10, {type: "mousedown"}, targetWin);
+
+    EventUtils.synthesizeMouse(target, 11, bottomPixel - 25, {type: "mousemove"},
+                               targetWin);
+
+    EventUtils.synthesizeMouse(target, 12, bottomPixel - 15, {type: "mousemove"},
+                               targetWin);
+
+    let clientX = 5;
+    let clientY = bottomPixel - 10;
+
+    let event = targetWin.document.createEvent("DragEvents");
+    event.initDragEvent("dragenter", true, true, targetWin, 0, 0, 0, clientX,
+                        clientY, false, false, false, false, 0, null,
+                        dataTransfer);
+    target.dispatchEvent(event);
+
+    event = targetWin.document.createEvent("DragEvents");
+    event.initDragEvent("dragover", true, true, targetWin, 0, 0, 0, clientX + 1,
+                        clientY + 2, false, false, false, false, 0, null,
+                        dataTransfer);
+    target.dispatchEvent(event);
+
+    EventUtils.synthesizeMouse(target, clientX + 2, clientY + 1,
+                               {type: "mouseup"}, targetWin);
+
+    event = targetWin.document.createEvent("DragEvents");
+    event.initDragEvent("drop", true, true, targetWin, 0, 0, 0, clientX + 2,
+                        clientY + 3, false, false, false, false, 0, null,
+                        dataTransfer);
+    target.dispatchEvent(event);
+
+    event = targetWin.document.createEvent("DragEvents");
+    event.initDragEvent("dragend", true, true, targetWin, 0, 0, 0, clientX + 3,
+                        clientY + 2, false, false, false, false, 0, null,
+                        dataTransfer);
+    target.dispatchEvent(event);
+
+    ds.endDragSession(true);
+  });
+}
--- a/browser/devtools/sourceeditor/test/browser_sourceeditor_initialization.js
+++ b/browser/devtools/sourceeditor/test/browser_sourceeditor_initialization.js
@@ -169,16 +169,18 @@ function editorLoaded()
 
     editor.setSelection(0, editor.getCharCount() - 1);
     EventUtils.synthesizeKey("VK_TAB", {}, testWin);
     is(editor.getText(), "              a\n         b\n        c", "lines indented");
 
     EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, testWin);
     is(editor.getText(), "       a\n  b\n c", "lines outdented (shift-tab)");
 
+    testEclipseBug362107();
+    testBug687577();
     testBackspaceKey();
     testReturnKey();
   }
 
   // Test the read-only mode.
 
   editor.setText("foofoo");
 
@@ -391,8 +393,71 @@ function testClipboardEvents()
     is(addedCharCount, 4, "event.addedCharCount is correct");
 
     is(editor.getText(), "test-test-", "paste works after copy");
     testEnd();
   };
 
   waitForClipboard("foobar", doCut, onCut, testEnd);
 }
+
+function testEclipseBug362107()
+{
+  // Test for Eclipse Bug 362107:
+  // https://bugs.eclipse.org/bugs/show_bug.cgi?id=362107
+  let OS = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+  if (OS != "Linux") {
+    return;
+  }
+
+  editor.setText("line 1\nline 2\nline 3");
+  editor.setCaretOffset(16);
+
+  EventUtils.synthesizeKey("VK_UP", {ctrlKey: true}, testWin);
+  is(editor.getCaretOffset(), 7, "Ctrl-Up works");
+
+  EventUtils.synthesizeKey("VK_UP", {ctrlKey: true}, testWin);
+  is(editor.getCaretOffset(), 0, "Ctrl-Up works twice");
+
+  EventUtils.synthesizeKey("VK_DOWN", {ctrlKey: true}, testWin);
+  is(editor.getCaretOffset(), 13, "Ctrl-Down works");
+
+  EventUtils.synthesizeKey("VK_DOWN", {ctrlKey: true}, testWin);
+  is(editor.getCaretOffset(), 20, "Ctrl-Down works twice");
+}
+
+function testBug687577()
+{
+  // Test for Mozilla Bug 687577:
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=687577
+  let OS = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+  if (OS != "Linux") {
+    return;
+  }
+
+  editor.setText("line foobar 1\nline foobar 2\nline foobar 3");
+  editor.setCaretOffset(39);
+
+  EventUtils.synthesizeKey("VK_LEFT", {ctrlKey: true, shiftKey: true}, testWin);
+  let selection = editor.getSelection();
+  is(selection.start, 33, "select.start after Ctrl-Shift-Left");
+  is(selection.end, 39, "select.end after Ctrl-Shift-Left");
+
+  EventUtils.synthesizeKey("VK_UP", {ctrlKey: true, shiftKey: true}, testWin);
+  selection = editor.getSelection();
+  is(selection.start, 14, "select.start after Ctrl-Shift-Up");
+  is(selection.end, 39, "select.end after Ctrl-Shift-Up");
+
+  EventUtils.synthesizeKey("VK_UP", {ctrlKey: true, shiftKey: true}, testWin);
+  selection = editor.getSelection();
+  is(selection.start, 0, "select.start after Ctrl-Shift-Up (again)");
+  is(selection.end, 39, "select.end after Ctrl-Shift-Up (again)");
+
+  EventUtils.synthesizeKey("VK_DOWN", {ctrlKey: true, shiftKey: true}, testWin);
+  selection = editor.getSelection();
+  is(selection.start, 27, "select.start after Ctrl-Shift-Down");
+  is(selection.end, 39, "select.end after Ctrl-Shift-Down");
+
+  EventUtils.synthesizeKey("VK_DOWN", {ctrlKey: true, shiftKey: true}, testWin);
+  selection = editor.getSelection();
+  is(selection.start, 39, "select.start after Ctrl-Shift-Down (again)");
+  is(selection.end, 41, "select.end after Ctrl-Shift-Down (again)");
+}