merge m-c to fx-team
authorTim Taubert <tim.taubert@gmx.de>
Tue, 01 Nov 2011 13:23:20 +0100
changeset 79472 6e219763ddd0f9519f7b2a99c52694507f89b043
parent 79467 f79946f0bb2a2cc3f33ba537e4edb2335de936a5 (current diff)
parent 79471 41c52a9e1337163736f5e297478ee773567b7484 (diff)
child 79519 cd9add22f090445f1d73e999aaa4c0050b8e4e16
child 79522 5b1c3d9c342bc048a1fb57a28cddcfcaee4ea865
push id21407
push usertim.taubert@gmx.de
push dateTue, 01 Nov 2011 12:25:29 +0000
treeherdermozilla-central@6e219763ddd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone10.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge m-c to fx-team
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -49,16 +49,20 @@
 #endif
 
 pref("browser.chromeURL","chrome://browser/content/");
 pref("browser.hiddenWindowChromeURL", "chrome://browser/content/hiddenWindow.xul");
 
 // Enables some extra Extension System Logging (can reduce performance)
 pref("extensions.logging.enabled", false);
 
+// Enables strict compatibility. To be toggled in bug 698653, to make addons
+// compatibile by default.
+pref("extensions.strictCompatibility", true);
+
 // Preferences for AMO integration
 pref("extensions.getAddons.cache.enabled", true);
 pref("extensions.getAddons.maxResults", 15);
 pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%");
 pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%");
 pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%?src=firefox");
 pref("extensions.webservice.discoverURL", "https://services.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%");
 
--- a/browser/base/content/test/browser_bug553455.js
+++ b/browser/base/content/test/browser_bug553455.js
@@ -861,16 +861,17 @@ var XPInstallObserver = {
   }
 };
 
 function test() {
   requestLongerTimeout(4);
   waitForExplicitFinish();
 
   Services.prefs.setBoolPref("extensions.logging.enabled", true);
+  Services.prefs.setBoolPref("extensions.strictCompatibility", true);
 
   Services.obs.addObserver(XPInstallObserver, "addon-install-started", false);
   Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false);
   Services.obs.addObserver(XPInstallObserver, "addon-install-failed", false);
   Services.obs.addObserver(XPInstallObserver, "addon-install-complete", false);
 
   registerCleanupFunction(function() {
     // Make sure no more test parts run in case we were timed out
@@ -879,16 +880,17 @@ function test() {
 
     AddonManager.getAllInstalls(function(aInstalls) {
       aInstalls.forEach(function(aInstall) {
         aInstall.cancel();
       });
     });
 
     Services.prefs.clearUserPref("extensions.logging.enabled");
+    Services.prefs.clearUserPref("extensions.strictCompatibility");
 
     Services.obs.removeObserver(XPInstallObserver, "addon-install-started");
     Services.obs.removeObserver(XPInstallObserver, "addon-install-blocked");
     Services.obs.removeObserver(XPInstallObserver, "addon-install-failed");
     Services.obs.removeObserver(XPInstallObserver, "addon-install-complete");
   });
 
   runNextTest();
--- 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)");
+}
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -43,16 +43,19 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
 const PREF_EM_UPDATE_ENABLED          = "extensions.update.enabled";
 const PREF_EM_LAST_APP_VERSION        = "extensions.lastAppVersion";
 const PREF_EM_LAST_PLATFORM_VERSION   = "extensions.lastPlatformVersion";
 const PREF_EM_AUTOUPDATE_DEFAULT      = "extensions.update.autoUpdateDefault";
+const PREF_EM_STRICT_COMPATIBILITY    = "extensions.strictCompatibility";
+
+const STRICT_COMPATIBILITY_DEFAULT    = true;
 
 const VALID_TYPES_REGEXP = /^[\w\-]+$/;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 var EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ];
 
 const CATEGORY_PROVIDER_MODULE = "addon-provider-module";
@@ -276,16 +279,17 @@ function AddonType(aId, aLocaleURI, aLoc
     });
   }
   else {
     this.name = aLocaleKey;
   }
 }
 
 var gStarted = false;
+var gStrictCompatibility = STRICT_COMPATIBILITY_DEFAULT;
 
 /**
  * This is the real manager, kept here rather than in AddonManager to keep its
  * contents hidden from API users.
  */
 var AddonManagerInternal = {
   installListeners: [],
   addonListeners: [],
@@ -369,16 +373,21 @@ var AddonManagerInternal = {
       Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION,
                                  Services.appinfo.version);
       Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION,
                                  Services.appinfo.platformVersion);
       Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION,
                                 (appChanged === undefined ? 0 : -1));
     }
 
+    try {
+      gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY);
+    } catch (e) {}
+    Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this, false);
+
     // Ensure all default providers have had a chance to register themselves
     DEFAULT_PROVIDERS.forEach(function(url) {
       try {
         Components.utils.import(url, {});
       }
       catch (e) {
         ERROR("Exception loading default provider \"" + url + "\"", e);
       }
@@ -490,29 +499,56 @@ var AddonManagerInternal = {
       callProvider(aProvider, "shutdown");
   },
 
   /**
    * Shuts down the addon manager and all registered providers, this must clean
    * up everything in order for automated tests to fake restarts.
    */
   shutdown: function AMI_shutdown() {
+    Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this);
+
     this.providers.forEach(function(provider) {
       callProvider(provider, "shutdown");
     });
 
     this.installListeners.splice(0, this.installListeners.length);
     this.addonListeners.splice(0, this.addonListeners.length);
     this.typeListeners.splice(0, this.typeListeners.length);
     for (let type in this.startupChanges)
       delete this.startupChanges[type];
     gStarted = false;
   },
 
   /**
+   * Notified when a preference we're interested in has changed.
+   *
+   * @see nsIObserver
+   */
+  observe: function AMI_observe(aSubject, aTopic, aData) {
+    switch (aData) {
+    case PREF_EM_STRICT_COMPATIBILITY:
+      let oldValue = gStrictCompatibility;
+      try {
+        gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY);
+      } catch(e) {
+        gStrictCompatibility = STRICT_COMPATIBILITY_DEFAULT;
+      }
+
+      // XXXunf Currently, this won't notify listeners that an addon's
+      // compatibility status has changed if the addon's appDisabled state
+      // doesn't change.
+      if (gStrictCompatibility != oldValue)
+        this.updateAddonAppDisabledStates();
+
+      break;
+    }
+  },
+
+  /**
    * Performs a background update check by starting an update for all add-ons
    * that can be updated.
    */
   backgroundUpdateCheck: function AMI_backgroundUpdateCheck() {
     if (!Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED))
       return;
 
     Services.obs.notifyObservers(null, "addons-background-update-start", null);
@@ -1090,16 +1126,20 @@ var AddonManagerInternal = {
     return this.typesProxy;
   },
 
   get autoUpdateDefault() {
     try {
       return Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT);
     } catch(e) { }
     return true;
+  },
+
+  get strictCompatibility() {
+    return gStrictCompatibility;
   }
 };
 
 /**
  * Should not be used outside of core Mozilla code. This is a private API for
  * the startup and platform integration code to use. Refer to the methods on
  * AddonManagerInternal for documentation however note that these methods are
  * subject to change at any time.
@@ -1410,14 +1450,18 @@ var AddonManager = {
   shouldAutoUpdate: function AM_shouldAutoUpdate(aAddon) {
     if (!("applyBackgroundUpdates" in aAddon))
       return false;
     if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)
       return true;
     if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE)
       return false;
     return this.autoUpdateDefault;
+  },
+
+  get strictCompatibility() {
+    return AddonManagerInternal.strictCompatibility;
   }
 };
 
 Object.freeze(AddonManagerInternal);
 Object.freeze(AddonManagerPrivate);
 Object.freeze(AddonManager);
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/ChromeManifestParser.jsm
@@ -0,0 +1,192 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Extension Manager.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Blair McBride <bmcbride@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["ChromeManifestParser"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const MSG_JAR_FLUSH = "AddonJarFlush";
+
+
+/**
+ * Sends local and remote notifications to flush a JAR file cache entry
+ *
+ * @param aJarFile
+ *        The ZIP/XPI/JAR file as a nsIFile
+ */
+function flushJarCache(aJarFile) {
+  Services.obs.notifyObservers(aJarFile, "flush-cache-entry", null);
+  Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIChromeFrameMessageManager)
+    .sendAsyncMessage(MSG_JAR_FLUSH, aJarFile.path);
+}
+
+
+/**
+ * Parses chrome manifest files.
+ */
+var ChromeManifestParser = {
+
+  /**
+   * Reads and parses a chrome manifest file located at a specified URI, and all
+   * secondary manifests it references.
+   *
+   * @param  aURI
+   *         A nsIURI pointing to a chrome manifest.
+   *         Typically a file: or jar: URI.
+   * @return Array of objects describing each manifest instruction, in the form:
+   *         { type: instruction-type, baseURI: string-uri, args: [arguments] }
+   **/
+  parseSync: function CMP_parseSync(aURI) {
+    function parseLine(aLine) {
+      let line = aLine.trim();
+      if (line.length == 0 || line.charAt(0) == '#')
+        return;
+      let tokens = line.split(/\s+/);
+      let type = tokens.shift();
+      if (type == "manifest") {
+        let uri = NetUtil.newURI(tokens.shift(), null, aURI);
+        data = data.concat(this.parseSync(uri));
+      } else {
+        data.push({type: type, baseURI: baseURI, args: tokens});
+      }
+    }
+
+    let contents = "";
+    try {
+      if (aURI.scheme == "jar")
+        contents = this._readFromJar(aURI);
+      else
+        contents = this._readFromFile(aURI);
+    } catch (e) {
+      // Silently fail.
+    }
+
+    if (!contents)
+      return [];
+
+    let baseURI = NetUtil.newURI(".", null, aURI).spec;
+
+    let data = [];
+    let lines = contents.split("\n");
+    lines.forEach(parseLine.bind(this));
+    return data;
+  },
+  
+  _readFromJar: function CMP_readFromJar(aURI) {
+    let data = "";
+    let entries = [];
+    let readers = [];
+    
+    try {
+      // Deconstrict URI, which can be nested jar: URIs. 
+      let uri = aURI.clone();
+      while (uri instanceof Ci.nsIJARURI) {
+        entries.push(uri.JAREntry);
+        uri = uri.JARFile;
+      }
+
+      // Open the base jar.
+      let reader = Cc["@mozilla.org/libjar/zip-reader;1"].
+                   createInstance(Ci.nsIZipReader);
+      reader.open(uri.QueryInterface(Ci.nsIFileURL).file);
+      readers.push(reader);
+  
+      // Open the nested jars.
+      for (let i = entries.length - 1; i > 0; i--) {
+        let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+                          createInstance(Ci.nsIZipReader);
+        innerReader.openInner(reader, entries[i]);
+        readers.push(innerReader);
+        reader = innerReader;
+      }
+      
+      // First entry is the actual file we want to read.
+      let zis = reader.getInputStream(entries[0]);
+      data = NetUtil.readInputStreamToString(zis, zis.available());
+    }
+    finally {
+      // Close readers in reverse order.
+      for (let i = readers.length - 1; i >= 0; i--) {
+        readers[i].close();
+        flushJarCache(readers[i].file);
+      }
+    }
+    
+    return data;
+  },
+  
+  _readFromFile: function CMP_readFromFile(aURI) {
+    let file = aURI.QueryInterface(Ci.nsIFileURL).file;
+    if (!file.exists() || !file.isFile())
+      return "";
+    
+    let data = "";
+    let fis = Cc["@mozilla.org/network/file-input-stream;1"].
+              createInstance(Ci.nsIFileInputStream);
+    try {
+      fis.init(file, -1, -1, false);
+      data = NetUtil.readInputStreamToString(fis, fis.available());
+    } finally {
+      fis.close();
+    }
+    return data;
+  },
+
+  /**
+  * Detects if there were any instructions of a specified type in a given
+  * chrome manifest.
+  *
+  * @param  aManifest
+  *         Manifest data, as returned by ChromeManifestParser.parseSync().
+  * @param  aType
+  *         Instruction type to filter by.
+  * @return True if any matching instructions were found in the manifest.
+  */
+  hasType: function CMP_hasType(aManifest, aType) {
+    return aManifest.some(function(aEntry) {
+      return aEntry.type == aType;
+    });
+  }
+};
--- a/toolkit/mozapps/extensions/Makefile.in
+++ b/toolkit/mozapps/extensions/Makefile.in
@@ -66,16 +66,17 @@ EXTRA_PP_JS_MODULES = \
   AddonManager.jsm \
   AddonRepository.jsm \
   AddonUpdateChecker.jsm \
   PluginProvider.jsm \
   XPIProvider.jsm \
   $(NULL)
 
 EXTRA_JS_MODULES = \
+  ChromeManifestParser.jsm \
   LightweightThemeManager.jsm \
   SpellCheckDictionaryBootstrap.js \
   $(NULL)
 
 ifdef ENABLE_TESTS
 DIRS += test
 endif
 
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -16,16 +16,17 @@
 #
 # The Initial Developer of the Original Code is
 # the Mozilla Foundation.
 # Portions created by the Initial Developer are Copyright (C) 2009
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Dave Townsend <dtownsend@oxymoronical.com>
+#   Blair McBride <bmcbride@mozilla.com>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
@@ -43,16 +44,17 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 var EXPORTED_SYMBOLS = [];
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/AddonManager.jsm");
 Components.utils.import("resource://gre/modules/AddonRepository.jsm");
+Components.utils.import("resource://gre/modules/ChromeManifestParser.jsm");
 Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm");
 Components.utils.import("resource://gre/modules/FileUtils.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 
 const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
 const PREF_INSTALL_CACHE              = "extensions.installCache";
 const PREF_BOOTSTRAP_ADDONS           = "extensions.bootstrappedAddons";
 const PREF_PENDING_OPERATIONS         = "extensions.pendingOperations";
@@ -117,17 +119,17 @@ const PREFIX_ITEM_URI                 = 
 const RDFURI_ITEM_ROOT                = "urn:mozilla:item:root"
 const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
 const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
 
 const TOOLKIT_ID                      = "toolkit@mozilla.org";
 
 const BRANCH_REGEXP                   = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
 
-const DB_SCHEMA                       = 6;
+const DB_SCHEMA                       = 8;
 const REQ_VERSION                     = 2;
 
 #ifdef MOZ_COMPATIBILITY_NIGHTLY
 const PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE +
                                     ".nightly";
 #else
 const PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." +
                                     Services.appinfo.version.replace(BRANCH_REGEXP, "$1");
@@ -141,17 +143,18 @@ const PROP_LOCALE_SINGLE = ["name", "des
 const PROP_LOCALE_MULTI  = ["developers", "translators", "contributors"];
 const PROP_TARGETAPP     = ["id", "minVersion", "maxVersion"];
 
 // Properties that only exist in the database
 const DB_METADATA        = ["installDate", "updateDate", "size", "sourceURI",
                             "releaseNotesURI", "applyBackgroundUpdates"];
 const DB_BOOL_METADATA   = ["visible", "active", "userDisabled", "appDisabled",
                             "pendingUninstall", "bootstrap", "skinnable",
-                            "softDisabled", "foreignInstall"];
+                            "softDisabled", "foreignInstall",
+                            "hasBinaryComponents", "strictCompatibility"];
 
 const BOOTSTRAP_REASONS = {
   APP_STARTUP     : 1,
   APP_SHUTDOWN    : 2,
   ADDON_ENABLE    : 3,
   ADDON_DISABLE   : 4,
   ADDON_INSTALL   : 5,
   ADDON_UNINSTALL : 6,
@@ -699,19 +702,20 @@ function loadManifestFromRDF(aUri, aStre
     if (!addon.id)
       throw new Error("No ID in install manifest");
     if (!gIDTest.test(addon.id))
       throw new Error("Illegal add-on ID " + addon.id);
     if (!addon.version)
       throw new Error("No version in install manifest");
   }
 
-  // Only read the bootstrapped property for extensions
+  // Only read the bootstrap and strictCompatibility properties for extensions.
   if (addon.type == "extension") {
     addon.bootstrap = getRDFProperty(ds, root, "bootstrap") == "true";
+    addon.strictCompatibility = getRDFProperty(ds, root, "strictCompatibility") == "true";
     if (addon.optionsType &&
         addon.optionsType != AddonManager.OPTIONS_TYPE_DIALOG &&
         addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE &&
         addon.optionsType != AddonManager.OPTIONS_TYPE_TAB) {
       throw new Error("Install manifest specifies unknown type: " + addon.optionsType);
     }
   }
   else {
@@ -719,16 +723,17 @@ function loadManifestFromRDF(aUri, aStre
     if (addon.type == "dictionary")
       addon.bootstrap = true;
 
     // Only extensions are allowed to provide an optionsURL, optionsType or aboutURL. For
     // all other types they are silently ignored
     addon.optionsURL = null;
     addon.optionsType = null;
     addon.aboutURL = null;
+    addon.strictCompatibility = true;
 
     if (addon.type == "theme") {
       if (!addon.internalName)
         throw new Error("Themes must include an internalName property");
       addon.skinnable = getRDFProperty(ds, root, "skinnable") == "true";
     }
   }
 
@@ -845,16 +850,23 @@ function loadManifestFromDir(aDir) {
   let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
             createInstance(Ci.nsIBufferedInputStream);
   bis.init(fis, 4096);
 
   try {
     let addon = loadManifestFromRDF(Services.io.newFileURI(file), bis);
     addon._sourceBundle = aDir.clone().QueryInterface(Ci.nsILocalFile);
     addon.size = getFileSize(aDir);
+
+    file = aDir.clone();
+    file.append("chrome.manifest");
+    let chromeManifest = ChromeManifestParser.parseSync(Services.io.newFileURI(file));
+    addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
+                                                             "binary-component");
+
     return addon;
   }
   finally {
     bis.close();
     fis.close();
   }
 }
 
@@ -877,16 +889,26 @@ function loadManifestFromZipReader(aZipR
     let addon = loadManifestFromRDF(uri, bis);
     addon._sourceBundle = aZipReader.file;
 
     addon.size = 0;
     let entries = aZipReader.findEntries(null);
     while (entries.hasMore())
       addon.size += aZipReader.getEntry(entries.getNext()).realSize;
 
+    // Binary components can only be loaded from unpacked addons.
+    if (addon.unpack) {
+      uri = buildJarURI(aZipReader.file, "chrome.manifest");
+      let chromeManifest = ChromeManifestParser.parseSync(uri);
+      addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
+                                                               "binary-component");
+    } else {
+      addon.hasBinaryComponents = false;
+    }
+
     return addon;
   }
   finally {
     bis.close();
     zis.close();
   }
 }
 
@@ -3875,17 +3897,17 @@ var XPIProvider = {
 };
 
 const FIELDS_ADDON = "internal_id, id, location, version, type, internalName, " +
                      "updateURL, updateKey, optionsURL, optionsType, aboutURL, " +
                      "iconURL, icon64URL, defaultLocale, visible, active, " +
                      "userDisabled, appDisabled, pendingUninstall, descriptor, " +
                      "installDate, updateDate, applyBackgroundUpdates, bootstrap, " +
                      "skinnable, size, sourceURI, releaseNotesURI, softDisabled, " +
-                     "foreignInstall";
+                     "foreignInstall, hasBinaryComponents, strictCompatibility";
 
 /**
  * A helper function to log an SQL error.
  *
  * @param  aError
  *         The storage error code associated with the error
  * @param  aErrorString
  *         An error message
@@ -4021,17 +4043,18 @@ var XPIDatabase = {
     addAddonMetadata_addon: "INSERT INTO addon VALUES (NULL, :id, :location, " +
                             ":version, :type, :internalName, :updateURL, " +
                             ":updateKey, :optionsURL, :optionsType, :aboutURL, " +
                             ":iconURL, :icon64URL, :locale, :visible, :active, " +
                             ":userDisabled, :appDisabled, :pendingUninstall, " +
                             ":descriptor, :installDate, :updateDate, " +
                             ":applyBackgroundUpdates, :bootstrap, :skinnable, " +
                             ":size, :sourceURI, :releaseNotesURI, :softDisabled, " +
-                            ":foreignInstall)",
+                            ":foreignInstall, :hasBinaryComponents, " +
+                            ":strictCompatibility)",
     addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " +
                                    "(:internal_id, :name, :locale)",
     addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " +
                              "homepageURL) VALUES (:name, :description, " +
                              ":creator, :homepageURL)",
     addAddonMetadata_strings: "INSERT INTO locale_strings VALUES (:locale, " +
                               ":type, :value)",
     addAddonMetadata_targetApplication: "INSERT INTO targetApplication VALUES " +
@@ -4555,16 +4578,18 @@ var XPIDatabase = {
                                   "userDisabled INTEGER, appDisabled INTEGER, " +
                                   "pendingUninstall INTEGER, descriptor TEXT, " +
                                   "installDate INTEGER, updateDate INTEGER, " +
                                   "applyBackgroundUpdates INTEGER, " +
                                   "bootstrap INTEGER, skinnable INTEGER, " +
                                   "size INTEGER, sourceURI TEXT, " +
                                   "releaseNotesURI TEXT, softDisabled INTEGER, " +
                                   "foreignInstall INTEGER, " +
+                                  "hasBinaryComponents INTEGER, " +
+                                  "strictCompatibility INTEGER, " +
                                   "UNIQUE (id, location)");
       this.connection.createTable("targetApplication",
                                   "addon_internal_id INTEGER, " +
                                   "id TEXT, minVersion TEXT, maxVersion TEXT, " +
                                   "UNIQUE (addon_internal_id, id)");
       this.connection.createTable("targetPlatform",
                                   "addon_internal_id INTEGER, " +
                                   "os, abi TEXT, " +
@@ -6903,16 +6928,23 @@ AddonInternal.prototype = {
     return matchedOS && !needsABI;
   },
 
   isCompatibleWith: function(aAppVersion, aPlatformVersion) {
     let app = this.matchingTargetApplication;
     if (!app)
       return false;
 
+    // Only extensions can be compatible by default; themes always use strict
+    // compatibility checking.
+    if (this.type == "extension" && !AddonManager.strictCompatibility &&
+        !this.strictCompatibility && !this.hasBinaryComponents) {
+      return true;
+    }
+
     if (!aAppVersion)
       aAppVersion = Services.appinfo.version;
     if (!aPlatformVersion)
       aPlatformVersion = Services.appinfo.platformVersion;
 
     let version;
     if (app.id == Services.appinfo.ID)
       version = aAppVersion;
@@ -7118,17 +7150,18 @@ function AddonWrapper(aAddon) {
       return [repositoryAddon[aProp], true];
     }
 
     return [objValue, false];
   }
 
   ["id", "version", "type", "isCompatible", "isPlatformCompatible",
    "providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled",
-   "softDisabled", "skinnable", "size", "foreignInstall"].forEach(function(aProp) {
+   "softDisabled", "skinnable", "size", "foreignInstall", "hasBinaryComponents",
+   "strictCompatibility"].forEach(function(aProp) {
      this.__defineGetter__(aProp, function() aAddon[aProp]);
   }, this);
 
   ["fullDescription", "developerComments", "eula", "supportURL",
    "contributionURL", "contributionAmount", "averageRating", "reviewCount",
    "reviewURL", "totalDownloads", "weeklyDownloads", "dailyUsers",
    "repositoryStatus"].forEach(function(aProp) {
     this.__defineGetter__(aProp, function() {
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_chromemanifest_1/chrome.manifest
@@ -0,0 +1,6 @@
+content test-addon-1 chrome/content
+# comment!  
+  locale test-addon-1 en-US locale/en-US
+      # commentaire!  
+  locale test-addon-1    fr-FR locale/fr-FR  
+overlay	chrome://browser/content/browser.xul    chrome://test-addon-1/content/overlay.xul
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_chromemanifest_1/install.rdf
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>addon1@tests.mozilla.org</em:id>
+    <em:version>1.0</em:version>
+
+    <!-- Front End MetaData -->
+    <em:name>Test 1</em:name>
+    <em:description>Test Description</em:description>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>2</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_chromemanifest_2/chrome.manifest
@@ -0,0 +1,7 @@
+content test-addon-1 chrome/content
+  
+  locale test-addon-1 en-US locale/en-US
+  locale test-addon-1    fr-FR locale/fr-FR  
+overlay	chrome://browser/content/browser.xul    chrome://test-addon-1/content/overlay.xul
+binary-component components/something.so
+manifest thisdoesntexist.manifest
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_chromemanifest_2/install.rdf
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>addon2@tests.mozilla.org</em:id>
+    <em:version>1.0</em:version>
+
+    <!-- Front End MetaData -->
+    <em:name>Test 2</em:name>
+    <em:description>Test Description</em:description>
+    <em:unpack>true</em:unpack>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>2</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_chromemanifest_3/chrome.manifest
@@ -0,0 +1,9 @@
+content test-addon-1 chrome/content
+  
+  locale test-addon-1 en-US locale/en-US
+  locale test-addon-1    fr-FR locale/fr-FR  
+overlay	chrome://browser/content/browser.xul    chrome://test-addon-1/content/overlay.xul
+   
+     binary-component   components/something.so  
+
+ manifest  jar:inner.jar!/nested.manifest
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b4a40052f4279e0437d488dfc1569461dfede19b
GIT binary patch
literal 180
zc$^FHW@Zs#U|`^2C~1thS5H{gZUW@l05Lxh=cN{xq^9WQCgx?P0hz&J1>ydlYeG)=
z>Uds#{4^-w#7Td@GoGOup1OJ$eSJJnp1*7w!q9ZZCU(EZiY0b44xeUa2=HcP5@En?
b1qd)QC_q_g76y2;vOyFuGS~v?M6e<NW%Vup
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_chromemanifest_3/install.rdf
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>addon3@tests.mozilla.org</em:id>
+    <em:version>1.0</em:version>
+
+    <!-- Front End MetaData -->
+    <em:name>Test 3</em:name>
+    <em:description>Test Description</em:description>
+    <em:unpack>true</em:unpack>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>1</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_chromemanifest_4/chrome.manifest
@@ -0,0 +1,6 @@
+content test-addon-1 chrome/content
+  
+  locale test-addon-1 en-US locale/en-US
+  locale test-addon-1    fr-FR locale/fr-FR  
+overlay	chrome://browser/content/browser.xul    chrome://test-addon-1/content/overlay.xul
+   manifest     components/components.manifest  
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_chromemanifest_4/components/components.manifest
@@ -0,0 +1,2 @@
+binary-component mycomponent.dll
+manifest other/something.manifest
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_chromemanifest_4/components/other/something.manifest
@@ -0,0 +1,1 @@
+binary-component thermalnuclearwar.dll
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_chromemanifest_4/install.rdf
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>addon4@tests.mozilla.org</em:id>
+    <em:version>1.0</em:version>
+
+    <!-- Front End MetaData -->
+    <em:name>Test 4</em:name>
+    <em:description>Test Description</em:description>
+    <em:unpack>true</em:unpack>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>1</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_chromemanifest_5/chrome.manifest
@@ -0,0 +1,7 @@
+content test-addon-1 chrome/content
+  
+  locale test-addon-1 en-US locale/en-US
+  locale test-addon-1    fr-FR locale/fr-FR  
+overlay	chrome://browser/content/browser.xul    chrome://test-addon-1/content/overlay.xul
+   
+     binary-component   components/something.so  
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_chromemanifest_5/install.rdf
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>addon5@tests.mozilla.org</em:id>
+    <em:version>1.0</em:version>
+
+    <!-- Front End MetaData -->
+    <em:name>Test 5</em:name>
+    <em:description>Test Description</em:description>
+    <em:unpack>false</em:unpack>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>2</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_migrate8/chrome.manifest
@@ -0,0 +1,6 @@
+content test-addon-1 chrome/content
+  
+  locale test-addon-1 en-US locale/en-US
+  locale test-addon-1    fr-FR locale/fr-FR  
+overlay	chrome://browser/content/browser.xul    chrome://test-addon-1/content/overlay.xul
+binary-component components/something.so
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_migrate8/install.rdf
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>addon8@tests.mozilla.org</em:id>
+    <em:version>1.0</em:version>
+
+    <!-- Front End MetaData -->
+    <em:name>Test 8</em:name>
+    <em:description>Test Description</em:description>
+    <em:unpack>true</em:unpack>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>2</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+  </Description>
+</RDF>
--- a/toolkit/mozapps/extensions/test/browser/browser_bug557956.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug557956.js
@@ -2,16 +2,18 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Test that the compatibility dialog that normally displays during startup
 // appears to work correctly.
 
 const URI_EXTENSION_UPDATE_DIALOG = "chrome://mozapps/content/extensions/update.xul";
 
+Services.prefs.setBoolPref(PREF_STRICT_COMPAT, true);
+
 /**
  * Test add-ons:
  *
  * Addon    minVersion   maxVersion   Notes
  * addon1   0            *
  * addon2   0            0
  * addon3   0            0
  * addon4   1            *
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -21,16 +21,17 @@ const TESTROOT2 = "http://example.org/" 
 const CHROMEROOT = pathParts.join("/") + "/";
 const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
 const PREF_UPDATEURL = "extensions.update.url";
 
 const MANAGER_URI = "about:addons";
 const INSTALL_URI = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
 const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
 const PREF_SEARCH_MAXRESULTS = "extensions.getAddons.maxResults";
+const PREF_STRICT_COMPAT = "extensions.strictCompatibility";
 
 var gPendingTests = [];
 var gTestsRun = 0;
 var gTestStart = null;
 
 var gUseInContentUI = !gTestInWindow && ("switchToTabHavingURI" in window);
 
 var gDiscoveryURL = Services.prefs.getCharPref(PREF_DISCOVERURL);
@@ -39,19 +40,20 @@ var gUpdateURL = Services.prefs.getCharP
 // Turn logging on for all tests
 Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true);
 // Turn off remote results in searches
 Services.prefs.setIntPref(PREF_SEARCH_MAXRESULTS, 0);
 registerCleanupFunction(function() {
   Services.prefs.clearUserPref(PREF_LOGGING_ENABLED);
   try {
     Services.prefs.clearUserPref(PREF_SEARCH_MAXRESULTS);
-  }
-  catch (e) {
-  }
+  } catch (e) {}
+  try {
+    Services.prefs.clearUserPref(PREF_STRICT_COMPAT);
+  } catch (e) {}
 
   Services.prefs.setCharPref(PREF_DISCOVERURL, gDiscoveryURL);
   Services.prefs.setCharPref(PREF_UPDATEURL, gUpdateURL);
 
   // Throw an error if the add-ons manager window is open anywhere
   var windows = Services.wm.getEnumerator("Addons:Manager");
   if (windows.hasMoreElements())
     ok(false, "Found unexpected add-ons manager window still open");
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -3,16 +3,19 @@
  */
 
 const AM_Cc = Components.classes;
 const AM_Ci = Components.interfaces;
 
 const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
 const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}");
 
+const PREF_EM_CHECK_UPDATE_SECURITY   = "extensions.checkUpdateSecurity";
+const PREF_EM_STRICT_COMPATIBILITY    = "extensions.strictCompatibility";
+
 Components.utils.import("resource://gre/modules/AddonManager.jsm");
 Components.utils.import("resource://gre/modules/AddonRepository.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/FileUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 
 var gInternalManager = null;
@@ -489,17 +492,17 @@ function writeLocaleStrings(aData) {
 function createInstallRDF(aData) {
   var rdf = '<?xml version="1.0"?>\n';
   rdf += '<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\n' +
          '     xmlns:em="http://www.mozilla.org/2004/em-rdf#">\n';
   rdf += '<Description about="urn:mozilla:install-manifest">\n';
 
   ["id", "version", "type", "internalName", "updateURL", "updateKey",
    "optionsURL", "optionsType", "aboutURL", "iconURL", "icon64URL",
-   "skinnable", "bootstrap"].forEach(function(aProp) {
+   "skinnable", "bootstrap", "strictCompatibility"].forEach(function(aProp) {
     if (aProp in aData)
       rdf += "<em:" + aProp + ">" + escapeXML(aData[aProp]) + "</em:" + aProp + ">\n";
   });
 
   rdf += writeLocaleStrings(aData);
 
   if ("targetPlatforms" in aData) {
     aData.targetPlatforms.forEach(function(aPlatform) {
@@ -1098,16 +1101,20 @@ Services.prefs.setBoolPref("extensions.s
 
 // Point update checks to the local machine for fast failures
 Services.prefs.setCharPref("extensions.update.url", "http://127.0.0.1/updateURL");
 Services.prefs.setCharPref("extensions.blocklist.url", "http://127.0.0.1/blocklistURL");
 
 // By default ignore bundled add-ons
 Services.prefs.setBoolPref("extensions.installDistroAddons", false);
 
+// By default use strict compatibility
+Services.prefs.setBoolPref("extensions.strictCompatibility", true);
+
+
 // Register a temporary directory for the tests.
 const gTmpD = gProfD.clone();
 gTmpD.append("temp");
 gTmpD.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
 registerDirectory("TmpD", gTmpD);
 
 // Write out an empty blocklist.xml file to the profile to ensure nothing
 // is blocklisted by default
@@ -1141,9 +1148,17 @@ do_register_cleanup(function() {
 
   testDir.leafName = "staged";
   do_check_false(testDir.exists());
 
   testDir.leafName = "staged-xpis";
   do_check_false(testDir.exists());
 
   shutdownManager();
+
+  // Clear commonly set prefs.
+  try {
+    Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY);
+  } catch (e) {}
+  try {
+    Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY);
+  } catch (e) {}
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js
@@ -0,0 +1,108 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests ChromeManifestParser.js
+
+Components.utils.import("resource://gre/modules/ChromeManifestParser.jsm");
+
+
+function run_test() {
+  do_test_pending();
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2");
+
+  startupManager();
+  
+  installAllFiles([do_get_addon("test_chromemanifest_1"),
+                   do_get_addon("test_chromemanifest_2"),
+                   do_get_addon("test_chromemanifest_3"),
+                   do_get_addon("test_chromemanifest_4")],
+                  function() {
+
+    restartManager();
+    run_test_1();
+  });
+}
+
+function run_test_1() {
+  AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                               "addon2@tests.mozilla.org",
+                               "addon3@tests.mozilla.org",
+                               "addon4@tests.mozilla.org"],
+                              function([a1, a2, a3, a4]) {
+    // addon1
+    let a1Uri = a1.getResourceURI("/").spec;
+    let expected = [
+      {type: "content", baseURI: a1Uri, args: ["test-addon-1", "chrome/content"]},
+      {type: "locale", baseURI: a1Uri, args: ["test-addon-1", "en-US", "locale/en-US"]},
+      {type: "locale", baseURI: a1Uri, args: ["test-addon-1", "fr-FR", "locale/fr-FR"]},
+      {type: "overlay", baseURI: a1Uri, args: ["chrome://browser/content/browser.xul", "chrome://test-addon-1/content/overlay.xul"]}
+    ];
+    let manifestURI = a1.getResourceURI("chrome.manifest");
+    let manifest = ChromeManifestParser.parseSync(manifestURI);
+
+    do_check_true(Array.isArray(manifest));
+    do_check_eq(manifest.length, expected.length);
+    for (let i = 0; i < manifest.length; i++) {
+      do_check_eq(JSON.stringify(manifest[i]), JSON.stringify(expected[i]));
+    }
+
+    // addon2
+    let a2Uri = a2.getResourceURI("/").spec;
+    expected = [
+      {type: "content", baseURI: a2Uri, args: ["test-addon-1", "chrome/content"]},
+      {type: "locale", baseURI: a2Uri, args: ["test-addon-1", "en-US", "locale/en-US"]},
+      {type: "locale", baseURI: a2Uri, args: ["test-addon-1", "fr-FR", "locale/fr-FR"]},
+      {type: "overlay", baseURI: a2Uri, args: ["chrome://browser/content/browser.xul", "chrome://test-addon-1/content/overlay.xul"]},
+      {type: "binary-component", baseURI: a2Uri, args: ["components/something.so"]}
+    ];
+    manifestURI = a2.getResourceURI("chrome.manifest");
+    manifest = ChromeManifestParser.parseSync(manifestURI);
+
+    do_check_true(Array.isArray(manifest));
+    do_check_eq(manifest.length, expected.length);
+    for (let i = 0; i < manifest.length; i++) {
+      do_check_eq(JSON.stringify(manifest[i]), JSON.stringify(expected[i]));
+    }
+
+    // addon3
+    let a3Uri = a3.getResourceURI("/").spec;
+    expected = [
+      {type: "content", baseURI: a3Uri, args: ["test-addon-1", "chrome/content"]},
+      {type: "locale", baseURI: a3Uri, args: ["test-addon-1", "en-US", "locale/en-US"]},
+      {type: "locale", baseURI: a3Uri, args: ["test-addon-1", "fr-FR", "locale/fr-FR"]},
+      {type: "overlay", baseURI: a3Uri, args: ["chrome://browser/content/browser.xul", "chrome://test-addon-1/content/overlay.xul"]},
+      {type: "binary-component", baseURI: a3Uri, args: ["components/something.so"]},
+      {type: "locale", baseURI: "jar:" + a3.getResourceURI("/inner.jar").spec + "!/", args: ["test-addon-1", "en-NZ", "locale/en-NZ"]},
+    ];
+    manifestURI = a3.getResourceURI("chrome.manifest");
+    manifest = ChromeManifestParser.parseSync(manifestURI);
+
+    do_check_true(Array.isArray(manifest));
+    do_check_eq(manifest.length, expected.length);
+    for (let i = 0; i < manifest.length; i++) {
+      do_check_eq(JSON.stringify(manifest[i]), JSON.stringify(expected[i]));
+    }
+
+    // addon4
+    let a4Uri = a4.getResourceURI("/").spec;
+    expected = [
+      {type: "content", baseURI: a4Uri, args: ["test-addon-1", "chrome/content"]},
+      {type: "locale", baseURI: a4Uri, args: ["test-addon-1", "en-US", "locale/en-US"]},
+      {type: "locale", baseURI: a4Uri, args: ["test-addon-1", "fr-FR", "locale/fr-FR"]},
+      {type: "overlay", baseURI: a4Uri, args: ["chrome://browser/content/browser.xul", "chrome://test-addon-1/content/overlay.xul"]},
+      {type: "binary-component", baseURI: a4.getResourceURI("components/").spec, args: ["mycomponent.dll"]},
+      {type: "binary-component", baseURI: a4.getResourceURI("components/other/").spec, args: ["thermalnuclearwar.dll"]}
+    ];
+    manifestURI = a4.getResourceURI("chrome.manifest");
+    manifest = ChromeManifestParser.parseSync(manifestURI);
+
+    do_check_true(Array.isArray(manifest));
+    do_check_eq(manifest.length, expected.length);
+    for (let i = 0; i < manifest.length; i++) {
+      do_check_eq(JSON.stringify(manifest[i]), JSON.stringify(expected[i]));
+    }
+
+    do_test_finished();
+  });
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_1.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_1.js
@@ -32,17 +32,18 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL
  *
  * ***** END LICENSE BLOCK *****
  */
 
 // Disables security checking our updates which haven't been signed
-Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
 
 var ADDONS = [
   "test_bug470377_1",
   "test_bug470377_2",
   "test_bug470377_3",
   "test_bug470377_4",
   "test_bug470377_5",
 ];
@@ -65,17 +66,17 @@ function run_test() {
 
     AddonManager.getAddonsByIDs(["bug470377_1@tests.mozilla.org",
                                  "bug470377_2@tests.mozilla.org",
                                  "bug470377_3@tests.mozilla.org",
                                  "bug470377_4@tests.mozilla.org",
                                  "bug470377_5@tests.mozilla.org"],
                                  function([a1, a2, a3, a4, a5]) {
       do_check_eq(a1, null);
-      do_check_eq(a2, null);
-      do_check_eq(a3, null);
+      do_check_neq(a2, null);
+      do_check_neq(a3, null);
       do_check_neq(a4, null);
       do_check_neq(a5, null);
 
       server.stop(do_test_finished);
     });
   }, true);
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_1_strictcompat.js
@@ -0,0 +1,82 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Dave Townsend <dtownsend@oxymoronical.com>.
+ *
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+// Disables security checking our updates which haven't been signed
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true);
+
+var ADDONS = [
+  "test_bug470377_1",
+  "test_bug470377_2",
+  "test_bug470377_3",
+  "test_bug470377_4",
+  "test_bug470377_5",
+];
+
+do_load_httpd_js();
+var server;
+
+function run_test() {
+  do_test_pending();
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2");
+
+  server = new nsHttpServer();
+  server.registerDirectory("/", do_get_file("data/test_bug470377"));
+  server.start(4444);
+
+  startupManager();
+
+  installAllFiles([do_get_addon(a) for each (a in ADDONS)], function() {
+    restartManager();
+
+    AddonManager.getAddonsByIDs(["bug470377_1@tests.mozilla.org",
+                                 "bug470377_2@tests.mozilla.org",
+                                 "bug470377_3@tests.mozilla.org",
+                                 "bug470377_4@tests.mozilla.org",
+                                 "bug470377_5@tests.mozilla.org"],
+                                 function([a1, a2, a3, a4, a5]) {
+      do_check_eq(a1, null);
+      do_check_eq(a2, null);
+      do_check_eq(a3, null);
+      do_check_neq(a4, null);
+      do_check_neq(a5, null);
+
+      server.stop(do_test_finished);
+    });
+  }, true);
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js
@@ -31,16 +31,18 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL
  *
  * ***** END LICENSE BLOCK *****
  */
 
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
+
 function run_test() {
   do_test_pending();
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2.2.3", "2");
 
   // inject the add-ons into the profile
   var dest = gProfD.clone();
   dest.append("extensions");
   dest.append("bug470377_1@tests.mozilla.org");
@@ -82,19 +84,19 @@ function run_test_1() {
                                "bug470377_2@tests.mozilla.org",
                                "bug470377_3@tests.mozilla.org",
                                "bug470377_4@tests.mozilla.org",
                                "bug470377_5@tests.mozilla.org"],
                                function([a1, a2, a3, a4, a5]) {
     do_check_neq(a1, null);
     do_check_false(a1.isActive);
     do_check_neq(a2, null);
-    do_check_false(a2.isActive);
+    do_check_true(a2.isActive);
     do_check_neq(a3, null);
-    do_check_false(a3.isActive);
+    do_check_true(a3.isActive);
     do_check_neq(a4, null);
     do_check_true(a4.isActive);
     do_check_neq(a5, null);
     do_check_true(a5.isActive);
 
     run_test_2();
   });
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js
@@ -0,0 +1,140 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Dave Townsend <dtownsend@oxymoronical.com>.
+ *
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+function run_test() {
+  do_test_pending();
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2.2.3", "2");
+  Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true);
+
+  // inject the add-ons into the profile
+  var dest = gProfD.clone();
+  dest.append("extensions");
+  dest.append("bug470377_1@tests.mozilla.org");
+  dest.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
+  var source = do_get_file("data/test_bug470377/install_1.rdf");
+  source.copyTo(dest, "install.rdf");
+  dest = gProfD.clone();
+  dest.append("extensions");
+  dest.append("bug470377_2@tests.mozilla.org");
+  dest.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
+  source = do_get_file("data/test_bug470377/install_2.rdf");
+  source.copyTo(dest, "install.rdf");
+  dest = gProfD.clone();
+  dest.append("extensions");
+  dest.append("bug470377_3@tests.mozilla.org");
+  dest.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
+  source = do_get_file("data/test_bug470377/install_3.rdf");
+  source.copyTo(dest, "install.rdf");
+  dest = gProfD.clone();
+  dest.append("extensions");
+  dest.append("bug470377_4@tests.mozilla.org");
+  dest.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
+  source = do_get_file("data/test_bug470377/install_4.rdf");
+  source.copyTo(dest, "install.rdf");
+  dest = gProfD.clone();
+  dest.append("extensions");
+  dest.append("bug470377_5@tests.mozilla.org");
+  dest.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
+  source = do_get_file("data/test_bug470377/install_5.rdf");
+  source.copyTo(dest, "install.rdf");
+
+  startupManager();
+
+  run_test_1();
+}
+
+function run_test_1() {
+  AddonManager.getAddonsByIDs(["bug470377_1@tests.mozilla.org",
+                               "bug470377_2@tests.mozilla.org",
+                               "bug470377_3@tests.mozilla.org",
+                               "bug470377_4@tests.mozilla.org",
+                               "bug470377_5@tests.mozilla.org"],
+                               function([a1, a2, a3, a4, a5]) {
+    do_check_neq(a1, null);
+    do_check_false(a1.isActive);
+    do_check_neq(a2, null);
+    do_check_false(a2.isActive);
+    do_check_neq(a3, null);
+    do_check_false(a3.isActive);
+    do_check_neq(a4, null);
+    do_check_true(a4.isActive);
+    do_check_neq(a5, null);
+    do_check_true(a5.isActive);
+
+    run_test_2();
+  });
+}
+
+function run_test_2() {
+  // Disable compatibility checks
+  var channel = "default";
+  try {
+    channel = Services.prefs.getCharPref("app.update.channel");
+  }
+  catch (e) { }
+
+  if (channel != "aurora" &&
+      channel != "beta" &&
+      channel != "release") {
+    Services.prefs.setBoolPref("extensions.checkCompatibility.nightly", false);
+  }
+  else {
+    Services.prefs.setBoolPref("extensions.checkCompatibility.2.2", false);
+  }
+  restartManager();
+
+  AddonManager.getAddonsByIDs(["bug470377_1@tests.mozilla.org",
+                               "bug470377_2@tests.mozilla.org",
+                               "bug470377_3@tests.mozilla.org",
+                               "bug470377_4@tests.mozilla.org",
+                               "bug470377_5@tests.mozilla.org"],
+                               function([a1, a2, a3, a4, a5]) {
+    do_check_neq(a1, null);
+    do_check_false(a1.isActive);
+    do_check_neq(a2, null);
+    do_check_true(a2.isActive);
+    do_check_neq(a3, null);
+    do_check_true(a3.isActive);
+    do_check_neq(a4, null);
+    do_check_true(a4.isActive);
+    do_check_neq(a5, null);
+    do_check_true(a5.isActive);
+
+    do_test_finished();
+  });
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
@@ -4,17 +4,18 @@
 
 // Checks that we rebuild something sensible from a corrupt database
 
 
 do_load_httpd_js();
 var testserver;
 
 // The test extension uses an insecure update url.
-Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
 
 // Will be enabled
 var addon1 = {
   id: "addon1@tests.mozilla.org",
   version: "1.0",
   name: "Test 1",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
@@ -30,43 +31,43 @@ var addon2 = {
   name: "Test 2",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "2",
     maxVersion: "2"
   }]
 };
 
-// Will get a compatibility update and be enabled
+// Will get a compatibility update and stay enabled
 var addon3 = {
   id: "addon3@tests.mozilla.org",
   version: "1.0",
   name: "Test 3",
   updateURL: "http://localhost:4444/data/test_corrupt.rdf",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "1",
     maxVersion: "1"
   }]
 };
 
-// Will get a compatibility update and be disabled
+// Will get a compatibility update and be enabled
 var addon4 = {
   id: "addon4@tests.mozilla.org",
   version: "1.0",
   name: "Test 4",
   updateURL: "http://localhost:4444/data/test_corrupt.rdf",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "1",
     maxVersion: "1"
   }]
 };
 
-// Stays incompatible
+// Would stay incompatible with strict compat
 var addon5 = {
   id: "addon5@tests.mozilla.org",
   version: "1.0",
   name: "Test 5",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "1",
     maxVersion: "1"
@@ -212,19 +213,19 @@ function run_test_1() {
 
     do_check_neq(a4, null);
     do_check_false(a4.isActive);
     do_check_true(a4.userDisabled);
     do_check_false(a4.appDisabled);
     do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
 
     do_check_neq(a5, null);
-    do_check_false(a5.isActive);
+    do_check_true(a5.isActive);
     do_check_false(a5.userDisabled);
-    do_check_true(a5.appDisabled);
+    do_check_false(a5.appDisabled);
     do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
 
     do_check_neq(a6, null);
     do_check_true(a6.isActive);
     do_check_false(a6.userDisabled);
     do_check_false(a6.appDisabled);
     do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
 
@@ -280,31 +281,33 @@ function run_test_1() {
       do_check_false(a2.appDisabled);
       do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
 
       // The compatibility update won't be recovered but it should still be
       // active for this session
       do_check_neq(a3, null);
       do_check_true(a3.isActive);
       do_check_false(a3.userDisabled);
-      do_check_true(a3.appDisabled);
-      do_check_eq(a3.pendingOperations, AddonManager.PENDING_DISABLE);
+      do_check_false(a3.appDisabled);
+      do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
 
-      // The compatibility update won't be recovered and it will not have been
-      // able to tell that it was previously userDisabled
+      // The compatibility update won't be recovered and with strict
+      // compatibility it would not have been able to tell that it was
+      // previously userDisabled. However, without strict compat, it wasn't
+      // appDisabled, so it knows it must have been userDisabled.
       do_check_neq(a4, null);
       do_check_false(a4.isActive);
-      do_check_false(a4.userDisabled);
-      do_check_true(a4.appDisabled);
+      do_check_true(a4.userDisabled);
+      do_check_false(a4.appDisabled);
       do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
 
       do_check_neq(a5, null);
-      do_check_false(a5.isActive);
+      do_check_true(a5.isActive);
       do_check_false(a5.userDisabled);
-      do_check_true(a5.appDisabled);
+      do_check_false(a5.appDisabled);
       do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
 
       do_check_neq(a6, null);
       do_check_true(a6.isActive);
       do_check_false(a6.userDisabled);
       do_check_false(a6.appDisabled);
       do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
 
@@ -349,31 +352,31 @@ function run_test_1() {
 
         do_check_neq(a2, null);
         do_check_false(a2.isActive);
         do_check_true(a2.userDisabled);
         do_check_false(a2.appDisabled);
         do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
 
         do_check_neq(a3, null);
-        do_check_false(a3.isActive);
+        do_check_true(a3.isActive);
         do_check_false(a3.userDisabled);
-        do_check_true(a3.appDisabled);
+        do_check_false(a3.appDisabled);
         do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
 
         do_check_neq(a4, null);
         do_check_false(a4.isActive);
-        do_check_false(a4.userDisabled);
-        do_check_true(a4.appDisabled);
+        do_check_true(a4.userDisabled);
+        do_check_false(a4.appDisabled);
         do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
 
         do_check_neq(a5, null);
-        do_check_false(a5.isActive);
+        do_check_true(a5.isActive);
         do_check_false(a5.userDisabled);
-        do_check_true(a5.appDisabled);
+        do_check_false(a5.appDisabled);
         do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
 
         do_check_neq(a6, null);
         do_check_true(a6.isActive);
         do_check_false(a6.userDisabled);
         do_check_false(a6.appDisabled);
         do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js
@@ -0,0 +1,403 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Checks that we rebuild something sensible from a corrupt database
+
+
+do_load_httpd_js();
+var testserver;
+
+// The test extension uses an insecure update url.
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true);
+
+// Will be enabled
+var addon1 = {
+  id: "addon1@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 1",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "2",
+    maxVersion: "2"
+  }]
+};
+
+// Will be disabled
+var addon2 = {
+  id: "addon2@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 2",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "2",
+    maxVersion: "2"
+  }]
+};
+
+// Will get a compatibility update and be enabled
+var addon3 = {
+  id: "addon3@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 3",
+  updateURL: "http://localhost:4444/data/test_corrupt.rdf",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "1",
+    maxVersion: "1"
+  }]
+};
+
+// Will get a compatibility update and be disabled
+var addon4 = {
+  id: "addon4@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 4",
+  updateURL: "http://localhost:4444/data/test_corrupt.rdf",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "1",
+    maxVersion: "1"
+  }]
+};
+
+// Stays incompatible
+var addon5 = {
+  id: "addon5@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 5",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "1",
+    maxVersion: "1"
+  }]
+};
+
+// Enabled bootstrapped
+var addon6 = {
+  id: "addon6@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 6",
+  bootstrap: "true",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "2",
+    maxVersion: "2"
+  }]
+};
+
+// Disabled bootstrapped
+var addon7 = {
+  id: "addon7@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 7",
+  bootstrap: "true",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "2",
+    maxVersion: "2"
+  }]
+};
+
+// The default theme
+var theme1 = {
+  id: "theme1@tests.mozilla.org",
+  version: "1.0",
+  name: "Theme 1",
+  internalName: "classic/1.0",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "2",
+    maxVersion: "2"
+  }]
+};
+
+// The selected theme
+var theme2 = {
+  id: "theme2@tests.mozilla.org",
+  version: "1.0",
+  name: "Theme 2",
+  internalName: "test/1.0",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "2",
+    maxVersion: "2"
+  }]
+};
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+function run_test() {
+  do_test_pending();
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2");
+
+  writeInstallRDFForExtension(addon1, profileDir);
+  writeInstallRDFForExtension(addon2, profileDir);
+  writeInstallRDFForExtension(addon3, profileDir);
+  writeInstallRDFForExtension(addon4, profileDir);
+  writeInstallRDFForExtension(addon5, profileDir);
+  writeInstallRDFForExtension(addon6, profileDir);
+  writeInstallRDFForExtension(addon7, profileDir);
+  writeInstallRDFForExtension(theme1, profileDir);
+  writeInstallRDFForExtension(theme2, profileDir);
+
+  // Create and configure the HTTP server.
+  testserver = new nsHttpServer();
+  testserver.registerDirectory("/addons/", do_get_file("addons"));
+  testserver.registerDirectory("/data/", do_get_file("data"));
+  testserver.start(4444);
+
+  // Startup the profile and setup the initial state
+  startupManager();
+
+  AddonManager.getAddonsByIDs(["addon2@tests.mozilla.org",
+                               "addon3@tests.mozilla.org",
+                               "addon4@tests.mozilla.org",
+                               "addon7@tests.mozilla.org",
+                               "theme2@tests.mozilla.org"], function([a2, a3, a4,
+                                                                      a7, t2]) {
+    // Set up the initial state
+    a2.userDisabled = true;
+    a4.userDisabled = true;
+    a7.userDisabled = true;
+    t2.userDisabled = false;
+    a3.findUpdates({
+      onUpdateFinished: function() {
+        a4.findUpdates({
+          onUpdateFinished: function() {
+            restartManager();
+
+            run_test_1();
+          }
+        }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
+      }
+    }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
+  });
+}
+
+function end_test() {
+  testserver.stop(do_test_finished);
+}
+
+function run_test_1() {
+  AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                               "addon2@tests.mozilla.org",
+                               "addon3@tests.mozilla.org",
+                               "addon4@tests.mozilla.org",
+                               "addon5@tests.mozilla.org",
+                               "addon6@tests.mozilla.org",
+                               "addon7@tests.mozilla.org",
+                               "theme1@tests.mozilla.org",
+                               "theme2@tests.mozilla.org"], function([a1, a2, a3,
+                                                                      a4, a5, a6,
+                                                                      a7, t1, t2]) {
+    do_check_neq(a1, null);
+    do_check_true(a1.isActive);
+    do_check_false(a1.userDisabled);
+    do_check_false(a1.appDisabled);
+    do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(a2, null);
+    do_check_false(a2.isActive);
+    do_check_true(a2.userDisabled);
+    do_check_false(a2.appDisabled);
+    do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(a3, null);
+    do_check_true(a3.isActive);
+    do_check_false(a3.userDisabled);
+    do_check_false(a3.appDisabled);
+    do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(a4, null);
+    do_check_false(a4.isActive);
+    do_check_true(a4.userDisabled);
+    do_check_false(a4.appDisabled);
+    do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(a5, null);
+    do_check_false(a5.isActive);
+    do_check_false(a5.userDisabled);
+    do_check_true(a5.appDisabled);
+    do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(a6, null);
+    do_check_true(a6.isActive);
+    do_check_false(a6.userDisabled);
+    do_check_false(a6.appDisabled);
+    do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(a7, null);
+    do_check_false(a7.isActive);
+    do_check_true(a7.userDisabled);
+    do_check_false(a7.appDisabled);
+    do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(t1, null);
+    do_check_false(t1.isActive);
+    do_check_true(t1.userDisabled);
+    do_check_false(t1.appDisabled);
+    do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(t2, null);
+    do_check_true(t2.isActive);
+    do_check_false(t2.userDisabled);
+    do_check_false(t2.appDisabled);
+    do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
+
+    // After restarting the database won't be open and so can be replaced with
+    // a bad file
+    restartManager();
+    var dbfile = gProfD.clone();
+    dbfile.append("extensions.sqlite");
+    dbfile.remove(true);
+    dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
+
+    // Accessing the add-ons should open and recover the database
+    AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                                 "addon2@tests.mozilla.org",
+                                 "addon3@tests.mozilla.org",
+                                 "addon4@tests.mozilla.org",
+                                 "addon5@tests.mozilla.org",
+                                 "addon6@tests.mozilla.org",
+                                 "addon7@tests.mozilla.org",
+                                 "theme1@tests.mozilla.org",
+                                 "theme2@tests.mozilla.org"], function([a1, a2, a3,
+                                                                        a4, a5, a6,
+                                                                        a7, t1, t2]) {
+      // Should be correctly recovered
+      do_check_neq(a1, null);
+      do_check_true(a1.isActive);
+      do_check_false(a1.userDisabled);
+      do_check_false(a1.appDisabled);
+      do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
+
+      // Should be correctly recovered
+      do_check_neq(a2, null);
+      do_check_false(a2.isActive);
+      do_check_true(a2.userDisabled);
+      do_check_false(a2.appDisabled);
+      do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
+
+      // The compatibility update won't be recovered but it should still be
+      // active for this session
+      do_check_neq(a3, null);
+      do_check_true(a3.isActive);
+      do_check_false(a3.userDisabled);
+      do_check_true(a3.appDisabled);
+      do_check_eq(a3.pendingOperations, AddonManager.PENDING_DISABLE);
+
+      // The compatibility update won't be recovered and it will not have been
+      // able to tell that it was previously userDisabled
+      do_check_neq(a4, null);
+      do_check_false(a4.isActive);
+      do_check_false(a4.userDisabled);
+      do_check_true(a4.appDisabled);
+      do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
+
+      do_check_neq(a5, null);
+      do_check_false(a5.isActive);
+      do_check_false(a5.userDisabled);
+      do_check_true(a5.appDisabled);
+      do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
+
+      do_check_neq(a6, null);
+      do_check_true(a6.isActive);
+      do_check_false(a6.userDisabled);
+      do_check_false(a6.appDisabled);
+      do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
+
+      do_check_neq(a7, null);
+      do_check_false(a7.isActive);
+      do_check_true(a7.userDisabled);
+      do_check_false(a7.appDisabled);
+      do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
+
+      // Should be correctly recovered
+      do_check_neq(t1, null);
+      do_check_false(t1.isActive);
+      do_check_true(t1.userDisabled);
+      do_check_false(t1.appDisabled);
+      do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
+
+      // Should be correctly recovered
+      do_check_neq(t2, null);
+      do_check_true(t2.isActive);
+      do_check_false(t2.userDisabled);
+      do_check_false(t2.appDisabled);
+      do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
+
+      restartManager();
+
+      AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                                   "addon2@tests.mozilla.org",
+                                   "addon3@tests.mozilla.org",
+                                   "addon4@tests.mozilla.org",
+                                   "addon5@tests.mozilla.org",
+                                   "addon6@tests.mozilla.org",
+                                   "addon7@tests.mozilla.org",
+                                   "theme1@tests.mozilla.org",
+                                   "theme2@tests.mozilla.org"], function([a1, a2, a3,
+                                                                          a4, a5, a6,
+                                                                          a7, t1, t2]) {
+        do_check_neq(a1, null);
+        do_check_true(a1.isActive);
+        do_check_false(a1.userDisabled);
+        do_check_false(a1.appDisabled);
+        do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(a2, null);
+        do_check_false(a2.isActive);
+        do_check_true(a2.userDisabled);
+        do_check_false(a2.appDisabled);
+        do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(a3, null);
+        do_check_false(a3.isActive);
+        do_check_false(a3.userDisabled);
+        do_check_true(a3.appDisabled);
+        do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(a4, null);
+        do_check_false(a4.isActive);
+        do_check_false(a4.userDisabled);
+        do_check_true(a4.appDisabled);
+        do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(a5, null);
+        do_check_false(a5.isActive);
+        do_check_false(a5.userDisabled);
+        do_check_true(a5.appDisabled);
+        do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(a6, null);
+        do_check_true(a6.isActive);
+        do_check_false(a6.userDisabled);
+        do_check_false(a6.appDisabled);
+        do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(a7, null);
+        do_check_false(a7.isActive);
+        do_check_true(a7.userDisabled);
+        do_check_false(a7.appDisabled);
+        do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(t1, null);
+        do_check_false(t1.isActive);
+        do_check_true(t1.userDisabled);
+        do_check_false(t1.appDisabled);
+        do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(t2, null);
+        do_check_true(t2.isActive);
+        do_check_false(t2.userDisabled);
+        do_check_false(t2.appDisabled);
+        do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
+
+        end_test();
+      });
+    });
+  });
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests detection of binary components via parsing of chrome manifests.
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+function run_test() {
+  do_test_pending();
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2");
+
+  startupManager();
+  
+  installAllFiles([do_get_addon("test_chromemanifest_1"),
+                   do_get_addon("test_chromemanifest_2"),
+                   do_get_addon("test_chromemanifest_3"),
+                   do_get_addon("test_chromemanifest_4"),
+                   do_get_addon("test_chromemanifest_5")],
+                  function() {
+
+    restartManager();
+
+    AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                                 "addon2@tests.mozilla.org",
+                                 "addon3@tests.mozilla.org",
+                                 "addon4@tests.mozilla.org",
+                                 "addon5@tests.mozilla.org"],
+                                function([a1, a2, a3, a4, a5]) {
+      // addon1 has no binary components
+      do_check_neq(a1, null);
+      do_check_false(a1.userDisabled);
+      do_check_false(a1.hasBinaryComponents);
+      do_check_true(a1.isCompatible);
+      do_check_false(a1.appDisabled);
+      do_check_true(a1.isActive);
+      do_check_true(isExtensionInAddonsList(profileDir, a1.id));
+
+      // addon2 has a binary component, is compatible
+      do_check_neq(a2, null);
+      do_check_false(a2.userDisabled);
+      do_check_true(a2.hasBinaryComponents);
+      do_check_true(a2.isCompatible);
+      do_check_false(a2.appDisabled);
+      do_check_true(a2.isActive);
+      do_check_true(isExtensionInAddonsList(profileDir, a2.id));
+
+      // addon3 has a binary component, is incompatible
+      do_check_neq(a3, null);
+      do_check_false(a3.userDisabled);
+      do_check_true(a2.hasBinaryComponents);
+      do_check_false(a3.isCompatible);
+      do_check_true(a3.appDisabled);
+      do_check_false(a3.isActive);
+      do_check_false(isExtensionInAddonsList(profileDir, a3.id));
+
+      // addon4 has a binary component listed in a sub-manifest, is incompatible
+      do_check_neq(a4, null);
+      do_check_false(a4.userDisabled);
+      do_check_true(a2.hasBinaryComponents);
+      do_check_false(a4.isCompatible);
+      do_check_true(a4.appDisabled);
+      do_check_false(a4.isActive);
+      do_check_false(isExtensionInAddonsList(profileDir, a4.id));
+
+      // addon5 has a binary component, but is set to not unpack
+      do_check_neq(a5, null);
+      do_check_false(a5.userDisabled);
+      if (TEST_UNPACKED)
+        do_check_true(a5.hasBinaryComponents);
+      else
+        do_check_false(a5.hasBinaryComponents);
+      do_check_true(a5.isCompatible);
+      do_check_false(a5.appDisabled);
+      do_check_true(a5.isActive);
+      do_check_true(isExtensionInAddonsList(profileDir, a5.id));
+
+      do_test_finished();
+    });
+  });
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_install.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js
@@ -16,17 +16,18 @@ Components.utils.import("resource://gre/
 const ADDON1_SIZE = 705 + 16 + 16;
 
 do_load_httpd_js();
 var testserver;
 var gInstallDate;
 var gInstall = null;
 
 // The test extension uses an insecure update url.
-Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
 
 const profileDir = gProfD.clone();
 profileDir.append("extensions");
 
 function run_test() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
 
   startupManager();
@@ -666,24 +667,24 @@ function run_test_11() {
     do_check_eq(installs[1].addon.id, "addon5@tests.mozilla.org");
     do_check_false(installs[1].addon.appDisabled);
     do_check_eq(installs[1].version, "3.0");
     do_check_eq(installs[1].name, "Multi Test 2");
     do_check_eq(installs[1].state, AddonManager.STATE_DOWNLOADED);
     do_check_true(hasFlag(installs[1].addon.operationsRequiringRestart,
                           AddonManager.OP_NEEDS_RESTART_INSTALL));
 
-    // Comes from addon6.xpi and is incompatible
+    // Comes from addon6.xpi and would be incompatible with strict compat enabled
     do_check_eq(installs[2].sourceURI, install.sourceURI);
     do_check_eq(installs[2].addon.id, "addon6@tests.mozilla.org");
-    do_check_true(installs[2].addon.appDisabled);
+    do_check_false(installs[2].addon.appDisabled);
     do_check_eq(installs[2].version, "2.0");
     do_check_eq(installs[2].name, "Multi Test 3");
     do_check_eq(installs[2].state, AddonManager.STATE_DOWNLOADED);
-    do_check_false(hasFlag(installs[2].addon.operationsRequiringRestart,
+    do_check_true(hasFlag(installs[2].addon.operationsRequiringRestart,
                            AddonManager.OP_NEEDS_RESTART_INSTALL));
 
     // Comes from addon7.jar and is made compatible by an update check
     do_check_eq(installs[3].sourceURI, install.sourceURI);
     do_check_eq(installs[3].addon.id, "addon7@tests.mozilla.org");
     do_check_false(installs[3].addon.appDisabled);
     do_check_eq(installs[3].version, "5.0");
     do_check_eq(installs[3].name, "Multi Test 4");
@@ -697,18 +698,17 @@ function run_test_11() {
       prepare_test({
         "addon4@tests.mozilla.org": [
           "onInstalling"
         ],
         "addon5@tests.mozilla.org": [
           "onInstalling"
         ],
         "addon6@tests.mozilla.org": [
-          ["onInstalling", false],
-          "onInstalled"
+          "onInstalling"
         ],
         "addon7@tests.mozilla.org": [
           "onInstalling"
         ]
       }, {
         "addon4@tests.mozilla.org": [
           "onInstallStarted",
           "onInstallEnded"
@@ -782,18 +782,17 @@ function run_test_12() {
     prepare_test({
       "addon4@tests.mozilla.org": [
         "onInstalling"
       ],
       "addon5@tests.mozilla.org": [
         "onInstalling"
       ],
       "addon6@tests.mozilla.org": [
-        ["onInstalling", false],
-        "onInstalled"
+        "onInstalling"
       ],
       "addon7@tests.mozilla.org": [
         "onInstalling"
       ]
     }, {
       "NO_ID": [
         "onDownloadStarted",
         "onNewInstall",
@@ -846,20 +845,20 @@ function check_test_12() {
   // Comes from addon5.jar and is compatible by default
   do_check_eq(installs[1].sourceURI, gInstall.sourceURI);
   do_check_eq(installs[1].addon.id, "addon5@tests.mozilla.org");
   do_check_false(installs[1].addon.appDisabled);
   do_check_eq(installs[1].version, "3.0");
   do_check_eq(installs[1].name, "Multi Test 2");
   do_check_eq(installs[1].state, AddonManager.STATE_INSTALLED);
 
-  // Comes from addon6.xpi and is incompatible
+  // Comes from addon6.xpi and would be incompatible with strict compat enabled
   do_check_eq(installs[2].sourceURI, gInstall.sourceURI);
   do_check_eq(installs[2].addon.id, "addon6@tests.mozilla.org");
-  do_check_true(installs[2].addon.appDisabled);
+  do_check_false(installs[2].addon.appDisabled);
   do_check_eq(installs[2].version, "2.0");
   do_check_eq(installs[2].name, "Multi Test 3");
   do_check_eq(installs[2].state, AddonManager.STATE_INSTALLED);
 
   // Comes from addon7.jar and is made compatible by an update check
   do_check_eq(installs[3].sourceURI, gInstall.sourceURI);
   do_check_eq(installs[3].addon.id, "addon7@tests.mozilla.org");
   do_check_false(installs[3].addon.appDisabled);
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js
@@ -0,0 +1,1623 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This verifies that add-ons can be installed from XPI files
+
+// Maximum error in file modification times. Some file systems don't store
+// modification times exactly. As long as we are closer than this then it
+// still passes.
+const MAX_TIME_DIFFERENCE = 3000;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+// install.rdf size, icon.png, icon64.png size
+const ADDON1_SIZE = 705 + 16 + 16;
+
+do_load_httpd_js();
+var testserver;
+var gInstallDate;
+var gInstall = null;
+
+// The test extension uses an insecure update url.
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true);
+
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+function run_test() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+
+  startupManager();
+  // Make sure we only register once despite multiple calls
+  AddonManager.addInstallListener(InstallListener);
+  AddonManager.addAddonListener(AddonListener);
+  AddonManager.addInstallListener(InstallListener);
+  AddonManager.addAddonListener(AddonListener);
+
+  // Create and configure the HTTP server.
+  testserver = new nsHttpServer();
+  testserver.registerDirectory("/addons/", do_get_file("addons"));
+  testserver.registerDirectory("/data/", do_get_file("data"));
+  testserver.registerPathHandler("/redirect", function(aRequest, aResponse) {
+    aResponse.setStatusLine(null, 301, "Moved Permanently");
+    let url = aRequest.host + ":" + aRequest.port + aRequest.queryString;
+    aResponse.setHeader("Location", "http://" + url);
+  });
+  testserver.start(4444);
+
+  do_test_pending();
+  run_test_1();
+}
+
+function end_test() {
+  testserver.stop(do_test_finished);
+}
+
+// Checks that an install from a local file proceeds as expected
+function run_test_1() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  AddonManager.getInstallForFile(do_get_addon("test_install1"), function(install) {
+    ensure_test_completed();
+
+    do_check_neq(install, null);
+    do_check_eq(install.linkedInstalls, null);
+    do_check_eq(install.type, "extension");
+    do_check_eq(install.version, "1.0");
+    do_check_eq(install.name, "Test 1");
+    do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
+    do_check_true(install.addon.hasResource("install.rdf"));
+    do_check_eq(install.addon.install, install);
+    do_check_eq(install.addon.size, ADDON1_SIZE);
+    do_check_true(hasFlag(install.addon.operationsRequiringRestart,
+                          AddonManager.OP_NEEDS_RESTART_INSTALL));
+    let file = do_get_addon("test_install1");
+    let uri = Services.io.newFileURI(file).spec;
+    do_check_eq(install.addon.getResourceURI("install.rdf").spec, "jar:" + uri + "!/install.rdf");
+    do_check_eq(install.addon.iconURL, "jar:" + uri + "!/icon.png");
+    do_check_eq(install.addon.icon64URL, "jar:" + uri + "!/icon64.png");
+    do_check_eq(install.iconURL, null);
+
+    do_check_eq(install.sourceURI.spec, uri);
+    do_check_eq(install.addon.sourceURI.spec, uri);
+
+    AddonManager.getAllInstalls(function(activeInstalls) {
+      do_check_eq(activeInstalls.length, 1);
+      do_check_eq(activeInstalls[0], install);
+
+      AddonManager.getInstallsByTypes(["foo"], function(fooInstalls) {
+        do_check_eq(fooInstalls.length, 0);
+
+        AddonManager.getInstallsByTypes(["extension"], function(extensionInstalls) {
+          do_check_eq(extensionInstalls.length, 1);
+          do_check_eq(extensionInstalls[0], install);
+
+          prepare_test({
+            "addon1@tests.mozilla.org": [
+              "onInstalling"
+            ]
+          }, [
+            "onInstallStarted",
+            "onInstallEnded",
+          ], check_test_1);
+          install.install();
+        });
+      });
+    });
+  });
+}
+
+function check_test_1() {
+  ensure_test_completed();
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) {
+    do_check_eq(olda1, null);
+
+    AddonManager.getAddonsWithOperationsByTypes(null, function(pendingAddons) {
+      do_check_eq(pendingAddons.length, 1);
+      do_check_eq(pendingAddons[0].id, "addon1@tests.mozilla.org");
+      let uri = NetUtil.newURI(pendingAddons[0].iconURL);
+      if (uri instanceof AM_Ci.nsIJARURI) {
+        let jarURI = uri.QueryInterface(AM_Ci.nsIJARURI);
+        let archiveURI = jarURI.JARFile;
+        let archiveFile = archiveURI.QueryInterface(AM_Ci.nsIFileURL).file;
+        let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+                        createInstance(Ci.nsIZipReader);
+        try {
+          zipReader.open(archiveFile);
+          do_check_true(zipReader.hasEntry(jarURI.JAREntry));
+        }
+        finally {
+          zipReader.close();
+        }
+      }
+      else {
+        let iconFile = uri.QueryInterface(AM_Ci.nsIFileURL).file;
+        do_check_true(iconFile.exists());
+      }
+
+      // Make the pending install have a sensible date
+      let updateDate = Date.now();
+      let extURI = pendingAddons[0].getResourceURI("");
+      let ext = extURI.QueryInterface(AM_Ci.nsIFileURL).file;
+      setExtensionModifiedTime(ext, updateDate);
+
+      // The pending add-on cannot be disabled or enabled.
+      do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_ENABLE));
+      do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_DISABLE));
+
+      restartManager();
+
+      AddonManager.getAllInstalls(function(activeInstalls) {
+        do_check_eq(activeInstalls, 0);
+
+        AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+          do_check_neq(a1, null);
+          do_check_eq(a1.type, "extension");
+          do_check_eq(a1.version, "1.0");
+          do_check_eq(a1.name, "Test 1");
+          do_check_true(isExtensionInAddonsList(profileDir, a1.id));
+          do_check_true(do_get_addon("test_install1").exists());
+          do_check_in_crash_annotation(a1.id, a1.version);
+          do_check_eq(a1.size, ADDON1_SIZE);
+
+          do_check_eq(a1.sourceURI.spec,
+                      Services.io.newFileURI(do_get_addon("test_install1")).spec);
+          let difference = a1.installDate.getTime() - updateDate;
+          if (Math.abs(difference) > MAX_TIME_DIFFERENCE)
+            do_throw("Add-on install time was out by " + difference + "ms");
+
+          difference = a1.updateDate.getTime() - updateDate;
+          if (Math.abs(difference) > MAX_TIME_DIFFERENCE)
+            do_throw("Add-on update time was out by " + difference + "ms");
+
+          do_check_true(a1.hasResource("install.rdf"));
+          do_check_false(a1.hasResource("foo.bar"));
+
+          let uri = do_get_addon_root_uri(profileDir, "addon1@tests.mozilla.org");
+          do_check_eq(a1.getResourceURI("install.rdf").spec, uri + "install.rdf");
+          do_check_eq(a1.iconURL, uri + "icon.png");
+          do_check_eq(a1.icon64URL, uri + "icon64.png");
+
+          a1.uninstall();
+          restartManager();
+          do_check_not_in_crash_annotation(a1.id, a1.version);
+
+          run_test_2();
+        });
+      });
+    });
+  });
+}
+
+// Tests that an install from a url downloads.
+function run_test_2() {
+  let url = "http://localhost:4444/addons/test_install2_1.xpi";
+  AddonManager.getInstallForURL(url, function(install) {
+    do_check_neq(install, null);
+    do_check_eq(install.linkedInstalls, null);
+    do_check_eq(install.version, "1.0");
+    do_check_eq(install.name, "Test 2");
+    do_check_eq(install.state, AddonManager.STATE_AVAILABLE);
+    do_check_eq(install.iconURL, null);
+    do_check_eq(install.sourceURI.spec, url);
+
+    AddonManager.getAllInstalls(function(activeInstalls) {
+      do_check_eq(activeInstalls.length, 1);
+      do_check_eq(activeInstalls[0], install);
+
+      prepare_test({}, [
+        "onDownloadStarted",
+        "onDownloadEnded",
+      ], check_test_2);
+
+      install.addListener({
+        onDownloadProgress: function(install) {
+          do_execute_soon(function() {
+            Components.utils.forceGC();
+          });
+        }
+      });
+
+      install.install();
+    });
+  }, "application/x-xpinstall", null, "Test 2", null, "1.0");
+}
+
+function check_test_2(install) {
+  ensure_test_completed();
+  do_check_eq(install.version, "2.0");
+  do_check_eq(install.name, "Real Test 2");
+  do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
+  do_check_eq(install.addon.install, install);
+  do_check_true(hasFlag(install.addon.operationsRequiringRestart,
+                        AddonManager.OP_NEEDS_RESTART_INSTALL));
+  do_check_eq(install.iconURL, null);
+
+  // Pause the install here and start it again in run_test_3
+  do_execute_soon(function() { run_test_3(install); });
+  return false;
+}
+
+// Tests that the downloaded XPI installs ok
+function run_test_3(install) {
+  prepare_test({
+    "addon2@tests.mozilla.org": [
+      "onInstalling"
+    ]
+  }, [
+    "onInstallStarted",
+    "onInstallEnded",
+  ], check_test_3);
+  install.install();
+}
+
+function check_test_3(aInstall) {
+  // Make the pending install have a sensible date
+  let updateDate = Date.now();
+  let extURI = aInstall.addon.getResourceURI("");
+  let ext = extURI.QueryInterface(AM_Ci.nsIFileURL).file;
+  setExtensionModifiedTime(ext, updateDate);
+
+  ensure_test_completed();
+  AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) {
+    do_check_eq(olda2, null);
+    restartManager();
+
+    AddonManager.getAllInstalls(function(installs) {
+      do_check_eq(installs, 0);
+
+      AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+        do_check_neq(a2, null);
+        do_check_eq(a2.type, "extension");
+        do_check_eq(a2.version, "2.0");
+        do_check_eq(a2.name, "Real Test 2");
+        do_check_true(isExtensionInAddonsList(profileDir, a2.id));
+        do_check_true(do_get_addon("test_install2_1").exists());
+        do_check_in_crash_annotation(a2.id, a2.version);
+        do_check_eq(a2.sourceURI.spec,
+                    "http://localhost:4444/addons/test_install2_1.xpi");
+
+        let difference = a2.installDate.getTime() - updateDate;
+        if (Math.abs(difference) > MAX_TIME_DIFFERENCE)
+          do_throw("Add-on install time was out by " + difference + "ms");
+
+        difference = a2.updateDate.getTime() - updateDate;
+        if (Math.abs(difference) > MAX_TIME_DIFFERENCE)
+          do_throw("Add-on update time was out by " + difference + "ms");
+
+        gInstallDate = a2.installDate.getTime();
+
+        run_test_4();
+      });
+    });
+  });
+}
+
+// Tests that installing a new version of an existing add-on works
+function run_test_4() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://localhost:4444/addons/test_install2_2.xpi";
+  AddonManager.getInstallForURL(url, function(install) {
+    ensure_test_completed();
+
+    do_check_neq(install, null);
+    do_check_eq(install.version, "3.0");
+    do_check_eq(install.name, "Test 3");
+    do_check_eq(install.state, AddonManager.STATE_AVAILABLE);
+
+    AddonManager.getAllInstalls(function(activeInstalls) {
+      do_check_eq(activeInstalls.length, 1);
+      do_check_eq(activeInstalls[0], install);
+      do_check_eq(install.existingAddon, null);
+
+      prepare_test({}, [
+        "onDownloadStarted",
+        "onDownloadEnded",
+      ], check_test_4);
+      install.install();
+    });
+  }, "application/x-xpinstall", null, "Test 3", null, "3.0");
+}
+
+function check_test_4(install) {
+  ensure_test_completed();
+
+  do_check_eq(install.version, "3.0");
+  do_check_eq(install.name, "Real Test 3");
+  do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
+  do_check_neq(install.existingAddon);
+  do_check_eq(install.existingAddon.id, "addon2@tests.mozilla.org");
+  do_check_eq(install.addon.install, install);
+  do_check_true(hasFlag(install.addon.operationsRequiringRestart,
+                        AddonManager.OP_NEEDS_RESTART_INSTALL));
+
+  run_test_5();
+  // Installation will continue when there is nothing returned.
+}
+
+// Continue installing the new version
+function run_test_5() {
+  prepare_test({
+    "addon2@tests.mozilla.org": [
+      "onInstalling"
+    ]
+  }, [
+    "onInstallStarted",
+    "onInstallEnded",
+  ], check_test_5);
+}
+
+function check_test_5(install) {
+  ensure_test_completed();
+
+  do_check_eq(install.existingAddon.pendingUpgrade.install, install);
+
+  AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) {
+    do_check_neq(olda2, null);
+    do_check_true(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE));
+
+    AddonManager.getInstallsByTypes(null, function(installs) {
+      do_check_eq(installs.length, 1);
+      do_check_eq(installs[0].addon, olda2.pendingUpgrade);
+      restartManager();
+
+      AddonManager.getInstallsByTypes(null, function(installs) {
+        do_check_eq(installs.length, 0);
+
+        AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+          do_check_neq(a2, null);
+          do_check_eq(a2.type, "extension");
+          do_check_eq(a2.version, "3.0");
+          do_check_eq(a2.name, "Real Test 3");
+          do_check_true(a2.isActive);
+          do_check_true(isExtensionInAddonsList(profileDir, a2.id));
+          do_check_true(do_get_addon("test_install2_2").exists());
+          do_check_in_crash_annotation(a2.id, a2.version);
+          do_check_eq(a2.sourceURI.spec,
+                      "http://localhost:4444/addons/test_install2_2.xpi");
+
+          do_check_eq(a2.installDate.getTime(), gInstallDate);
+          // Update date should be later (or the same if this test is too fast)
+          do_check_true(a2.installDate <= a2.updateDate);
+
+          a2.uninstall();
+          restartManager();
+
+          run_test_6();
+        });
+      });
+    });
+  });
+}
+
+// Tests that an install that requires a compatibility update works
+function run_test_6() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://localhost:4444/addons/test_install3.xpi";
+  AddonManager.getInstallForURL(url, function(install) {
+    ensure_test_completed();
+
+    do_check_neq(install, null);
+    do_check_eq(install.version, "1.0");
+    do_check_eq(install.name, "Real Test 4");
+    do_check_eq(install.state, AddonManager.STATE_AVAILABLE);
+
+    AddonManager.getInstallsByTypes(null, function(activeInstalls) {
+      do_check_eq(activeInstalls.length, 1);
+      do_check_eq(activeInstalls[0], install);
+
+      prepare_test({}, [
+        "onDownloadStarted",
+        "onDownloadEnded",
+      ], check_test_6);
+      install.install();
+    });
+  }, "application/x-xpinstall", null, "Real Test 4", null, "1.0");
+}
+
+function check_test_6(install) {
+  ensure_test_completed();
+  do_check_eq(install.version, "1.0");
+  do_check_eq(install.name, "Real Test 4");
+  do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
+  do_check_eq(install.existingAddon, null);
+  do_check_false(install.addon.appDisabled);
+  run_test_7();
+  return true;
+}
+
+// Continue the install
+function run_test_7() {
+  prepare_test({
+    "addon3@tests.mozilla.org": [
+      "onInstalling"
+    ]
+  }, [
+    "onInstallStarted",
+    "onInstallEnded",
+  ], check_test_7);
+}
+
+function check_test_7() {
+  ensure_test_completed();
+  AddonManager.getAddonByID("addon3@tests.mozilla.org", function(olda3) {
+    do_check_eq(olda3, null);
+    restartManager();
+
+    AddonManager.getAllInstalls(function(installs) {
+      do_check_eq(installs, 0);
+
+      AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
+        do_check_neq(a3, null);
+        do_check_eq(a3.type, "extension");
+        do_check_eq(a3.version, "1.0");
+        do_check_eq(a3.name, "Real Test 4");
+        do_check_true(a3.isActive);
+        do_check_false(a3.appDisabled);
+        do_check_true(isExtensionInAddonsList(profileDir, a3.id));
+        do_check_true(do_get_addon("test_install3").exists());
+        a3.uninstall();
+        restartManager();
+
+        run_test_8();
+      });
+    });
+  });
+}
+
+function run_test_8() {
+  AddonManager.addInstallListener(InstallListener);
+  AddonManager.addAddonListener(AddonListener);
+
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  AddonManager.getInstallForFile(do_get_addon("test_install3"), function(install) {
+    do_check_true(install.addon.isCompatible);
+
+    prepare_test({
+      "addon3@tests.mozilla.org": [
+        "onInstalling"
+      ]
+    }, [
+      "onInstallStarted",
+      "onInstallEnded",
+    ], check_test_8);
+    install.install();
+  });
+}
+
+function check_test_8() {
+  restartManager();
+
+  AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
+    do_check_neq(a3, null);
+    do_check_eq(a3.type, "extension");
+    do_check_eq(a3.version, "1.0");
+    do_check_eq(a3.name, "Real Test 4");
+    do_check_true(a3.isActive);
+    do_check_false(a3.appDisabled);
+    do_check_true(isExtensionInAddonsList(profileDir, a3.id));
+    do_check_true(do_get_addon("test_install3").exists());
+    a3.uninstall();
+    restartManager();
+
+    run_test_9();
+  });
+}
+
+// Test that after cancelling a download it is removed from the active installs
+function run_test_9() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://localhost:4444/addons/test_install3.xpi";
+  AddonManager.getInstallForURL(url, function(install) {
+    ensure_test_completed();
+
+    do_check_neq(install, null);
+    do_check_eq(install.version, "1.0");
+    do_check_eq(install.name, "Real Test 4");
+    do_check_eq(install.state, AddonManager.STATE_AVAILABLE);
+
+    AddonManager.getInstallsByTypes(null, function(activeInstalls) {
+      do_check_eq(activeInstalls.length, 1);
+      do_check_eq(activeInstalls[0], install);
+
+      prepare_test({}, [
+        "onDownloadStarted",
+        "onDownloadEnded",
+      ], check_test_9);
+      install.install();
+    });
+  }, "application/x-xpinstall", null, "Real Test 4", null, "1.0");
+}
+
+function check_test_9(install) {
+  prepare_test({}, [
+    "onDownloadCancelled"
+  ]);
+
+  install.cancel();
+
+  ensure_test_completed();
+
+  AddonManager.getAllInstalls(function(activeInstalls) {
+    do_check_eq(activeInstalls.length, 0);
+
+    run_test_10();
+  });
+}
+
+// Tests that after cancelling a pending install it is removed from the active
+// installs
+function run_test_10() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://localhost:4444/addons/test_install3.xpi";
+  AddonManager.getInstallForURL(url, function(install) {
+    ensure_test_completed();
+
+    do_check_neq(install, null);
+    do_check_eq(install.version, "1.0");
+    do_check_eq(install.name, "Real Test 4");
+    do_check_eq(install.state, AddonManager.STATE_AVAILABLE);
+
+    AddonManager.getInstallsByTypes(null, function(activeInstalls) {
+      do_check_eq(activeInstalls.length, 1);
+      do_check_eq(activeInstalls[0], install);
+
+      prepare_test({
+        "addon3@tests.mozilla.org": [
+          "onInstalling"
+        ]
+      }, [
+        "onDownloadStarted",
+        "onDownloadEnded",
+        "onInstallStarted",
+        "onInstallEnded"
+      ], check_test_10);
+      install.install();
+    });
+  }, "application/x-xpinstall", null, "Real Test 4", null, "1.0");
+}
+
+function check_test_10(install) {
+  prepare_test({
+    "addon3@tests.mozilla.org": [
+      "onOperationCancelled"
+    ]
+  }, [
+    "onInstallCancelled"
+  ]);
+
+  install.cancel();
+
+  ensure_test_completed();
+
+  AddonManager.getAllInstalls(function(activeInstalls) {
+    do_check_eq(activeInstalls.length, 0);
+
+    restartManager();
+
+    // Check that the install did not complete
+    AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
+      do_check_eq(a3, null);
+
+      run_test_11();
+    });
+  });
+}
+
+// Tests that a multi-package install shows up as multiple installs with the
+// correct sourceURI.
+function run_test_11() {
+  prepare_test({ }, [
+    "onNewInstall",
+    "onNewInstall",
+    "onNewInstall",
+    "onNewInstall"
+  ]);
+
+  AddonManager.getInstallForFile(do_get_addon("test_install4"), function(install) {
+    ensure_test_completed();
+    do_check_neq(install, null);
+    do_check_neq(install.linkedInstalls, null);
+    do_check_eq(install.linkedInstalls.length, 3);
+
+    // Might be in any order so sort them based on ID
+    let installs = [install].concat(install.linkedInstalls);
+    installs.sort(function(a, b) {
+      if (a.addon.id < b.addon.id)
+        return -1;
+      if (a.addon.id > b.addon.id)
+        return 1;
+      return 0;
+    });
+
+    // Comes from addon4.xpi and is made compatible by an update check
+    do_check_eq(installs[0].sourceURI, install.sourceURI);
+    do_check_eq(installs[0].addon.id, "addon4@tests.mozilla.org");
+    do_check_false(installs[0].addon.appDisabled);
+    do_check_eq(installs[0].version, "1.0");
+    do_check_eq(installs[0].name, "Multi Test 1");
+    do_check_eq(installs[0].state, AddonManager.STATE_DOWNLOADED);
+    do_check_true(hasFlag(installs[0].addon.operationsRequiringRestart,
+                          AddonManager.OP_NEEDS_RESTART_INSTALL));
+
+    // Comes from addon5.jar and is compatible by default
+    do_check_eq(installs[1].sourceURI, install.sourceURI);
+    do_check_eq(installs[1].addon.id, "addon5@tests.mozilla.org");
+    do_check_false(installs[1].addon.appDisabled);
+    do_check_eq(installs[1].version, "3.0");
+    do_check_eq(installs[1].name, "Multi Test 2");
+    do_check_eq(installs[1].state, AddonManager.STATE_DOWNLOADED);
+    do_check_true(hasFlag(installs[1].addon.operationsRequiringRestart,
+                          AddonManager.OP_NEEDS_RESTART_INSTALL));
+
+    // Comes from addon6.xpi and is incompatible
+    do_check_eq(installs[2].sourceURI, install.sourceURI);
+    do_check_eq(installs[2].addon.id, "addon6@tests.mozilla.org");
+    do_check_true(installs[2].addon.appDisabled);
+    do_check_eq(installs[2].version, "2.0");
+    do_check_eq(installs[2].name, "Multi Test 3");
+    do_check_eq(installs[2].state, AddonManager.STATE_DOWNLOADED);
+    do_check_false(hasFlag(installs[2].addon.operationsRequiringRestart,
+                           AddonManager.OP_NEEDS_RESTART_INSTALL));
+
+    // Comes from addon7.jar and is made compatible by an update check
+    do_check_eq(installs[3].sourceURI, install.sourceURI);
+    do_check_eq(installs[3].addon.id, "addon7@tests.mozilla.org");
+    do_check_false(installs[3].addon.appDisabled);
+    do_check_eq(installs[3].version, "5.0");
+    do_check_eq(installs[3].name, "Multi Test 4");
+    do_check_eq(installs[3].state, AddonManager.STATE_DOWNLOADED);
+    do_check_true(hasFlag(installs[3].addon.operationsRequiringRestart,
+                          AddonManager.OP_NEEDS_RESTART_INSTALL));
+
+    AddonManager.getAllInstalls(function(aInstalls) {
+      do_check_eq(aInstalls.length, 4);
+
+      prepare_test({
+        "addon4@tests.mozilla.org": [
+          "onInstalling"
+        ],
+        "addon5@tests.mozilla.org": [
+          "onInstalling"
+        ],
+        "addon6@tests.mozilla.org": [
+          ["onInstalling", false],
+          "onInstalled"
+        ],
+        "addon7@tests.mozilla.org": [
+          "onInstalling"
+        ]
+      }, {
+        "addon4@tests.mozilla.org": [
+          "onInstallStarted",
+          "onInstallEnded"
+        ],
+        "addon5@tests.mozilla.org": [
+          "onInstallStarted",
+          "onInstallEnded"
+        ],
+        "addon6@tests.mozilla.org": [
+          "onInstallStarted",
+          "onInstallEnded"
+        ],
+        "addon7@tests.mozilla.org": [
+          "onInstallStarted",
+          "onInstallEnded"
+        ]
+      }, check_test_11);
+
+      installs[0].install();
+      installs[1].install();
+      installs[3].install();
+
+      // Note that we install addon6 last. Since it doesn't need a restart to
+      // install it completes asynchronously which would otherwise make the
+      // onInstallStarted/onInstallEnded events go out of sequence unless this
+      // is the last install operation
+      installs[2].install();
+    });
+  });
+}
+
+function check_test_11() {
+  restartManager();
+
+  AddonManager.getAddonsByIDs(["addon4@tests.mozilla.org",
+                               "addon5@tests.mozilla.org",
+                               "addon6@tests.mozilla.org",
+                               "addon7@tests.mozilla.org"],
+                               function([a4, a5, a6, a7]) {
+    do_check_neq(a4, null);
+    do_check_neq(a5, null);
+    do_check_neq(a6, null);
+    do_check_neq(a7, null);
+
+    a4.uninstall();
+    a5.uninstall();
+    a6.uninstall();
+    a7.uninstall();
+
+    restartManager();
+
+    run_test_12();
+  });
+}
+
+// Same as test 11 but for a remote XPI
+function run_test_12() {
+  prepare_test({ }, [
+    "onNewInstall",
+  ]);
+
+  let url = "http://localhost:4444/addons/test_install4.xpi";
+  AddonManager.getInstallForURL(url, function(install) {
+    gInstall = install;
+
+    ensure_test_completed();
+    do_check_neq(install, null);
+    do_check_eq(install.linkedInstalls, null);
+    do_check_eq(install.state, AddonManager.STATE_AVAILABLE);
+
+    prepare_test({
+      "addon4@tests.mozilla.org": [
+        "onInstalling"
+      ],
+      "addon5@tests.mozilla.org": [
+        "onInstalling"
+      ],
+      "addon6@tests.mozilla.org": [
+        ["onInstalling", false],
+        "onInstalled"
+      ],
+      "addon7@tests.mozilla.org": [
+        "onInstalling"
+      ]
+    }, {
+      "NO_ID": [
+        "onDownloadStarted",
+        "onNewInstall",
+        "onNewInstall",
+        "onNewInstall",
+        "onDownloadEnded"
+      ],
+      "addon4@tests.mozilla.org": [
+        "onInstallStarted",
+        "onInstallEnded"
+      ],
+      "addon5@tests.mozilla.org": [
+        "onInstallStarted",
+        "onInstallEnded"
+      ],
+      "addon6@tests.mozilla.org": [
+        "onInstallStarted",
+        "onInstallEnded"
+      ],
+      "addon7@tests.mozilla.org": [
+        "onInstallStarted",
+        "onInstallEnded"
+      ]
+    }, check_test_12);
+    install.install();
+  }, "application/x-xpinstall", null, "Multi Test 4");
+}
+
+function check_test_12() {
+  do_check_eq(gInstall.linkedInstalls.length, 3);
+
+  // Might be in any order so sort them based on ID
+  let installs = [gInstall].concat(gInstall.linkedInstalls);
+  installs.sort(function(a, b) {
+    if (a.addon.id < b.addon.id)
+      return -1;
+    if (a.addon.id > b.addon.id)
+      return 1;
+    return 0;
+  });
+
+  // Comes from addon4.xpi and is made compatible by an update check
+  do_check_eq(installs[0].sourceURI, gInstall.sourceURI);
+  do_check_eq(installs[0].addon.id, "addon4@tests.mozilla.org");
+  do_check_false(installs[0].addon.appDisabled);
+  do_check_eq(installs[0].version, "1.0");
+  do_check_eq(installs[0].name, "Multi Test 1");
+  do_check_eq(installs[0].state, AddonManager.STATE_INSTALLED);
+
+  // Comes from addon5.jar and is compatible by default
+  do_check_eq(installs[1].sourceURI, gInstall.sourceURI);
+  do_check_eq(installs[1].addon.id, "addon5@tests.mozilla.org");
+  do_check_false(installs[1].addon.appDisabled);
+  do_check_eq(installs[1].version, "3.0");
+  do_check_eq(installs[1].name, "Multi Test 2");
+  do_check_eq(installs[1].state, AddonManager.STATE_INSTALLED);
+
+  // Comes from addon6.xpi and is incompatible
+  do_check_eq(installs[2].sourceURI, gInstall.sourceURI);
+  do_check_eq(installs[2].addon.id, "addon6@tests.mozilla.org");
+  do_check_true(installs[2].addon.appDisabled);
+  do_check_eq(installs[2].version, "2.0");
+  do_check_eq(installs[2].name, "Multi Test 3");
+  do_check_eq(installs[2].state, AddonManager.STATE_INSTALLED);
+
+  // Comes from addon7.jar and is made compatible by an update check
+  do_check_eq(installs[3].sourceURI, gInstall.sourceURI);
+  do_check_eq(installs[3].addon.id, "addon7@tests.mozilla.org");
+  do_check_false(installs[3].addon.appDisabled);
+  do_check_eq(installs[3].version, "5.0");
+  do_check_eq(installs[3].name, "Multi Test 4");
+  do_check_eq(installs[3].state, AddonManager.STATE_INSTALLED);
+
+  restartManager();
+
+  AddonManager.getAddonsByIDs(["addon4@tests.mozilla.org",
+                               "addon5@tests.mozilla.org",
+                               "addon6@tests.mozilla.org",
+                               "addon7@tests.mozilla.org"],
+                               function([a4, a5, a6, a7]) {
+    do_check_neq(a4, null);
+    do_check_neq(a5, null);
+    do_check_neq(a6, null);
+    do_check_neq(a7, null);
+
+    a4.uninstall();
+    a5.uninstall();
+    a6.uninstall();
+    a7.uninstall();
+
+    restartManager();
+
+    run_test_13();
+  });
+}
+
+
+// Tests that cancelling an upgrade leaves the original add-on's pendingOperations
+// correct
+function run_test_13() {
+  installAllFiles([do_get_addon("test_install2_1")], function() {
+    restartManager();
+
+    prepare_test({ }, [
+      "onNewInstall"
+    ]);
+
+    let url = "http://localhost:4444/addons/test_install2_2.xpi";
+    AddonManager.getInstallForURL(url, function(install) {
+      ensure_test_completed();
+
+      do_check_neq(install, null);
+      do_check_eq(install.version, "3.0");
+      do_check_eq(install.name, "Test 3");
+      do_check_eq(install.state, AddonManager.STATE_AVAILABLE);
+
+      AddonManager.getAllInstalls(function(activeInstalls) {
+        do_check_eq(activeInstalls.length, 1);
+        do_check_eq(activeInstalls[0], install);
+        do_check_eq(install.existingAddon, null);
+
+        prepare_test({
+          "addon2@tests.mozilla.org": [
+            "onInstalling"
+          ]
+        }, [
+          "onDownloadStarted",
+          "onDownloadEnded",
+          "onInstallStarted",
+          "onInstallEnded",
+        ], check_test_13);
+        install.install();
+      });
+    }, "application/x-xpinstall", null, "Test 3", null, "3.0");
+  });
+}
+
+function check_test_13(install) {
+  ensure_test_completed();
+
+  do_check_eq(install.version, "3.0");
+  do_check_eq(install.name, "Real Test 3");
+  do_check_eq(install.state, AddonManager.STATE_INSTALLED);
+  do_check_neq(install.existingAddon, null);
+  do_check_eq(install.existingAddon.id, "addon2@tests.mozilla.org");
+  do_check_eq(install.addon.install, install);
+
+  AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) {
+    do_check_neq(olda2, null);
+    do_check_true(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE));
+    do_check_eq(olda2.pendingUpgrade, install.addon);
+
+    do_check_true(hasFlag(install.addon.pendingOperations,
+                          AddonManager.PENDING_INSTALL));
+
+    prepare_test({
+      "addon2@tests.mozilla.org": [
+        "onOperationCancelled"
+      ]
+    }, [
+      "onInstallCancelled",
+    ]);
+
+    install.cancel();
+
+    do_check_false(hasFlag(install.addon.pendingOperations, AddonManager.PENDING_INSTALL));
+
+    do_check_false(hasFlag(olda2.pendingOperations, AddonManager.PENDING_UPGRADE));
+    do_check_eq(olda2.pendingUpgrade, null);
+
+    restartManager();
+
+    // Check that the upgrade did not complete
+    AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+      do_check_eq(a2.version, "2.0");
+
+      a2.uninstall();
+
+      restartManager();
+
+      run_test_14();
+    });
+  });
+}
+
+// Check that cancelling the install from onDownloadStarted actually cancels it
+function run_test_14() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://localhost:4444/addons/test_install2_1.xpi";
+  AddonManager.getInstallForURL(url, function(install) {
+    ensure_test_completed();
+
+    do_check_eq(install.file, null);
+
+    prepare_test({ }, [
+      "onDownloadStarted"
+    ], check_test_14);
+    install.install();
+  }, "application/x-xpinstall");
+}
+
+function check_test_14(install) {
+  prepare_test({ }, [
+    "onDownloadCancelled"
+  ]);
+
+  install.cancel();
+
+  ensure_test_completed();
+
+  install.addListener({
+    onDownloadProgress: function() {
+      do_throw("Download should not have continued");
+    },
+    onDownloadEnded: function() {
+      do_throw("Download should not have continued");
+    }
+  });
+
+  // Allow the listener to return to see if it continues downloading. The
+  // The listener only really tests if we give it time to see progress, the
+  // file check isn't ideal either
+  do_execute_soon(function() {
+    do_check_eq(install.file, null);
+
+    run_test_15();
+  });
+}
+
+// Checks that cancelling the install from onDownloadEnded actually cancels it
+function run_test_15() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://localhost:4444/addons/test_install2_1.xpi";
+  AddonManager.getInstallForURL(url, function(install) {
+    ensure_test_completed();
+
+    do_check_eq(install.file, null);
+
+    prepare_test({ }, [
+      "onDownloadStarted",
+      "onDownloadEnded"
+    ], check_test_15);
+    install.install();
+  }, "application/x-xpinstall");
+}
+
+function check_test_15(install) {
+  prepare_test({ }, [
+    "onDownloadCancelled"
+  ]);
+
+  install.cancel();
+
+  ensure_test_completed();
+
+  install.addListener({
+    onInstallStarted: function() {
+      do_throw("Install should not have continued");
+    }
+  });
+
+  // Allow the listener to return to see if it starts installing
+  do_execute_soon(run_test_16);
+}
+
+// Verify that the userDisabled value carries over to the upgrade by default
+function run_test_16() {
+  restartManager();
+
+  let url = "http://localhost:4444/addons/test_install2_1.xpi";
+  AddonManager.getInstallForURL(url, function(aInstall) {
+    aInstall.addListener({
+      onInstallStarted: function() {
+        do_check_false(aInstall.addon.userDisabled);
+        aInstall.addon.userDisabled = true;
+      },
+
+      onInstallEnded: function() {
+        restartManager();
+
+        AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+          do_check_true(a2.userDisabled);
+          do_check_false(a2.isActive);
+
+          let url = "http://localhost:4444/addons/test_install2_2.xpi";
+          AddonManager.getInstallForURL(url, function(aInstall) {
+            aInstall.addListener({
+              onInstallEnded: function() {
+                do_check_true(aInstall.addon.userDisabled);
+
+                restartManager();
+
+                AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+                  do_check_true(a2.userDisabled);
+                  do_check_false(a2.isActive);
+
+                  a2.uninstall();
+                  restartManager();
+
+                  run_test_17();
+                });
+              }
+            });
+            aInstall.install();
+          }, "application/x-xpinstall");
+        });
+      }
+    });
+    aInstall.install();
+  }, "application/x-xpinstall");
+}
+
+// Verify that changing the userDisabled value before onInstallEnded works
+function run_test_17() {
+  let url = "http://localhost:4444/addons/test_install2_1.xpi";
+  AddonManager.getInstallForURL(url, function(aInstall) {
+    aInstall.addListener({
+      onInstallEnded: function() {
+        do_check_false(aInstall.addon.userDisabled);
+
+        restartManager();
+
+        AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+          do_check_false(a2.userDisabled);
+          do_check_true(a2.isActive);
+
+          let url = "http://localhost:4444/addons/test_install2_2.xpi";
+          AddonManager.getInstallForURL(url, function(aInstall) {
+            aInstall.addListener({
+              onInstallStarted: function() {
+                do_check_false(aInstall.addon.userDisabled);
+                aInstall.addon.userDisabled = true;
+              },
+
+              onInstallEnded: function() {
+                restartManager();
+
+                AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+                  do_check_true(a2.userDisabled);
+                  do_check_false(a2.isActive);
+
+                  a2.uninstall();
+                  restartManager();
+
+                  run_test_18();
+                });
+              }
+            });
+            aInstall.install();
+          }, "application/x-xpinstall");
+        });
+      }
+    });
+    aInstall.install();
+  }, "application/x-xpinstall");
+}
+
+// Verify that changing the userDisabled value before onInstallEnded works
+function run_test_18() {
+  let url = "http://localhost:4444/addons/test_install2_1.xpi";
+  AddonManager.getInstallForURL(url, function(aInstall) {
+    aInstall.addListener({
+      onInstallStarted: function() {
+        do_check_false(aInstall.addon.userDisabled);
+        aInstall.addon.userDisabled = true;
+      },
+
+      onInstallEnded: function() {
+        restartManager();
+
+        AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+          do_check_true(a2.userDisabled);
+          do_check_false(a2.isActive);
+
+          let url = "http://localhost:4444/addons/test_install2_2.xpi";
+          AddonManager.getInstallForURL(url, function(aInstall) {
+            aInstall.addListener({
+              onInstallStarted: function() {
+                do_check_true(aInstall.addon.userDisabled);
+                aInstall.addon.userDisabled = false;
+              },
+
+              onInstallEnded: function() {
+                restartManager();
+
+                AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+                  do_check_false(a2.userDisabled);
+                  do_check_true(a2.isActive);
+
+                  a2.uninstall();
+                  restartManager();
+
+                  run_test_18_1();
+                });
+              }
+            });
+            aInstall.install();
+          }, "application/x-xpinstall");
+        });
+      }
+    });
+    aInstall.install();
+  }, "application/x-xpinstall");
+}
+
+
+// Checks that metadata is not stored if the pref is set to false
+function run_test_18_1() {
+  Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", true);
+  Services.prefs.setCharPref("extensions.getAddons.get.url",
+                             "http://localhost:4444/data/test_install.xml");
+
+  Services.prefs.setBoolPref("extensions.addon2@tests.mozilla.org.getAddons.cache.enabled", false);
+
+  let url = "http://localhost:4444/addons/test_install2_1.xpi";
+  AddonManager.getInstallForURL(url, function(aInstall) {
+    aInstall.addListener({
+      onInstallEnded: function(aInstall, aAddon) {
+        do_check_neq(aAddon.fullDescription, "Repository description");
+
+        restartManager();
+
+        AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+          do_check_neq(a2.fullDescription, "Repository description");
+
+          a2.uninstall();
+          restartManager();
+
+          Services.prefs.setBoolPref("extensions.addon2@tests.mozilla.org.getAddons.cache.enabled", true);
+          run_test_19();
+        });
+      }
+    });
+    aInstall.install();
+  }, "application/x-xpinstall");
+}
+
+// Checks that metadata is downloaded for new installs and is visible before and
+// after restart
+function run_test_19() {
+  let url = "http://localhost:4444/addons/test_install2_1.xpi";
+  AddonManager.getInstallForURL(url, function(aInstall) {
+    aInstall.addListener({
+      onInstallEnded: function(aInstall, aAddon) {
+        do_check_eq(aAddon.fullDescription, "Repository description");
+
+        restartManager();
+
+        AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+          do_check_eq(a2.fullDescription, "Repository description");
+
+          a2.uninstall();
+          restartManager();
+
+          run_test_20();
+        });
+      }
+    });
+    aInstall.install();
+  }, "application/x-xpinstall");
+}
+
+// Do the same again to make sure it works when the data is already in the cache
+function run_test_20() {
+  let url = "http://localhost:4444/addons/test_install2_1.xpi";
+  AddonManager.getInstallForURL(url, function(aInstall) {
+    aInstall.addListener({
+      onInstallEnded: function(aInstall, aAddon) {
+        do_check_eq(aAddon.fullDescription, "Repository description");
+
+        restartManager();
+
+        AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+          do_check_eq(a2.fullDescription, "Repository description");
+
+          a2.uninstall();
+          restartManager();
+
+          Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", false);
+          run_test_21();
+        });
+      }
+    });
+    aInstall.install();
+  }, "application/x-xpinstall");
+}
+
+// Verify that installing an add-on that is already pending install cancels the
+// first install
+function run_test_21() {
+  installAllFiles([do_get_addon("test_install2_1")], function() {
+    AddonManager.getAllInstalls(function(aInstalls) {
+      do_check_eq(aInstalls.length, 1);
+
+      prepare_test({
+        "addon2@tests.mozilla.org": [
+          "onOperationCancelled",
+          "onInstalling"
+        ]
+      }, [
+        "onNewInstall",
+        "onDownloadStarted",
+        "onDownloadEnded",
+        "onInstallStarted",
+        "onInstallCancelled",
+        "onInstallEnded",
+      ], check_test_21);
+
+      let url = "http://localhost:4444/addons/test_install2_1.xpi";
+      AddonManager.getInstallForURL(url, function(aInstall) {
+        aInstall.install();
+      }, "application/x-xpinstall");
+    });
+  });
+}
+
+function check_test_21(aInstall) {
+  AddonManager.getAllInstalls(function(aInstalls) {
+    do_check_eq(aInstalls.length, 1);
+    do_check_eq(aInstalls[0], aInstall);
+
+    prepare_test({
+      "addon2@tests.mozilla.org": [
+        "onOperationCancelled"
+      ]
+    }, [
+      "onInstallCancelled",
+    ]);
+
+    aInstall.cancel();
+
+    ensure_test_completed();
+
+    restartManager();
+
+    AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+      do_check_eq(a2, null);
+
+      run_test_22();
+    });
+  });
+}
+
+// Tests that an install can be restarted after being cancelled
+function run_test_22() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://localhost:4444/addons/test_install3.xpi";
+  AddonManager.getInstallForURL(url, function(aInstall) {
+    ensure_test_completed();
+
+    do_check_neq(aInstall, null);
+    do_check_eq(aInstall.state, AddonManager.STATE_AVAILABLE);
+
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadEnded",
+    ], check_test_22);
+    aInstall.install();
+  }, "application/x-xpinstall");
+}
+
+function check_test_22(aInstall) {
+  prepare_test({}, [
+    "onDownloadCancelled"
+  ]);
+
+  aInstall.cancel();
+
+  ensure_test_completed();
+
+  prepare_test({
+    "addon3@tests.mozilla.org": [
+      "onInstalling"
+    ]
+  }, [
+    "onDownloadStarted",
+    "onDownloadEnded",
+    "onInstallStarted",
+    "onInstallEnded"
+  ], finish_test_22);
+
+  aInstall.install();
+}
+
+function finish_test_22(aInstall) {
+  prepare_test({
+    "addon3@tests.mozilla.org": [
+      "onOperationCancelled"
+    ]
+  }, [
+    "onInstallCancelled"
+  ]);
+
+  aInstall.cancel();
+
+  ensure_test_completed();
+
+  run_test_23();
+}
+
+// Tests that an install can be restarted after being cancelled when a hash
+// was provided
+function run_test_23() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://localhost:4444/addons/test_install3.xpi";
+  AddonManager.getInstallForURL(url, function(aInstall) {
+    ensure_test_completed();
+
+    do_check_neq(aInstall, null);
+    do_check_eq(aInstall.state, AddonManager.STATE_AVAILABLE);
+
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadEnded",
+    ], check_test_23);
+    aInstall.install();
+  }, "application/x-xpinstall", do_get_addon_hash("test_install3"));
+}
+
+function check_test_23(aInstall) {
+  prepare_test({}, [
+    "onDownloadCancelled"
+  ]);
+
+  aInstall.cancel();
+
+  ensure_test_completed();
+
+  prepare_test({
+    "addon3@tests.mozilla.org": [
+      "onInstalling"
+    ]
+  }, [
+    "onDownloadStarted",
+    "onDownloadEnded",
+    "onInstallStarted",
+    "onInstallEnded"
+  ], finish_test_23);
+
+  aInstall.install();
+}
+
+function finish_test_23(aInstall) {
+  prepare_test({
+    "addon3@tests.mozilla.org": [
+      "onOperationCancelled"
+    ]
+  }, [
+    "onInstallCancelled"
+  ]);
+
+  aInstall.cancel();
+
+  ensure_test_completed();
+
+  run_test_24();
+}
+
+// Tests that an install with a bad hash can be restarted after it fails, though
+// it will only fail again
+function run_test_24() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://localhost:4444/addons/test_install3.xpi";
+  AddonManager.getInstallForURL(url, function(aInstall) {
+    ensure_test_completed();
+
+    do_check_neq(aInstall, null);
+    do_check_eq(aInstall.state, AddonManager.STATE_AVAILABLE);
+
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadFailed",
+    ], check_test_24);
+    aInstall.install();
+  }, "application/x-xpinstall", "sha1:foo");
+}
+
+function check_test_24(aInstall) {
+  prepare_test({ }, [
+    "onDownloadStarted",
+    "onDownloadFailed"
+  ], run_test_25);
+
+  aInstall.install();
+}
+
+// Tests that installs with a hash for a local file work
+function run_test_25() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = Services.io.newFileURI(do_get_addon("test_install3")).spec;
+  AddonManager.getInstallForURL(url, function(aInstall) {
+    ensure_test_completed();
+
+    do_check_neq(aInstall, null);
+    do_check_eq(aInstall.state, AddonManager.STATE_DOWNLOADED);
+    do_check_eq(aInstall.error, 0);
+
+    prepare_test({ }, [
+      "onDownloadCancelled"
+    ]);
+
+    aInstall.cancel();
+
+    ensure_test_completed();
+
+    run_test_26();
+  }, "application/x-xpinstall", do_get_addon_hash("test_install3"));
+}
+
+function run_test_26() {
+  prepare_test({ }, [
+    "onNewInstall",
+    "onDownloadStarted",
+    "onDownloadCancelled"
+  ]);
+
+  let observerService = AM_Cc["@mozilla.org/network/http-activity-distributor;1"].
+                        getService(AM_Ci.nsIHttpActivityDistributor);
+  observerService.addObserver({
+    observeActivity: function(aChannel, aType, aSubtype, aTimestamp, aSizeData,
+                              aStringData) {
+      aChannel.QueryInterface(AM_Ci.nsIChannel);
+      // Wait for the final event for the redirected URL
+      if (aChannel.URI.spec != "http://localhost:4444/addons/test_install1.xpi" ||
+          aType != AM_Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION ||
+          aSubtype != AM_Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE)
+        return;
+
+      // Request should have been cancelled
+      do_check_eq(aChannel.status, Components.results.NS_BINDING_ABORTED);
+
+      observerService.removeObserver(this);
+
+      run_test_27();
+    }
+  });
+
+  let url = "http://localhost:4444/redirect?/addons/test_install1.xpi";
+  AddonManager.getInstallForURL(url, function(aInstall) {
+    aInstall.addListener({
+      onDownloadProgress: function(aInstall) {
+        aInstall.cancel();
+      }
+    });
+
+    aInstall.install();
+  }, "application/x-xpinstall");
+}
+
+
+// Tests that an install can be restarted during onDownloadCancelled after being
+// cancelled in mid-download
+function run_test_27() {
+  prepare_test({ }, [
+    "onNewInstall"
+  ]);
+
+  let url = "http://localhost:4444/addons/test_install3.xpi";
+  AddonManager.getInstallForURL(url, function(aInstall) {
+    ensure_test_completed();
+
+    do_check_neq(aInstall, null);
+    do_check_eq(aInstall.state, AddonManager.STATE_AVAILABLE);
+
+    aInstall.addListener({
+      onDownloadProgress: function() {
+        aInstall.removeListener(this);
+        aInstall.cancel();
+      }
+    });
+
+    prepare_test({}, [
+      "onDownloadStarted",
+      "onDownloadCancelled",
+    ], check_test_27);
+    aInstall.install();
+  }, "application/x-xpinstall");
+}
+
+function check_test_27(aInstall) {
+  prepare_test({
+    "addon3@tests.mozilla.org": [
+      "onInstalling"
+    ]
+  }, [
+    "onDownloadStarted",
+    "onDownloadEnded",
+    "onInstallStarted",
+    "onInstallEnded"
+  ], finish_test_27);
+
+  aInstall.install();
+}
+
+function finish_test_27(aInstall) {
+  prepare_test({
+    "addon3@tests.mozilla.org": [
+      "onOperationCancelled"
+    ]
+  }, [
+    "onInstallCancelled"
+  ]);
+
+  aInstall.cancel();
+
+  ensure_test_completed();
+
+  do_test_finished();
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js
@@ -4,17 +4,18 @@
 
 // Checks that we rebuild something sensible from a corrupt database
 
 
 do_load_httpd_js();
 var testserver;
 
 // The test extension uses an insecure update url.
-Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
 
 // Will be enabled
 var addon1 = {
   id: "addon1@tests.mozilla.org",
   version: "1.0",
   name: "Test 1",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
@@ -30,43 +31,43 @@ var addon2 = {
   name: "Test 2",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "2",
     maxVersion: "2"
   }]
 };
 
-// Will get a compatibility update and be enabled
+// Will get a compatibility update and stay enabled
 var addon3 = {
   id: "addon3@tests.mozilla.org",
   version: "1.0",
   name: "Test 3",
   updateURL: "http://localhost:4444/data/test_corrupt.rdf",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "1",
     maxVersion: "1"
   }]
 };
 
-// Will get a compatibility update and be disabled
+// Will get a compatibility update and be enabled
 var addon4 = {
   id: "addon4@tests.mozilla.org",
   version: "1.0",
   name: "Test 4",
   updateURL: "http://localhost:4444/data/test_corrupt.rdf",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "1",
     maxVersion: "1"
   }]
 };
 
-// Stays incompatible
+// Would stay incompatible with strict compat
 var addon5 = {
   id: "addon5@tests.mozilla.org",
   version: "1.0",
   name: "Test 5",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "1",
     maxVersion: "1"
@@ -212,19 +213,19 @@ function run_test_1() {
 
     do_check_neq(a4, null);
     do_check_false(a4.isActive);
     do_check_true(a4.userDisabled);
     do_check_false(a4.appDisabled);
     do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
 
     do_check_neq(a5, null);
-    do_check_false(a5.isActive);
+    do_check_true(a5.isActive);
     do_check_false(a5.userDisabled);
-    do_check_true(a5.appDisabled);
+    do_check_false(a5.appDisabled);
     do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
 
     do_check_neq(a6, null);
     do_check_true(a6.isActive);
     do_check_false(a6.userDisabled);
     do_check_false(a6.appDisabled);
     do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
 
@@ -280,31 +281,33 @@ function run_test_1() {
       do_check_false(a2.appDisabled);
       do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
 
       // The compatibility update won't be recovered but it should still be
       // active for this session
       do_check_neq(a3, null);
       do_check_true(a3.isActive);
       do_check_false(a3.userDisabled);
-      do_check_true(a3.appDisabled);
-      do_check_eq(a3.pendingOperations, AddonManager.PENDING_DISABLE);
+      do_check_false(a3.appDisabled);
+      do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
 
-      // The compatibility update won't be recovered and it will not have been
-      // able to tell that it was previously userDisabled
+      // The compatibility update won't be recovered and with strict
+      // compatibility it would not have been able to tell that it was
+      // previously userDisabled. However, without strict compat, it wasn't
+      // appDisabled, so it knows it must have been userDisabled.
       do_check_neq(a4, null);
       do_check_false(a4.isActive);
-      do_check_false(a4.userDisabled);
-      do_check_true(a4.appDisabled);
+      do_check_true(a4.userDisabled);
+      do_check_false(a4.appDisabled);
       do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
 
       do_check_neq(a5, null);
-      do_check_false(a5.isActive);
+      do_check_true(a5.isActive);
       do_check_false(a5.userDisabled);
-      do_check_true(a5.appDisabled);
+      do_check_false(a5.appDisabled);
       do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
 
       do_check_neq(a6, null);
       do_check_true(a6.isActive);
       do_check_false(a6.userDisabled);
       do_check_false(a6.appDisabled);
       do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
 
@@ -352,31 +355,31 @@ function run_test_1() {
 
         do_check_neq(a2, null);
         do_check_false(a2.isActive);
         do_check_true(a2.userDisabled);
         do_check_false(a2.appDisabled);
         do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
 
         do_check_neq(a3, null);
-        do_check_false(a3.isActive);
+        do_check_true(a3.isActive);
         do_check_false(a3.userDisabled);
-        do_check_true(a3.appDisabled);
+        do_check_false(a3.appDisabled);
         do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
 
         do_check_neq(a4, null);
         do_check_false(a4.isActive);
-        do_check_false(a4.userDisabled);
-        do_check_true(a4.appDisabled);
+        do_check_true(a4.userDisabled);
+        do_check_false(a4.appDisabled);
         do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
 
         do_check_neq(a5, null);
-        do_check_false(a5.isActive);
+        do_check_true(a5.isActive);
         do_check_false(a5.userDisabled);
-        do_check_true(a5.appDisabled);
+        do_check_false(a5.appDisabled);
         do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
 
         do_check_neq(a6, null);
         do_check_true(a6.isActive);
         do_check_false(a6.userDisabled);
         do_check_false(a6.appDisabled);
         do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js
@@ -0,0 +1,407 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Checks that we rebuild something sensible from a corrupt database
+
+
+do_load_httpd_js();
+var testserver;
+
+// The test extension uses an insecure update url.
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true);
+
+// Will be enabled
+var addon1 = {
+  id: "addon1@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 1",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "2",
+    maxVersion: "2"
+  }]
+};
+
+// Will be disabled
+var addon2 = {
+  id: "addon2@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 2",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "2",
+    maxVersion: "2"
+  }]
+};
+
+// Will get a compatibility update and be enabled
+var addon3 = {
+  id: "addon3@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 3",
+  updateURL: "http://localhost:4444/data/test_corrupt.rdf",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "1",
+    maxVersion: "1"
+  }]
+};
+
+// Will get a compatibility update and be disabled
+var addon4 = {
+  id: "addon4@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 4",
+  updateURL: "http://localhost:4444/data/test_corrupt.rdf",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "1",
+    maxVersion: "1"
+  }]
+};
+
+// Stays incompatible
+var addon5 = {
+  id: "addon5@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 5",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "1",
+    maxVersion: "1"
+  }]
+};
+
+// Enabled bootstrapped
+var addon6 = {
+  id: "addon6@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 6",
+  bootstrap: "true",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "2",
+    maxVersion: "2"
+  }]
+};
+
+// Disabled bootstrapped
+var addon7 = {
+  id: "addon7@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 7",
+  bootstrap: "true",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "2",
+    maxVersion: "2"
+  }]
+};
+
+// The default theme
+var theme1 = {
+  id: "theme1@tests.mozilla.org",
+  version: "1.0",
+  name: "Theme 1",
+  internalName: "classic/1.0",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "2",
+    maxVersion: "2"
+  }]
+};
+
+// The selected theme
+var theme2 = {
+  id: "theme2@tests.mozilla.org",
+  version: "1.0",
+  name: "Theme 2",
+  internalName: "test/1.0",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "2",
+    maxVersion: "2"
+  }]
+};
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+function run_test() {
+  do_test_pending();
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2");
+
+  writeInstallRDFForExtension(addon1, profileDir);
+  writeInstallRDFForExtension(addon2, profileDir);
+  writeInstallRDFForExtension(addon3, profileDir);
+  writeInstallRDFForExtension(addon4, profileDir);
+  writeInstallRDFForExtension(addon5, profileDir);
+  writeInstallRDFForExtension(addon6, profileDir);
+  writeInstallRDFForExtension(addon7, profileDir);
+  writeInstallRDFForExtension(theme1, profileDir);
+  writeInstallRDFForExtension(theme2, profileDir);
+
+  // Create and configure the HTTP server.
+  testserver = new nsHttpServer();
+  testserver.registerDirectory("/addons/", do_get_file("addons"));
+  testserver.registerDirectory("/data/", do_get_file("data"));
+  testserver.start(4444);
+
+  // Startup the profile and setup the initial state
+  startupManager();
+
+  AddonManager.getAddonsByIDs(["addon2@tests.mozilla.org",
+                               "addon3@tests.mozilla.org",
+                               "addon4@tests.mozilla.org",
+                               "addon7@tests.mozilla.org",
+                               "theme2@tests.mozilla.org"], function([a2, a3, a4,
+                                                                      a7, t2]) {
+    // Set up the initial state
+    a2.userDisabled = true;
+    a4.userDisabled = true;
+    a7.userDisabled = true;
+    t2.userDisabled = false;
+    a3.findUpdates({
+      onUpdateFinished: function() {
+        a4.findUpdates({
+          onUpdateFinished: function() {
+            restartManager();
+
+            run_test_1();
+          }
+        }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
+      }
+    }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
+  });
+}
+
+function end_test() {
+  testserver.stop(do_test_finished);
+}
+
+function run_test_1() {
+  AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                               "addon2@tests.mozilla.org",
+                               "addon3@tests.mozilla.org",
+                               "addon4@tests.mozilla.org",
+                               "addon5@tests.mozilla.org",
+                               "addon6@tests.mozilla.org",
+                               "addon7@tests.mozilla.org",
+                               "theme1@tests.mozilla.org",
+                               "theme2@tests.mozilla.org"], function([a1, a2, a3,
+                                                                      a4, a5, a6,
+                                                                      a7, t1, t2]) {
+    do_check_neq(a1, null);
+    do_check_true(a1.isActive);
+    do_check_false(a1.userDisabled);
+    do_check_false(a1.appDisabled);
+    do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(a2, null);
+    do_check_false(a2.isActive);
+    do_check_true(a2.userDisabled);
+    do_check_false(a2.appDisabled);
+    do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(a3, null);
+    do_check_true(a3.isActive);
+    do_check_false(a3.userDisabled);
+    do_check_false(a3.appDisabled);
+    do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(a4, null);
+    do_check_false(a4.isActive);
+    do_check_true(a4.userDisabled);
+    do_check_false(a4.appDisabled);
+    do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(a5, null);
+    do_check_false(a5.isActive);
+    do_check_false(a5.userDisabled);
+    do_check_true(a5.appDisabled);
+    do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(a6, null);
+    do_check_true(a6.isActive);
+    do_check_false(a6.userDisabled);
+    do_check_false(a6.appDisabled);
+    do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(a7, null);
+    do_check_false(a7.isActive);
+    do_check_true(a7.userDisabled);
+    do_check_false(a7.appDisabled);
+    do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(t1, null);
+    do_check_false(t1.isActive);
+    do_check_true(t1.userDisabled);
+    do_check_false(t1.appDisabled);
+    do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
+
+    do_check_neq(t2, null);
+    do_check_true(t2.isActive);
+    do_check_false(t2.userDisabled);
+    do_check_false(t2.appDisabled);
+    do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
+
+    // After restarting the database won't be open so lock the file for writing
+    restartManager();
+    var dbfile = gProfD.clone();
+    dbfile.append("extensions.sqlite");
+    var fstream = AM_Cc["@mozilla.org/network/file-output-stream;1"].
+                  createInstance(AM_Ci.nsIFileOutputStream);
+    fstream.init(dbfile, FileUtils.MODE_TRUNCATE | FileUtils.MODE_WRONLY, FileUtils.PERMS_FILE, 0);
+
+    // Accessing the add-ons should open and recover the database
+    AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                                 "addon2@tests.mozilla.org",
+                                 "addon3@tests.mozilla.org",
+                                 "addon4@tests.mozilla.org",
+                                 "addon5@tests.mozilla.org",
+                                 "addon6@tests.mozilla.org",
+                                 "addon7@tests.mozilla.org",
+                                 "theme1@tests.mozilla.org",
+                                 "theme2@tests.mozilla.org"], function([a1, a2, a3,
+                                                                        a4, a5, a6,
+                                                                        a7, t1, t2]) {
+      // Should be correctly recovered
+      do_check_neq(a1, null);
+      do_check_true(a1.isActive);
+      do_check_false(a1.userDisabled);
+      do_check_false(a1.appDisabled);
+      do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
+
+      // Should be correctly recovered
+      do_check_neq(a2, null);
+      do_check_false(a2.isActive);
+      do_check_true(a2.userDisabled);
+      do_check_false(a2.appDisabled);
+      do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
+
+      // The compatibility update won't be recovered but it should still be
+      // active for this session
+      do_check_neq(a3, null);
+      do_check_true(a3.isActive);
+      do_check_false(a3.userDisabled);
+      do_check_true(a3.appDisabled);
+      do_check_eq(a3.pendingOperations, AddonManager.PENDING_DISABLE);
+
+      // The compatibility update won't be recovered and it will not have been
+      // able to tell that it was previously userDisabled
+      do_check_neq(a4, null);
+      do_check_false(a4.isActive);
+      do_check_false(a4.userDisabled);
+      do_check_true(a4.appDisabled);
+      do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
+
+      do_check_neq(a5, null);
+      do_check_false(a5.isActive);
+      do_check_false(a5.userDisabled);
+      do_check_true(a5.appDisabled);
+      do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
+
+      do_check_neq(a6, null);
+      do_check_true(a6.isActive);
+      do_check_false(a6.userDisabled);
+      do_check_false(a6.appDisabled);
+      do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
+
+      do_check_neq(a7, null);
+      do_check_false(a7.isActive);
+      do_check_true(a7.userDisabled);
+      do_check_false(a7.appDisabled);
+      do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
+
+      // Should be correctly recovered
+      do_check_neq(t1, null);
+      do_check_false(t1.isActive);
+      do_check_true(t1.userDisabled);
+      do_check_false(t1.appDisabled);
+      do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
+
+      // Should be correctly recovered
+      do_check_neq(t2, null);
+      do_check_true(t2.isActive);
+      do_check_false(t2.userDisabled);
+      do_check_false(t2.appDisabled);
+      do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
+
+      // Restarting will actually apply changes to extensions.ini which will
+      // then be put into the in-memory database when we next fail to load the
+      // real thing
+      restartManager();
+
+      AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                                   "addon2@tests.mozilla.org",
+                                   "addon3@tests.mozilla.org",
+                                   "addon4@tests.mozilla.org",
+                                   "addon5@tests.mozilla.org",
+                                   "addon6@tests.mozilla.org",
+                                   "addon7@tests.mozilla.org",
+                                   "theme1@tests.mozilla.org",
+                                   "theme2@tests.mozilla.org"], function([a1, a2, a3,
+                                                                          a4, a5, a6,
+                                                                          a7, t1, t2]) {
+        do_check_neq(a1, null);
+        do_check_true(a1.isActive);
+        do_check_false(a1.userDisabled);
+        do_check_false(a1.appDisabled);
+        do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(a2, null);
+        do_check_false(a2.isActive);
+        do_check_true(a2.userDisabled);
+        do_check_false(a2.appDisabled);
+        do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(a3, null);
+        do_check_false(a3.isActive);
+        do_check_false(a3.userDisabled);
+        do_check_true(a3.appDisabled);
+        do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(a4, null);
+        do_check_false(a4.isActive);
+        do_check_false(a4.userDisabled);
+        do_check_true(a4.appDisabled);
+        do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(a5, null);
+        do_check_false(a5.isActive);
+        do_check_false(a5.userDisabled);
+        do_check_true(a5.appDisabled);
+        do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(a6, null);
+        do_check_true(a6.isActive);
+        do_check_false(a6.userDisabled);
+        do_check_false(a6.appDisabled);
+        do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(a7, null);
+        do_check_false(a7.isActive);
+        do_check_true(a7.userDisabled);
+        do_check_false(a7.appDisabled);
+        do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(t1, null);
+        do_check_false(t1.isActive);
+        do_check_true(t1.userDisabled);
+        do_check_false(t1.appDisabled);
+        do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
+
+        do_check_neq(t2, null);
+        do_check_true(t2.isActive);
+        do_check_false(t2.userDisabled);
+        do_check_false(t2.appDisabled);
+        do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
+
+        fstream.close();
+        end_test();
+      });
+    });
+  });
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js
@@ -111,16 +111,23 @@ function run_test() {
 
   stagedXPIs.append("addon7@tests.mozilla.org");
   stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
 
   let addon7 = do_get_addon("test_migrate7");
   addon7.copyTo(stagedXPIs, "tmp.xpi");
   stagedXPIs = stagedXPIs.parent;
 
+  stagedXPIs.append("addon8@tests.mozilla.org");
+  stagedXPIs.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
+
+  let addon7 = do_get_addon("test_migrate8");
+  addon7.copyTo(stagedXPIs, "tmp.xpi");
+  stagedXPIs = stagedXPIs.parent;
+
   let old = do_get_file("data/test_migrate.rdf");
   old.copyTo(gProfD, "extensions.rdf");
 
   let oldCache = gProfD.clone();
   oldCache.append("extensions.cache");
   oldCache.create(AM_Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
 
   // Theme state is determined by the selected theme pref
@@ -139,75 +146,92 @@ function run_test() {
 
   AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                                "addon2@tests.mozilla.org",
                                "addon3@tests.mozilla.org",
                                "addon4@tests.mozilla.org",
                                "addon5@tests.mozilla.org",
                                "addon6@tests.mozilla.org",
                                "addon7@tests.mozilla.org",
+                               "addon8@tests.mozilla.org",
                                "theme1@tests.mozilla.org",
                                "theme2@tests.mozilla.org"], function([a1, a2, a3,
                                                                       a4, a5, a6,
-                                                                      a7, t1, t2]) {
+                                                                      a7, a8, t1,
+                                                                      t2]) {
     // addon1 was user and app enabled in the old extensions.rdf
     do_check_neq(a1, null);
     do_check_false(a1.userDisabled);
     do_check_false(a1.appDisabled);
     do_check_true(a1.isActive);
     do_check_true(isExtensionInAddonsList(profileDir, a1.id));
+    do_check_false(a1.hasBinaryComponents);
 
     // addon2 was user disabled and app enabled in the old extensions.rdf
     do_check_neq(a2, null);
     do_check_true(a2.userDisabled);
     do_check_false(a2.appDisabled);
     do_check_false(a2.isActive);
     do_check_false(isExtensionInAddonsList(profileDir, a2.id));
+    do_check_false(a2.hasBinaryComponents);
 
     // addon3 was pending user disable and app disabled in the old extensions.rdf
     do_check_neq(a3, null);
     do_check_true(a3.userDisabled);
     do_check_true(a3.appDisabled);
     do_check_false(a3.isActive);
     do_check_false(isExtensionInAddonsList(profileDir, a3.id));
+    do_check_false(a3.hasBinaryComponents);
 
     // addon4 was pending user enable and app disabled in the old extensions.rdf
     do_check_neq(a4, null);
     do_check_false(a4.userDisabled);
     do_check_true(a4.appDisabled);
     do_check_false(a4.isActive);
     do_check_false(isExtensionInAddonsList(profileDir, a4.id));
+    do_check_false(a4.hasBinaryComponents);
 
     // addon5 was disabled and compatible but a new version has been installed
     // since, it should still be disabled but should be incompatible
     do_check_neq(a5, null);
     do_check_true(a5.userDisabled);
     do_check_true(a5.appDisabled);
     do_check_false(a5.isActive);
     do_check_false(isExtensionInAddonsList(profileDir, a5.id));
+    do_check_false(a5.hasBinaryComponents);
 
     // addon6 should be installed and compatible and packed unless unpacking is
     // forced
     do_check_neq(a6, null);
     do_check_false(a6.userDisabled);
     do_check_false(a6.appDisabled);
     do_check_true(a6.isActive);
     do_check_true(isExtensionInAddonsList(profileDir, a6.id));
     if (Services.prefs.getBoolPref("extensions.alwaysUnpack"))
       do_check_eq(a6.getResourceURI("install.rdf").scheme, "file");
     else
       do_check_eq(a6.getResourceURI("install.rdf").scheme, "jar");
+    do_check_false(a6.hasBinaryComponents);
 
     // addon7 should be installed and compatible and unpacked
     do_check_neq(a7, null);
     do_check_false(a7.userDisabled);
     do_check_false(a7.appDisabled);
     do_check_true(a7.isActive);
     do_check_true(isExtensionInAddonsList(profileDir, a7.id));
     do_check_eq(a7.getResourceURI("install.rdf").scheme, "file");
+    do_check_false(a7.hasBinaryComponents);
+
+    // addon8 should be installed and compatible and have binary components
+    do_check_neq(a8, null);
+    do_check_false(a8.userDisabled);
+    do_check_false(a8.appDisabled);
+    do_check_true(a8.isActive);
+    do_check_true(isExtensionInAddonsList(profileDir, a8.id));
+    do_check_true(a8.hasBinaryComponents);
 
     // Theme 1 was previously enabled
     do_check_neq(t1, null);
     do_check_false(t1.userDisabled);
     do_check_false(t1.appDisabled);
     do_check_true(t1.isActive);
     do_check_true(isThemeInAddonsList(profileDir, t1.id));
     do_check_false(hasFlag(t1.permissions, AddonManager.PERM_CAN_ENABLE));
--- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js
@@ -36,16 +36,17 @@ var addon3 = {
     maxVersion: "1"
   }]
 };
 
 var addon4 = {
   id: "addon4@tests.mozilla.org",
   version: "2.0",
   name: "Test 4",
+  strictCompatibility: true,
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "1",
     maxVersion: "1"
   }]
 };
 
 var addon5 = {
@@ -152,37 +153,43 @@ function run_test() {
                                "addon5@tests.mozilla.org",
                                "addon6@tests.mozilla.org"],
                                function([a1, a2, a3, a4, a5, a6]) {
     // addon1 was enabled in the database
     do_check_neq(a1, null);
     do_check_false(a1.userDisabled);
     do_check_false(a1.appDisabled);
     do_check_true(a1.isActive);
+    do_check_false(a1.strictCompatibility);
     // addon2 was disabled in the database
     do_check_neq(a2, null);
     do_check_true(a2.userDisabled);
     do_check_false(a2.appDisabled);
     do_check_false(a2.isActive);
+    do_check_false(a2.strictCompatibility);
     // addon3 was pending-disable in the database
     do_check_neq(a3, null);
     do_check_true(a3.userDisabled);
     do_check_false(a3.appDisabled);
     do_check_false(a3.isActive);
+    do_check_false(a3.strictCompatibility);
     // addon4 was pending-enable in the database
     do_check_neq(a4, null);
     do_check_false(a4.userDisabled);
     do_check_false(a4.appDisabled);
     do_check_true(a4.isActive);
+    do_check_true(a4.strictCompatibility);
     // addon5 was enabled in the database but needed a compatibiltiy update
     do_check_neq(a5, null);
     do_check_false(a5.userDisabled);
     do_check_false(a5.appDisabled);
     do_check_true(a5.isActive);
+    do_check_false(a5.strictCompatibility);
     // addon6 was disabled and compatible but a new version has been installed
     // since, it should still be disabled but should be incompatible
     do_check_neq(a6, null);
     do_check_true(a6.userDisabled);
     do_check_true(a6.appDisabled);
     do_check_false(a6.isActive);
+    do_check_false(a6.strictCompatibility);
     do_test_finished();
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js
@@ -39,16 +39,17 @@ var addon3 = {
     maxVersion: "2"
   }]
 };
 
 var addon4 = {
   id: "addon4@tests.mozilla.org",
   version: "2.0",
   name: "Test 4",
+  strictCompatibility: true,
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "1",
     maxVersion: "2"
   }]
 };
 
 var addon5 = {
@@ -93,56 +94,61 @@ function prepare_profile() {
   writeInstallRDFForExtension(addon1, profileDir);
   writeInstallRDFForExtension(addon2, profileDir);
   writeInstallRDFForExtension(addon3, profileDir);
   writeInstallRDFForExtension(addon4, profileDir);
   writeInstallRDFForExtension(addon5, profileDir);
   writeInstallRDFForExtension(addon6, profileDir);
 
   startupManager();
-  AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
-                               "addon2@tests.mozilla.org",
-                               "addon3@tests.mozilla.org",
-                               "addon4@tests.mozilla.org",
-                               "addon5@tests.mozilla.org",
-                               "addon6@tests.mozilla.org"],
-                               function([a1, a2, a3, a4, a5, a6]) {
-    a2.userDisabled = true;
-    a2.applyBackgroundUpdates = false;
-    a4.userDisabled = true;
-    a6.userDisabled = true;
-
-    a6.findUpdates({
-      onUpdateAvailable: function(aAddon, aInstall6) {
-        AddonManager.getInstallForURL("http://localhost:4444/addons/test_migrate4_7.xpi", function(aInstall7) {
-          completeAllInstalls([aInstall6, aInstall7], function() {
-            restartManager();
+  installAllFiles([do_get_addon("test_migrate8")],
+                  function() {
+    restartManager();
 
-            AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
-                                         "addon2@tests.mozilla.org",
-                                         "addon3@tests.mozilla.org",
-                                         "addon4@tests.mozilla.org",
-                                         "addon5@tests.mozilla.org",
-                                         "addon6@tests.mozilla.org"],
-                                         function([a1, a2, a3, a4, a5, a6]) {
-              a3.userDisabled = true;
-              a4.userDisabled = false;
-
-              a5.findUpdates({
-                onUpdateFinished: function() {
-                  shutdownManager();
-
-                  perform_migration();
-                }
-              }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+    AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                                 "addon2@tests.mozilla.org",
+                                 "addon3@tests.mozilla.org",
+                                 "addon4@tests.mozilla.org",
+                                 "addon5@tests.mozilla.org",
+                                 "addon6@tests.mozilla.org"],
+                                 function([a1, a2, a3, a4, a5, a6]) {
+      a2.userDisabled = true;
+      a2.applyBackgroundUpdates = false;
+      a4.userDisabled = true;
+      a6.userDisabled = true;
+  
+      a6.findUpdates({
+        onUpdateAvailable: function(aAddon, aInstall6) {
+          AddonManager.getInstallForURL("http://localhost:4444/addons/test_migrate4_7.xpi", function(aInstall7) {
+            completeAllInstalls([aInstall6, aInstall7], function() {
+              restartManager();
+  
+              AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                                           "addon2@tests.mozilla.org",
+                                           "addon3@tests.mozilla.org",
+                                           "addon4@tests.mozilla.org",
+                                           "addon5@tests.mozilla.org",
+                                           "addon6@tests.mozilla.org"],
+                                           function([a1, a2, a3, a4, a5, a6]) {
+                a3.userDisabled = true;
+                a4.userDisabled = false;
+  
+                a5.findUpdates({
+                  onUpdateFinished: function() {
+                    shutdownManager();
+  
+                    perform_migration();
+                  }
+                }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+              });
             });
-          });
-        }, "application/x-xpinstall");
-      }
-    }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+          }, "application/x-xpinstall");
+        }
+      }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+    });
   });
 }
 
 function perform_migration() {
   let dbfile = gProfD.clone();
   dbfile.append("extensions.sqlite");
   let db = AM_Cc["@mozilla.org/storage/service;1"].
            getService(AM_Ci.mozIStorageService).
@@ -164,79 +170,103 @@ function test_results() {
   check_startup_changes("enabled", []);
 
   AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                                "addon2@tests.mozilla.org",
                                "addon3@tests.mozilla.org",
                                "addon4@tests.mozilla.org",
                                "addon5@tests.mozilla.org",
                                "addon6@tests.mozilla.org",
-                               "addon7@tests.mozilla.org"],
-                               function([a1, a2, a3, a4, a5, a6, a7]) {
+                               "addon7@tests.mozilla.org",
+                               "addon8@tests.mozilla.org"],
+                               function([a1, a2, a3, a4, a5, a6, a7, a8]) {
     // addon1 was enabled
     do_check_neq(a1, null);
     do_check_false(a1.userDisabled);
     do_check_false(a1.appDisabled);
     do_check_true(a1.isActive);
     do_check_true(a1.applyBackgroundUpdates);
     do_check_true(a1.foreignInstall);
+    do_check_false(a1.hasBinaryComponents);
+    do_check_false(a1.strictCompatibility);
 
     // addon2 was disabled
     do_check_neq(a2, null);
     do_check_true(a2.userDisabled);
     do_check_false(a2.appDisabled);
     do_check_false(a2.isActive);
     do_check_false(a2.applyBackgroundUpdates);
     do_check_true(a2.foreignInstall);
+    do_check_false(a2.hasBinaryComponents);
+    do_check_false(a2.strictCompatibility);
 
     // addon3 was pending-disable in the database
     do_check_neq(a3, null);
     do_check_true(a3.userDisabled);
     do_check_false(a3.appDisabled);
     do_check_false(a3.isActive);
     do_check_true(a3.applyBackgroundUpdates);
     do_check_true(a3.foreignInstall);
+    do_check_false(a3.hasBinaryComponents);
+    do_check_false(a3.strictCompatibility);
 
     // addon4 was pending-enable in the database
     do_check_neq(a4, null);
     do_check_false(a4.userDisabled);
     do_check_false(a4.appDisabled);
     do_check_true(a4.isActive);
     do_check_true(a4.applyBackgroundUpdates);
     do_check_true(a4.foreignInstall);
+    do_check_false(a4.hasBinaryComponents);
+    do_check_true(a4.strictCompatibility);
 
     // addon5 was enabled in the database but needed a compatibiltiy update
     do_check_neq(a5, null);
     do_check_false(a5.userDisabled);
     do_check_false(a5.appDisabled);
     do_check_true(a5.isActive);
     do_check_true(a5.applyBackgroundUpdates);
     do_check_true(a5.foreignInstall);
+    do_check_false(a5.hasBinaryComponents);
+    do_check_false(a5.strictCompatibility);
 
     // addon6 was disabled and compatible but a new version has been installed
     do_check_neq(a6, null);
     do_check_eq(a6.version, "2.0");
     do_check_true(a6.userDisabled);
     do_check_false(a6.appDisabled);
     do_check_false(a6.isActive);
     do_check_true(a6.applyBackgroundUpdates);
     do_check_true(a6.foreignInstall);
     do_check_eq(a6.sourceURI.spec, "http://localhost:4444/addons/test_migrate4_6.xpi");
     do_check_eq(a6.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml");
+    do_check_false(a6.hasBinaryComponents);
+    do_check_false(a6.strictCompatibility);
 
     // addon7 was installed manually
     do_check_neq(a7, null);
     do_check_eq(a7.version, "1.0");
     do_check_false(a7.userDisabled);
     do_check_false(a7.appDisabled);
     do_check_true(a7.isActive);
     do_check_true(a7.applyBackgroundUpdates);
     do_check_false(a7.foreignInstall);
     do_check_eq(a7.sourceURI.spec, "http://localhost:4444/addons/test_migrate4_7.xpi");
     do_check_eq(a7.releaseNotesURI, null);
+    do_check_false(a7.hasBinaryComponents);
+    do_check_false(a7.strictCompatibility);
+
+    // addon8 was enabled and has binary components
+    do_check_neq(a8, null);
+    do_check_false(a8.userDisabled);
+    do_check_false(a8.appDisabled);
+    do_check_true(a8.isActive);
+    do_check_true(a8.hasBinaryComponents);
+    do_check_false(a8.strictCompatibility);
+
     testserver.stop(do_test_finished);
   });
 }
 
 function run_test() {
   do_test_pending();
 
   prepare_profile();
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_strictcompatibility.js
@@ -0,0 +1,129 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests AddonManager.strictCompatibility and it's related preference,
+// extensions.strictCompatibility, and the strictCompatibility option in
+// install.rdf
+
+
+// Always compatible
+var addon1 = {
+  id: "addon1@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 1",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "1",
+    maxVersion: "1"
+  }]
+};
+
+// Incompatible in strict compatibility mode
+var addon2 = {
+  id: "addon2@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 2",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "0.1",
+    maxVersion: "0.2"
+  }]
+};
+
+// Theme - always uses strict compatibility, so is always incompatible
+var addon3 = {
+  id: "addon3@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 3",
+  internalName: "test-theme-3",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "0.1",
+    maxVersion: "0.2"
+  }]
+};
+
+// Opt-in to strict compatibility - always incompatible
+var addon4 = {
+  id: "addon4@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 4",
+  strictCompatibility: true,
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "0.1",
+    maxVersion: "0.2"
+  }]
+};
+
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+
+function do_check_compat_status(aStrict, aAddonCompat, aCallback) {
+  do_check_eq(AddonManager.strictCompatibility, aStrict);
+  AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                               "addon2@tests.mozilla.org",
+                               "addon3@tests.mozilla.org",
+                               "addon4@tests.mozilla.org"],
+                              function([a1, a2, a3, a4]) {
+    do_check_neq(a1, null);
+    do_check_eq(a1.isCompatible, aAddonCompat[0]);
+    do_check_eq(a1.appDisabled, !aAddonCompat[0]);
+    do_check_false(a1.strictCompatibility);
+
+    do_check_neq(a2, null);
+    do_check_eq(a2.isCompatible, aAddonCompat[1]);
+    do_check_eq(a2.appDisabled, !aAddonCompat[1]);
+    do_check_false(a2.strictCompatibility);
+
+    do_check_neq(a3, null);
+    do_check_eq(a3.isCompatible, aAddonCompat[2]);
+    do_check_eq(a3.appDisabled, !aAddonCompat[2]);
+    do_check_true(a3.strictCompatibility);
+
+    do_check_neq(a4, null);
+    do_check_eq(a4.isCompatible, aAddonCompat[3]);
+    do_check_eq(a4.appDisabled, !aAddonCompat[3]);
+    do_check_true(a4.strictCompatibility);
+
+    aCallback();
+  });
+}
+
+
+function run_test() {
+  do_test_pending();
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+  
+  writeInstallRDFForExtension(addon1, profileDir);
+  writeInstallRDFForExtension(addon2, profileDir);
+  writeInstallRDFForExtension(addon3, profileDir);
+  writeInstallRDFForExtension(addon4, profileDir);
+
+  startupManager();
+  
+  // Should default to enabling strict compat.
+  do_check_compat_status(true, [true, false, false, false], run_test_1);
+}
+
+function run_test_1() {
+  Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
+  do_check_compat_status(false, [true, true, false, false], run_test_2);
+}
+
+function run_test_2() {
+  restartManager();
+  do_check_compat_status(false, [true, true, false, false], run_test_3);
+}
+
+function run_test_3() {
+  Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true);
+  do_check_compat_status(true, [true, false, false, false], run_test_4);
+}
+
+function run_test_4() {
+  restartManager();
+  do_check_compat_status(true, [true, false, false, false], do_test_finished);
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js
@@ -3,17 +3,18 @@
  */
 
 // This verifies that add-on update checks work
 
 const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
 const PREF_SELECTED_LOCALE = "general.useragent.locale";
 
 // The test extension uses an insecure update url.
-Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
 // This test requires lightweight themes update to be enabled even if the app
 // doesn't support lightweight themes.
 Services.prefs.setBoolPref("lightweightThemes.update.enabled", true);
 
 Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm");
 
 const PARAMS = "?%REQ_VERSION%/%ITEM_ID%/%ITEM_VERSION%/%ITEM_MAXAPPVERSION%/" +
                "%ITEM_STATUS%/%APP_ID%/%APP_VERSION%/%CURRENT_APP_VERSION%/" +
@@ -230,26 +231,26 @@ function check_test_2() {
   });
 }
 
 
 // Check that an update check finds compatibility updates and applies them
 function run_test_3() {
   AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
     do_check_neq(a2, null);
-    do_check_false(a2.isActive);
-    do_check_false(a2.isCompatible);
-    do_check_true(a2.appDisabled);
+    do_check_true(a2.isActive);
+    do_check_true(a2.isCompatible);
+    do_check_false(a2.appDisabled);
     do_check_true(a2.isCompatibleWith("0"));
 
     a2.findUpdates({
       onCompatibilityUpdateAvailable: function(addon) {
         do_check_true(a2.isCompatible);
         do_check_false(a2.appDisabled);
-        do_check_false(a2.isActive);
+        do_check_true(a2.isActive);
       },
 
       onUpdateAvailable: function(addon, install) {
         do_throw("Should not have seen an available update");
       },
 
       onNoUpdateAvailable: function(addon) {
         do_check_eq(addon, a2);
@@ -271,21 +272,21 @@ function check_test_3() {
     run_test_4();
   });
 }
 
 // Checks that we see no compatibility information when there is none.
 function run_test_4() {
   AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
     do_check_neq(a3, null);
-    do_check_false(a3.isActive);
-    do_check_false(a3.isCompatible);
-    do_check_true(a3.appDisabled);
+    do_check_true(a3.isActive);
+    do_check_true(a3.isCompatible);
+    do_check_false(a3.appDisabled);
     do_check_true(a3.isCompatibleWith("5"));
-    do_check_false(a3.isCompatibleWith("2"));
+    do_check_true(a3.isCompatibleWith("2"));
 
     a3.findUpdates({
       sawUpdate: false,
       onCompatibilityUpdateAvailable: function(addon) {
         do_throw("Should not have seen compatibility information");
       },
 
       onNoCompatibilityUpdateAvailable: function(addon) {
@@ -304,28 +305,28 @@ function run_test_4() {
   });
 }
 
 // Checks that compatibility info for future apps are detected but don't make
 // the item compatibile.
 function run_test_5() {
   AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
     do_check_neq(a3, null);
-    do_check_false(a3.isActive);
-    do_check_false(a3.isCompatible);
-    do_check_true(a3.appDisabled);
+    do_check_true(a3.isActive);
+    do_check_true(a3.isCompatible);
+    do_check_false(a3.appDisabled);
     do_check_true(a3.isCompatibleWith("5"));
-    do_check_false(a3.isCompatibleWith("2"));
+    do_check_true(a3.isCompatibleWith("2"));
 
     a3.findUpdates({
       sawUpdate: false,
       onCompatibilityUpdateAvailable: function(addon) {
-        do_check_false(a3.isCompatible);
-        do_check_true(a3.appDisabled);
-        do_check_false(a3.isActive);
+        do_check_true(a3.isCompatible);
+        do_check_false(a3.appDisabled);
+        do_check_true(a3.isActive);
         this.sawUpdate = true;
       },
 
       onNoCompatibilityUpdateAvailable: function(addon) {
         do_throw("Should have seen some compatibility information");
       },
 
       onUpdateAvailable: function(addon, install) {
@@ -339,19 +340,19 @@ function run_test_5() {
       }
     }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0");
   });
 }
 
 function check_test_5() {
   AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
     do_check_neq(a3, null);
-    do_check_false(a3.isActive);
-    do_check_false(a3.isCompatible);
-    do_check_true(a3.appDisabled);
+    do_check_true(a3.isActive);
+    do_check_true(a3.isCompatible);
+    do_check_false(a3.appDisabled);
 
     a3.uninstall();
     restartManager();
 
     run_test_6();
   });
 }
 
@@ -604,17 +605,17 @@ function run_test_8() {
         do_check_eq(item_maxappversion, "3");
         do_check_eq(item_status, "userDisabled");
         do_check_eq(app_version, "1");
         do_check_eq(update_type, "49");
         break;
       case "addon3@tests.mozilla.org":
         do_check_eq(item_version, "1.3+");
         do_check_eq(item_maxappversion, "0");
-        do_check_eq(item_status, "userEnabled,incompatible");
+        do_check_eq(item_status, "userEnabled");
         do_check_eq(app_version, "1");
         do_check_eq(update_type, "112");
         break;
       case "addon4@tests.mozilla.org":
         do_check_eq(item_version, "0.5ab6");
         do_check_eq(item_maxappversion, "5");
         do_check_eq(item_status, "userEnabled");
         do_check_eq(app_version, "2");
@@ -736,31 +737,31 @@ function run_test_10() {
 }
 
 // Tests that an update check for a new application will decrease a
 // targetApplication's maxVersion.
 function run_test_11() {
   AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) {
     a4.findUpdates({
       onUpdateFinished: function(addon) {
-        do_check_false(addon.isCompatible);
+        do_check_true(addon.isCompatible);
 
         run_test_12();
       }
     }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
   });
 }
 
 // Check that the decreased maxVersion applied and disables the add-on
 function run_test_12() {
   restartManager();
 
   AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) {
-    do_check_false(a4.isActive);
-    do_check_false(a4.isCompatible);
+    do_check_true(a4.isActive);
+    do_check_true(a4.isCompatible);
 
     a4.uninstall();
     restartManager();
 
     run_test_13();
   });
 }
 
@@ -779,19 +780,19 @@ function run_test_13() {
       maxVersion: "0"
     }],
     name: "Test Addon 7",
   }, profileDir);
   restartManager();
 
   AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) {
     do_check_neq(a7, null);
-    do_check_false(a7.isActive);
-    do_check_false(a7.isCompatible);
-    do_check_true(a7.appDisabled);
+    do_check_true(a7.isActive);
+    do_check_true(a7.isCompatible);
+    do_check_false(a7.appDisabled);
     do_check_true(a7.isCompatibleWith("0"));
 
     a7.findUpdates({
       sawUpdate: false,
       onCompatibilityUpdateAvailable: function(addon) {
         do_throw("Should have not have seen compatibility information");
       },
 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js
@@ -0,0 +1,1021 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This verifies that add-on update checks work
+
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
+
+// The test extension uses an insecure update url.
+Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
+// This test requires lightweight themes update to be enabled even if the app
+// doesn't support lightweight themes.
+Services.prefs.setBoolPref("lightweightThemes.update.enabled", true);
+
+Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm");
+
+const PARAMS = "?%REQ_VERSION%/%ITEM_ID%/%ITEM_VERSION%/%ITEM_MAXAPPVERSION%/" +
+               "%ITEM_STATUS%/%APP_ID%/%APP_VERSION%/%CURRENT_APP_VERSION%/" +
+               "%APP_OS%/%APP_ABI%/%APP_LOCALE%/%UPDATE_TYPE%";
+
+var gInstallDate;
+
+do_load_httpd_js();
+var testserver;
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+function run_test() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+  Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false);
+  Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "fr-FR");
+  Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true);
+
+  // Create and configure the HTTP server.
+  testserver = new nsHttpServer();
+  testserver.registerDirectory("/data/", do_get_file("data"));
+  testserver.registerDirectory("/addons/", do_get_file("addons"));
+  testserver.start(4444);
+
+  writeInstallRDFForExtension({
+    id: "addon1@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/test_update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 1",
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "addon2@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/test_update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "0",
+      maxVersion: "0"
+    }],
+    name: "Test Addon 2",
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "addon3@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/test_update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "5",
+      maxVersion: "5"
+    }],
+    name: "Test Addon 3",
+  }, profileDir);
+
+  startupManager();
+
+  do_test_pending();
+  run_test_1();
+}
+
+function end_test() {
+  Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY);
+
+  testserver.stop(do_test_finished);
+}
+
+// Verify that an update is available and can be installed.
+function run_test_1() {
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+    do_check_neq(a1, null);
+    do_check_eq(a1.version, "1.0");
+    do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT);
+    do_check_eq(a1.releaseNotesURI, null);
+
+    a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
+
+    prepare_test({
+      "addon1@tests.mozilla.org": [
+        ["onPropertyChanged", ["applyBackgroundUpdates"]]
+      ]
+    });
+    a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
+    check_test_completed();
+
+    a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
+
+    prepare_test({}, [
+      "onNewInstall",
+    ]);
+
+    a1.findUpdates({
+      onNoCompatibilityUpdateAvailable: function(addon) {
+        do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification");
+      },
+
+      onUpdateAvailable: function(addon, install) {
+        ensure_test_completed();
+
+        AddonManager.getAllInstalls(function(aInstalls) {
+          do_check_eq(aInstalls.length, 1);
+          do_check_eq(aInstalls[0], install);
+
+          do_check_eq(addon, a1);
+          do_check_eq(install.name, addon.name);
+          do_check_eq(install.version, "2.0");
+          do_check_eq(install.state, AddonManager.STATE_AVAILABLE);
+          do_check_eq(install.existingAddon, addon);
+          do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml");
+
+          // Verify that another update check returns the same AddonInstall
+          a1.findUpdates({
+            onNoCompatibilityUpdateAvailable: function(addon) {
+              do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification");
+            },
+
+            onUpdateAvailable: function(newAddon, newInstall) {
+              AddonManager.getAllInstalls(function(aInstalls) {
+                do_check_eq(aInstalls.length, 1);
+                do_check_eq(aInstalls[0], install);
+                do_check_eq(newAddon, addon);
+                do_check_eq(newInstall, install);
+
+                prepare_test({}, [
+                  "onDownloadStarted",
+                  "onDownloadEnded",
+                ], check_test_1);
+                install.install();
+              });
+            },
+
+            onNoUpdateAvailable: function(addon) {
+              do_throw("Should not have seen onNoUpdateAvailable notification");
+            }
+          }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+        });
+      },
+
+      onNoUpdateAvailable: function(addon) {
+        do_throw("Should not have seen onNoUpdateAvailable notification");
+      }
+    }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+  });
+}
+
+function check_test_1(install) {
+  ensure_test_completed();
+  do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
+  run_test_2(install);
+  return false;
+}
+
+// Continue installing the update.
+function run_test_2(install) {
+  // Verify that another update check returns no new update
+  install.existingAddon.findUpdates({
+    onNoCompatibilityUpdateAvailable: function(addon) {
+      do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification");
+    },
+
+    onUpdateAvailable: function(addon, install) {
+      do_throw("Should find no available update when one is already downloading");
+    },
+
+    onNoUpdateAvailable: function(addon) {
+      AddonManager.getAllInstalls(function(aInstalls) {
+        do_check_eq(aInstalls.length, 1);
+        do_check_eq(aInstalls[0], install);
+
+        prepare_test({
+          "addon1@tests.mozilla.org": [
+            "onInstalling"
+          ]
+        }, [
+          "onInstallStarted",
+          "onInstallEnded",
+        ], check_test_2);
+        install.install();
+      });
+    }
+  }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+}
+
+function check_test_2() {
+  ensure_test_completed();
+
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) {
+    do_check_neq(olda1, null);
+    do_check_eq(olda1.version, "1.0");
+    do_check_true(isExtensionInAddonsList(profileDir, olda1.id));
+
+    shutdownManager();
+
+    startupManager();
+
+    do_check_true(isExtensionInAddonsList(profileDir, olda1.id));
+
+    AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+      do_check_neq(a1, null);
+      do_check_eq(a1.version, "2.0");
+      do_check_true(isExtensionInAddonsList(profileDir, a1.id));
+      do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE);
+      do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml");
+
+      a1.uninstall();
+      restartManager();
+
+      run_test_3();
+    });
+  });
+}
+
+
+// Check that an update check finds compatibility updates and applies them
+function run_test_3() {
+  AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+    do_check_neq(a2, null);
+    do_check_false(a2.isActive);
+    do_check_false(a2.isCompatible);
+    do_check_true(a2.appDisabled);
+    do_check_true(a2.isCompatibleWith("0"));
+
+    a2.findUpdates({
+      onCompatibilityUpdateAvailable: function(addon) {
+        do_check_true(a2.isCompatible);
+        do_check_false(a2.appDisabled);
+        do_check_false(a2.isActive);
+      },
+
+      onUpdateAvailable: function(addon, install) {
+        do_throw("Should not have seen an available update");
+      },
+
+      onNoUpdateAvailable: function(addon) {
+        do_check_eq(addon, a2);
+        restartManager();
+        check_test_3();
+      }
+    }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+  });
+}
+
+function check_test_3() {
+  AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+    do_check_neq(a2, null);
+    do_check_true(a2.isActive);
+    do_check_true(a2.isCompatible);
+    do_check_false(a2.appDisabled);
+    a2.uninstall();
+
+    run_test_4();
+  });
+}
+
+// Checks that we see no compatibility information when there is none.
+function run_test_4() {
+  AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
+    do_check_neq(a3, null);
+    do_check_false(a3.isActive);
+    do_check_false(a3.isCompatible);
+    do_check_true(a3.appDisabled);
+    do_check_true(a3.isCompatibleWith("5"));
+    do_check_false(a3.isCompatibleWith("2"));
+
+    a3.findUpdates({
+      sawUpdate: false,
+      onCompatibilityUpdateAvailable: function(addon) {
+        do_throw("Should not have seen compatibility information");
+      },
+
+      onNoCompatibilityUpdateAvailable: function(addon) {
+        this.sawUpdate = true;
+      },
+
+      onUpdateAvailable: function(addon, install) {
+        do_throw("Should not have seen an available update");
+      },
+
+      onNoUpdateAvailable: function(addon) {
+        do_check_true(this.sawUpdate);
+        run_test_5();
+      }
+    }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+  });
+}
+
+// Checks that compatibility info for future apps are detected but don't make
+// the item compatibile.
+function run_test_5() {
+  AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
+    do_check_neq(a3, null);
+    do_check_false(a3.isActive);
+    do_check_false(a3.isCompatible);
+    do_check_true(a3.appDisabled);
+    do_check_true(a3.isCompatibleWith("5"));
+    do_check_false(a3.isCompatibleWith("2"));
+
+    a3.findUpdates({
+      sawUpdate: false,
+      onCompatibilityUpdateAvailable: function(addon) {
+        do_check_false(a3.isCompatible);
+        do_check_true(a3.appDisabled);
+        do_check_false(a3.isActive);
+        this.sawUpdate = true;
+      },
+
+      onNoCompatibilityUpdateAvailable: function(addon) {
+        do_throw("Should have seen some compatibility information");
+      },
+
+      onUpdateAvailable: function(addon, install) {
+        do_throw("Should not have seen an available update");
+      },
+
+      onNoUpdateAvailable: function(addon) {
+        do_check_true(this.sawUpdate);
+        restartManager();
+        check_test_5();
+      }
+    }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0");
+  });
+}
+
+function check_test_5() {
+  AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) {
+    do_check_neq(a3, null);
+    do_check_false(a3.isActive);
+    do_check_false(a3.isCompatible);
+    do_check_true(a3.appDisabled);
+
+    a3.uninstall();
+    restartManager();
+
+    run_test_6();
+  });
+}
+
+// Test that background update checks work
+function run_test_6() {
+  writeInstallRDFForExtension({
+    id: "addon1@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/test_update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 1",
+  }, profileDir);
+  restartManager();
+
+  prepare_test({}, [
+    "onNewInstall",
+    "onDownloadStarted",
+    "onDownloadEnded"
+  ], continue_test_6);
+
+  // Fake a timer event to cause a background update and wait for the magic to
+  // happen
+  gInternalManager.notify(null);
+}
+
+function continue_test_6(install) {
+  do_check_neq(install.existingAddon, null);
+  do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org");
+
+  prepare_test({
+    "addon1@tests.mozilla.org": [
+      "onInstalling"
+    ]
+  }, [
+    "onInstallStarted",
+    "onInstallEnded",
+  ], check_test_6);
+}
+
+function check_test_6(install) {
+  do_check_eq(install.existingAddon.pendingUpgrade.install, install);
+
+  restartManager();
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+    do_check_neq(a1, null);
+    do_check_eq(a1.version, "2.0");
+    do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml");
+    a1.uninstall();
+    restartManager();
+
+    run_test_7();
+  });
+}
+
+// Test that background update checks work for lightweight themes
+function run_test_7() {
+  LightweightThemeManager.currentTheme = {
+    id: "1",
+    version: "1",
+    name: "Test LW Theme",
+    description: "A test theme",
+    author: "Mozilla",
+    homepageURL: "http://localhost:4444/data/index.html",
+    headerURL: "http://localhost:4444/data/header.png",
+    footerURL: "http://localhost:4444/data/footer.png",
+    previewURL: "http://localhost:4444/data/preview.png",
+    iconURL: "http://localhost:4444/data/icon.png",
+    updateURL: "http://localhost:4444/data/lwtheme.js"
+  };
+
+  // XXX The lightweight theme manager strips non-https updateURLs so hack it
+  // back in.
+  let themes = JSON.parse(Services.prefs.getCharPref("lightweightThemes.usedThemes"));
+  do_check_eq(themes.length, 1);
+  themes[0].updateURL = "http://localhost:4444/data/lwtheme.js";
+  Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
+
+  testserver.registerPathHandler("/data/lwtheme.js", function(request, response) {
+    response.write(JSON.stringify({
+      id: "1",
+      version: "2",
+      name: "Updated Theme",
+      description: "A test theme",
+      author: "Mozilla",
+      homepageURL: "http://localhost:4444/data/index2.html",
+      headerURL: "http://localhost:4444/data/header.png",
+      footerURL: "http://localhost:4444/data/footer.png",
+      previewURL: "http://localhost:4444/data/preview.png",
+      iconURL: "http://localhost:4444/data/icon2.png",
+      updateURL: "http://localhost:4444/data/lwtheme.js"
+    }));
+  });
+
+  AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) {
+    do_check_neq(p1, null);
+    do_check_eq(p1.version, "1");
+    do_check_eq(p1.name, "Test LW Theme");
+    do_check_true(p1.isActive);
+    do_check_eq(p1.installDate.getTime(), p1.updateDate.getTime());
+
+    // 5 seconds leeway seems like a lot, but tests can run slow and really if
+    // this is within 5 seconds it is fine. If it is going to be wrong then it
+    // is likely to be hours out at least
+    do_check_true((Date.now() - p1.installDate.getTime()) < 5000);
+
+    gInstallDate = p1.installDate.getTime();
+
+    prepare_test({
+      "1@personas.mozilla.org": [
+        ["onInstalling", false],
+        "onInstalled"
+      ]
+    }, [
+      "onExternalInstall"
+    ], check_test_7);
+
+    // Fake a timer event to cause a background update and wait for the magic to
+    // happen
+    gInternalManager.notify(null);
+  });
+}
+
+function check_test_7() {
+  AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) {
+    do_check_neq(p1, null);
+    do_check_eq(p1.version, "2");
+    do_check_eq(p1.name, "Updated Theme");
+    do_check_eq(p1.installDate.getTime(), gInstallDate);
+    do_check_true(p1.installDate.getTime() < p1.updateDate.getTime());
+
+    // 5 seconds leeway seems like a lot, but tests can run slow and really if
+    // this is within 5 seconds it is fine. If it is going to be wrong then it
+    // is likely to be hours out at least
+    do_check_true((Date.now() - p1.updateDate.getTime()) < 5000);
+
+    gInstallDate = p1.installDate.getTime();
+
+    run_test_8();
+  });
+}
+
+// Verify the parameter escaping in update urls.
+function run_test_8() {
+  writeInstallRDFForExtension({
+    id: "addon1@tests.mozilla.org",
+    version: "5.0",
+    updateURL: "http://localhost:4444/data/param_test.rdf" + PARAMS,
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "2"
+    }],
+    name: "Test Addon 1",
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "addon2@tests.mozilla.org",
+    version: "67.0.5b1",
+    updateURL: "http://localhost:4444/data/param_test.rdf" + PARAMS,
+    targetApplications: [{
+      id: "toolkit@mozilla.org",
+      minVersion: "0",
+      maxVersion: "3"
+    }],
+    name: "Test Addon 2",
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "addon3@tests.mozilla.org",
+    version: "1.3+",
+    updateURL: "http://localhost:4444/data/param_test.rdf" + PARAMS,
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "0",
+      maxVersion: "0"
+    }, {
+      id: "toolkit@mozilla.org",
+      minVersion: "0",
+      maxVersion: "3"
+    }],
+    name: "Test Addon 3",
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "addon4@tests.mozilla.org",
+    version: "0.5ab6",
+    updateURL: "http://localhost:4444/data/param_test.rdf" + PARAMS,
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "5"
+    }],
+    name: "Test Addon 4",
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "addon5@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/param_test.rdf" + PARAMS,
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 5",
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "addon6@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/param_test.rdf" + PARAMS,
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 6",
+  }, profileDir);
+
+  restartManager();
+
+  AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+    a2.userDisabled = true;
+    restartManager();
+
+    testserver.registerPathHandler("/data/param_test.rdf", function(request, response) {
+      do_check_neq(request.queryString, "");
+      let [req_version, item_id, item_version,
+           item_maxappversion, item_status,
+           app_id, app_version, current_app_version,
+           app_os, app_abi, app_locale, update_type] =
+           [decodeURIComponent(a) for each (a in request.queryString.split("/"))];
+
+      do_check_eq(req_version, "2");
+
+      switch(item_id) {
+      case "addon1@tests.mozilla.org":
+        do_check_eq(item_version, "5.0");
+        do_check_eq(item_maxappversion, "2");
+        do_check_eq(item_status, "userEnabled");
+        do_check_eq(app_version, "1");
+        do_check_eq(update_type, "97");
+        break;
+      case "addon2@tests.mozilla.org":
+        do_check_eq(item_version, "67.0.5b1");
+        do_check_eq(item_maxappversion, "3");
+        do_check_eq(item_status, "userDisabled");
+        do_check_eq(app_version, "1");
+        do_check_eq(update_type, "49");
+        break;
+      case "addon3@tests.mozilla.org":
+        do_check_eq(item_version, "1.3+");
+        do_check_eq(item_maxappversion, "0");
+        do_check_eq(item_status, "userEnabled,incompatible");
+        do_check_eq(app_version, "1");
+        do_check_eq(update_type, "112");
+        break;
+      case "addon4@tests.mozilla.org":
+        do_check_eq(item_version, "0.5ab6");
+        do_check_eq(item_maxappversion, "5");
+        do_check_eq(item_status, "userEnabled");
+        do_check_eq(app_version, "2");
+        do_check_eq(update_type, "98");
+        break;
+      case "addon5@tests.mozilla.org":
+        do_check_eq(item_version, "1.0");
+        do_check_eq(item_maxappversion, "1");
+        do_check_eq(item_status, "userEnabled");
+        do_check_eq(app_version, "1");
+        do_check_eq(update_type, "35");
+        break;
+      case "addon6@tests.mozilla.org":
+        do_check_eq(item_version, "1.0");
+        do_check_eq(item_maxappversion, "1");
+        do_check_eq(item_status, "userEnabled");
+        do_check_eq(app_version, "1");
+        do_check_eq(update_type, "99");
+        break;
+      default:
+        do_throw("Update request for unexpected add-on " + item_id);
+      }
+
+      do_check_eq(app_id, "xpcshell@tests.mozilla.org");
+      do_check_eq(current_app_version, "1");
+      do_check_eq(app_os, "XPCShell");
+      do_check_eq(app_abi, "noarch-spidermonkey");
+      do_check_eq(app_locale, "fr-FR");
+
+      request.setStatusLine(null, 500, "Server Error");
+    });
+
+    AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                                 "addon2@tests.mozilla.org",
+                                 "addon3@tests.mozilla.org",
+                                 "addon4@tests.mozilla.org",
+                                 "addon5@tests.mozilla.org",
+                                 "addon6@tests.mozilla.org"],
+                                 function([a1, a2, a3, a4, a5, a6]) {
+      let count = 6;
+
+      function run_next_test() {
+        a1.uninstall();
+        a2.uninstall();
+        a3.uninstall();
+        a4.uninstall();
+        a5.uninstall();
+        a6.uninstall();
+
+        restartManager();
+        run_test_9();
+      }
+
+      let compatListener = {
+        onUpdateFinished: function(addon, error) {
+          if (--count == 0)
+            run_next_test();
+        }
+      };
+
+      let updateListener = {
+        onUpdateAvailable: function(addon, update) {
+          // Dummy so the update checker knows we care about new versions
+        },
+
+        onUpdateFinished: function(addon, error) {
+          if (--count == 0)
+            run_next_test();
+        }
+      };
+
+      a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+      a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
+      a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
+      a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2");
+      a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
+      a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
+    });
+  });
+}
+
+// Tests that if an install.rdf claims compatibility then the add-on will be
+// seen as compatible regardless of what the update.rdf says.
+function run_test_9() {
+  writeInstallRDFForExtension({
+    id: "addon4@tests.mozilla.org",
+    version: "5.0",
+    updateURL: "http://localhost:4444/data/test_update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "0",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 1",
+  }, profileDir);
+
+  restartManager();
+
+  AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) {
+    do_check_true(a4.isActive);
+    do_check_true(a4.isCompatible);
+
+    run_test_10();
+  });
+}
+
+// Tests that a normal update check won't decrease a targetApplication's
+// maxVersion.
+function run_test_10() {
+  AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) {
+    a4.findUpdates({
+      onUpdateFinished: function(addon) {
+        do_check_true(addon.isCompatible);
+
+        run_test_11();
+      }
+    }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
+  });
+}
+
+// Tests that an update check for a new application will decrease a
+// targetApplication's maxVersion.
+function run_test_11() {
+  AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) {
+    a4.findUpdates({
+      onUpdateFinished: function(addon) {
+        do_check_false(addon.isCompatible);
+
+        run_test_12();
+      }
+    }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
+  });
+}
+
+// Check that the decreased maxVersion applied and disables the add-on
+function run_test_12() {
+  restartManager();
+
+  AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) {
+    do_check_false(a4.isActive);
+    do_check_false(a4.isCompatible);
+
+    a4.uninstall();
+    restartManager();
+
+    run_test_13();
+  });
+}
+
+// Tests that no compatibility update is passed to the listener when there is
+// compatibility info for the current version of the app but not for the
+// version of the app that the caller requested an update check for.
+function run_test_13() {
+  // Not initially compatible but the update check will make it compatible
+  writeInstallRDFForExtension({
+    id: "addon7@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/test_update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "0",
+      maxVersion: "0"
+    }],
+    name: "Test Addon 7",
+  }, profileDir);
+  restartManager();
+
+  AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) {
+    do_check_neq(a7, null);
+    do_check_false(a7.isActive);
+    do_check_false(a7.isCompatible);
+    do_check_true(a7.appDisabled);
+    do_check_true(a7.isCompatibleWith("0"));
+
+    a7.findUpdates({
+      sawUpdate: false,
+      onCompatibilityUpdateAvailable: function(addon) {
+        do_throw("Should have not have seen compatibility information");
+      },
+
+      onUpdateAvailable: function(addon, install) {
+        do_throw("Should not have seen an available update");
+      },
+
+      onUpdateFinished: function(addon) {
+        do_check_true(addon.isCompatible);
+        restartManager();
+        check_test_13();
+      }
+    }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0");
+  });
+}
+
+function check_test_13() {
+  AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) {
+    do_check_neq(a7, null);
+    do_check_true(a7.isActive);
+    do_check_true(a7.isCompatible);
+    do_check_false(a7.appDisabled);
+
+    a7.uninstall();
+    restartManager();
+
+    run_test_14();
+  });
+}
+
+// Test that background update checks doesn't update an add-on that isn't
+// allowed to update automatically.
+function run_test_14() {
+  // Have an add-on there that will be updated so we see some events from it
+  writeInstallRDFForExtension({
+    id: "addon1@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/test_update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 1",
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "addon8@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/test_update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 8",
+  }, profileDir);
+  restartManager();
+
+  AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) {
+    a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
+
+    // The background update check will find updates for both add-ons but only
+    // proceed to install one of them.
+    AddonManager.addInstallListener({
+      onNewInstall: function(aInstall) {
+        if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" &&
+            aInstall.existingAddon.id != "addon8@tests.mozilla.org")
+          do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id);
+      },
+
+      onDownloadStarted: function(aInstall) {
+        do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org");
+      },
+
+      onDownloadEnded: function(aInstall) {
+        do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org");
+      },
+
+      onDownloadFailed: function(aInstall) {
+        do_throw("Should not have seen onDownloadFailed event");
+      },
+
+      onDownloadCancelled: function(aInstall) {
+        do_throw("Should not have seen onDownloadCancelled event");
+      },
+
+      onInstallStarted: function(aInstall) {
+        do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org");
+      },
+
+      onInstallEnded: function(aInstall) {
+        do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org");
+        check_test_14(aInstall);
+      },
+
+      onInstallFailed: function(aInstall) {
+        do_throw("Should not have seen onInstallFailed event");
+      },
+
+      onInstallCancelled: function(aInstall) {
+        do_throw("Should not have seen onInstallCancelled event");
+      },
+    });
+
+    // Fake a timer event
+    gInternalManager.notify(null);
+  });
+}
+
+function check_test_14(install) {
+  do_check_eq(install.existingAddon.pendingUpgrade.install, install);
+
+  restartManager();
+  AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                               "addon8@tests.mozilla.org"], function([a1, a8]) {
+    do_check_neq(a1, null);
+    do_check_eq(a1.version, "2.0");
+    a1.uninstall();
+
+    do_check_neq(a8, null);
+    do_check_eq(a8.version, "1.0");
+    a8.uninstall();
+
+    restartManager();
+
+    run_test_15();
+  });
+}
+
+// Test that background update checks doesn't update an add-on that is
+// pending uninstall
+function run_test_15() {
+  // Have an add-on there that will be updated so we see some events from it
+  writeInstallRDFForExtension({
+    id: "addon1@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/test_update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 1",
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "addon8@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/test_update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 8",
+  }, profileDir);
+  restartManager();
+
+  AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) {
+    a8.uninstall();
+    do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE));
+
+    // The background update check will find updates for both add-ons but only
+    // proceed to install one of them.
+    AddonManager.addInstallListener({
+      onNewInstall: function(aInstall) {
+        if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" &&
+            aInstall.existingAddon.id != "addon8@tests.mozilla.org")
+          do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id);
+      },
+
+      onDownloadStarted: function(aInstall) {
+        do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org");
+      },
+
+      onDownloadEnded: function(aInstall) {
+        do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org");
+      },
+
+      onDownloadFailed: function(aInstall) {
+        do_throw("Should not have seen onDownloadFailed event");
+      },
+
+      onDownloadCancelled: function(aInstall) {
+        do_throw("Should not have seen onDownloadCancelled event");
+      },
+
+      onInstallStarted: function(aInstall) {
+        do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org");
+      },
+
+      onInstallEnded: function(aInstall) {
+        do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org");
+        check_test_15(aInstall);
+      },
+
+      onInstallFailed: function(aInstall) {
+        do_throw("Should not have seen onInstallFailed event");
+      },
+
+      onInstallCancelled: function(aInstall) {
+        do_throw("Should not have seen onInstallCancelled event");
+      },
+    });
+
+    // Fake a timer event
+    gInternalManager.notify(null);
+  });
+}
+
+function check_test_15(aInstall) {
+  restartManager();
+  AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                               "addon8@tests.mozilla.org"], function([a1, a8]) {
+    do_check_neq(a1, null);
+    do_check_eq(a1.version, "2.0");
+    a1.uninstall();
+
+    do_check_eq(a8, null);
+
+    restartManager();
+
+    end_test();
+  });
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js
@@ -1,13 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-// This verifies that app upgrades produce the expected behaviours.
+// This verifies that app upgrades produce the expected behaviours,
+// with strict compatibility checking disabled.
+
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
 
 // Enable loading extensions from the application scope
 Services.prefs.setIntPref("extensions.enabledScopes",
                           AddonManager.SCOPE_PROFILE +
                           AddonManager.SCOPE_APPLICATION);
 
 const profileDir = gProfD.clone();
 profileDir.append("extensions");
@@ -16,17 +19,17 @@ const globalDir = Services.dirsvc.get("X
 globalDir.append("extensions");
 
 var gGlobalExisted = globalDir.exists();
 var gInstallTime = Date.now();
 
 function run_test() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
 
-  // Will be enabled in the first version and disabled in subsequent versions
+  // Will be compatible in the first version and incompatible in subsequent versions
   writeInstallRDFForExtension({
     id: "addon1@tests.mozilla.org",
     version: "1.0",
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "1",
       maxVersion: "1"
     }],
@@ -47,29 +50,29 @@ function run_test() {
       maxVersion: "2"
     }],
     name: "Test Addon 2",
     targetPlatforms: [
       "XPCShell_noarch-spidermonkey"
     ]
   }, profileDir);
 
-  // Will be disabled in the first version and enabled in the second.
+  // Will be incompatible in the first version and enabled in the second.
   writeInstallRDFForExtension({
     id: "addon3@tests.mozilla.org",
     version: "1.0",
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "2",
       maxVersion: "2"
     }],
     name: "Test Addon 3",
   }, profileDir);
 
-  // Will be enabled in both versions but will change version in between
+  // Will be compatible in both versions but will change version in between
   var dest = writeInstallRDFForExtension({
     id: "addon4@tests.mozilla.org",
     version: "1.0",
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "1",
       maxVersion: "1"
     }],
@@ -105,27 +108,27 @@ function run_test_1() {
 
     do_check_neq(a1, null);
     do_check_true(isExtensionInAddonsList(profileDir, a1.id));
 
     do_check_neq(a2, null);
     do_check_true(isExtensionInAddonsList(profileDir, a2.id));
 
     do_check_neq(a3, null);
-    do_check_false(isExtensionInAddonsList(profileDir, a3.id));
+    do_check_true(isExtensionInAddonsList(profileDir, a3.id));
 
     do_check_neq(a4, null);
     do_check_true(isExtensionInAddonsList(globalDir, a4.id));
     do_check_eq(a4.version, "1.0");
 
     run_test_2();
   });
 }
 
-// Test that upgrading the application disables now incompatible add-ons
+// Test that upgrading the application doesn't disable now incompatible add-ons
 function run_test_2() {
   // Upgrade the extension
   var dest = writeInstallRDFForExtension({
     id: "addon4@tests.mozilla.org",
     version: "2.0",
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "2",
@@ -138,17 +141,17 @@ function run_test_2() {
   restartManager("2");
   AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                                "addon2@tests.mozilla.org",
                                "addon3@tests.mozilla.org",
                                "addon4@tests.mozilla.org"],
                                function([a1, a2, a3, a4]) {
 
     do_check_neq(a1, null);
-    do_check_false(isExtensionInAddonsList(profileDir, a1.id));
+    do_check_true(isExtensionInAddonsList(profileDir, a1.id));
 
     do_check_neq(a2, null);
     do_check_true(isExtensionInAddonsList(profileDir, a2.id));
 
     do_check_neq(a3, null);
     do_check_true(isExtensionInAddonsList(profileDir, a3.id));
 
     do_check_neq(a4, null);
@@ -183,17 +186,17 @@ function run_test_3() {
 
   AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                                "addon2@tests.mozilla.org",
                                "addon3@tests.mozilla.org",
                                "addon4@tests.mozilla.org"],
                                function([a1, a2, a3, a4]) {
 
     do_check_neq(a1, null);
-    do_check_false(isExtensionInAddonsList(profileDir, a1.id));
+    do_check_true(isExtensionInAddonsList(profileDir, a1.id));
 
     do_check_neq(a2, null);
     do_check_true(isExtensionInAddonsList(profileDir, a2.id));
 
     do_check_neq(a3, null);
     do_check_true(isExtensionInAddonsList(profileDir, a3.id));
 
     do_check_neq(a4, null);
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js
@@ -0,0 +1,211 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This verifies that app upgrades produce the expected behaviours,
+// with strict compatibility checking enabled.
+
+// Enable loading extensions from the application scope
+Services.prefs.setIntPref("extensions.enabledScopes",
+                          AddonManager.SCOPE_PROFILE +
+                          AddonManager.SCOPE_APPLICATION);
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+const globalDir = Services.dirsvc.get("XCurProcD", AM_Ci.nsILocalFile);
+globalDir.append("extensions");
+
+var gGlobalExisted = globalDir.exists();
+var gInstallTime = Date.now();
+
+function run_test() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+
+  // Will be enabled in the first version and disabled in subsequent versions
+  writeInstallRDFForExtension({
+    id: "addon1@tests.mozilla.org",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 1",
+    targetPlatforms: [
+      "XPCShell",
+      "WINNT_x86",
+    ]
+  }, profileDir);
+
+  // Works in all tested versions
+  writeInstallRDFForExtension({
+    id: "addon2@tests.mozilla.org",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "2"
+    }],
+    name: "Test Addon 2",
+    targetPlatforms: [
+      "XPCShell_noarch-spidermonkey"
+    ]
+  }, profileDir);
+
+  // Will be disabled in the first version and enabled in the second.
+  writeInstallRDFForExtension({
+    id: "addon3@tests.mozilla.org",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "2",
+      maxVersion: "2"
+    }],
+    name: "Test Addon 3",
+  }, profileDir);
+
+  // Will be enabled in both versions but will change version in between
+  var dest = writeInstallRDFForExtension({
+    id: "addon4@tests.mozilla.org",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 4",
+  }, globalDir);
+  setExtensionModifiedTime(dest, gInstallTime);
+
+  do_test_pending();
+
+  Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true);
+
+  run_test_1();
+}
+
+function end_test() {
+  if (!gGlobalExisted) {
+    globalDir.remove(true);
+  }
+  else {
+    globalDir.append(do_get_expected_addon_name("addon4@tests.mozilla.org"));
+    globalDir.remove(true);
+  }
+
+  Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY);
+
+  do_test_finished();
+}
+
+// Test that the test extensions are all installed
+function run_test_1() {
+  startupManager();
+
+  AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                               "addon2@tests.mozilla.org",
+                               "addon3@tests.mozilla.org",
+                               "addon4@tests.mozilla.org"],
+                               function([a1, a2, a3, a4]) {
+
+    do_check_neq(a1, null);
+    do_check_true(isExtensionInAddonsList(profileDir, a1.id));
+
+    do_check_neq(a2, null);
+    do_check_true(isExtensionInAddonsList(profileDir, a2.id));
+
+    do_check_neq(a3, null);
+    do_check_false(isExtensionInAddonsList(profileDir, a3.id));
+
+    do_check_neq(a4, null);
+    do_check_true(isExtensionInAddonsList(globalDir, a4.id));
+    do_check_eq(a4.version, "1.0");
+
+    run_test_2();
+  });
+}
+
+// Test that upgrading the application disables now incompatible add-ons
+function run_test_2() {
+  // Upgrade the extension
+  var dest = writeInstallRDFForExtension({
+    id: "addon4@tests.mozilla.org",
+    version: "2.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "2",
+      maxVersion: "2"
+    }],
+    name: "Test Addon 4",
+  }, globalDir);
+  setExtensionModifiedTime(dest, gInstallTime);
+
+  restartManager("2");
+  AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                               "addon2@tests.mozilla.org",
+                               "addon3@tests.mozilla.org",
+                               "addon4@tests.mozilla.org"],
+                               function([a1, a2, a3, a4]) {
+
+    do_check_neq(a1, null);
+    do_check_false(isExtensionInAddonsList(profileDir, a1.id));
+
+    do_check_neq(a2, null);
+    do_check_true(isExtensionInAddonsList(profileDir, a2.id));
+
+    do_check_neq(a3, null);
+    do_check_true(isExtensionInAddonsList(profileDir, a3.id));
+
+    do_check_neq(a4, null);
+    do_check_true(isExtensionInAddonsList(globalDir, a4.id));
+    do_check_eq(a4.version, "2.0");
+
+    run_test_3();
+  });
+}
+
+// Test that nothing changes when only the build ID changes.
+function run_test_3() {
+  // Upgrade the extension
+  var dest = writeInstallRDFForExtension({
+    id: "addon4@tests.mozilla.org",
+    version: "3.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "3",
+      maxVersion: "3"
+    }],
+    name: "Test Addon 4",
+  }, globalDir);
+  setExtensionModifiedTime(dest, gInstallTime);
+
+  // Simulates a simple Build ID change, the platform deletes extensions.ini
+  // whenever the application is changed.
+  var file = gProfD.clone();
+  file.append("extensions.ini");
+  file.remove(true);
+  restartManager();
+
+  AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                               "addon2@tests.mozilla.org",
+                               "addon3@tests.mozilla.org",
+                               "addon4@tests.mozilla.org"],
+                               function([a1, a2, a3, a4]) {
+
+    do_check_neq(a1, null);
+    do_check_false(isExtensionInAddonsList(profileDir, a1.id));
+
+    do_check_neq(a2, null);
+    do_check_true(isExtensionInAddonsList(profileDir, a2.id));
+
+    do_check_neq(a3, null);
+    do_check_true(isExtensionInAddonsList(profileDir, a3.id));
+
+    do_check_neq(a4, null);
+    do_check_true(isExtensionInAddonsList(globalDir, a4.id));
+    do_check_eq(a4.version, "2.0");
+
+    end_test();
+  });
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -65,22 +65,28 @@ skip-if = os == "android"
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_bug468528.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_bug470377_1.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
+[test_bug470377_1_strictcompat.js]
+# Bug 676992: test consistently hangs on Android
+skip-if = os == "android"
 [test_bug470377_2.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_bug470377_3.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
+[test_bug470377_3_strictcompat.js]
+# Bug 676992: test consistently hangs on Android
+skip-if = os == "android"
 [test_bug470377_4.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_bug514327_1.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_bug514327_2.js]
 # Bug 676992: test consistently hangs on Android
@@ -115,17 +121,19 @@ fail-if = os == "android"
 fail-if = os == "android"
 [test_bug619730.js]
 [test_bug620837.js]
 [test_bug655254.js]
 [test_bug659772.js]
 [test_bug675371.js]
 [test_cacheflush.js]
 [test_checkcompatibility.js]
+[test_ChromeManifestParser.js]
 [test_corrupt.js]
+[test_corrupt_strictcompat.js]
 [test_dictionary.js]
 [test_disable.js]
 [test_distribution.js]
 [test_dss.js]
 # Bug 676992: test consistently fails on Android
 fail-if = os == "android"
 [test_duplicateplugins.js]
 # Bug 676992: test consistently hangs on Android
@@ -145,47 +153,58 @@ skip-if = os == "android"
 [test_gfxBlacklist_Equal_DriverOld.js]
 [test_gfxBlacklist_Equal_OK.js]
 [test_gfxBlacklist_GTE_DriverOld.js]
 [test_gfxBlacklist_GTE_OK.js]
 [test_gfxBlacklist_OK.js]
 [test_gfxBlacklist_OS.js]
 [test_gfxBlacklist_Vendor.js]
 [test_gfxBlacklist_prefs.js]
+[test_hasbinarycomponents.js]
 [test_install.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
+[test_install_strictcompat.js]
+# Bug 676992: test consistently hangs on Android
+skip-if = os == "android"
 [test_locale.js]
 [test_locked.js]
+[test_locked_strictcompat.js]
 [test_manifest.js]
 [test_migrate1.js]
 [test_migrate2.js]
 [test_migrate3.js]
 [test_migrate4.js]
 [test_migrateAddonRepository.js]
 [test_permissions.js]
 [test_plugins.js]
 # Bug 676992: test consistently fails on Android
 fail-if = os == "android"
 [test_registry.js]
 [test_safemode.js]
 [test_startup.js]
 # Bug 676992: test consistently fails on Android
 fail-if = os == "android"
+[test_strictcompatibility.js]
 [test_targetPlatforms.js]
 [test_theme.js]
 # Bug 676992: test consistently fails on Android
 fail-if = os == "android"
 [test_types.js]
 [test_uninstall.js]
 [test_update.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
+[test_update_strictcompat.js]
+# Bug 676992: test consistently hangs on Android
+skip-if = os == "android"
 [test_updatecheck.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_updateid.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_upgrade.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
-
+[test_upgrade_strictcompat.js]
+# Bug 676992: test consistently hangs on Android
+skip-if = os == "android"
--- a/toolkit/mozapps/update/test/chrome/utils.js
+++ b/toolkit/mozapps/update/test/chrome/utils.js
@@ -811,16 +811,17 @@ function setupPrefs() {
   }
   let extUpdateUrl = URL_UPDATE + "?addonID=%ITEM_ID%&platformVersion=" +
                      getNewerPlatformVersion();
   Services.prefs.setCharPref(PREF_EXTENSIONS_UPDATE_URL, extUpdateUrl);
   debugDump("extensions.update.url: " + extUpdateUrl);
 
   Services.prefs.setIntPref(PREF_APP_UPDATE_IDLETIME, 0);
   Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 0);
+  Services.prefs.setBoolPref(PREF_EXTENSIONS_STRICT_COMPAT, true);
 }
 
 /**
  * Resets the most common preferences used by tests to their original values.
  */
 function resetPrefs() {
   if (gAppUpdateURL !== undefined) {
     Services.prefs.setCharPref(PREF_APP_UPDATE_URL_OVERRIDE, gAppUpdateURL);
@@ -908,16 +909,20 @@ function resetPrefs() {
   catch (e) {
   }
 
   try {
     Services.prefs.deleteBranch(PREF_APP_UPDATE_NEVER_BRANCH);
   }
   catch(e) {
   }
+
+  if (Services.prefs.prefHasUserValue(PREF_EXTENSIONS_STRICT_COMPAT)) {
+		Services.prefs.clearUserPref(PREF_EXTENSIONS_STRICT_COMPAT);
+  }
 }
 
 /**
  * Disables pre-existing add-ons so they don't interfere with the tests,
  * installs the test add-ons, sets the noupdate test add-ons' userDisabled value
  * for the test, and calls the callback specified in the aCallback parameter. If
  * the app.update.test.disabledAddons has a user value then setting the noupdate
  * test add-ons' userDisabled value for the test is the only thing that is done.
--- a/toolkit/mozapps/update/test/shared.js
+++ b/toolkit/mozapps/update/test/shared.js
@@ -68,16 +68,17 @@ const PREF_APP_UPDATE_URL_OVERRIDE      
 const PREF_APP_UPDATE_CERT_INVALID_ATTR_NAME = PREF_APP_UPDATE_CERTS_BRANCH +
                                                "1.invalidName";
 
 const PREF_APP_PARTNER_BRANCH             = "app.partner.";
 const PREF_DISTRIBUTION_ID                = "distribution.id";
 const PREF_DISTRIBUTION_VERSION           = "distribution.version";
 
 const PREF_EXTENSIONS_UPDATE_URL          = "extensions.update.url";
+const PREF_EXTENSIONS_STRICT_COMPAT       = "extensions.strictCompatibility";
 
 const NS_APP_PROFILE_DIR_STARTUP   = "ProfDS";
 const NS_APP_USER_PROFILE_50_DIR   = "ProfD";
 const NS_GRE_DIR                   = "GreD";
 const NS_XPCOM_CURRENT_PROCESS_DIR = "XCurProcD";
 const XRE_UPDATE_ROOT_DIR          = "UpdRootD";
 
 const CRC_ERROR   = 4;