Merge MC -> JM
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 02 Nov 2011 09:23:25 -0700
changeset 82925 d6352d960dd24bb29dc3365b9b2e686e7fbcd812
parent 82924 f951e9151626a490a37a3c12f3b3c5d084a56dc0 (current diff)
parent 80910 921e1db5cf11f7877a9f85bb91aa99fce618e2bc (diff)
child 82926 ef1f81733ed8764409930afeacd268323fdd1e6c
push idunknown
push userunknown
push dateunknown
milestone10.0a1
Merge MC -> JM
browser/components/sessionstore/test/browser/browser_form_restore_events.js
browser/components/sessionstore/test/browser/browser_form_restore_events_sample.html
build/pymake/.hg_archival.txt
dom/interfaces/html/nsIDOMNSHTMLElement.idl
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeCompiler.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/BytecodeGenerator.cpp
js/src/frontend/BytecodeGenerator.h
js/src/frontend/FoldConstants.cpp
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/SemanticAnalysis.cpp
js/src/jsanalyze.cpp
js/src/jsanalyze.h
js/src/jsapi.cpp
js/src/jsdbgapi.cpp
js/src/jsfriendapi.h
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsinterp.cpp
js/src/jsobj.cpp
js/src/jsopcode.cpp
js/src/jsopcode.h
js/src/jsopcode.tbl
js/src/jspropertytree.cpp
js/src/jsprvtd.h
js/src/jspubtd.h
js/src/jsscope.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsstr.cpp
js/src/jstracer.cpp
js/src/methodjit/BaseAssembler.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/FastBuiltins.cpp
js/src/methodjit/FastOps.cpp
js/src/methodjit/FrameState.h
js/src/methodjit/MethodJIT.cpp
js/src/methodjit/StubCalls.cpp
js/src/methodjit/StubCalls.h
js/src/shell/js.cpp
js/src/vm/Debugger.cpp
js/src/vm/GlobalObject.cpp
js/src/vm/Stack-inl.h
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCJSRuntime.cpp
layout/reftests/svg/pattern-scale-01.svg
toolkit/components/places/utils.js
toolkit/content/tests/browser/common/mockFilePicker.js
--- a/accessible/src/base/nsAccessNode.cpp
+++ b/accessible/src/base/nsAccessNode.cpp
@@ -49,17 +49,17 @@
 #include "nsApplicationAccessibleWrap.h"
 #include "nsIAccessibleDocument.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIDocument.h"
 #include "nsIDOMCSSPrimitiveValue.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMElement.h"
-#include "nsIDOMNSHTMLElement.h"
+#include "nsIDOMHTMLElement.h"
 #include "nsIDOMWindow.h"
 #include "nsPIDOMWindow.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIFrame.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
@@ -332,20 +332,20 @@ nsAccessNode::GetRootDocument(nsIAccessi
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAccessNode::GetInnerHTML(nsAString& aInnerHTML)
 {
   aInnerHTML.Truncate();
 
-  nsCOMPtr<nsIDOMNSHTMLElement> domNSElement(do_QueryInterface(mContent));
-  NS_ENSURE_TRUE(domNSElement, NS_ERROR_NULL_POINTER);
+  nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(mContent);
+  NS_ENSURE_TRUE(htmlElement, NS_ERROR_NULL_POINTER);
 
-  return domNSElement->GetInnerHTML(aInnerHTML);
+  return htmlElement->GetInnerHTML(aInnerHTML);
 }
 
 NS_IMETHODIMP
 nsAccessNode::ScrollTo(PRUint32 aScrollType)
 {
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -57,17 +57,17 @@
 #include "States.h"
 
 #include "nsIDOMElement.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMDocumentXBL.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMHTMLFormElement.h"
 #include "nsIDOMNodeFilter.h"
-#include "nsIDOMNSHTMLElement.h"
+#include "nsIDOMHTMLElement.h"
 #include "nsIDOMTreeWalker.h"
 #include "nsIDOMXULButtonElement.h"
 #include "nsIDOMXULDocument.h"
 #include "nsIDOMXULElement.h"
 #include "nsIDOMXULLabelElement.h"
 #include "nsIDOMXULSelectCntrlEl.h"
 #include "nsIDOMXULSelectCntrlItemEl.h"
 #include "nsPIDOMWindow.h"
@@ -1419,17 +1419,17 @@ nsAccessible::GetAttributesInternal(nsIP
 
   // Expose 'text-indent' attribute.
   rv = GetComputedStyleValue(EmptyString(), NS_LITERAL_STRING("text-indent"),
                              value);
   if (NS_SUCCEEDED(rv))
     nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textIndent, value);
 
   // Expose draggable object attribute?
-  nsCOMPtr<nsIDOMNSHTMLElement> htmlElement = do_QueryInterface(mContent);
+  nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(mContent);
   if (htmlElement) {
     bool draggable = false;
     htmlElement->GetDraggable(&draggable);
     if (draggable) {
       nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::draggable,
                              NS_LITERAL_STRING("true"));
     }
   }
--- a/accessible/src/base/nsBaseWidgetAccessible.cpp
+++ b/accessible/src/base/nsBaseWidgetAccessible.cpp
@@ -40,17 +40,16 @@
 #include "nsBaseWidgetAccessible.h"
 
 #include "States.h"
 #include "nsAccessibilityService.h"
 #include "nsAccUtils.h"
 #include "nsCoreUtils.h"
 #include "nsHyperTextAccessibleWrap.h"
 
-#include "nsIDOMNSHTMLElement.h"
 #include "nsGUIEvent.h"
 #include "nsILink.h"
 #include "nsIFrame.h"
 #include "nsINameSpaceManager.h"
 #include "nsIURI.h"
 
 using namespace mozilla::a11y;
 
--- a/accessible/src/html/nsHTMLFormControlAccessible.cpp
+++ b/accessible/src/html/nsHTMLFormControlAccessible.cpp
@@ -41,17 +41,16 @@
 #include "Relation.h"
 #include "States.h"
 #include "nsAccUtils.h"
 #include "nsTextEquivUtils.h"
 
 #include "nsIAccessibleRelation.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMHTMLInputElement.h"
-#include "nsIDOMNSHTMLElement.h"
 #include "nsIDOMNSEditableElement.h"
 #include "nsIDOMHTMLFormElement.h"
 #include "nsIDOMHTMLLegendElement.h"
 #include "nsIDOMHTMLTextAreaElement.h"
 #include "nsIDOMNodeList.h"
 #include "nsIEditor.h"
 #include "nsIFrame.h"
 #include "nsINameSpaceManager.h"
--- a/accessible/src/msaa/nsAccessNodeWrap.cpp
+++ b/accessible/src/msaa/nsAccessNodeWrap.cpp
@@ -46,17 +46,17 @@
 #include "nsCoreUtils.h"
 #include "nsRootAccessible.h"
 #include "nsWinUtils.h"
 #include "Statistics.h"
 
 #include "nsAttrName.h"
 #include "nsIDocument.h"
 #include "nsIDOMNodeList.h"
-#include "nsIDOMNSHTMLElement.h"
+#include "nsIDOMHTMLElement.h"
 #include "nsIFrame.h"
 #include "nsINameSpaceManager.h"
 #include "nsPIDOMWindow.h"
 #include "nsIServiceManager.h"
 
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
@@ -543,22 +543,22 @@ nsAccessNodeWrap::get_childAt(unsigned a
 }
 
 STDMETHODIMP 
 nsAccessNodeWrap::get_innerHTML(BSTR __RPC_FAR *aInnerHTML)
 {
 __try {
   *aInnerHTML = nsnull;
 
-  nsCOMPtr<nsIDOMNSHTMLElement> domNSElement(do_QueryInterface(GetNode()));
-  if (!domNSElement)
+  nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(GetNode());
+  if (!htmlElement)
     return E_FAIL; // Node already shut down
 
   nsAutoString innerHTML;
-  domNSElement->GetInnerHTML(innerHTML);
+  htmlElement->GetInnerHTML(innerHTML);
   if (innerHTML.IsEmpty())
     return S_FALSE;
 
   *aInnerHTML = ::SysAllocStringLen(innerHTML.get(), innerHTML.Length());
   if (!*aInnerHTML)
     return E_OUTOFMEMORY;
 
 } __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -37,17 +37,17 @@ const nsIAccessibleTableCell = Component
 const nsIAccessibleValue = Components.interfaces.nsIAccessibleValue;
 
 const nsIObserverService = Components.interfaces.nsIObserverService;
 
 const nsIDOMDocument = Components.interfaces.nsIDOMDocument;
 const nsIDOMEvent = Components.interfaces.nsIDOMEvent;
 const nsIDOMHTMLDocument = Components.interfaces.nsIDOMHTMLDocument;
 const nsIDOMNode = Components.interfaces.nsIDOMNode;
-const nsIDOMNSHTMLElement = Components.interfaces.nsIDOMNSHTMLElement;
+const nsIDOMHTMLElement = Components.interfaces.nsIDOMHTMLElement;
 const nsIDOMWindow = Components.interfaces.nsIDOMWindow;
 const nsIDOMXULElement = Components.interfaces.nsIDOMXULElement;
 
 const nsIPropertyElement = Components.interfaces.nsIPropertyElement;
 
 ////////////////////////////////////////////////////////////////////////////////
 // OS detect
 const MAC = (navigator.platform.indexOf("Mac") != -1)? true : false;
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -819,26 +819,26 @@ function synthClick(aNodeOrID, aCheckerO
   {
     var targetNode = this.DOMNode;
     if (targetNode instanceof nsIDOMDocument) {
       targetNode =
         this.DOMNode.body ? this.DOMNode.body : this.DOMNode.documentElement;
     }
 
     // Scroll the node into view, otherwise synth click may fail.
-    if (targetNode instanceof nsIDOMNSHTMLElement) {
+    if (targetNode instanceof nsIDOMHTMLElement) {
       targetNode.scrollIntoView(true);
     } else if (targetNode instanceof nsIDOMXULElement) {
       var targetAcc = getAccessible(targetNode);
       targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE);
     }
 
     var x = 1, y = 1;
     if (aArgs && ("where" in aArgs) && aArgs.where == "right") {
-      if (targetNode instanceof nsIDOMNSHTMLElement)
+      if (targetNode instanceof nsIDOMHTMLElement)
         x = targetNode.offsetWidth - 1;
       else if (targetNode instanceof nsIDOMXULElement)
         x = targetNode.boxObject.width - 1;
     }
     synthesizeMouse(targetNode, x, y, aArgs ? aArgs : {});
   }
 
   this.finalCheck = function synthClick_finalCheck()
--- 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/aboutDialog.js
+++ b/browser/base/content/aboutDialog.js
@@ -245,17 +245,17 @@ appUpdater.prototype =
    * @param  aKeyPrefix
    *         The prefix for the properties file entry to use for setting the
    *         label and accesskey.
    */
   setupUpdateButton: function(aKeyPrefix) {
     this.updateBtn.label = this.bundle.GetStringFromName(aKeyPrefix + ".label");
     this.updateBtn.accessKey = this.bundle.GetStringFromName(aKeyPrefix + ".accesskey");
     if (!document.commandDispatcher.focusedElement ||
-        document.commandDispatcher.focusedElement.isSameNode(this.updateBtn))
+        document.commandDispatcher.focusedElement == this.updateBtn)
       this.updateBtn.focus();
   },
 
   /**
    * Handles oncommand for the update button.
    */
   buttonOnCommand: function() {
     if (this.isPending) {
--- a/browser/base/content/aboutSyncTabs.js
+++ b/browser/base/content/aboutSyncTabs.js
@@ -126,33 +126,47 @@ let RemoteTabViewer = {
       this._tabsList.clearSelection();
     }
   },
 
   bookmarkSingleTab: function() {
     let item = this._tabsList.selectedItems[0];
     let uri = Weave.Utils.makeURI(item.getAttribute("url"));
     let title = item.getAttribute("title");
-    PlacesUIUtils.showMinimalAddBookmarkUI(uri, title);
+    PlacesUIUtils.showBookmarkDialog({ action: "add"
+                                     , type: "bookmark"
+                                     , uri: uri
+                                     , title: title
+                                     , hiddenRows: [ "description"
+                                                   , "location"
+                                                   , "folderPicker"
+                                                   , "loadInSidebar"
+                                                   , "keyword" ]
+                                     });
   },
 
   bookmarkSelectedTabs: function() {
     let items = this._tabsList.selectedItems;
     let URIs = [];
     for (let i = 0;i < items.length;i++) {
       if (items[i].getAttribute("type") == "tab") {
         let uri = Weave.Utils.makeURI(items[i].getAttribute("url"));
         if (!uri)
           continue;
 
         URIs.push(uri);
       }
     }
-    if (URIs.length)
-      PlacesUIUtils.showMinimalAddMultiBookmarkUI(URIs);
+    if (URIs.length) {
+      PlacesUIUtils.showBookmarkDialog({ action: "add"
+                                       , type: "folder"
+                                       , URIList: URIs
+                                       , hiddenRows: [ "description" ]
+                                       });
+    }
   },
 
   _generateTabList: function() {
     let engine = Weave.Engines.get("tabs");
     let list = this._tabsList;
 
     // clear out existing richlistitems
     let count = list.getRowCount();
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -373,21 +373,33 @@ var PlacesCommandHook = {
    * @param aURL (string)
    *        the address of the link target
    * @param aTitle
    *        The link text
    */
   bookmarkLink: function PCH_bookmarkLink(aParent, aURL, aTitle) {
     var linkURI = makeURI(aURL);
     var itemId = PlacesUtils.getMostRecentBookmarkForURI(linkURI);
-    if (itemId == -1)
-      PlacesUIUtils.showMinimalAddBookmarkUI(linkURI, aTitle);
+    if (itemId == -1) {
+      PlacesUIUtils.showBookmarkDialog({ action: "add"
+                                       , type: "bookmark"
+                                       , uri: linkURI
+                                       , title: aTitle
+                                       , hiddenRows: [ "description"
+                                                     , "location"
+                                                     , "loadInSidebar"
+                                                     , "folderPicker"
+                                                     , "keyword" ]
+                                       });
+    }
     else {
-      PlacesUIUtils.showItemProperties(itemId,
-                                       PlacesUtils.bookmarks.TYPE_BOOKMARK);
+      PlacesUIUtils.showBookmarkDialog({ action: "edit"
+                                       , type: "bookmark"
+                                       , itemId: itemId
+                                       });
     }
   },
 
   /**
    * List of nsIURI objects characterizing the tabs currently open in the
    * browser, modulo pinned tabs.  The URIs will be in the order in which their
    * corresponding tabs appeared and duplicates are discarded.
    */
@@ -406,17 +418,21 @@ var PlacesCommandHook = {
 
   /**
    * Adds a folder with bookmarks to all of the currently open tabs in this 
    * window.
    */
   bookmarkCurrentPages: function PCH_bookmarkCurrentPages() {
     let pages = this.uniqueCurrentPages;
     if (pages.length > 1) {
-      PlacesUIUtils.showMinimalAddMultiBookmarkUI(pages);
+    PlacesUIUtils.showBookmarkDialog({ action: "add"
+                                     , type: "folder"
+                                     , URIList: pages
+                                     , hiddenRows: [ "description" ]
+                                     });
     }
   },
 
   /**
    * Updates disabled state for the "Bookmark All Tabs" command.
    */
   updateBookmarkAllTabsCommand:
   function PCH_updateBookmarkAllTabsCommand() {
@@ -446,20 +462,28 @@ var PlacesCommandHook = {
     var title = (arguments.length > 1) ? feedTitle : doc.title;
  
     var description;
     if (arguments.length > 2)
       description = feedSubtitle;
     else
       description = PlacesUIUtils.getDescriptionFromDocument(doc);
 
-    var toolbarIP =
-      new InsertionPoint(PlacesUtils.bookmarks.toolbarFolder, -1);
-    PlacesUIUtils.showMinimalAddLivemarkUI(feedURI, gBrowser.currentURI,
-                                           title, description, toolbarIP, true);
+    var toolbarIP = new InsertionPoint(PlacesUtils.toolbarFolderId, -1);
+    PlacesUIUtils.showBookmarkDialog({ action: "add"
+                                     , type: "livemark"
+                                     , feedURI: feedURI
+                                     , siteURI: gBrowser.currentURI
+                                     , title: title
+                                     , description: description
+                                     , defaultInsertionPoint: toolbarIP
+                                     , hiddenRows: [ "feedLocation"
+                                                   , "siteLocation"
+                                                   , "description" ]
+                                     });
   },
 
   /**
    * Opens the Places Organizer. 
    * @param   aLeftPaneRoot
    *          The query to select in the organizer window - options
    *          are: History, AllBookmarks, BookmarksMenu, BookmarksToolbar,
    *          UnfiledBookmarks and Tags.
@@ -942,17 +966,17 @@ var PlacesMenuDNDHandler = {
 };
 
 
 var PlacesStarButton = {
   _hasBookmarksObserver: false,
   uninit: function PSB_uninit()
   {
     if (this._hasBookmarksObserver) {
-      PlacesUtils.bookmarks.removeObserver(this);
+      PlacesUtils.removeLazyBookmarkObserver(this);
     }
     if (this._pendingStmt) {
       this._pendingStmt.cancel();
       delete this._pendingStmt;
     }
   },
 
   QueryInterface: XPCOMUtils.generateQI([
@@ -1006,17 +1030,17 @@ var PlacesStarButton = {
       this._itemIds = this._itemIds.filter(
         function (id) aItemIds.indexOf(id) == -1
       ).concat(aItemIds);
       this._updateStateInternal();
 
       // Start observing bookmarks if needed.
       if (!this._hasBookmarksObserver) {
         try {
-          PlacesUtils.bookmarks.addObserver(this, false);
+          PlacesUtils.addLazyBookmarkObserver(this);
           this._hasBookmarksObserver = true;
         } catch(ex) {
           Components.utils.reportError("PlacesStarButton failed adding a bookmarks observer: " + ex);
         }
       }
 
       delete this._pendingStmt;
     }, this);
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -352,16 +352,35 @@ window[chromehidden~="toolbar"] toolbar:
 
 /*  Full Screen UI */
 
 #fullscr-toggler {
   height: 1px;
   background: black;
 }
 
+#full-screen-warning-container {
+  pointer-events: none;
+  position: fixed;
+  top: 0;
+  left: 0;
+  min-width: 100%;
+  min-height: 100%;
+}
+
+#full-screen-warning-container[fade-warning-out] {
+  -moz-transition-property: opacity !important;
+  -moz-transition-duration: 500ms !important;
+  opacity: 0.0;
+}
+
+#full-screen-warning-message {
+  pointer-events: auto;
+}
+
 #nav-bar[mode="text"] > #window-controls > toolbarbutton > .toolbarbutton-icon {
   display: -moz-box;
 }
 
 #nav-bar[mode="text"] > #window-controls > toolbarbutton > .toolbarbutton-text {
   display: none;
 }
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1341,17 +1341,16 @@ function BrowserStartup() {
     document.getElementById("status-bar").setAttribute("hideresizer", "true");
 
   if (!window.toolbar.visible) {
     // adjust browser UI for popups
     if (gURLBar) {
       gURLBar.setAttribute("readonly", "true");
       gURLBar.setAttribute("enablehistory", "false");
     }
-    goSetCommandEnabled("Browser:OpenLocation", false);
     goSetCommandEnabled("cmd_newNavigatorTab", false);
   }
 
 #ifdef MENUBAR_CAN_AUTOHIDE
   updateAppButtonDisplay();
 #endif
 
   CombinedStopReload.init();
@@ -1665,18 +1664,29 @@ function delayedStartup(isLoadingBlank, 
 #else
   if (Win7Features)
     Win7Features.onOpenWindow();
 #endif
 
   // called when we go into full screen, even if it is
   // initiated by a web page script
   window.addEventListener("fullscreen", onFullScreen, true);
+
+  // Called when we enter DOM full-screen mode. Note we can already be in browser
+  // full-screen mode when we enter DOM full-screen mode.
+  window.addEventListener("mozfullscreenchange", onMozFullScreenChange, true);
+
+  // When a restricted key is pressed in DOM full-screen mode, we should display
+  // the "Press ESC to exit" warning message.
+  window.addEventListener("MozShowFullScreenWarning", onShowFullScreenWarning, true);
+
   if (window.fullScreen)
     onFullScreen();
+  if (document.mozFullScreen)
+    onMozFullScreenChange();
 
 #ifdef MOZ_SERVICES_SYNC
   // initialize the sync UI
   gSyncUI.init();
 #endif
 
   TabView.init();
 
@@ -2120,17 +2130,17 @@ function loadOneOrMoreURIs(aURIString)
   try {
     gBrowser.loadTabs(aURIString.split("|"), false, true);
   }
   catch (e) {
   }
 }
 
 function focusAndSelectUrlBar() {
-  if (gURLBar && !gURLBar.readOnly) {
+  if (gURLBar) {
     if (window.fullScreen)
       FullScreen.mouseoverToggle(true);
     if (isElementVisible(gURLBar)) {
       gURLBar.focus();
       gURLBar.select();
       return true;
     }
   }
@@ -2834,16 +2844,24 @@ function BrowserFullScreen()
 {
   window.fullScreen = !window.fullScreen;
 }
 
 function onFullScreen(event) {
   FullScreen.toggle(event);
 }
 
+function onMozFullScreenChange(event) {
+  FullScreen.enterDomFullScreen(event);
+}
+
+function onShowFullScreenWarning(event) {
+  FullScreen.showWarning(false);
+}
+
 function getWebNavigation()
 {
   try {
     return gBrowser.webNavigation;
   } catch (e) {
     return null;
   }
 }
@@ -3117,17 +3135,26 @@ function openHomeDialog(aURL)
 }
 
 var bookmarksButtonObserver = {
   onDrop: function (aEvent)
   {
     let name = { };
     let url = browserDragAndDrop.drop(aEvent, name);
     try {
-      PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(url), name);
+      PlacesUIUtils.showBookmarkDialog({ action: "add"
+                                       , type: "bookmark"
+                                       , uri: makeURI(url)
+                                       , title: name
+                                       , hiddenRows: [ "description"
+                                                     , "location"
+                                                     , "loadInSidebar"
+                                                     , "folderPicker"
+                                                     , "keyword" ]
+                                       });
     } catch(ex) { }
   },
 
   onDragOver: function (aEvent)
   {
     browserDragAndDrop.dragOver(aEvent);
     aEvent.dropEffect = "link";
   },
@@ -3816,35 +3843,40 @@ var FullScreen = {
 
     // show/hide all menubars, toolbars (except the full screen toolbar)
     this.showXULChrome("toolbar", !enterFS);
     document.getElementById("View:FullScreen").setAttribute("checked", enterFS);
 
     if (enterFS) {
       // Add a tiny toolbar to receive mouseover and dragenter events, and provide affordance.
       // This will help simulate the "collapse" metaphor while also requiring less code and
-      // events than raw listening of mouse coords.
-      let fullScrToggler = document.getElementById("fullscr-toggler");
-      if (!fullScrToggler) {
-        fullScrToggler = document.createElement("hbox");
-        fullScrToggler.id = "fullscr-toggler";
-        fullScrToggler.collapsed = true;
-        gNavToolbox.parentNode.insertBefore(fullScrToggler, gNavToolbox.nextSibling);
+      // events than raw listening of mouse coords. We don't add the toolbar in DOM full-screen
+      // mode, only browser full-screen mode.
+      if (!document.mozFullScreen) {
+        let fullScrToggler = document.getElementById("fullscr-toggler");
+        if (!fullScrToggler) {
+          fullScrToggler = document.createElement("hbox");
+          fullScrToggler.id = "fullscr-toggler";
+          fullScrToggler.collapsed = true;
+          gNavToolbox.parentNode.insertBefore(fullScrToggler, gNavToolbox.nextSibling);
+        }
+        fullScrToggler.addEventListener("mouseover", this._expandCallback, false);
+        fullScrToggler.addEventListener("dragenter", this._expandCallback, false);
       }
-      fullScrToggler.addEventListener("mouseover", this._expandCallback, false);
-      fullScrToggler.addEventListener("dragenter", this._expandCallback, false);
-
       if (gPrefService.getBoolPref("browser.fullscreen.autohide"))
         gBrowser.mPanelContainer.addEventListener("mousemove",
                                                   this._collapseCallback, false);
 
       document.addEventListener("keypress", this._keyToggleCallback, false);
       document.addEventListener("popupshown", this._setPopupOpen, false);
       document.addEventListener("popuphidden", this._setPopupOpen, false);
-      this._shouldAnimate = true;
+      // We don't animate the toolbar collapse if in DOM full-screen mode,
+      // as the size of the content area would still be changing after the
+      // mozfullscreenchange event fired, which could confuse content script.
+      this._shouldAnimate = !document.mozFullScreen;
       this.mouseoverToggle(false);
 
       // Autohide prefs
       gPrefService.addObserver("browser.fullscreen", this, false);
     }
     else {
       // The user may quit fullscreen during an animation
       clearInterval(this._animationInterval);
@@ -3855,30 +3887,54 @@ var FullScreen = {
       this._isAnimating = false;
       // This is needed if they use the context menu to quit fullscreen
       this._isPopupOpen = false;
 
       this.cleanup();
     }
   },
 
+  enterDomFullScreen : function(event) {
+    if (!document.mozFullScreen) {
+      return;
+    }
+    this.showWarning(true);
+
+    // Cancel any "hide the toolbar" animation which is in progress, and make
+    // the toolbar hide immediately.
+    clearInterval(this._animationInterval);
+    clearTimeout(this._animationTimeout);
+    this._isAnimating = false;
+    this._shouldAnimate = false;
+    this.mouseoverToggle(false);
+
+    // If there's a full-screen toggler, remove its listeners, so that mouseover
+    // the top of the screen will not cause the toolbar to re-appear.
+    let fullScrToggler = document.getElementById("fullscr-toggler");
+    if (fullScrToggler) {
+      fullScrToggler.removeEventListener("mouseover", this._expandCallback, false);
+      fullScrToggler.removeEventListener("dragenter", this._expandCallback, false);
+    }
+  },
+
   cleanup: function () {
     if (window.fullScreen) {
       gBrowser.mPanelContainer.removeEventListener("mousemove",
                                                    this._collapseCallback, false);
       document.removeEventListener("keypress", this._keyToggleCallback, false);
       document.removeEventListener("popupshown", this._setPopupOpen, false);
       document.removeEventListener("popuphidden", this._setPopupOpen, false);
       gPrefService.removeObserver("browser.fullscreen", this);
 
       let fullScrToggler = document.getElementById("fullscr-toggler");
       if (fullScrToggler) {
         fullScrToggler.removeEventListener("mouseover", this._expandCallback, false);
         fullScrToggler.removeEventListener("dragenter", this._expandCallback, false);
       }
+      this.cancelWarning();
     }
   },
 
   observe: function(aSubject, aTopic, aData)
   {
     if (aData == "browser.fullscreen.autohide") {
       if (gPrefService.getBoolPref("browser.fullscreen.autohide")) {
         gBrowser.mPanelContainer.addEventListener("mousemove",
@@ -3989,16 +4045,94 @@ var FullScreen = {
         return;
       }
       gNavToolbox.style.marginTop = (animateFrameAmount * -1) + "px";
     }
 
     FullScreen._animationInterval = setInterval(animateUpFrame, 70);
   },
 
+  cancelWarning: function(event) {
+    if (!this.warningBox) {
+      return;
+    }
+    if (this.onWarningHidden) {
+      this.warningBox.removeEventListener("transitionend", this.onWarningHidden, false);
+      this.onWarningHidden = null;
+    }
+    if (this.warningFadeOutTimeout) {
+      clearTimeout(this.warningFadeOutTimeout);
+      this.warningFadeOutTimeout = null;
+    }
+    if (this.revealBrowserTimeout) {
+      clearTimeout(this.revealBrowserTimeout);
+      this.revealBrowserTimeout = null;
+    }
+    this.warningBox.removeAttribute("fade-warning-out");
+    this.warningBox.removeAttribute("stop-obscuring-browser");
+    this.warningBox.removeAttribute("obscure-browser");
+    this.warningBox.setAttribute("hidden", true);
+    this.warningBox = null;
+  },
+
+  warningBox: null,
+  warningFadeOutTimeout: null,
+  revealBrowserTimeout: null,  
+  onWarningHidden: null,
+
+  // Fade in a warning that document has entered full-screen, and then fade it
+  // out after a few seconds.
+  showWarning: function(obscureBackground) {
+    if (!document.mozFullScreen || !gPrefService.getBoolPref("full-screen-api.warning.enabled")) {
+      return;
+    }
+    if (this.warningBox) {
+      // Warning is already showing. Reset the timer which fades out the warning message,
+      // and we'll restart the timer down below.
+      if (this.warningFadeOutTimeout) {
+        clearTimeout(this.warningFadeOutTimeout);
+        this.warningFadeOutTimeout = null;
+      }
+    } else {
+      this.warningBox = document.getElementById("full-screen-warning-container");
+      // Add a listener to clean up state after the warning is hidden.
+      this.onWarningHidden =
+        function(event) {
+          if (event.propertyName != "opacity")
+            return;
+          this.cancelWarning();
+        }.bind(this);
+      this.warningBox.addEventListener("transitionend", this.onWarningHidden, false);
+      this.warningBox.removeAttribute("hidden");
+    }
+
+    if (obscureBackground) {
+      // Partially obscure the <browser> element underneath the warning panel...
+      this.warningBox.setAttribute("obscure-browser", "true");
+      // ...But set a timeout to stop obscuring the browser after a few moments.
+      this.warningBox.removeAttribute("stop-obscuring-browser");
+      this.revealBrowserTimeout =
+        setTimeout(
+          function() {
+            if (this.warningBox)
+              this.warningBox.setAttribute("stop-obscuring-browser", "true");
+          }.bind(this),
+          1250);
+    }
+
+    // Set a timeout to fade the warning out after a few moments.
+    this.warningFadeOutTimeout =
+      setTimeout(
+        function() {
+          if (this.warningBox)
+            this.warningBox.setAttribute("fade-warning-out", "true");
+        }.bind(this),
+        3000);
+  },
+
   mouseoverToggle: function(aShow, forceHide)
   {
     // Don't do anything if:
     // a) we're already in the state we want,
     // b) we're animating and will become collapsed soon, or
     // c) we can't collapse because it would be undesirable right now
     if (aShow != this._isChromeCollapsed || (!aShow && this._isAnimating) ||
         (!aShow && !this._safeToCollapse(forceHide)))
@@ -4028,17 +4162,20 @@ var FullScreen = {
                                                    this._collapseCallback, false);
     }
 
     // Hiding/collapsing the toolbox interferes with the tab bar's scrollbox,
     // so we just move it off-screen instead. See bug 430687.
     gNavToolbox.style.marginTop =
       aShow ? "" : -gNavToolbox.getBoundingClientRect().height + "px";
 
-    document.getElementById("fullscr-toggler").collapsed = aShow;
+    let toggler = document.getElementById("fullscr-toggler");
+    if (toggler) {
+      toggler.collapsed = aShow;
+    }
     this._isChromeCollapsed = !aShow;
     if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 2)
       this._shouldAnimate = true;
   },
 
   showXULChrome: function(aTag, aShow)
   {
     var els = document.getElementsByTagNameNS(this._XULNS, aTag);
@@ -5583,19 +5720,26 @@ function contentAreaClick(event, isPanel
       event.preventDefault();
       return true;
     }
 
     if (linkNode.getAttribute("rel") == "sidebar") {
       // This is the Opera convention for a special link that, when clicked,
       // allows to add a sidebar panel.  The link's title attribute contains
       // the title that should be used for the sidebar panel.
-      PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(href),
-                                             linkNode.getAttribute("title"),
-                                             null, null, true, true);
+      PlacesUIUtils.showBookmarkDialog({ action: "add"
+                                       , type: "bookmark"
+                                       , uri: makeURI(href)
+                                       , title: linkNode.getAttribute("title")
+                                       , loadBookmarkInSidebar: true
+                                       , hiddenRows: [ "description"
+                                                     , "location"
+                                                     , "folderPicker"
+                                                     , "keyword" ]
+                                       });
       event.preventDefault();
       return true;
     }
   }
 
   handleLinkClick(event, href, linkNode);
 
   // Mark the page as a user followed link.  This is done so that history can
@@ -6621,18 +6765,28 @@ function AddKeywordForSearchField() {
 
   var postData;
 
   if (isURLEncoded)
     postData = formData.join("&");
   else
     spec += "?" + formData.join("&");
 
-  PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(spec), title, description, null,
-                                         null, null, "", postData, charset);
+  PlacesUIUtils.showBookmarkDialog({ action: "add"
+                                   , type: "bookmark"
+                                   , uri: makeURI(spec)
+                                   , title: title
+                                   , description: description
+                                   , keyword: ""
+                                   , postData: postData
+                                   , charSet: charset
+                                   , hiddenRows: [ "location"
+                                                 , "loadInSidebar"
+                                                 , "folderPicker" ]
+                                   });
 }
 
 function SwitchDocumentDirection(aWindow) {
   aWindow.document.dir = (aWindow.document.dir == "ltr" ? "rtl" : "ltr");
   for (var run = 0; run < aWindow.frames.length; run++)
     SwitchDocumentDirection(aWindow.frames[run]);
 }
 
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -964,36 +964,54 @@
                   contentcontextmenu="contentAreaContextMenu"
                   autocompletepopup="PopupAutoComplete"
                   onclick="return contentAreaClick(event, false);"/>
       <statuspanel id="statusbar-display" inactive="true"/>
     </vbox>
     <vbox id="browser-border-end" hidden="true" layer="true"/>
   </hbox>
 
+  <hbox id="full-screen-warning-container" hidden="true" fadeout="true">
+    <hbox style="min-width: 100%;" pack="center"> <!-- Inner hbox needed due to bug 579776. -->
+      <hbox id="full-screen-warning-message">
+        <description id="full-screen-warning-text" value="&domFullScreenWarning.label;"></description>
+      </hbox>
+    </hbox>
+  </hbox>
+
   <vbox id="browser-bottombox" layer="true">
     <toolbar id="inspector-toolbar"
              nowindowdrag="true"
              hidden="true">
       <vbox flex="1">
         <resizer id="inspector-top-resizer" flex="1" 
                  class="inspector-resizer"
                  dir="top" disabled="true"
                  element="inspector-tree-box"/>
         <hbox>
+#ifdef XP_MACOSX
+          <toolbarbutton id="highlighter-closebutton"
+                         oncommand="InspectorUI.closeInspectorUI(false);"
+                         tooltiptext="&inspectCloseButton.tooltiptext;"/>
+#endif
           <toolbarbutton id="inspector-inspect-toolbutton"
                          label="&inspectButton.label;"
                          accesskey="&inspectButton.accesskey;"
                          command="Inspector:Inspect"/>
           <arrowscrollbox id="inspector-breadcrumbs"
                           flex="1" orient="horizontal"
                           clicktoscroll="true"/>
           <hbox id="inspector-tools">
             <!-- registered tools go here -->
           </hbox>
+#ifndef XP_MACOSX
+          <toolbarbutton id="highlighter-closebutton"
+                         oncommand="InspectorUI.closeInspectorUI(false);"
+                         tooltiptext="&inspectCloseButton.tooltiptext;"/>
+#endif
           <resizer id="inspector-end-resizer"
                    class="inspector-resizer"
                    dir="top" disabled="true"
                    element="inspector-tree-box"/>
         </hbox>
       </vbox>
     </toolbar>
     <toolbar id="addon-bar"
--- a/browser/base/content/highlighter.css
+++ b/browser/base/content/highlighter.css
@@ -25,24 +25,27 @@
 #highlighter-veil-rightbox {
   -moz-box-flex: 1;
 }
 
 #highlighter-veil-middlebox:-moz-locale-dir(rtl) {
   -moz-box-direction: reverse;
 }
 
-#highlighter-close-button {
-  position: absolute;
-  pointer-events: auto;
-  z-index: 1;
+.inspector-breadcrumbs-button {
+  direction: ltr;
 }
 
-.inspector-breadcrumbs-button {
-  direction: ltr;
+.inspector-resizer {
+  display: none;
+}
+
+#inspector-toolbar[treepanel-open] > vbox > #inspector-top-resizer,
+#inspector-toolbar[treepanel-open] > vbox > hbox > #inspector-end-resizer {
+  display: -moz-box;
 }
 
 /*
  * Node Infobar
  */
 
 #highlighter-nodeinfobar-container {
   position: absolute;
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1392,21 +1392,34 @@ nsContextMenu.prototype = {
   addBookmarkForFrame: function CM_addBookmarkForFrame() {
     var doc = this.target.ownerDocument;
     var uri = doc.documentURIObject;
 
     var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
     if (itemId == -1) {
       var title = doc.title;
       var description = PlacesUIUtils.getDescriptionFromDocument(doc);
-      PlacesUIUtils.showMinimalAddBookmarkUI(uri, title, description);
+      PlacesUIUtils.showBookmarkDialog({ action: "add"
+                                       , type: "bookmark"
+                                       , uri: uri
+                                       , title: title
+                                       , description: description
+                                       , hiddenRows: [ "description"
+                                                     , "location"
+                                                     , "loadInSidebar"
+                                                     , "folderPicker"
+                                                     , "keyword" ]
+                                       });
     }
-    else
-      PlacesUIUtils.showItemProperties(itemId,
-                                       PlacesUtils.bookmarks.TYPE_BOOKMARK);
+    else {
+      PlacesUIUtils.showBookmarkDialog({ action: "edit"
+                                       , type: "bookmark"
+                                       , itemId: itemId
+                                       });
+    }
   },
 
   savePageAs: function CM_savePageAs() {
     saveDocument(this.browser.contentDocument);
   },
 
   sendPage: function CM_sendPage() {
     MailIntegration.sendLinkForWindow(this.browser.contentWindow);  
@@ -1438,23 +1451,23 @@ nsContextMenu.prototype = {
         break;
       case "hidecontrols":
         media.removeAttribute("controls");
         break;
       case "showcontrols":
         media.setAttribute("controls", "true");
         break;
       case "showstats":
-        var event = document.createEvent("CustomEvent");
-        event.initCustomEvent("media-showStatistics", false, true, true);
+        var event = document.createEvent("CustomEvent");
+        event.initCustomEvent("media-showStatistics", false, true, true);
         media.dispatchEvent(event);
         break;
       case "hidestats":
-        var event = document.createEvent("CustomEvent");
-        event.initCustomEvent("media-showStatistics", false, true, false);
+        var event = document.createEvent("CustomEvent");
+        event.initCustomEvent("media-showStatistics", false, true, false);
         media.dispatchEvent(event);
         break;
     }
   },
 
   copyMediaLocation : function () {
     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
                     getService(Ci.nsIClipboardHelper);
--- 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/base/content/test/browser_popupUI.js
+++ b/browser/base/content/test/browser_popupUI.js
@@ -31,18 +31,18 @@ function findPopup() {
 }
 
 function testPopupUI(win) {
   var doc = win.document;
 
   ok(win.gURLBar, "location bar exists in the popup");
   isnot(win.gURLBar.clientWidth, 0, "location bar is visible in the popup");
   ok(win.gURLBar.readOnly, "location bar is read-only in the popup");
-  is(doc.getElementById("Browser:OpenLocation").getAttribute("disabled"), "true",
-     "'open location' command is disabled in the popup");
+  isnot(doc.getElementById("Browser:OpenLocation").getAttribute("disabled"), "true",
+     "'open location' command is not disabled in the popup");
 
   let historyButton = doc.getAnonymousElementByAttribute(win.gURLBar, "anonid",
                                                          "historydropmarker");
   is(historyButton.clientWidth, 0, "history dropdown button is hidden in the popup");
 
   EventUtils.synthesizeKey("t", { accelKey: true }, win);
   is(win.gBrowser.browsers.length, 1, "Accel+T doesn't open a new tab in the popup");
 
--- a/browser/base/content/test/browser_save_video.js
+++ b/browser/base/content/test/browser_save_video.js
@@ -1,17 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.reset();
+
 /**
  * TestCase for bug 564387
  * <https://bugzilla.mozilla.org/show_bug.cgi?id=564387>
  */
 function test() {
   waitForExplicitFinish();
+  var fileName;
 
   gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/bug564387.html");
 
   registerCleanupFunction(function () {
     gBrowser.addTab();
     gBrowser.removeCurrentTab();
   });
 
@@ -30,60 +34,57 @@ function test() {
     });
   });
 
   function contextMenuOpened(event) {
     event.currentTarget.removeEventListener("popupshown", contextMenuOpened);
 
     // Create the folder the video will be saved into.
     var destDir = createTemporarySaveDirectory();
+    var destFile = destDir.clone();
 
-    mockFilePickerSettings.destDir = destDir;
-    mockFilePickerSettings.filterIndex = 1; // kSaveAsType_URL
-    mockFilePickerRegisterer.register();
+    MockFilePicker.displayDirectory = destDir;
+    MockFilePicker.showCallback = function(fp) {
+      fileName = fp.defaultString;
+      destFile.append (fileName);
+      MockFilePicker.returnFiles = [destFile];
+      MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+    };
 
     mockTransferCallback = onTransferComplete;
     mockTransferRegisterer.register();
 
     registerCleanupFunction(function () {
       mockTransferRegisterer.unregister();
-      mockFilePickerRegisterer.unregister();
+      MockFilePicker.reset();
       destDir.remove(true);
     });
 
     // Select "Save Video As" option from context menu
     var saveVideoCommand = document.getElementById("context-savevideo");
     saveVideoCommand.doCommand();
 
     event.target.hidePopup();
   }
 
   function onTransferComplete(downloadSuccess) {
     ok(downloadSuccess, "Video file should have been downloaded successfully");
 
-    // Read the name of the saved file.
-    var fileName = mockFilePickerResults.selectedFile.leafName;
-
     is(fileName, "Bug564387-expectedName.ogv",
        "Video file name is correctly retrieved from Content-Disposition http header");
 
     finish();
   }
 }
 
 Cc["@mozilla.org/moz/jssubscript-loader;1"]
   .getService(Ci.mozIJSSubScriptLoader)
   .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
                  this);
 
-Cc["@mozilla.org/moz/jssubscript-loader;1"]
-  .getService(Ci.mozIJSSubScriptLoader)
-  .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockFilePicker.js",
-                 this);
-
 function createTemporarySaveDirectory() {
   var saveDir = Cc["@mozilla.org/file/directory_service;1"]
                   .getService(Ci.nsIProperties)
                   .get("TmpD", Ci.nsIFile);
   saveDir.append("testsavedir");
   if (!saveDir.exists())
     saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
   return saveDir;
--- a/browser/components/dirprovider/Makefile.in
+++ b/browser/components/dirprovider/Makefile.in
@@ -40,17 +40,19 @@ topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = browserdir
 LIBRARY_NAME = browserdir_s
 
+ifdef ENABLE_TESTS
 DIRS = tests
+endif
 
 FORCE_STATIC_LIB = 1
 FORCE_USE_PIC = 1
 
 # Because we are an application component, link against the CRT statically
 # (on Windows, but only if we're not building our own CRT for jemalloc)
 ifndef MOZ_MEMORY
 USE_STATIC_LIBS      = 1
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -581,19 +581,16 @@ PlacesViewBase.prototype = {
 
   nodeHistoryDetailsChanged: function() { },
   nodeTagsChanged: function() { },
   nodeDateAddedChanged: function() { },
   nodeLastModifiedChanged: function() { },
   nodeKeywordChanged: function() { },
   sortingChanged: function() { },
   batching: function() { },
-  // Replaced by containerStateChanged.
-  containerOpened: function() { },
-  containerClosed: function() { },
 
   nodeInserted:
   function PVB_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
     let parentElt = aParentPlacesNode._DOMElement;
     if (!parentElt)
       throw "aParentPlacesNode node must have _DOMElement set";
 
     if (!parentElt._built)
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -132,17 +132,16 @@ PlacesController.prototype = {
     this.cutNodes = [];
   },
 
   terminate: function PC_terminate() {
     this._releaseClipboardOwnership();
   },
 
   supportsCommand: function PC_supportsCommand(aCommand) {
-    //LOG("supportsCommand: " + command);
     // Non-Places specific commands that we also support
     switch (aCommand) {
     case "cmd_undo":
     case "cmd_redo":
     case "cmd_cut":
     case "cmd_copy":
     case "cmd_paste":
     case "cmd_delete":
@@ -773,27 +772,16 @@ PlacesController.prototype = {
     if (performed) {
       // Select the new item.
       let insertedNodeId = PlacesUtils.bookmarks
                                       .getIdForItemAt(ip.itemId, ip.index);
       this._view.selectItems([insertedNodeId], false);
     }
   },
 
-
-  /**
-   * Create a new Bookmark folder somewhere. Prompts the user for the name
-   * of the folder.
-   */
-  newFolder: function PC_newFolder() {
-    Cu.reportError("PlacesController.newFolder is deprecated and will be \
-                   removed in a future release.  Use newItem instead.");
-    this.newItem("folder");
-  },
-
   /**
    * Create a new Bookmark separator somewhere.
    */
   newSeparator: function PC_newSeparator() {
     var ip = this._view.insertionPoint;
     if (!ip)
       throw Cr.NS_ERROR_NOT_AVAILABLE;
     var txn = PlacesUIUtils.ptm.createSeparator(ip.itemId, ip.index);
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -85,17 +85,18 @@ PlacesTreeView.prototype = {
    * This is called once both the result and the tree are set.
    */
   _finishInit: function PTV__finishInit() {
     let selection = this.selection;
     if (selection)
       selection.selectEventsSuppressed = true;
 
     if (!this._rootNode.containerOpen) {
-      // This triggers containerOpened which then builds the visible section.
+      // This triggers containerStateChanged which then builds the visible
+      // section.
       this._rootNode.containerOpen = true;
     }
     else
       this.invalidateContainer(this._rootNode);
 
     // "Activate" the sorting column and update commands.
     this.sortingChanged(this._result.sortingMode);
 
@@ -853,27 +854,21 @@ PlacesTreeView.prototype = {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATEADDED);
   },
 
   nodeLastModifiedChanged:
   function PTV_nodeLastModifiedChanged(aNode, aNewValue) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_LASTMODIFIED);
   },
 
-  containerOpened: function PTV_containerOpened(aNode) {
+  containerStateChanged:
+  function PTV_containerStateChanged(aNode, aOldState, aNewState) {
     this.invalidateContainer(aNode);
   },
 
-  containerClosed: function PTV_containerClosed(aNode) {
-    this.invalidateContainer(aNode);
-  },
-
-  containerStateChanged:
-  function PTV_containerStateChanged(aNode, aOldState, aNewState) {},
-
   invalidateContainer: function PTV_invalidateContainer(aContainer) {
     NS_ASSERT(this._result, "Need to have a result to update");
     if (!this._tree)
       return;
 
     let startReplacement, replaceCount;
     if (aContainer == this._rootNode) {
       startReplacement = 0;
--- a/browser/components/places/src/PlacesUIUtils.jsm
+++ b/browser/components/places/src/PlacesUIUtils.jsm
@@ -327,248 +327,16 @@ var PlacesUIUtils = {
                                                        : data.uri;
           return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(data.uri),
                                                      container, index, title);
         }
     }
     return null;
   },
 
-  _reportDeprecatedAddBookmarkMethod:
-  function PUIU__reportDeprecatedAddBookmarkMethod() {
-    // Removes "PUIU_".
-    let oldFuncName = arguments.callee.caller.name.slice(5);
-    Cu.reportError(oldFuncName + " is deprecated and will be removed in a " +
-                   "future release. Use showBookmarkDialog instead.");
-  },
-
-  /**
-   * This is here for compatibility reasons, use ShowBookmarkDialog instead.
-   */
-  showAddBookmarkUI: function PUIU_showAddBookmarkUI(
-    aURI, aTitle, aDescription, aDefaultInsertionPoint, aShowPicker,
-    aLoadInSidebar, aKeyword, aPostData, aCharSet) {
-    this._reportDeprecatedAddBookmarkMethod();
-
-    var info = {
-      action: "add",
-      type: "bookmark"
-    };
-
-    if (aURI)
-      info.uri = aURI;
-
-    // allow default empty title
-    if (typeof(aTitle) == "string")
-      info.title = aTitle;
-
-    if (aDescription)
-      info.description = aDescription;
-
-    if (aDefaultInsertionPoint) {
-      info.defaultInsertionPoint = aDefaultInsertionPoint;
-      if (!aShowPicker)
-        info.hiddenRows = ["folderPicker"];
-    }
-
-    if (aLoadInSidebar)
-      info.loadBookmarkInSidebar = true;
-
-    if (typeof(aKeyword) == "string") {
-      info.keyword = aKeyword;
-      if (typeof(aPostData) == "string")
-        info.postData = aPostData;
-      if (typeof(aCharSet) == "string")
-        info.charSet = aCharSet;
-    }
-
-    return this.showBookmarkDialog(info);
-  },
-
-  /**
-   * This is here for compatibility reasons, use ShowBookmarkDialog instead.
-   */
-  showMinimalAddBookmarkUI:
-  function PUIU_showMinimalAddBookmarkUI(
-    aURI, aTitle, aDescription, aDefaultInsertionPoint, aShowPicker,
-    aLoadInSidebar, aKeyword, aPostData, aCharSet) {
-    this._reportDeprecatedAddBookmarkMethod();
-
-    var info = {
-      action: "add",
-      type: "bookmark",
-      hiddenRows: ["description"]
-    };
-    if (aURI)
-      info.uri = aURI;
-
-    // allow default empty title
-    if (typeof(aTitle) == "string")
-      info.title = aTitle;
-
-    if (aDescription)
-      info.description = aDescription;
-
-    if (aDefaultInsertionPoint) {
-      info.defaultInsertionPoint = aDefaultInsertionPoint;
-      if (!aShowPicker)
-        info.hiddenRows.push("folderPicker");
-    }
-
-    if (aLoadInSidebar)
-      info.loadBookmarkInSidebar = true;
-    else
-      info.hiddenRows = info.hiddenRows.concat(["location", "loadInSidebar"]);
-
-    if (typeof(aKeyword) == "string") {
-      info.keyword = aKeyword;
-      // Hide the Tags field if we are adding a keyword.
-      info.hiddenRows.push("tags");
-      // Keyword related params.
-      if (typeof(aPostData) == "string")
-        info.postData = aPostData;
-      if (typeof(aCharSet) == "string")
-        info.charSet = aCharSet;
-    }
-    else
-      info.hiddenRows.push("keyword");
-
-    return this.showBookmarkDialog(info, undefined, true);
-  },
-
-  /**
-   * This is here for compatibility reasons, use ShowBookmarkDialog instead.
-   */
-  showAddLivemarkUI: function PUIU_showAddLivemarkURI(aFeedURI,
-                                                      aSiteURI,
-                                                      aTitle,
-                                                      aDescription,
-                                                      aDefaultInsertionPoint,
-                                                      aShowPicker) {
-    this._reportDeprecatedAddBookmarkMethod();
-
-    var info = {
-      action: "add",
-      type: "livemark"
-    };
-
-    if (aFeedURI)
-      info.feedURI = aFeedURI;
-    if (aSiteURI)
-      info.siteURI = aSiteURI;
-
-    // allow default empty title
-    if (typeof(aTitle) == "string")
-      info.title = aTitle;
-
-    if (aDescription)
-      info.description = aDescription;
-
-    if (aDefaultInsertionPoint) {
-      info.defaultInsertionPoint = aDefaultInsertionPoint;
-      if (!aShowPicker)
-        info.hiddenRows = ["folderPicker"];
-    }
-    return this.showBookmarkDialog(info);
-  },
-
-  /**
-   * This is here for compatibility reasons, use ShowBookmarkDialog instead.
-   */
-  showMinimalAddLivemarkUI:
-  function PUIU_showMinimalAddLivemarkURI(
-    aFeedURI, aSiteURI, aTitle, aDescription, aDefaultInsertionPoint,
-    aShowPicker) {
-
-    this._reportDeprecatedAddBookmarkMethod();
-
-    var info = {
-      action: "add",
-      type: "livemark",
-      hiddenRows: ["feedLocation", "siteLocation", "description"]
-    };
-
-    if (aFeedURI)
-      info.feedURI = aFeedURI;
-    if (aSiteURI)
-      info.siteURI = aSiteURI;
-
-    // allow default empty title
-    if (typeof(aTitle) == "string")
-      info.title = aTitle;
-
-    if (aDescription)
-      info.description = aDescription;
-
-    if (aDefaultInsertionPoint) {
-      info.defaultInsertionPoint = aDefaultInsertionPoint;
-      if (!aShowPicker)
-        info.hiddenRows.push("folderPicker");
-    }
-    return this.showBookmarkDialog(info, undefined, true);
-  },
-
-  /**
-   * This is here for compatibility reasons, use ShowBookmarkDialog instead.
-   */
-  showMinimalAddMultiBookmarkUI: function PUIU_showAddMultiBookmarkUI(aURIList) {
-    this._reportDeprecatedAddBookmarkMethod();
-
-    if (aURIList.length == 0)
-      throw("showAddMultiBookmarkUI expects a list of nsIURI objects");
-    var info = {
-      action: "add",
-      type: "folder",
-      hiddenRows: ["description"],
-      URIList: aURIList
-    };
-    return this.showBookmarkDialog(info, undefined, true);
-  },
-
-  /**
-   * This is here for compatibility reasons, use ShowBookmarkDialog instead.
-   */
-  showItemProperties: function PUIU_showItemProperties(aItemId, aType, aReadOnly) {
-    this._reportDeprecatedAddBookmarkMethod();
-
-    var info = {
-      action: "edit",
-      type: aType,
-      itemId: aItemId,
-      readOnly: aReadOnly
-    };
-    return this.showBookmarkDialog(info);
-  },
-
-  /**
-   * This is here for compatibility reasons, use ShowBookmarkDialog instead.
-   */
-  showAddFolderUI:
-  function PUIU_showAddFolderUI(aTitle, aDefaultInsertionPoint, aShowPicker) {
-    this._reportDeprecatedAddBookmarkMethod();
-
-    var info = {
-      action: "add",
-      type: "folder",
-      hiddenRows: []
-    };
-
-    // allow default empty title
-    if (typeof(aTitle) == "string")
-      info.title = aTitle;
-
-    if (aDefaultInsertionPoint) {
-      info.defaultInsertionPoint = aDefaultInsertionPoint;
-      if (!aShowPicker)
-        info.hiddenRows.push("folderPicker");
-    }
-    return this.showBookmarkDialog(info);
-  },
-
-
   /**
    * Shows the bookmark dialog corresponding to the specified info.
    *
    * @param aInfo
    *        Describes the item to be edited/added in the dialog.
    *        See documentation at the top of bookmarkProperties.js
    * @param aWindow
    *        Owner window for the new dialog.
--- a/browser/components/places/tests/unit/head_bookmarks.js
+++ b/browser/components/places/tests/unit/head_bookmarks.js
@@ -102,10 +102,10 @@ let (backup_date = new Date().toLocaleFo
 }
 
 // Smart bookmarks constants.
 const SMART_BOOKMARKS_VERSION = 2;
 const SMART_BOOKMARKS_ON_TOOLBAR = 1;
 const SMART_BOOKMARKS_ON_MENU = 3; // Takes in count the additional separator.
 
 // Default bookmarks constants.
-const DEFAULT_BOOKMARKS_ON_TOOLBAR = 2;
+const DEFAULT_BOOKMARKS_ON_TOOLBAR = 1;
 const DEFAULT_BOOKMARKS_ON_MENU = 1;
--- a/browser/components/places/tests/unit/test_browserGlue_prefs.js
+++ b/browser/components/places/tests/unit/test_browserGlue_prefs.js
@@ -200,17 +200,17 @@ let gTests = [
     print("Simulate Places init");
     bg.QueryInterface(Ci.nsIObserver).observe(null,
                                               TOPIC_BROWSERGLUE_TEST,
                                               TOPICDATA_FORCE_PLACES_INIT);
 
     // Check bookmarks.html has been restored.
     itemId =
       PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId,
-                                           SMART_BOOKMARKS_ON_TOOLBAR + 1);
+                                           SMART_BOOKMARKS_ON_TOOLBAR);
     do_check_true(itemId > 0);
     // Check preferences have been reverted.
     do_check_false(Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS));
 
     run_next_test();
   },
 
   function test_restore_import()
@@ -232,17 +232,17 @@ let gTests = [
     print("Simulate Places init");
     bg.QueryInterface(Ci.nsIObserver).observe(null,
                                               TOPIC_BROWSERGLUE_TEST,
                                               TOPICDATA_FORCE_PLACES_INIT);
 
     // Check bookmarks.html has been restored.
     itemId =
       PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId,
-                                           SMART_BOOKMARKS_ON_TOOLBAR + 1);
+                                           SMART_BOOKMARKS_ON_TOOLBAR);
     do_check_true(itemId > 0);
     // Check preferences have been reverted.
     do_check_false(Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS));
     do_check_false(Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML));
 
     run_next_test();
   }
 
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -3187,27 +3187,30 @@ SessionStoreService.prototype = {
     if (aEntry.postdata_b64) {
       var postdata = atob(aEntry.postdata_b64);
       var stream = Cc["@mozilla.org/io/string-input-stream;1"].
                    createInstance(Ci.nsIStringInputStream);
       stream.setData(postdata, postdata.length);
       shEntry.postData = stream;
     }
 
+    let childDocIdents = {};
     if (aEntry.docIdentifier) {
       // If we have a serialized document identifier, try to find an SHEntry
       // which matches that doc identifier and adopt that SHEntry's
       // BFCacheEntry.  If we don't find a match, insert shEntry as the match
       // for the document identifier.
       let matchingEntry = aDocIdentMap[aEntry.docIdentifier];
       if (!matchingEntry) {
-        aDocIdentMap[aEntry.docIdentifier] = shEntry;
+        matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents};
+        aDocIdentMap[aEntry.docIdentifier] = matchingEntry;
       }
       else {
-        shEntry.adoptBFCacheEntry(matchingEntry);
+        shEntry.adoptBFCacheEntry(matchingEntry.shEntry);
+        childDocIdents = matchingEntry.childDocIdents;
       }
     }
 
     if (aEntry.owner_b64) {
       var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
                        createInstance(Ci.nsIStringInputStream);
       var binaryData = atob(aEntry.owner_b64);
       ownerInput.setData(binaryData, binaryData.length);
@@ -3219,18 +3222,34 @@ SessionStoreService.prototype = {
       } catch (ex) { debug(ex); }
     }
 
     if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
       for (var i = 0; i < aEntry.children.length; i++) {
         //XXXzpao Wallpaper patch for bug 514751
         if (!aEntry.children[i].url)
           continue;
+
+        // We're getting sessionrestore.js files with a cycle in the
+        // doc-identifier graph, likely due to bug 698656.  (That is, we have
+        // an entry where doc identifier A is an ancestor of doc identifier B,
+        // and another entry where doc identifier B is an ancestor of A.)
+        //
+        // If we were to respect these doc identifiers, we'd create a cycle in
+        // the SHEntries themselves, which causes the docshell to loop forever
+        // when it looks for the root SHEntry.
+        //
+        // So as a hack to fix this, we restrict the scope of a doc identifier
+        // to be a node's siblings and cousins, and pass childDocIdents, not
+        // aDocIdents, to _deserializeHistoryEntry.  That is, we say that two
+        // SHEntries with the same doc identifier have the same document iff
+        // they have the same parent or their parents have the same document.
+
         shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap,
-                                                       aDocIdentMap), i);
+                                                       childDocIdents), i);
       }
     }
     
     return shEntry;
   },
 
   /**
    * restores all sessionStorage "super cookies"
@@ -3270,58 +3289,41 @@ SessionStoreService.prototype = {
         if (!hasExpectedURL(aDocument, aURL))
           return;
 
         let node = key.charAt(0) == "#" ? aDocument.getElementById(key.slice(1)) :
                                           XPathHelper.resolve(aDocument, key);
         if (!node)
           continue;
 
-        let eventType;
         let value = aData[key];
         if (typeof value == "string" && node.type != "file") {
           if (node.value == value)
             continue; // don't dispatch an input event for no change
 
           node.value = value;
-          eventType = "input";
+
+          let event = aDocument.createEvent("UIEvents");
+          event.initUIEvent("input", true, true, aDocument.defaultView, 0);
+          node.dispatchEvent(event);
         }
-        else if (typeof value == "boolean") {
-          if (node.checked == value)
-            continue; // don't dispatch a change event for no change
-
+        else if (typeof value == "boolean")
           node.checked = value;
-          eventType = "change";
-        }
-        else if (typeof value == "number") {
+        else if (typeof value == "number")
           try {
             node.selectedIndex = value;
-            eventType = "change";
           } catch (ex) { /* throws for invalid indices */ }
-        }
-        else if (value && value.fileList && value.type == "file" && node.type == "file") {
+        else if (value && value.fileList && value.type == "file" && node.type == "file")
           node.mozSetFileNameArray(value.fileList, value.fileList.length);
-          eventType = "input";
-        }
         else if (value && typeof value.indexOf == "function" && node.options) {
           Array.forEach(node.options, function(aOpt, aIx) {
             aOpt.selected = value.indexOf(aIx) > -1;
-
-            // Only fire the event here if this wasn't selected by default
-            if (!aOpt.defaultSelected)
-              eventType = "change";
           });
         }
-
-        // Fire events for this node if applicable
-        if (eventType) {
-          let event = aDocument.createEvent("UIEvents");
-          event.initUIEvent(eventType, true, true, aDocument.defaultView, 0);
-          node.dispatchEvent(event);
-        }
+        // NB: dispatching "change" events might have unintended side-effects
       }
     }
 
     let selectedPageStyle = aBrowser.__SS_restore_pageStyle;
     function restoreTextDataAndScrolling(aContent, aData, aPrefix) {
       if (aData.formdata)
         restoreFormData(aContent.document, aData.formdata, aData.url);
       if (aData.innerHTML) {
--- a/browser/components/sessionstore/test/browser/Makefile.in
+++ b/browser/components/sessionstore/test/browser/Makefile.in
@@ -45,18 +45,16 @@ relativesrcdir  = browser/components/ses
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 # browser_506482.js is disabled because of frequent failures (bug 538672)
 # browser_526613.js is disabled because of frequent failures (bug 534489)
 
 _BROWSER_TEST_FILES = \
 	head.js \
-	browser_form_restore_events.js \
-	browser_form_restore_events_sample.html \
 	browser_248970_a.js \
 	browser_248970_b.js \
 	browser_248970_b_sample.html \
 	browser_339445.js \
 	browser_339445_sample.html \
 	browser_345898.js \
 	browser_346337.js \
 	browser_346337_sample.html \
@@ -95,16 +93,18 @@ include $(topsrcdir)/config/rules.mk
 	browser_464620_a.html \
 	browser_464620_b.js \
 	browser_464620_b.html \
 	browser_464620_xd.html \
 	browser_465215.js \
 	browser_465223.js \
 	browser_466937.js \
 	browser_466937_sample.html \
+	browser_476161.js \
+	browser_476161_sample.html \
 	browser_477657.js \
 	browser_480148.js \
 	browser_480893.js \
 	browser_483330.js \
 	browser_485482.js \
 	browser_485482_sample.html \
 	browser_485563.js \
 	browser_490040.js \
@@ -149,16 +149,18 @@ include $(topsrcdir)/config/rules.mk
 	browser_628270.js \
 	browser_635418.js \
 	browser_636279.js \
 	browser_645428.js \
 	browser_659591.js \
 	browser_662812.js \
 	browser_665702-state_session.js \
 	browser_682507.js \
+	browser_687710.js \
+	browser_687710_2.js \
 	browser_694378.js \
 	$(NULL)
 
 ifneq ($(OS_ARCH),Darwin)
 _BROWSER_TEST_FILES += \
 	browser_597071.js \
 	browser_625016.js \
 	$(NULL)
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser/browser_476161.js
@@ -0,0 +1,68 @@
+/* ***** 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 sessionstore test code.
+ *
+ * The Initial Developer of the Original Code is
+ * Simon Bünzli <zeniko@gmail.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 test() {
+  /** Test for Bug 476161 **/
+  
+  waitForExplicitFinish();
+  
+  let testURL = "http://mochi.test:8888/browser/" +
+    "browser/components/sessionstore/test/browser/browser_476161_sample.html";
+  let tab = gBrowser.addTab(testURL);
+  tab.linkedBrowser.addEventListener("load", function(aEvent) {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+    let doc = tab.linkedBrowser.contentDocument;
+    
+    doc.getElementById("modify1").value += Math.random();
+    doc.getElementById("modify2").value += " " + Date.now();
+    
+    let tab2 = gBrowser.duplicateTab(tab);
+    tab2.linkedBrowser.addEventListener("load", function(aEvent) {
+      tab2.linkedBrowser.removeEventListener("load", arguments.callee, true);
+      let doc = tab2.linkedBrowser.contentDocument;
+      let changed = doc.getElementById("changed").textContent.trim().split();
+      
+      is(changed.sort().join(" "), "modify1 modify2",
+         "input events were only dispatched for modified text fields");
+      
+      // clean up
+      gBrowser.removeTab(tab2);
+      gBrowser.removeTab(tab);
+      
+      finish();
+    }, true);
+  }, true);
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser/browser_476161_sample.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Test for bug 476161</title>
+
+<script>
+
+document.addEventListener("input", function(aEvent) {
+  var inputEl = aEvent.originalTarget;
+  var changedEl = document.getElementById("changed");
+  
+  changedEl.textContent += " " + inputEl.id;
+}, false);
+
+</script>
+
+<h3>Text fields with changed text</h3>
+<input type="text" id="modify1">
+<input type="text" id="modify2" value="preset value">
+
+<h3>Text fields with unchanged text</h3>
+<input type="text" id="unchanged1">
+<input type="text" id="unchanged2" value="preset value">
+
+<h3>Changed field IDs</h3>
+<div id="changed"></div>
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser/browser_687710.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that sessionrestore handles cycles in the shentry graph properly.
+//
+// These cycles shouldn't be there in the first place, but they cause hangs
+// when they mysteriously appear (bug 687710).  Docshell code assumes this
+// graph is a tree and tires to walk to the root.  But if there's a cycle,
+// there is no root, and we loop forever.
+
+let stateBackup = ss.getBrowserState();
+
+let state = {windows:[{tabs:[{entries:[
+  {
+    docIdentifier: 1,
+    url: "http://example.com",
+    children: [
+      {
+        docIdentifier: 2,
+        url: "http://example.com"
+      }
+    ]
+  },
+  {
+    docIdentifier: 2,
+    url: "http://example.com",
+    children: [
+      {
+        docIdentifier: 1,
+        url: "http://example.com"
+      }
+    ]
+  }
+]}]}]}
+
+function test() {
+  registerCleanupFunction(function () {
+    ss.setBrowserState(stateBackup);
+  });
+
+  /* This test fails by hanging. */
+  ss.setBrowserState(JSON.stringify(state));
+  ok(true, "Didn't hang!");
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser/browser_687710_2.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the fix for bug 687710 isn't too aggressive -- shentries which are
+// cousins should be able to share bfcache entries.
+
+let stateBackup = ss.getBrowserState();
+
+let state = {entries:[
+  {
+    docIdentifier: 1,
+    url: "http://example.com?1",
+    children: [{ docIdentifier: 10,
+                 url: "http://example.com?10" }]
+  },
+  {
+    docIdentifier: 1,
+    url: "http://example.com?1#a",
+    children: [{ docIdentifier: 10,
+                 url: "http://example.com?10#aa" }]
+  }
+]};
+
+function test()
+{
+  registerCleanupFunction(function () {
+    ss.setBrowserState(stateBackup);
+  });
+
+  let tab = gBrowser.addTab("about:blank");
+  ss.setTabState(tab, JSON.stringify(state));
+  let history = tab.linkedBrowser.webNavigation.sessionHistory;
+
+  is(history.count, 2, "history.count");
+  for (let i = 0; i < history.count; i++) {
+    for (let j = 0; j < history.count; j++) {
+      compareEntries(i, j, history);
+    }
+  }
+}
+
+function compareEntries(i, j, history)
+{
+  let e1 = history.getEntryAtIndex(i, false)
+                  .QueryInterface(Ci.nsISHEntry)
+                  .QueryInterface(Ci.nsISHContainer);
+
+  let e2 = history.getEntryAtIndex(j, false)
+                  .QueryInterface(Ci.nsISHEntry)
+                  .QueryInterface(Ci.nsISHContainer);
+
+  ok(e1.sharesDocumentWith(e2),
+     i + ' should share doc with ' + j);
+  is(e1.childCount, e2.childCount,
+     'Child count mismatch (' + i + ', ' + j + ')');
+
+  for (let c = 0; c < e1.childCount; c++) {
+    let c1 = e1.GetChildAt(c);
+    let c2 = e2.GetChildAt(c);
+
+    ok(c1.sharesDocumentWith(c2),
+       'Cousins should share documents. (' + i + ', ' + j + ', ' + c + ')');
+  }
+}
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser/browser_form_restore_events.js
+++ /dev/null
@@ -1,99 +0,0 @@
-/* ***** 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 sessionstore test code.
- *
- * The Initial Developer of the Original Code is
- * Simon Bünzli <zeniko@gmail.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 test() {
-  /** Originally a test for Bug 476161, but then expanded to include all input types in bug 640136 **/
-
-  waitForExplicitFinish();
-
-  let file = Components.classes["@mozilla.org/file/directory_service;1"]
-             .getService(Components.interfaces.nsIProperties)
-             .get("TmpD", Components.interfaces.nsIFile);
-
-  let testURL = "http://mochi.test:8888/browser/" +
-    "browser/components/sessionstore/test/browser/browser_form_restore_events_sample.html";
-  let tab = gBrowser.addTab(testURL);
-  tab.linkedBrowser.addEventListener("load", function(aEvent) {
-    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
-    let doc = tab.linkedBrowser.contentDocument;
-
-    // text fields
-    doc.getElementById("modify01").value += Math.random();
-    doc.getElementById("modify02").value += " " + Date.now();
-
-    // textareas
-    doc.getElementById("modify03").value += Math.random();
-    doc.getElementById("modify04").value += " " + Date.now();
-
-    // file
-    doc.getElementById("modify05").value = file.path;
-
-    // select
-    doc.getElementById("modify06").selectedIndex = 1;
-    var multipleChange = doc.getElementById("modify07");
-    Array.forEach(multipleChange.options, function(option) option.selected = true);
-
-    // checkbox
-    doc.getElementById("modify08").checked = true;
-    doc.getElementById("modify09").checked = false;
-
-    // radio
-    // select one then another in the same group - only last one should get event on restore
-    doc.getElementById("modify10").checked = true;
-    doc.getElementById("modify11").checked = true;
-
-
-    let tab2 = gBrowser.duplicateTab(tab);
-    tab2.linkedBrowser.addEventListener("load", function(aEvent) {
-      tab2.linkedBrowser.removeEventListener("load", arguments.callee, true);
-      let doc = tab2.linkedBrowser.contentDocument;
-      let inputFired = doc.getElementById("inputFired").textContent.trim().split();
-      let changeFired = doc.getElementById("changeFired").textContent.trim().split();
-
-      is(inputFired.sort().join(" "), "modify01 modify02 modify03 modify04 modify05",
-         "input events were only dispatched for modified input, textarea fields");
-
-      is(changeFired.sort().join(" "), "modify06 unchanged06 modify07 modify08 modify09 modify11",
-         "change events were only dispatched for modified select, checkbox, radio fields");
-
-      // clean up
-      gBrowser.removeTab(tab2);
-      gBrowser.removeTab(tab);
-
-      finish();
-    }, true);
-  }, true);
-}
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser/browser_form_restore_events_sample.html
+++ /dev/null
@@ -1,98 +0,0 @@
-<!DOCTYPE html>
-<title>Test for form restore events (originally bug 476161)</title>
-
-<script>
-
-document.addEventListener("input", function(aEvent) {
-  var inputEl = aEvent.originalTarget;
-  var changedEl = document.getElementById("inputFired");
-  changedEl.textContent += " " + inputEl.id;
-}, false);
-
-document.addEventListener("change", function(aEvent) {
-  var inputEl = aEvent.originalTarget;
-  var changedEl = document.getElementById("changeFired");
-  changedEl.textContent += " " + inputEl.id;
-}, false);
-
-</script>
-
-<!-- input events -->
-<h3>Text fields with changed text</h3>
-<input type="text" id="modify1">
-<input type="text" id="modify2" value="preset value">
-<input type="text" id="modify01">
-<input type="text" id="modify02" value="preset value">
-
-<h3>Text fields with unchanged text</h3>
-<input type="text" id="unchanged1">
-<input type="text" id="unchanged2" value="preset value">
-<input type="text" id="unchanged01">
-<input type="text" id="unchanged02" value="preset value">
-
-<h3>Textarea with changed text</h3>
-<textarea id="modify03"></textarea>
-<textarea id="modify04">preset value</textarea>
-
-<h3>Textarea with unchanged text</h3>
-<textarea id="unchanged03"></textarea>
-<textarea id="unchanged04">preset value</textarea>
-
-<h3>file field with changed value</h3>
-<input type="file" id="modify05">
-
-<h3>file field with unchanged value</h3>
-<input type="file" id="unchanged05">
-
-<!-- change events -->
-
-<h3>Select menu with changed selection</h3>
-<select id="modify06">
-  <option value="one">one</option>
-  <option value="two">two</option>
-  <option value="three">three</option>
-</select>
-
-<h3>Select menu with unchanged selection (change event still fires)</h3>
-<select id="unchanged06">
-  <option value="one">one</option>
-  <option value="two" selected>two</option>
-  <option value="three">three</option>
-</select>
-
-<h3>Multiple Select menu with changed selection</h3>
-<select id="modify07" multiple>
-  <option value="one">one</option>
-  <option value="two" selected>two</option>
-  <option value="three">three</option>
-</select>
-
-<h3>Select menu with unchanged selection</h3>
-<select id="unchanged07" multiple>
-  <option value="one">one</option>
-  <option value="two" selected>two</option>
-  <option value="three" selected>three</option>
-</select>
-
-<h3>checkbox with changed value</h3>
-<input type="checkbox" id="modify08">
-<input type="checkbox" id="modify09" checked>
-
-<h3>checkbox with unchanged value</h3>
-<input type="checkbox" id="unchanged08">
-<input type="checkbox" id="unchanged09" checked>
-
-<h3>radio with changed value</h3>
-<input type="radio" id="modify10"  name="group">Radio 1</input>
-<input type="radio" id="modify11"  name="group">Radio 2</input>
-<input type="radio" id="modify12" name="group" checked>Radio 3</input>
-
-<h3>radio with unchanged value</h3>
-<input type="radio" id="unchanged10"  name="group2">Radio 4</input>
-<input type="radio" id="unchanged11"  name="group2">Radio 5</input>
-<input type="radio" id="unchanged12" name="group2" checked>Radio 6</input>
-
-<h3>Changed field IDs</h3>
-<div id="changed"></div>
-<div id="inputFired"></div>
-<div id="changeFired"></div>
--- a/browser/components/tabview/tabitems.js
+++ b/browser/components/tabview/tabitems.js
@@ -147,17 +147,17 @@ function TabItem(tab, options) {
   });
 
   this.droppable(true);
 
   TabItems.register(this);
 
   // ___ reconnect to data from Storage
   if (!TabItems.reconnectingPaused())
-    this._reconnect();
+    this._reconnect(options);
 };
 
 TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
   // ----------
   // Function: toString
   // Prints [TabItem (tab)] for debug use
   toString: function TabItem_toString() {
     return "[TabItem (" + this.tab + ")]";
@@ -334,52 +334,64 @@ TabItem.prototype = Utils.extend(new Ite
       this._saveThumbnailDelayed = {url: url, timeout: timeout};
     }
   },
 
   // ----------
   // Function: _reconnect
   // Load the reciever's persistent data from storage. If there is none, 
   // treats it as a new tab. 
-  _reconnect: function TabItem__reconnect() {
+  //
+  // Parameters:
+  //   options - an object with additional parameters, see below
+  //
+  // Possible options:
+  //   groupItemId - if the tab doesn't have any data associated with it and
+  //                 groupItemId is available, add the tab to that group.
+  _reconnect: function TabItem__reconnect(options) {
     Utils.assertThrow(!this._reconnected, "shouldn't already be reconnected");
     Utils.assertThrow(this.tab, "should have a xul:tab");
 
     let tabData = Storage.getTabData(this.tab);
+    let groupItem;
 
     if (tabData && TabItems.storageSanity(tabData)) {
       this.loadThumbnail(tabData);
 
       if (this.parent)
         this.parent.remove(this, {immediately: true});
 
-      let groupItem;
-
-      if (tabData.groupID) {
+      if (tabData.groupID)
         groupItem = GroupItems.groupItem(tabData.groupID);
-      } else {
+      else
         groupItem = new GroupItem([], {immediately: true, bounds: tabData.bounds});
-      }
 
       if (groupItem) {
         groupItem.add(this, {immediately: true});
 
         // restore the active tab for each group between browser sessions
         if (tabData.active)
           groupItem.setActiveTab(this);
 
         // if it matches the selected tab or no active tab and the browser
         // tab is hidden, the active group item would be set.
         if (this.tab == gBrowser.selectedTab ||
             (!GroupItems.getActiveGroupItem() && !this.tab.hidden))
           UI.setActive(this.parent);
       }
     } else {
-      // create tab group by double click is handled in UI_init().
-      GroupItems.newTab(this, {immediately: true});
+      if (options && options.groupItemId)
+        groupItem = GroupItems.groupItem(options.groupItemId);
+
+      if (groupItem) {
+        groupItem.add(this, {immediately: true});
+      } else {
+        // create tab group by double click is handled in UI_init().
+        GroupItems.newTab(this, {immediately: true});
+      }
     }
 
     this._reconnected = true;
     this.save();
     this._sendToSubscribers("reconnected");
   },
 
   // ----------
@@ -829,22 +841,31 @@ let TabItems = {
       // XXX bug #635975 - don't unlink the tab if the dom window is closing.
       if (!tab.pinned && !UI.isDOMWindowClosing)
         self.unlink(tab);
     }
     for (let name in this._eventListeners) {
       AllTabs.register(name, this._eventListeners[name]);
     }
 
+    let activeGroupItem = GroupItems.getActiveGroupItem();
+    let activeGroupItemId = activeGroupItem ? activeGroupItem.id : null;
     // For each tab, create the link.
     AllTabs.tabs.forEach(function (tab) {
       if (tab.pinned)
         return;
 
-      self.link(tab, {immediately: true});
+      let options = {immediately: true};
+      // if tab is visible in the tabstrip and doesn't have any data stored in 
+      // the session store (see TabItem__reconnect), it implies that it is a 
+      // new tab which is created before Panorama is initialized. Therefore, 
+      // passing the active group id to the link() method for setting it up.
+      if (!tab.hidden && activeGroupItemId)
+         options.groupItemId = activeGroupItemId;
+      self.link(tab, options);
       self.update(tab);
     });
   },
 
   // ----------
   // Function: uninit
   uninit: function TabItems_uninit() {
     for (let name in this._eventListeners) {
--- a/browser/components/tabview/test/Makefile.in
+++ b/browser/components/tabview/test/Makefile.in
@@ -158,16 +158,17 @@ include $(topsrcdir)/config/rules.mk
                  browser_tabview_bug673196.js \
                  browser_tabview_bug673729.js \
                  browser_tabview_bug677310.js \
                  browser_tabview_bug679853.js \
                  browser_tabview_bug681599.js \
                  browser_tabview_bug685476.js \
                  browser_tabview_bug685692.js \
                  browser_tabview_bug686654.js \
+                 browser_tabview_bug697390.js \
                  browser_tabview_click_group.js \
                  browser_tabview_dragdrop.js \
                  browser_tabview_exit_button.js \
                  browser_tabview_expander.js \
                  browser_tabview_firstrun_pref.js \
                  browser_tabview_group.js \
                  browser_tabview_launch.js \
                  browser_tabview_multiwindow_search.js \
new file mode 100644
--- /dev/null
+++ b/browser/components/tabview/test/browser_tabview_bug697390.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let state = {
+  windows: [{
+    tabs: [{
+      entries: [{ url: "about:blank" }],
+      hidden: true,
+      extData: {"tabview-tab": '{"url":"about:blank","groupID":1,"bounds":{"left":120,"top":20,"width":20,"height":20}}'}
+    },{
+      entries: [{ url: "about:blank" }],
+      hidden: false,
+      extData: {"tabview-tab": '{"url":"about:blank","groupID":2,"bounds":{"left":20,"top":20,"width":20,"height":20}}'},
+    }],
+    selected: 2,
+    extData: {
+      "tabview-groups": '{"nextID":3,"activeGroupId":2, "totalNumber":2}',
+      "tabview-group":
+        '{"1":{"bounds":{"left":15,"top":5,"width":280,"height":232},"id":1},' +
+        '"2":{"bounds":{"left":309,"top":5,"width":267,"height":226},"id":2}}'
+    }
+  }]
+};
+
+function test() {
+  waitForExplicitFinish();
+
+  newWindowWithState(state, function(win) {
+    registerCleanupFunction(function() win.close());
+
+    win.gBrowser.addTab();
+
+    ok(win.gBrowser.tabs[0].hidden, "The first tab is hidden");
+    win.gBrowser.selectedTab = win.gBrowser.tabs[0];
+
+    function onTabViewFrameInitialized() {
+      win.removeEventListener(
+        "tabviewframeinitialized", onTabViewFrameInitialized, false);
+
+      let cw = win.TabView.getContentWindow();
+      is(cw.GroupItems.groupItem(1).getChild(0).tab, win.gBrowser.selectedTab, "The tab in group one matches the selected tab");
+      is(cw.GroupItems.groupItem(1).getChildren().length, 1, "The group one has only one tab item");
+      is(cw.GroupItems.groupItem(2).getChildren().length, 2, "The group one has only two tab item")
+
+      finish();
+    }
+    win.addEventListener(
+      "tabviewframeinitialized", onTabViewFrameInitialized, false);
+  });
+}
+
--- a/browser/devtools/highlighter/TreePanel.jsm
+++ b/browser/devtools/highlighter/TreePanel.jsm
@@ -226,22 +226,17 @@ TreePanel.prototype = {
       this.IUI.browser.ownerDocument.getElementById("browser-bottombox");
     treeBox = this.document.createElement("vbox");
     treeBox.id = "inspector-tree-box";
     treeBox.state = "open"; // for the registerTools API.
     treeBox.minHeight = 10;
     treeBox.flex = 1;
     toolbarParent.insertBefore(treeBox, toolbar);
 
-    let resizerTop =
-      this.IUI.browser.ownerDocument.getElementById("inspector-top-resizer");
-    let resizerEnd =
-      this.IUI.browser.ownerDocument.getElementById("inspector-end-resizer");
-    resizerTop.removeAttribute("disabled");
-    resizerEnd.removeAttribute("disabled");
+    this.IUI.toolbar.setAttribute("treepanel-open", "true");
 
     treeBox.appendChild(this.treeIFrame);
 
     let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
     {
       this.treeIFrame.removeEventListener("load",
         boundLoadedInitializeTreePanel, true);
       this.initializeIFrame();
@@ -259,22 +254,17 @@ TreePanel.prototype = {
   },
 
   /**
    * Close the TreePanel.
    */
   close: function TP_close()
   {
     if (this.openInDock) {
-      let resizerTop = 
-        this.IUI.browser.ownerDocument.getElementById("inspector-top-resizer");
-      let resizerEnd = 
-        this.IUI.browser.ownerDocument.getElementById("inspector-end-resizer");
-      resizerTop.setAttribute("disabled", "true");
-      resizerEnd.setAttribute("disabled", "true");
+      this.IUI.toolbar.removeAttribute("treepanel-open");
 
       let treeBox = this.container;
       let treeBoxParent = treeBox.parentNode;
       treeBoxParent.removeChild(treeBox);
     } else {
       this.container.hidePopup();
     }
 
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -119,30 +119,30 @@ Highlighter.prototype = {
     this._highlighting = false;
 
     this.highlighterContainer = this.chromeDoc.createElement("stack");
     this.highlighterContainer.id = "highlighter-container";
 
     this.veilContainer = this.chromeDoc.createElement("vbox");
     this.veilContainer.id = "highlighter-veil-container";
 
+    // The controlsBox will host the different interactive
+    // elements of the highlighter (buttons, toolbars, ...).
     let controlsBox = this.chromeDoc.createElement("box");
     controlsBox.id = "highlighter-controls";
     this.highlighterContainer.appendChild(this.veilContainer);
     this.highlighterContainer.appendChild(controlsBox);
 
     stack.appendChild(this.highlighterContainer);
 
     // The veil will make the whole page darker except
     // for the region of the selected box.
     this.buildVeil(this.veilContainer);
 
-    // The controlsBox will host the different interactive
-    // elements of the highlighter (buttons, toolbars, ...).
-    this.buildControls(controlsBox);
+    this.buildInfobar(controlsBox);
 
     this.browser.addEventListener("resize", this, true);
     this.browser.addEventListener("scroll", this, true);
 
     this.handleResize();
   },
 
   /**
@@ -196,30 +196,16 @@ Highlighter.prototype = {
     this.veilMiddleBox.appendChild(veilRightBox);
 
     aParent.appendChild(this.veilTopBox);
     aParent.appendChild(this.veilMiddleBox);
     aParent.appendChild(veilBottomBox);
   },
 
   /**
-   * Build the controls:
-   *
-   * <box id="highlighter-close-button"/>
-   *
-   * @param nsIDOMElement aParent
-   *        The container of the controls elements.
-   */
-  buildControls: function Highlighter_buildControls(aParent)
-  {
-    this.buildCloseButton(aParent);
-    this.buildInfobar(aParent);
-  },
-
-  /**
    * Build the node Infobar.
    *
    * <box id="highlighter-nodeinfobar-container">
    *   <box id="Highlighter-nodeinfobar-arrow-top"/>
    *   <vbox id="highlighter-nodeinfobar">
    *     <label id="highlighter-nodeinfobar-tagname"/>
    *     <label id="highlighter-nodeinfobar-id"/>
    *     <vbox id="highlighter-nodeinfobar-classes"/>
@@ -275,48 +261,23 @@ Highlighter.prototype = {
       idLabel: idLabel,
       classesBox: classesBox,
       container: container,
       barHeight: barHeight,
     };
   },
 
   /**
-   * Build the close button.
-   *
-   * @param nsIDOMElement aParent
-   *        The container of the close-button.
-   */
-  buildCloseButton: function Highlighter_buildCloseButton(aParent)
-  {
-    let closeButton = this.chromeDoc.createElement("box");
-    closeButton.id = "highlighter-close-button";
-    closeButton.appendChild(this.chromeDoc.createElement("image"));
-
-    let boundCloseEventHandler = this.IUI.closeInspectorUI.bind(this.IUI, false);
-
-    closeButton.addEventListener("click", boundCloseEventHandler, false);
-
-    aParent.appendChild(closeButton);
-
-    this.boundCloseEventHandler = boundCloseEventHandler;
-    this.closeButton = closeButton;
-  },
-
-  /**
    * Destroy the nodes.
    */
   destroy: function Highlighter_destroy()
   {
     this.browser.removeEventListener("scroll", this, true);
     this.browser.removeEventListener("resize", this, true);
-    this.closeButton.removeEventListener("click", this.boundCloseEventHandler, false);
     this.boundCloseEventHandler = null;
-    this.closeButton.parentNode.removeChild(this.closeButton);
-    this.closeButton = null;
     this._contentRect = null;
     this._highlightRect = null;
     this._highlighting = false;
     this.veilTopBox = null;
     this.veilLeftBox = null;
     this.veilMiddleBox = null;
     this.veilTransparentBox = null;
     this.veilContainer = null;
--- a/browser/devtools/highlighter/test/browser_inspector_bug_690361.js
+++ b/browser/devtools/highlighter/test/browser_inspector_bug_690361.js
@@ -77,17 +77,17 @@ function runInspectorTests()
   ok(!InspectorUI.toolbar.hidden, "toolbar is visible");
   ok(InspectorUI.inspecting, "Inspector is inspecting");
   ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
   ok(InspectorUI.highlighter, "Highlighter is up");
 
   salutation = doc.getElementById("salutation");
   InspectorUI.inspectNode(salutation);
 
-  let button = document.getElementById("highlighter-close-button");
+  let button = document.getElementById("highlighter-closebutton");
   button.click();
 }
 
 function closeInspectorTests()
 {
   Services.obs.removeObserver(closeInspectorTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
   Services.obs.addObserver(inspectorOpenedTrap,
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -1,10 +1,11 @@
 browser.jar:
 *   content/browser/inspector.html                (highlighter/inspector.html)
     content/browser/NetworkPanel.xhtml            (webconsole/NetworkPanel.xhtml)
 *   content/browser/scratchpad.xul                (scratchpad/scratchpad.xul)
 *   content/browser/scratchpad.js                 (scratchpad/scratchpad.js)
     content/browser/csshtmltree.xhtml             (styleinspector/csshtmltree.xhtml)
+    content/browser/devtools/cssruleview.xhtml    (styleinspector/cssruleview.xhtml)
+    content/browser/devtools/styleinspector.css   (styleinspector/styleinspector.css)
     content/browser/orion.js                      (sourceeditor/orion/orion.js)
     content/browser/orion.css                     (sourceeditor/orion/orion.css)
     content/browser/orion-mozilla.css             (sourceeditor/orion/mozilla.css)
-
--- 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
@@ -1,11 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+.viewContainer {
+  font-size: inherit; /* inherit browser's default monospace font size */
+}
 
 .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/browser/devtools/styleinspector/CssLogic.jsm
+++ b/browser/devtools/styleinspector/CssLogic.jsm
@@ -816,16 +816,46 @@ CssLogic.sheetMediaAllowed = function Cs
   } else {
     result = true;
   }
 
   return result;
 };
 
 /**
+ * Return a shortened version of a style sheet's source.
+ *
+ * @param {CSSStyleSheet} aSheet the DOM object for the style sheet.
+ */
+CssLogic.shortSource = function CssLogic_shortSource(aSheet)
+{
+    // Use a string like "inline" if there is no source href
+    if (!aSheet || !aSheet.href) {
+      return CssLogic.l10n("rule.sourceInline");
+    }
+
+    // We try, in turn, the filename, filePath, query string, whole thing
+    let url = Services.io.newURI(aSheet.href, null, null);
+    url = url.QueryInterface(Ci.nsIURL);
+    if (url.fileName) {
+      return url.fileName;
+    }
+
+    if (url.filePath) {
+      return url.filePath;
+    }
+
+    if (url.query) {
+      return url.query;
+    }
+
+    return this.domSheet.href;
+}
+
+/**
  * A safe way to access cached bits of information about a stylesheet.
  *
  * @constructor
  * @param {CssLogic} aCssLogic pointer to the CssLogic instance working with
  * this CssSheet object.
  * @param {CSSStyleSheet} aDomSheet reference to a DOM CSSStyleSheet object.
  * @param {boolean} aSystemSheet tells if the stylesheet is system-provided.
  * @param {number} aIndex tells the index/position of the stylesheet within the
@@ -880,41 +910,17 @@ CssSheet.prototype = {
    * @return {string} the shorthand source of the stylesheet.
    */
   get shortSource()
   {
     if (this._shortSource) {
       return this._shortSource;
     }
 
-    // Use a string like "inline" if there is no source href
-    if (!this.domSheet.href) {
-      this._shortSource = CssLogic.l10n("rule.sourceInline");
-      return this._shortSource;
-    }
-
-    // We try, in turn, the filename, filePath, query string, whole thing
-    let url = Services.io.newURI(this.domSheet.href, null, null);
-    url = url.QueryInterface(Ci.nsIURL);
-    if (url.fileName) {
-      this._shortSource = url.fileName;
-      return this._shortSource;
-    }
-
-    if (url.filePath) {
-      this._shortSource = url.filePath;
-      return this._shortSource;
-    }
-
-    if (url.query) {
-      this._shortSource = url.query;
-      return this._shortSource;
-    }
-
-    this._shortSource = this.domSheet.href;
+    this._shortSource = CssLogic.shortSource(this.domSheet);
     return this._shortSource;
   },
 
   /**
    * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter.
    *
    * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or
    * false otherwise.
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -0,0 +1,1248 @@
+/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* ***** 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 Mozilla Inspector Module.
+ *
+ * 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):
+ *   Dave Camp (dcamp@mozilla.com) (Original Author)
+ *
+ * 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"
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+const FOCUS_FORWARD = Ci.nsIFocusManager.MOVEFOCUS_FORWARD;
+const FOCUS_BACKWARD = Ci.nsIFocusManager.MOVEFOCUS_BACKWARD;
+
+/**
+ * These regular expressions are adapted from firebug's css.js, and are
+ * used to parse CSSStyleDeclaration's cssText attribute.
+ */
+
+// Used to split on css line separators
+const CSS_LINE_RE = /(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g;
+
+// Used to parse a single property line.
+const CSS_PROP_RE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(?:! (important))?;?$/;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/devtools/CssLogic.jsm");
+
+var EXPORTED_SYMBOLS = ["CssRuleView",
+                        "_ElementStyle",
+                        "_editableField"];
+
+/**
+ * Our model looks like this:
+ *
+ * ElementStyle:
+ *   Responsible for keeping track of which properties are overridden.
+ *   Maintains a list of Rule objects that apply to the element.
+ * Rule:
+ *   Manages a single style declaration or rule.
+ *   Responsible for applying changes to the properties in a rule.
+ *   Maintains a list of TextProperty objects.
+ * TextProperty:
+ *   Manages a single property from the cssText attribute of the
+ *     relevant declaration.
+ *   Maintains a list of computed properties that come from this
+ *     property declaration.
+ *   Changes to the TextProperty are sent to its related Rule for
+ *     application.
+ */
+
+/**
+ * ElementStyle maintains a list of Rule objects for a given element.
+ *
+ * @constructor
+ */
+function ElementStyle(aElement)
+{
+  this.element = aElement;
+  let doc = aElement.ownerDocument;
+
+  // To figure out how shorthand properties are interpreted by the
+  // engine, we will set properties on a dummy element and observe
+  // how their .style attribute reflects them as computed values.
+  this.dummyElement = doc.createElementNS(this.element.namespaceURI,
+                                          this.element.tagName);
+  this._populate();
+}
+// We're exporting _ElementStyle for unit tests.
+var _ElementStyle = ElementStyle;
+
+ElementStyle.prototype = {
+
+  // The element we're looking at.
+  element: null,
+
+  // Empty, unconnected element of the same type as this node, used
+  // to figure out how shorthand properties will be parsed.
+  dummyElement: null,
+
+  domUtils: Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils),
+
+  /**
+   * Refresh the list of rules to be displayed for the active element.
+   * Upon completion, this.rules[] will hold a list of Rule objects.
+   */
+  _populate: function ElementStyle_populate()
+  {
+    this.rules = [];
+
+    // Include the element's style first.
+    this.rules.push(new Rule(this, {
+      style: this.element.style,
+      selectorText: CssLogic.l10n("rule.sourceElement")
+    }));
+
+    // Get the styles that apply to the element.
+    try {
+      var domRules = this.domUtils.getCSSStyleRules(this.element);
+    } catch (ex) {
+      Services.console.logStringMessage("ElementStyle_populate error: " + ex);
+      return;
+    }
+
+    // getCSStyleRules returns ordered from least-specific to
+    // most-specific.
+    for (let i = domRules.Count() - 1; i >= 0; i--) {
+      let domRule = domRules.GetElementAt(i);
+
+      // XXX: Optionally provide access to system sheets.
+      let systemSheet = CssLogic.isSystemStyleSheet(domRule.parentStyleSheet);
+      if (systemSheet) {
+        continue;
+      }
+
+      // XXX: non-style rules.
+      if (domRule.type === Ci.nsIDOMCSSRule.STYLE_RULE) {
+        this.rules.push(new Rule(this, { domRule: domRule }));
+      }
+    }
+
+    // Mark overridden computed styles.
+    this.markOverridden();
+  },
+
+  /**
+   * Mark the properties listed in this.rules with an overridden flag
+   * if an earlier property overrides it.
+   */
+  markOverridden: function ElementStyle_markOverridden()
+  {
+    // Gather all the text properties applied by these rules, ordered
+    // from more- to less-specific.
+    let textProps = [];
+    for each (let rule in this.rules) {
+      textProps = textProps.concat(rule.textProps.slice(0).reverse());
+    }
+
+    // Gather all the computed properties applied by those text
+    // properties.
+    let computedProps = [];
+    for each (let textProp in textProps) {
+      computedProps = computedProps.concat(textProp.computed);
+    };
+
+    // Walk over the computed properties.  As we see a property name
+    // for the first time, mark that property's name as taken by this
+    // property.
+    //
+    // If we come across a property whose name is already taken, check
+    // its priority against the property that was found first:
+    //
+    //   If the new property is a higher priority, mark the old
+    //   property overridden and mark the property name as taken by
+    //   the new property.
+    //
+    //   If the new property is a lower or equal priority, mark it as
+    //   overridden.
+    //
+    // _overriddenDirty will be set on each prop, indicating whether its
+    // dirty status changed during this pass.
+    let taken = {};
+    for each (let computedProp in computedProps) {
+      let earlier = taken[computedProp.name];
+      let overridden;
+      if (earlier
+          && computedProp.priority === "important"
+          && earlier.priority !== "important") {
+        // New property is higher priority.  Mark the earlier property
+        // overridden (which will reverse its dirty state).
+        earlier._overriddenDirty = !earlier._overriddenDirty;
+        earlier.overridden = true;
+        overridden = false;
+      } else {
+        overridden = !!earlier;
+      }
+
+      computedProp._overriddenDirty = (!!computedProp.overridden != overridden);
+      computedProp.overridden = overridden;
+      if (!computedProp.overridden) {
+        taken[computedProp.name] = computedProp;
+      }
+    }
+
+    // For each TextProperty, mark it overridden if all of its
+    // computed properties are marked overridden.  Update the text
+    // property's associated editor, if any.  This will clear the
+    // _overriddenDirty state on all computed properties.
+    for each (let textProp in textProps) {
+      // _updatePropertyOverridden will return true if the
+      // overridden state has changed for the text property.
+      if (this._updatePropertyOverridden(textProp)) {
+        textProp.updateEditor();
+      }
+    }
+  },
+
+  /**
+   * Mark a given TextProperty as overridden or not depending on the
+   * state of its computed properties.  Clears the _overriddenDirty state
+   * on all computed properties.
+   *
+   * @param {TextProperty} aProp
+   *        The text property to update.
+   *
+   * @return True if the TextProperty's overridden state (or any of its
+   *         computed properties overridden state) changed.
+   */
+  _updatePropertyOverridden: function ElementStyle_updatePropertyOverridden(aProp)
+  {
+    let overridden = true;
+    let dirty = false;
+    for each (let computedProp in aProp.computed) {
+      if (!computedProp.overridden) {
+        overridden = false;
+      }
+      dirty = computedProp._overriddenDirty || dirty;
+      delete computedProp._overriddenDirty;
+    }
+
+    dirty = (!!aProp.overridden != overridden) || dirty;
+    aProp.overridden = overridden;
+    return dirty;
+  }
+}
+
+/**
+ * A single style rule or declaration.
+ *
+ * @param {ElementStyle} aElementStyle
+ *        The ElementStyle to which this rule belongs.
+ * @param {object} aOptions
+ *        The information used to construct this rule.  Properties include:
+ *          domRule: the nsIDOMCSSStyleRule to view, if any.
+ *          style: the nsIDOMCSSStyleDeclaration to view.  If omitted,
+ *            the domRule's style will be used.
+ *          selectorText: selector text to display.  If omitted, the domRule's
+ *            selectorText will be used.
+ * @constructor
+ */
+function Rule(aElementStyle, aOptions)
+{
+  this.elementStyle = aElementStyle;
+  this.domRule = aOptions.domRule || null;
+  this.style = aOptions.style || this.domRule.style;
+  this.selectorText = aOptions.selectorText || this.domRule.selectorText;
+
+  this._getTextProperties();
+}
+
+Rule.prototype = {
+  get title()
+  {
+    if (this._title) {
+      return this._title;
+    }
+    let sheet = this.domRule ? this.domRule.parentStyleSheet : null;
+    this._title = CssLogic.shortSource(sheet);
+    if (this.domRule) {
+      let line = this.elementStyle.domUtils.getRuleLine(this.domRule);
+      this._title += ":" + line;
+    }
+    return this._title;
+  },
+
+  /**
+   * Create a new TextProperty to include in the rule.
+   *
+   * @param {string} aName
+   *        The text property name (such as "background" or "border-top").
+   * @param {string} aValue
+   *        The property's value (not including priority).
+   * @param {string} aPriority
+   *        The property's priority (either "important" or an empty string).
+   */
+  createProperty: function Rule_createProperty(aName, aValue, aPriority)
+  {
+    let prop = new TextProperty(this, aName, aValue, aPriority);
+    this.textProps.push(prop);
+    this.applyProperties();
+    return prop;
+  },
+
+  /**
+   * Reapply all the properties in this rule, and update their
+   * computed styles.  Will re-mark overridden properties.
+   */
+  applyProperties: function Rule_applyProperties()
+  {
+    for each (let prop in this.textProps) {
+      if (!prop.enabled) {
+        continue;
+      }
+
+      this.style.setProperty(prop.name, prop.value, prop.priority);
+      // Refresh the property's value from the style, to reflect
+      // any changes made during parsing.
+      prop.value = this.style.getPropertyValue(prop.name);
+      prop.priority = this.style.getPropertyPriority(prop.name);
+      prop.updateComputed();
+    }
+
+    this.elementStyle.markOverridden();
+  },
+
+  /**
+   * Renames a property.
+   *
+   * @param {TextProperty} aProperty
+   *        The property to rename.
+   * @param {string} aName
+   *        The new property name (such as "background" or "border-top").
+   */
+  setPropertyName: function Rule_setPropertyName(aProperty, aName)
+  {
+    if (aName === aProperty.name) {
+      return;
+    }
+    this.style.removeProperty(aProperty.name);
+    aProperty.name = aName;
+    this.applyProperties();
+  },
+
+  /**
+   * Sets the value and priority of a property.
+   *
+   * @param {TextProperty} aProperty
+   *        The property to manipulate.
+   * @param {string} aValue
+   *        The property's value (not including priority).
+   * @param {string} aPriority
+   *        The property's priority (either "important" or an empty string).
+   */
+  setPropertyValue: function Rule_setPropertyValue(aProperty, aValue, aPriority)
+  {
+    if (aValue === aProperty.value && aPriority === aProperty.priority) {
+      return;
+    }
+    aProperty.value = aValue;
+    aProperty.priority = aPriority;
+    this.applyProperties();
+  },
+
+  /**
+   * Disables or enables given TextProperty.
+   */
+  setPropertyEnabled: function Rule_enableProperty(aProperty, aValue)
+  {
+    aProperty.enabled = !!aValue;
+    if (!aProperty.enabled) {
+      this.style.removeProperty(aProperty.name);
+    }
+    this.applyProperties();
+  },
+
+  /**
+   * Remove a given TextProperty from the rule and update the rule
+   * accordingly.
+   */
+  removeProperty: function Rule_removeProperty(aProperty)
+  {
+    this.textProps = this.textProps.filter(function(prop) prop != aProperty);
+    this.style.removeProperty(aProperty);
+    // Need to re-apply properties in case removing this TextProperty
+    // exposes another one.
+    this.applyProperties();
+  },
+
+  /**
+   * Get the list of TextProperties from the style.  Needs
+   * to parse the style's cssText.
+   */
+  _getTextProperties: function Rule_getTextProperties()
+  {
+    this.textProps = [];
+    let lines = this.style.cssText.match(CSS_LINE_RE);
+    for each (let line in lines) {
+      let matches = CSS_PROP_RE.exec(line);
+      if(!matches || !matches[2])
+        continue;
+
+      let prop = new TextProperty(this, matches[1], matches[2], matches[3] || "");
+      this.textProps.push(prop);
+    }
+  },
+}
+
+/**
+ * A single property in a rule's cssText.
+ *
+ * @param {Rule} aRule
+ *        The rule this TextProperty came from.
+ * @param {string} aName
+ *        The text property name (such as "background" or "border-top").
+ * @param {string} aValue
+ *        The property's value (not including priority).
+ * @param {string} aPriority
+ *        The property's priority (either "important" or an empty string).
+ *
+ */
+function TextProperty(aRule, aName, aValue, aPriority)
+{
+  this.rule = aRule;
+  this.name = aName;
+  this.value = aValue;
+  this.priority = aPriority;
+  this.enabled = true;
+  this.updateComputed();
+}
+
+TextProperty.prototype = {
+  /**
+   * Update the editor associated with this text property,
+   * if any.
+   */
+  updateEditor: function TextProperty_updateEditor()
+  {
+    if (this.editor) {
+      this.editor.update();
+    }
+  },
+
+  /**
+   * Update the list of computed properties for this text property.
+   */
+  updateComputed: function TextProperty_updateComputed()
+  {
+    if (!this.name) {
+      return;
+    }
+
+    // This is a bit funky.  To get the list of computed properties
+    // for this text property, we'll set the property on a dummy element
+    // and see what the computed style looks like.
+    let dummyElement = this.rule.elementStyle.dummyElement;
+    let dummyStyle = dummyElement.style;
+    dummyStyle.cssText = "";
+    dummyStyle.setProperty(this.name, this.value, this.priority);
+
+    this.computed = [];
+    for (let i = 0, n = dummyStyle.length; i < n; i++) {
+      let prop = dummyStyle.item(i);
+      this.computed.push({
+        name: prop,
+        value: dummyStyle.getPropertyValue(prop),
+        priority: dummyStyle.getPropertyPriority(prop),
+      });
+    }
+  },
+
+  setValue: function TextProperty_setValue(aValue, aPriority)
+  {
+    this.rule.setPropertyValue(this, aValue, aPriority);
+    this.updateEditor();
+  },
+
+  setName: function TextProperty_setName(aName)
+  {
+    this.rule.setPropertyName(this, aName);
+    this.updateEditor();
+  },
+
+  setEnabled: function TextProperty_setEnabled(aValue)
+  {
+    this.rule.setPropertyEnabled(this, aValue);
+    this.updateEditor();
+  },
+
+  remove: function TextProperty_remove()
+  {
+    this.rule.removeProperty(this);
+  }
+}
+
+
+/**
+ * View hierarchy mostly follows the model hierarchy.
+ *
+ * CssRuleView:
+ *   Owns an ElementStyle and creates a list of RuleEditors for its
+ *    Rules.
+ * RuleEditor:
+ *   Owns a Rule object and creates a list of TextPropertyEditors
+ *     for its TextProperties.
+ *   Manages creation of new text properties.
+ * TextPropertyEditor:
+ *   Owns a TextProperty object.
+ *   Manages changes to the TextProperty.
+ *   Can be expanded to display computed properties.
+ *   Can mark a property disabled or enabled.
+ */
+
+/**
+ * CssRuleView is a view of the style rules and declarations that
+ * apply to a given element.  After construction, the 'element'
+ * property will be available with the user interface.
+ *
+ * @param Document aDocument
+ *        The document that will contain the rule view.
+ * @constructor
+ */
+function CssRuleView(aDoc)
+{
+  this.doc = aDoc;
+
+  this.element = this.doc.createElementNS(HTML_NS, "div");
+  this.element.setAttribute("tabindex", "0");
+  this.element.classList.add("ruleview");
+}
+
+CssRuleView.prototype = {
+  // The element that we're inspecting.
+  _viewedElement: null,
+
+  /**
+   * Update the highlighted element.
+   *
+   * @param {nsIDOMElement} aElement
+   *        The node whose style rules we'll inspect.
+   */
+  highlight: function CssRuleView_highlight(aElement)
+  {
+    if (this._viewedElement === aElement) {
+      return;
+    }
+
+    this.clear();
+
+    this._viewedElement = aElement;
+    if (!this._viewedElement) {
+      return;
+    }
+
+    this._elementStyle = new ElementStyle(aElement);
+    this._createEditors();
+  },
+
+  /**
+   * Clear the rule view.
+   */
+  clear: function CssRuleView_clear()
+  {
+    while (this.element.hasChildNodes()) {
+      this.element.removeChild(this.element.lastChild);
+    }
+    this._viewedElement = null;
+    this._elementStyle = null;
+  },
+
+  /**
+   * Creates editor UI for each of the rules in _elementStyle.
+   */
+  _createEditors: function CssRuleView_createEditors()
+  {
+    for each (let rule in this._elementStyle.rules) {
+      // Don't hold a reference to this editor beyond the one held
+      // by the node.
+      let editor = new RuleEditor(this.doc, rule);
+      this.element.appendChild(editor.element);
+    }
+  },
+};
+
+/**
+ * Create a RuleEditor.
+ *
+ * @param object aDoc
+ *        The document holding this rule editor.
+ * @param Rule aRule
+ *        The Rule object we're editing.
+ * @constructor
+ */
+function RuleEditor(aDoc, aRule)
+{
+  this.doc = aDoc;
+  this.rule = aRule;
+
+  this._onNewProperty = this._onNewProperty.bind(this);
+
+  this._create();
+}
+
+RuleEditor.prototype = {
+  _create: function RuleEditor_create()
+  {
+    this.element = this.doc.createElementNS(HTML_NS, "div");
+    this.element._ruleEditor = this;
+
+    // Add the source link.
+    let source = createChild(this.element, "div", {
+      class: "ruleview-rule-source",
+      textContent: this.rule.title
+    });
+
+    let code = createChild(this.element, "div", {
+      class: "ruleview-code"
+    });
+
+    let header = createChild(code, "div", {});
+
+    let selectors = createChild(header, "span", {
+      class: "ruleview-selector",
+      textContent: this.rule.selectorText
+    });
+    appendText(header, " {");
+
+    this.propertyList = createChild(code, "ul", {
+      class: "ruleview-propertylist"
+    });
+
+    for each (let prop in this.rule.textProps) {
+      let propEditor = new TextPropertyEditor(this, prop);
+      this.propertyList.appendChild(propEditor.element);
+    }
+
+    this.closeBrace = createChild(code, "div", {
+      class: "ruleview-ruleclose",
+      tabindex: "0",
+      textContent: "}"
+    });
+
+    // We made the close brace focusable, tabbing to it
+    // or clicking on it should start the new property editor.
+    this.closeBrace.addEventListener("focus", function() {
+      this.newProperty();
+    }.bind(this), true);
+  },
+
+  /**
+   * Create a text input for a property name.  If a non-empty property
+   * name is given, we'll create a real TextProperty and add it to the
+   * rule.
+   */
+  newProperty: function RuleEditor_newProperty()
+  {
+    // While we're editing a new property, it doesn't make sense to
+    // start a second new property editor, so disable focusing the
+    // close brace for now.
+    this.closeBrace.removeAttribute("tabindex");
+
+    this.newPropItem = createChild(this.propertyList, "li", {
+      class: "ruleview-property ruleview-newproperty",
+    });
+
+    this.newPropSpan = createChild(this.newPropItem, "span", {
+      class: "ruleview-propertyname"
+    });
+
+    new InplaceEditor({
+      element: this.newPropSpan,
+      done: this._onNewProperty,
+      advanceChars: ":"
+    });
+  },
+
+  _onNewProperty: function RuleEditor_onNewProperty(aValue, aCommit)
+  {
+    // We're done, make the close brace focusable again.
+    this.closeBrace.setAttribute("tabindex", "0");
+
+    this.propertyList.removeChild(this.newPropItem);
+    delete this.newPropItem;
+    delete this.newPropSpan;
+
+    if (!aValue || !aCommit) {
+      return;
+    }
+
+    // Create an empty-valued property and start editing it.
+    let prop = this.rule.createProperty(aValue, "", "");
+    let editor = new TextPropertyEditor(this, prop);
+    this.propertyList.appendChild(editor.element);
+    editor.valueSpan.focus();
+  },
+};
+
+/**
+ * Create a TextPropertyEditor.
+ *
+ * @param {RuleEditor} aRuleEditor
+ *        The rule editor that owns this TextPropertyEditor.
+ * @param {TextProperty} aProperty
+ *        The text property to edit.
+ * @constructor
+ */
+function TextPropertyEditor(aRuleEditor, aProperty)
+{
+  this.doc = aRuleEditor.doc;
+  this.prop = aProperty;
+  this.prop.editor = this;
+
+  this._onEnableClicked = this._onEnableClicked.bind(this);
+  this._onExpandClicked = this._onExpandClicked.bind(this);
+  this._onStartEditing = this._onStartEditing.bind(this);
+  this._onNameDone = this._onNameDone.bind(this);
+  this._onValueDone = this._onValueDone.bind(this);
+
+  this._create();
+  this.update();
+}
+
+TextPropertyEditor.prototype = {
+  get editing() {
+    return !!(this.nameSpan.inplaceEditor || this.valueSpan.inplaceEditor);
+  },
+
+  /**
+   * Create the property editor's DOM.
+   */
+  _create: function TextPropertyEditor_create()
+  {
+    this.element = this.doc.createElementNS(HTML_NS, "li");
+    this.element.classList.add("ruleview-property");
+
+    // The enable checkbox will disable or enable the rule.
+    this.enable = createChild(this.element, "input", {
+      class: "ruleview-enableproperty",
+      type: "checkbox",
+      tabindex: "-1"
+    });
+    this.enable.addEventListener("click", this._onEnableClicked, true);
+
+    // Click to expand the computed properties of the text property.
+    this.expander = createChild(this.element, "span", {
+      class: "ruleview-expander"
+    });
+    this.expander.addEventListener("click", this._onExpandClicked, true);
+
+    // Property name, editable when focused.  Property name
+    // is committed when the editor is unfocused.
+    this.nameSpan = createChild(this.element, "span", {
+      class: "ruleview-propertyname",
+      tabindex: "0",
+    });
+    editableField({
+      start: this._onStartEditing,
+      element: this.nameSpan,
+      done: this._onNameDone,
+      advanceChars: ':'
+    });
+
+    appendText(this.element, ": ");
+
+    // Property value, editable when focused.  Changes to the
+    // property value are applied as they are typed, and reverted
+    // if the user presses escape.
+    this.valueSpan = createChild(this.element, "span", {
+      class: "ruleview-propertyvalue",
+      tabindex: "0",
+    });
+
+    editableField({
+      start: this._onStartEditing,
+      element: this.valueSpan,
+      done: this._onValueDone,
+      advanceChars: ';'
+    });
+
+    // Save the initial value as the last committed value,
+    // for restoring after pressing escape.
+    this.committed = { name: this.prop.name,
+                       value: this.prop.value,
+                       priority: this.prop.priority };
+
+    appendText(this.element, ";");
+
+    // Holds the viewers for the computed properties.
+    // will be populated in |_updateComputed|.
+    this.computed = createChild(this.element, "ul", {
+      class: "ruleview-computedlist",
+    });
+  },
+
+  /**
+   * Populate the span based on changes to the TextProperty.
+   */
+  update: function TextPropertyEditor_update()
+  {
+    if (this.prop.enabled) {
+      this.enable.style.removeProperty("visibility");
+      this.enable.setAttribute("checked", "");
+    } else {
+      this.enable.style.visibility = "visible";
+      this.enable.removeAttribute("checked");
+    }
+
+    if (this.prop.overridden && !this.editing) {
+      this.element.classList.add("ruleview-overridden");
+    } else {
+      this.element.classList.remove("ruleview-overridden");
+    }
+
+    this.nameSpan.textContent = this.prop.name;
+
+    // Combine the property's value and priority into one string for
+    // the value.
+    let val = this.prop.value;
+    if (this.prop.priority) {
+      val += " !" + this.prop.priority;
+    }
+    this.valueSpan.textContent = val;
+
+    // Populate the computed styles.
+    this._updateComputed();
+  },
+
+  _onStartEditing: function TextPropertyEditor_onStartEditing()
+  {
+    this.element.classList.remove("ruleview-overridden");
+  },
+
+  /**
+   * Populate the list of computed styles.
+   */
+  _updateComputed: function TextPropertyEditor_updateComputed()
+  {
+    // Clear out existing viewers.
+    while (this.computed.hasChildNodes()) {
+      this.computed.removeChild(this.computed.lastChild);
+    }
+
+    let showExpander = false;
+    for each (let computed in this.prop.computed) {
+      // Don't bother to duplicate information already
+      // shown in the text property.
+      if (computed.name === this.prop.name) {
+        continue;
+      }
+
+      showExpander = true;
+
+      let li = createChild(this.computed, "li", {
+        class: "ruleview-computed"
+      });
+
+      if (computed.overridden) {
+        li.classList.add("ruleview-overridden");
+      }
+
+      createChild(li, "span", {
+        class: "ruleview-propertyname",
+        textContent: computed.name
+      });
+      appendText(li, ": ");
+      createChild(li, "span", {
+        class: "ruleview-propertyvalue",
+        textContent: computed.value
+      });
+      appendText(li, ";");
+    }
+
+    // Show or hide the expander as needed.
+    if (showExpander) {
+      this.expander.style.visibility = "visible";
+    } else {
+      this.expander.style.visibility = "hidden";
+    }
+  },
+
+  /**
+   * Handles clicks on the disabled property.
+   */
+  _onEnableClicked: function TextPropertyEditor_onEnableClicked()
+  {
+    this.prop.setEnabled(this.enable.checked);
+  },
+
+  /**
+   * Handles clicks on the computed property expander.
+   */
+  _onExpandClicked: function TextPropertyEditor_onExpandClicked()
+  {
+    this.expander.classList.toggle("styleinspector-open");
+    this.computed.classList.toggle("styleinspector-open");
+  },
+
+  /**
+   * Called when the property name's inplace editor is closed.
+   * Ignores the change if the user pressed escape, otherwise
+   * commits it.
+   *
+   * @param {string} aValue
+   *        The value contained in the editor.
+   * @param {boolean} aCommit
+   *        True if the change should be applied.
+   */
+  _onNameDone: function TextPropertyEditor_onNameDone(aValue, aCommit)
+  {
+    if (!aCommit) {
+      return;
+    }
+    if (!aValue) {
+      this.prop.remove();
+      this.element.parentNode.removeChild(this.element);
+      return;
+    }
+    this.prop.setName(aValue);
+  },
+
+  /**
+   * Pull priority (!important) out of the value provided by a
+   * value editor.
+   *
+   * @param {string} aValue
+   *        The value from the text editor.
+   * @return an object with 'value' and 'priority' properties.
+   */
+  _parseValue: function TextPropertyEditor_parseValue(aValue)
+  {
+    let [value, priority] = aValue.split("!", 2);
+    return {
+      value: value.trim(),
+      priority: (priority ? priority.trim() : "")
+    };
+  },
+
+  /**
+   * Called when a value editor closes.  If the user pressed escape,
+   * revert to the value this property had before editing.
+   *
+   * @param {string} aValue
+   *        The value contained in the editor.
+   * @param {boolean} aCommit
+   *        True if the change should be applied.
+   */
+   _onValueDone: function PropertyEditor_onValueDone(aValue, aCommit)
+  {
+    if (aCommit) {
+      let val = this._parseValue(aValue);
+      this.prop.setValue(val.value, val.priority);
+      this.committed.value = this.prop.value;
+      this.committed.priority = this.prop.priority;
+    } else {
+      this.prop.setValue(this.committed.value, this.committed.priority);
+    }
+  },
+};
+
+/**
+ * Mark a span editable.  |editableField| will listen for the span to
+ * be focused and create an InlineEditor to handle text input.
+ * Changes will be committed when the InlineEditor's input is blurred
+ * or dropped when the user presses escape.
+ *
+ * @param {object} aOptions
+ *    Options for the editable field, including:
+ *    {Element} element:
+ *      (required) The span to be edited on focus.
+ *    {function} start:
+ *       Will be called when the inplace editor is initialized.
+ *    {function} change:
+ *       Will be called when the text input changes.  Will be called
+ *       with the current value of the text input.
+ *    {function} done:
+ *       Called when input is committed or blurred.  Called with
+ *       current value and a boolean telling the caller whether to
+ *       commit the change.
+ *    {string} advanceChars:
+ *       If any characters in advanceChars are typed, focus will advance
+ *       to the next element.
+ */
+function editableField(aOptions)
+{
+  aOptions.element.addEventListener("focus", function() {
+    new InplaceEditor(aOptions);
+  }, false);
+}
+var _editableField = editableField;
+
+function InplaceEditor(aOptions)
+{
+  this.elt = aOptions.element;
+  this.elt.inplaceEditor = this;
+
+  this.change = aOptions.change;
+  this.done = aOptions.done;
+  this.initial = aOptions.initial ? aOptions.initial : this.elt.textContent;
+  this.doc = this.elt.ownerDocument;
+
+  this._onBlur = this._onBlur.bind(this);
+  this._onKeyPress = this._onKeyPress.bind(this);
+  this._onInput = this._onInput.bind(this);
+
+  this._createInput();
+  this._autosize();
+
+  // Pull out character codes for advanceChars, listing the
+  // characters that should trigger a blur.
+  this._advanceCharCodes = {};
+  let advanceChars = aOptions.advanceChars || '';
+  for (let i = 0; i < advanceChars.length; i++) {
+    this._advanceCharCodes[advanceChars.charCodeAt(i)] = true;
+  }
+
+  // Hide the provided element and add our editor.
+  this.originalDisplay = this.elt.style.display;
+  this.elt.style.display = "none";
+  this.elt.parentNode.insertBefore(this.input, this.elt);
+
+  this.input.select();
+  this.input.focus();
+
+  this.input.addEventListener("blur", this._onBlur, false);
+  this.input.addEventListener("keypress", this._onKeyPress, false);
+  this.input.addEventListener("input", this._onInput, false);
+
+  if (aOptions.start) {
+    aOptions.start();
+  }
+}
+
+InplaceEditor.prototype = {
+  _createInput: function InplaceEditor_createEditor()
+  {
+    this.input = this.doc.createElementNS(HTML_NS, "input");
+    this.input.inplaceEditor = this;
+    this.input.classList.add("styleinspector-propertyeditor");
+    this.input.value = this.initial;
+
+    copyTextStyles(this.elt, this.input);
+  },
+
+  /**
+   * Get rid of the editor.
+   */
+  _clear: function InplaceEditor_clear()
+  {
+    this.input.removeEventListener("blur", this._onBlur, false);
+    this.input.removeEventListener("keypress", this._onKeyPress, false);
+    this.input.removeEventListener("oninput", this._onInput, false);
+    this._stopAutosize();
+
+    this.elt.parentNode.removeChild(this.input);
+    this.elt.style.display = this.originalDisplay;
+    this.input = null;
+
+    delete this.elt.inplaceEditor;
+    delete this.elt;
+  },
+
+  /**
+   * Keeps the editor close to the size of its input string.  This is pretty
+   * crappy, suggestions for improvement welcome.
+   */
+  _autosize: function InplaceEditor_autosize()
+  {
+    // Create a hidden, absolutely-positioned span to measure the text
+    // in the input.  Boo.
+
+    // We can't just measure the original element because a) we don't
+    // change the underlying element's text ourselves (we leave that
+    // up to the client), and b) without tweaking the style of the
+    // original element, it might wrap differently or something.
+    this._measurement = this.doc.createElementNS(HTML_NS, "span");
+    this.elt.parentNode.appendChild(this._measurement);
+    let style = this._measurement.style;
+    style.visibility = "hidden";
+    style.position = "absolute";
+    style.top = "0";
+    style.left = "0";
+    copyTextStyles(this.input, this._measurement);
+    this._updateSize();
+  },
+
+  /**
+   * Clean up the mess created by _autosize().
+   */
+  _stopAutosize: function InplaceEditor_stopAutosize()
+  {
+    if (!this._measurement) {
+      return;
+    }
+    this._measurement.parentNode.removeChild(this._measurement);
+    delete this._measurement;
+  },
+
+  /**
+   * Size the editor to fit its current contents.
+   */
+  _updateSize: function InplaceEditor_updateSize()
+  {
+    // Replace spaces with non-breaking spaces.  Otherwise setting
+    // the span's textContent will collapse spaces and the measurement
+    // will be wrong.
+    this._measurement.textContent = this.input.value.replace(' ', '\u00a0', 'g');
+
+    // We add a bit of padding to the end.  Should be enough to fit
+    // any letter that could be typed, otherwise we'll scroll before
+    // we get a chance to resize.  Yuck.
+    let width = this._measurement.offsetWidth + 10;
+
+    this.input.style.width = width + "px";
+  },
+
+  /**
+   * Handle loss of focus by calling the client's done handler and
+   * clearing out.
+   */
+  _onBlur: function InplaceEditor_onBlur(aEvent)
+  {
+    if (this.done) {
+      this.done(this.cancelled ? this.initial : this.input.value.trim(),
+                !this.cancelled);
+    }
+    this._clear();
+  },
+
+  _onKeyPress: function InplaceEditor_onKeyPress(aEvent)
+  {
+    let prevent = false;
+    if (aEvent.charCode in this._advanceCharCodes
+       || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN) {
+      // Focus the next element, triggering a blur which
+      // will eventually shut us down (making return roughly equal
+      // tab).
+      prevent = true;
+      moveFocus(this.input.ownerDocument.defaultView, FOCUS_FORWARD);
+    } else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE) {
+      // Cancel and blur ourselves.  |_onBlur| will call the user's
+      // done handler for us.
+      prevent = true;
+      this.cancelled = true;
+      this.input.blur();
+    } else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) {
+      // No need for leading spaces here.  This is particularly
+      // noticable when adding a property: it's very natural to type
+      // <name>: (which advances to the next property) then spacebar.
+      prevent = !this.input.value;
+    }
+
+    if (prevent) {
+      aEvent.preventDefault();
+    }
+  },
+
+  /**
+   * Handle changes the input text.
+   */
+  _onInput: function InplaceEditor_onInput(aEvent)
+  {
+    // Update size if we're autosizing.
+    if (this._measurement) {
+      this._updateSize();
+    }
+
+    // Call the user's change handler if available.
+    if (this.change) {
+      this.change(this.input.value.trim());
+    }
+  }
+};
+
+/**
+ * Helper functions
+ */
+
+/**
+ * Create a child element with a set of attributes.
+ *
+ * @param {Element} aParent
+ *        The parent node.
+ * @param {string} aTag
+ *        The tag name.
+ * @param {object} aAttributes
+ *        A set of attributes to set on the node.
+ */
+function createChild(aParent, aTag, aAttributes)
+{
+  let elt = aParent.ownerDocument.createElementNS(HTML_NS, aTag);
+  for (let attr in aAttributes) {
+    if (aAttributes.hasOwnProperty(attr)) {
+      if (attr === "textContent") {
+        elt.textContent = aAttributes[attr];
+      } else {
+        elt.setAttribute(attr, aAttributes[attr]);
+      }
+    }
+  }
+  aParent.appendChild(elt);
+  return elt;
+}
+
+/**
+ * Append a text node to an element.
+ */
+function appendText(aParent, aText)
+{
+  aParent.appendChild(aParent.ownerDocument.createTextNode(aText));
+}
+
+/**
+ * Copy text-related styles from one element to another.
+ */
+function copyTextStyles(aFrom, aTo)
+{
+  let win = aFrom.ownerDocument.defaultView;
+  let style = win.getComputedStyle(aFrom);
+  aTo.style.fontFamily = style.getPropertyCSSValue("font-family").cssText;
+  aTo.style.fontSize = style.getPropertyCSSValue("font-size").cssText;
+  aTo.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText;
+  aTo.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
+}
+
+/**
+ * Trigger a focus change similar to pressing tab/shift-tab.
+ */
+function moveFocus(aWin, aDirection)
+{
+  let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+  fm.moveFocus(aWin, null, aDirection, 0);
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/cssruleview.xhtml
@@ -0,0 +1,53 @@
+<!DOCTYPE html [
+  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+  %htmlDTD;
+  <!ENTITY % inspectorDTD SYSTEM "chrome://browser/locale/styleinspector.dtd">
+  %inspectorDTD;
+]>
+<!-- ***** 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 Mozilla Inspector Module.
+   -
+   - 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):
+   -   Dave Camp (dcamp@mozilla.com) (original author)
+   -
+   - 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 LGPL or the GPL. 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 ***** -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+  <meta http-equiv="Content-Type"
+    content="application/xhtml+xml; charset=UTF-8" />
+  <link rel="stylesheet" type="text/css"
+    href="chrome://browser/content/devtools/styleinspector.css" />
+  <link rel="stylesheet" type="text/css"
+    href="chrome://browser/skin/devtools/csshtmltree.css" />
+</head>
+<body role="application" id="ruleview-body"></body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/styleinspector.css
@@ -0,0 +1,40 @@
+/* ***** 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 Mozilla Inspector Module.
+ *
+ * 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):
+ *   Dave Camp <dcamp@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 ***** */
+
+.ruleview-computedlist:not(.styleinspector-open) {
+  display: none;
+}
--- a/browser/devtools/styleinspector/test/browser/Makefile.in
+++ b/browser/devtools/styleinspector/test/browser/Makefile.in
@@ -47,16 +47,20 @@ include $(topsrcdir)/config/rules.mk
 
 _BROWSER_TEST_FILES = \
   browser_styleinspector.js \
   browser_styleinspector_webconsole.js \
   browser_bug683672.js \
   browser_styleinspector_bug_672746_default_styles.js \
   browser_styleinspector_bug_672744_search_filter.js \
   browser_bug_692400_element_style.js \
+  browser_ruleview_editor.js \
+  browser_ruleview_manipulation.js \
+  browser_ruleview_override.js \
+  browser_ruleview_ui.js \
   head.js \
   $(NULL)
 
 _BROWSER_TEST_PAGES = \
   browser_styleinspector_webconsole.htm \
   browser_bug683672.html \
   $(NULL)
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser/browser_ruleview_editor.js
@@ -0,0 +1,121 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource:///modules/devtools/CssRuleView.jsm");
+
+let doc = content.document;
+
+function expectDone(aValue, aCommit, aNext)
+{
+  return function(aDoneValue, aDoneCommit) {
+    dump("aDoneValue: " + aDoneValue + " commit: " + aDoneCommit + "\n");
+
+    is(aDoneValue, aValue, "Should get expected value");
+    is(aDoneCommit, aDoneCommit, "Should get expected commit value");
+    aNext();
+  }
+}
+
+function clearBody()
+{
+  doc.body.innerHTML = "";
+}
+
+function createSpan()
+{
+  let span = doc.createElement("span");
+  span.setAttribute("tabindex", "0");
+  span.textContent = "Edit Me!";
+  doc.body.appendChild(span);
+  return span;
+}
+
+function testReturnCommit()
+{
+  clearBody();
+  let span = createSpan();
+  _editableField({
+    element: span,
+    initial: "explicit initial",
+    start: function() {
+      is(span.inplaceEditor.input.value, "explicit initial", "Explicit initial value should be used.");
+      span.inplaceEditor.input.value = "Test Value";
+      EventUtils.sendKey("return", span.inplaceEditor.input);
+    },
+    done: expectDone("Test Value", true, testBlurCommit)
+  });
+  span.focus();
+}
+
+function testBlurCommit()
+{
+  clearBody();
+  let span = createSpan();
+  _editableField({
+    element: span,
+    start: function() {
+      is(span.inplaceEditor.input.value, "Edit Me!", "textContent of the span used.");
+      span.inplaceEditor.input.value = "Test Value";
+      span.inplaceEditor.input.blur();
+    },
+    done: expectDone("Test Value", true, testAdvanceCharCommit)
+  });
+  span.focus();
+}
+
+function testAdvanceCharCommit()
+{
+  clearBody();
+  let span = createSpan();
+  _editableField({
+    element: span,
+    advanceChars: ":",
+    start: function() {
+      let input = span.inplaceEditor.input;
+      for each (let ch in "Test:") {
+        EventUtils.sendChar(ch, input);
+      }
+    },
+    done: expectDone("Test", true, testEscapeCancel)
+  });
+  span.focus();
+}
+
+function testEscapeCancel()
+{
+  clearBody();
+  let span = createSpan();
+  _editableField({
+    element: span,
+    initial: "initial text",
+    start: function() {
+      span.inplaceEditor.input.value = "Test Value";
+      EventUtils.sendKey("escape", span.inplaceEditor.input);
+    },
+    done: expectDone("initial text", false, finishTest)
+  });
+  span.focus();
+}
+
+
+function finishTest()
+{
+  doc = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
+    doc = content.document;
+    waitForFocus(testReturnCommit, content);
+  }, true);
+
+  content.location = "data:text/html,inline editor tests";
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser/browser_ruleview_manipulation.js
@@ -0,0 +1,70 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource:///modules/devtools/CssRuleView.jsm");
+
+let doc;
+
+function simpleOverride()
+{
+  doc.body.innerHTML = '<div id="testid">Styled Node</div>';
+  let element = doc.getElementById("testid");
+  let elementStyle = new _ElementStyle(element);
+
+  let elementRule = elementStyle.rules[0];
+  let firstProp = elementRule.createProperty("background-color", "green", "");
+  let secondProp = elementRule.createProperty("background-color", "blue", "");
+  is(elementRule.textProps[0], firstProp, "Rules should be in addition order.");
+  is(elementRule.textProps[1], secondProp, "Rules should be in addition order.");
+
+  is(element.style.getPropertyValue("background-color"), "blue", "Second property should have been used.");
+
+  secondProp.remove();
+  is(element.style.getPropertyValue("background-color"), "green", "After deleting second property, first should be used.");
+
+  secondProp = elementRule.createProperty("background-color", "blue", "");
+  is(element.style.getPropertyValue("background-color"), "blue", "New property should be used.");
+
+  is(elementRule.textProps[0], firstProp, "Rules shouldn't have switched places.");
+  is(elementRule.textProps[1], secondProp, "Rules shouldn't have switched places.");
+
+  secondProp.setEnabled(false);
+  is(element.style.getPropertyValue("background-color"), "green", "After disabling second property, first value should be used");
+
+  firstProp.setEnabled(false);
+  is(element.style.getPropertyValue("background-color"), "", "After disabling both properties, value should be empty.");
+
+  secondProp.setEnabled(true);
+  is(element.style.getPropertyValue("background-color"), "blue", "Value should be set correctly after re-enabling");
+
+  firstProp.setEnabled(true);
+  is(element.style.getPropertyValue("background-color"), "blue", "Re-enabling an earlier property shouldn't make it override a later property.");
+  is(elementRule.textProps[0], firstProp, "Rules shouldn't have switched places.");
+  is(elementRule.textProps[1], secondProp, "Rules shouldn't have switched places.");
+
+  firstProp.setValue("purple", "");
+  is(element.style.getPropertyValue("background-color"), "blue", "Modifying an earlier property shouldn't override a later property.");
+
+  finishTest();
+}
+
+function finishTest()
+{
+  doc = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
+    doc = content.document;
+    waitForFocus(simpleOverride, content);
+  }, true);
+
+  content.location = "data:text/html,basic style inspector tests";
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser/browser_ruleview_override.js
@@ -0,0 +1,134 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource:///modules/devtools/CssRuleView.jsm");
+
+let doc;
+
+function simpleOverride()
+{
+  let style = '' +
+    '#testid {' +
+    '  background-color: blue;' +
+    '} ' +
+    '.testclass {' +
+    '  background-color: green;' +
+    '}';
+
+  let styleNode = addStyle(doc, style);
+  doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
+
+  let elementStyle = new _ElementStyle(doc.getElementById("testid"));
+
+  let idRule = elementStyle.rules[1];
+  let idProp = idRule.textProps[0];
+  is(idProp.name, "background-color", "First ID prop should be background-color");
+  ok(!idProp.overridden, "ID prop should not be overridden.");
+
+  let classRule = elementStyle.rules[2];
+  let classProp = classRule.textProps[0];
+  is(classProp.name, "background-color", "First class prop should be background-color");
+  ok(classProp.overridden, "Class property should be overridden.");
+
+  // Override background-color by changing the element style.
+  let elementRule = elementStyle.rules[0];
+  elementRule.createProperty("background-color", "purple", "");
+  let elementProp = elementRule.textProps[0];
+  is(classProp.name, "background-color", "First element prop should now be background-color");
+
+  ok(!elementProp.overridden, "Element style property should not be overridden");
+  ok(idProp.overridden, "ID property should be overridden");
+  ok(classProp.overridden, "Class property should be overridden");
+
+  styleNode.parentNode.removeChild(styleNode);
+
+  partialOverride();
+}
+
+function partialOverride()
+{
+  let style = '' +
+    // Margin shorthand property...
+    '.testclass {' +
+    '  margin: 2px;' +
+    '}' +
+    // ... will be partially overridden.
+    '#testid {' +
+    '  margin-left: 1px;' +
+    '}';
+
+  let styleNode = addStyle(doc, style);
+  doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
+
+  let elementStyle = new _ElementStyle(doc.getElementById("testid"));
+
+  let classRule = elementStyle.rules[2];
+  let classProp = classRule.textProps[0];
+  ok(!classProp.overridden, "Class prop shouldn't be overridden, some props are still being used.");
+  for each (let computed in classProp.computed) {
+    if (computed.name.indexOf("margin-left") == 0) {
+      ok(computed.overridden, "margin-left props should be overridden.");
+    } else {
+      ok(!computed.overridden, "Non-margin-left props should not be overridden.");
+    }
+  }
+
+  styleNode.parentNode.removeChild(styleNode);
+
+  importantOverride();
+}
+
+function importantOverride()
+{
+  let style = '' +
+    // Margin shorthand property...
+    '.testclass {' +
+    '  background-color: green !important;' +
+    '}' +
+    // ... will be partially overridden.
+    '#testid {' +
+    '  background-color: blue;' +
+    '}';
+  let styleNode = addStyle(doc, style);
+  doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
+
+  let elementStyle = new _ElementStyle(doc.getElementById("testid"));
+
+  let idRule = elementStyle.rules[1];
+  let idProp = idRule.textProps[0];
+  ok(idProp.overridden, "Not-important rule should be overridden.");
+
+  let classRule = elementStyle.rules[2];
+  let classProp = classRule.textProps[0];
+  ok(!classProp.overridden, "Important rule should not be overridden.");
+
+  styleNode.parentNode.removeChild(styleNode);
+
+  let elementRule = elementStyle.rules[0];
+  let elementProp = elementRule.createProperty("background-color", "purple", "important");
+  ok(classProp.overridden, "New important prop should override class property.");
+  ok(!elementProp.overridden, "New important prop should not be overriden.");
+
+  finishTest();
+}
+
+function finishTest()
+{
+  doc = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
+    doc = content.document;
+    waitForFocus(simpleOverride, content);
+  }, true);
+
+  content.location = "data:text/html,basic style inspector tests";
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser/browser_ruleview_ui.js
@@ -0,0 +1,180 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource:///modules/devtools/CssRuleView.jsm");
+
+let doc;
+let ruleDialog;
+let ruleView;
+
+function waitForEditorFocus(aParent, aCallback)
+{
+  aParent.addEventListener("focus", function onFocus(evt) {
+    if (evt.target.inplaceEditor) {
+      aParent.removeEventListener("focus", onFocus, true);
+      let editor = evt.target.inplaceEditor;
+      executeSoon(function() {
+        aCallback(editor);
+      });
+    }
+  }, true);
+}
+
+function waitForEditorBlur(aEditor, aCallback)
+{
+  let input = aEditor.input;
+  input.addEventListener("blur", function onBlur() {
+    input.removeEventListener("blur", onBlur, false);
+    executeSoon(function() {
+      aCallback();
+    });
+  }, false);
+}
+
+function startTest()
+{
+  let style = '' +
+    '#testid {' +
+    '  background-color: blue;' +
+    '} ' +
+    '.testclass {' +
+    '  background-color: green;' +
+    '}';
+
+  let styleNode = addStyle(doc, style);
+  doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
+  let testElement = doc.getElementById("testid");
+
+  ruleDialog = openDialog("chrome://browser/content/devtools/cssruleview.xhtml",
+                          "cssruleviewtest",
+                          "width=200,height=350");
+  ruleDialog.addEventListener("load", function onLoad(evt) {
+    ruleDialog.removeEventListener("load", onLoad);
+    let doc = ruleDialog.document;
+    let body = doc.getElementById("ruleview-body");
+    ruleView = new CssRuleView(doc);
+    body.appendChild(ruleView.element);
+    ruleView.highlight(testElement);
+    waitForFocus(testCancelNew, ruleDialog);
+  }, true);
+}
+
+function testCancelNew()
+{
+  // Start at the beginning: start to add a rule to the element's style
+  // declaration, but leave it empty.
+
+  let elementRuleEditor = ruleView.element.children[0]._ruleEditor;
+  waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) {
+    is(elementRuleEditor.newPropSpan.inplaceEditor, aEditor, "Next focused editor should be the new property editor.");
+    let input = aEditor.input;
+    waitForEditorBlur(aEditor, function () {
+      is(elementRuleEditor.rule.textProps.length,  0, "Should have canceled creating a new text property.");
+      ok(!elementRuleEditor.propertyList.hasChildNodes(), "Should not have any properties.");
+      testCreateNew();
+    });
+    aEditor.input.blur();
+  });
+
+  EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
+                             { },
+                             ruleDialog);
+}
+
+function testCreateNew()
+{
+  // Create a new property.
+  let elementRuleEditor = ruleView.element.children[0]._ruleEditor;
+  waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) {
+    is(elementRuleEditor.newPropSpan.inplaceEditor, aEditor, "Next focused editor should be the new property editor.");
+    let input = aEditor.input;
+    input.value = "background-color";
+
+    waitForEditorFocus(elementRuleEditor.element, function onNewValue(aEditor) {
+      is(elementRuleEditor.rule.textProps.length,  1, "Should have created a new text property.");
+      is(elementRuleEditor.propertyList.children.length, 1, "Should have created a property editor.");
+      let textProp = elementRuleEditor.rule.textProps[0];
+      is(aEditor, textProp.editor.valueSpan.inplaceEditor, "Should be editing the value span now.");
+
+      aEditor.input.value = "purple";
+      waitForEditorBlur(aEditor, function() {
+        is(textProp.value, "purple", "Text prop should have been changed.");
+        testEditProperty();
+      });
+
+      aEditor.input.blur();
+    });
+    EventUtils.sendKey("return", input);
+  });
+
+  EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
+                             { },
+                             ruleDialog);
+}
+
+function testEditProperty()
+{
+  let idRuleEditor = ruleView.element.children[1]._ruleEditor;
+  let propEditor = idRuleEditor.rule.textProps[0].editor;
+  waitForEditorFocus(propEditor.element, function onNewElement(aEditor) {
+    is(propEditor.nameSpan.inplaceEditor, aEditor, "Next focused editor should be the name editor.");
+    let input = aEditor.input;
+    waitForEditorFocus(propEditor.element, function onNewName(aEditor) {
+      input = aEditor.input;
+      is(propEditor.valueSpan.inplaceEditor, aEditor, "Focus should have moved to the value.");
+
+      waitForEditorBlur(aEditor, function() {
+        is(idRuleEditor.rule.style.getPropertyValue("border-color"), "red",
+           "border-color should have been set.");
+        testDisableProperty();
+      });
+
+      for each (let ch in "red;") {
+        EventUtils.sendChar(ch, input);
+      }
+    });
+    for each (let ch in "border-color:") {
+      EventUtils.sendChar(ch, input);
+    }
+  });
+
+  EventUtils.synthesizeMouse(propEditor.nameSpan, 1, 1,
+                             { },
+                             ruleDialog);
+}
+
+function testDisableProperty()
+{
+  let idRuleEditor = ruleView.element.children[1]._ruleEditor;
+  let propEditor = idRuleEditor.rule.textProps[0].editor;
+
+  propEditor.enable.click();
+  is(idRuleEditor.rule.style.getPropertyValue("border-color"), "", "Border-color should have been unset.");
+  propEditor.enable.click();
+  is(idRuleEditor.rule.style.getPropertyValue("border-color"), "red",
+     "Border-color should have been reset.");
+  finishTest();
+}
+
+function finishTest()
+{
+  ruleDialog.close();
+  ruleDialog = ruleView = null;
+  doc = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
+    doc = content.document;
+    waitForFocus(startTest, content);
+  }, true);
+
+  content.location = "data:text/html,basic style inspector tests";
+}
--- a/browser/devtools/styleinspector/test/browser/head.js
+++ b/browser/devtools/styleinspector/test/browser/head.js
@@ -145,16 +145,25 @@ function testLogEntry(aOutputNode, aMatc
  * @param string aString
  *        The string to find.
  */
 function findLogEntry(aString)
 {
   testLogEntry(outputNode, aString, "found " + aString);
 }
 
+function addStyle(aDocument, aString)
+{
+  let node = aDocument.createElement('style');
+  node.setAttribute("type", "text/css");
+  node.textContent = aString;
+  aDocument.getElementsByTagName("head")[0].appendChild(node);
+  return node;
+}
+
 function openConsole()
 {
   HUDService.activateHUDForContext(tab);
 }
 
 function closeConsole()
 {
   HUDService.deactivateHUDForContext(tab);
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -80,16 +80,22 @@ XPCOMUtils.defineLazyGetter(this, "gcli"
 });
 
 XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () {
   var obj = {};
   Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj);
   return obj.StyleInspector;
 });
 
+XPCOMUtils.defineLazyGetter(this, "CssRuleView", function() {
+  let tmp = {};
+  Cu.import("resource:///modules/devtools/CssRuleView.jsm", tmp);
+  return tmp.CssRuleView;
+});
+
 XPCOMUtils.defineLazyGetter(this, "NetUtil", function () {
   var obj = {};
   Cu.import("resource://gre/modules/NetUtil.jsm", obj);
   return obj.NetUtil;
 });
 
 XPCOMUtils.defineLazyGetter(this, "PropertyPanel", function () {
   var obj = {};
@@ -4595,16 +4601,63 @@ function JSTermHelper(aJSTerm)
         styleInspector.panel.setAttribute("hudToolId", aJSTerm.hudId);
         styleInspector.open(aNode);
       });
     } else {
       aJSTerm.writeOutput(errstr + "\n", CATEGORY_OUTPUT, SEVERITY_ERROR);
     }
   };
 
+  aJSTerm.sandbox.inspectrules = function JSTH_inspectrules(aNode)
+  {
+    aJSTerm.helperEvaluated = true;
+    let doc = aJSTerm.parentNode.ownerDocument;
+    let win = doc.defaultView;
+    let panel = createElement(doc, "panel", {
+      label: "CSS Rules",
+      titlebar: "normal",
+      noautofocus: "true",
+      noautohide: "true",
+      close: "true",
+      width: 350,
+      height: (win.screen.height / 2)
+    });
+
+    let iframe = createAndAppendElement(panel, "iframe", {
+      src: "chrome://browser/content/devtools/cssruleview.xhtml",
+      flex: "1",
+    });
+
+    panel.addEventListener("load", function onLoad() {
+      panel.removeEventListener("load", onLoad, true);
+      let doc = iframe.contentDocument;
+      let view = new CssRuleView(doc);
+      let body = doc.getElementById("ruleview-body");
+      body.appendChild(view.element);
+      view.highlight(aNode);
+    }, true);
+
+    let parent = doc.getElementById("mainPopupSet");
+    parent.appendChild(panel);
+
+    panel.addEventListener("popuphidden", function onHide() {
+      panel.removeEventListener("popuphidden", onHide);
+      parent.removeChild(panel);
+    });
+
+    let footer = createElement(doc, "hbox", { align: "end" });
+    createAndAppendElement(footer, "spacer", { flex: 1});
+    createAndAppendElement(footer, "resizer", { dir: "bottomend" });
+    panel.appendChild(footer);
+
+    let anchor = win.gBrowser.selectedBrowser;
+    panel.openPopup(anchor, "end_before", 0, 0, false, false);
+
+  }
+
   /**
    * Prints aObject to the output.
    *
    * @param object aObject
    *        Object to print to the output.
    * @returns void
    */
   aJSTerm.sandbox.pprint = function JSTH_pprint(aObject)
--- a/browser/installer/removed-files.in
+++ b/browser/installer/removed-files.in
@@ -224,16 +224,17 @@ extensions/testpilot@labs.mozilla.com/sk
 extensions/testpilot@labs.mozilla.com/skin/win/notification-tail-up.png
 extensions/testpilot@labs.mozilla.com/tests/test_data_store.js
 greprefs/all.js
 greprefs/security-prefs.js
 greprefs/xpinstall.js
 install.rdf
 modules/ISO8601DateUtils.jsm
 modules/JSON.jsm
+modules/utils.js
 mozilla-runtime@BIN_SUFFIX@
 old-homepage-default.properties
 README.txt
 res/arrow.gif
 res/arrowd.gif
 res/broken-image.gif
 res/broken-image.png
 res/charsetData.properties
@@ -918,16 +919,52 @@ xpicleanup@BIN_SUFFIX@
   defaults/pref/services-sync.js
   defaults/profile/bookmarks.html
   defaults/profile/chrome/userChrome-example.css
   defaults/profile/chrome/userContent-example.css
   defaults/profile/localstore.rdf
   defaults/profile/mimeTypes.rdf
   defaults/profile/prefs.js
   greprefs.js
+  hyphenation/
+  hyphenation/hyph_af.dic
+  hyphenation/hyph_bg.dic
+  hyphenation/hyph_ca.dic
+  hyphenation/hyph_cy.dic
+  hyphenation/hyph_da.dic
+  hyphenation/hyph_de-1901.dic
+  hyphenation/hyph_de-1996.dic
+  hyphenation/hyph_de-CH.dic
+  hyphenation/hyph_en_US.dic
+  hyphenation/hyph_eo.dic
+  hyphenation/hyph_es.dic
+  hyphenation/hyph_et.dic
+  hyphenation/hyph_fi.dic
+  hyphenation/hyph_fr.dic
+  hyphenation/hyph_gl.dic
+  hyphenation/hyph_hr.dic
+  hyphenation/hyph_hsb.dic
+  hyphenation/hyph_hu.dic
+  hyphenation/hyph_ia.dic
+  hyphenation/hyph_is.dic
+  hyphenation/hyph_it.dic
+  hyphenation/hyph_kmr.dic
+  hyphenation/hyph_la.dic
+  hyphenation/hyph_lt.dic
+  hyphenation/hyph_mn.dic
+  hyphenation/hyph_nb.dic
+  hyphenation/hyph_nl.dic
+  hyphenation/hyph_nn.dic
+  hyphenation/hyph_pt.dic
+  hyphenation/hyph_ru.dic
+  hyphenation/hyph_sh.dic
+  hyphenation/hyph_sl.dic
+  hyphenation/hyph_sv.dic
+  hyphenation/hyph_tr.dic
+  hyphenation/hyph_uk.dic
   modules/AddonLogging.jsm
   modules/AddonManager.jsm
   modules/AddonRepository.jsm
   modules/AddonUpdateChecker.jsm
   modules/CertUtils.jsm
   modules/CrashSubmit.jsm
   modules/CSPUtils.jsm
   modules/ctypes.jsm
@@ -992,17 +1029,16 @@ xpicleanup@BIN_SUFFIX@
   modules/services-sync/type_records/passwords.js
   modules/services-sync/type_records/prefs.js
   modules/services-sync/type_records/tabs.js
   modules/services-sync/util.js
   modules/stylePanel.jsm
   modules/tabview/AllTabs.jsm
   modules/tabview/groups.jsm
   modules/tabview/utils.jsm
-  modules/utils.js
   modules/WindowDraggingUtils.jsm
   #ifdef XP_WIN
     modules/WindowsJumpLists.jsm
     modules/WindowsPreviewPerTab.jsm
   #endif
   modules/XPCOMUtils.jsm
   modules/XPIProvider.jsm
   res/contenteditable.css
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -85,16 +85,17 @@ can reach it easily. -->
 
 <!ENTITY fullScreenMinimize.tooltip "Minimize">
 <!ENTITY fullScreenRestore.tooltip "Restore">
 <!ENTITY fullScreenClose.tooltip "Close">
 <!ENTITY fullScreenAutohide.label "Hide Toolbars">
 <!ENTITY fullScreenAutohide.accesskey "H">
 <!ENTITY fullScreenExit.label "Exit Full Screen Mode">
 <!ENTITY fullScreenExit.accesskey "F">
+<!ENTITY domFullScreenWarning.label "Press ESC to leave full-screen mode">
 
 <!ENTITY closeWindow.label "Close Window">
 <!ENTITY closeWindow.accesskey "d">
 
 <!ENTITY bookmarksMenu.label "Bookmarks">
 <!ENTITY bookmarksMenu.accesskey "B">
 <!ENTITY bookmarkThisPageCmd.label "Bookmark This Page">
 <!ENTITY bookmarkThisPageCmd.commandkey "d">
@@ -207,19 +208,20 @@ can reach it easily. -->
   -  "Scratchpad" in your locale. You should feel free to find a close
   -  approximation to it or choose a word (or words) that means
   -  "simple discardable text editor". -->
 <!ENTITY scratchpad.label             "Scratchpad">
 <!ENTITY scratchpad.accesskey         "s">
 <!ENTITY scratchpad.keycode           "VK_F4">
 <!ENTITY scratchpad.keytext           "F4">
 
-<!ENTITY inspectPanelTitle.label      "HTML">
-<!ENTITY inspectButton.label          "Inspect">
-<!ENTITY inspectButton.accesskey      "I">
+<!ENTITY inspectPanelTitle.label        "HTML">
+<!ENTITY inspectButton.label            "Inspect">
+<!ENTITY inspectButton.accesskey        "I">
+<!ENTITY inspectCloseButton.tooltiptext "Close Inspector">
 
 <!ENTITY getMoreDevtoolsCmd.label        "Get More Tools">
 <!ENTITY getMoreDevtoolsCmd.accesskey    "M">
 
 <!ENTITY fileMenu.label         "File"> 
 <!ENTITY fileMenu.accesskey       "F">
 <!ENTITY newNavigatorCmd.label        "New Window">
 <!ENTITY newNavigatorCmd.key        "N">
--- a/browser/locales/en-US/profile/bookmarks.inc
+++ b/browser/locales/en-US/profile/bookmarks.inc
@@ -9,22 +9,16 @@
 
 #define bookmarks_toolbarfolder Bookmarks Toolbar Folder
 #define bookmarks_toolbarfolder_description Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar
 
 # LOCALIZATION NOTE (getting_started):
 # link title for http://en-US.www.mozilla.com/en-US/firefox/central/
 #define getting_started Getting Started
 
-# LOCALIZATION NOTE (latest_headlines):
-# link title for the live bookmarks sample, a redirect on
-# http://en-US.fxfeeds.mozilla.com/en-US/firefox/headlines.xml
-# Changing the redirect is subject to approval of l10n-drivers.
-#define latest_headlines Latest Headlines
-
 # LOCALIZATION NOTE (firefox_heading):
 # Firefox links folder name
 #define firefox_heading Mozilla Firefox
 
 # LOCALIZATION NOTE (firefox_help):
 # link title for http://en-US.www.mozilla.com/en-US/firefox/help/
 #define firefox_help Help and Tutorials
 
--- a/browser/locales/generic/extract-bookmarks.py
+++ b/browser/locales/generic/extract-bookmarks.py
@@ -63,22 +63,16 @@ template = '''#filter emptyLines
 
 #define bookmarks_toolbarfolder %s
 #define bookmarks_toolbarfolder_description %s
 
 # LOCALIZATION NOTE (getting_started):
 # link title for http://www.mozilla.com/en-US/firefox/central/
 #define getting_started %s
 
-# LOCALIZATION NOTE (latest_headlines):
-# link title for the live bookmarks sample, a redirect on
-# http://fxfeeds.mozilla.com/en-US/firefox/headlines.xml
-# Changing the redirect is subject to approval of l10n-drivers.
-#define latest_headlines %s
-
 # LOCALIZATION NOTE (firefox_heading):
 # Firefox links folder name
 #define firefox_heading %s
 
 # LOCALIZATION NOTE (firefox_help):
 # link title for http://www.mozilla.com/en-US/firefox/help/
 #define firefox_help %s
 
--- a/browser/locales/generic/profile/bookmarks.html.in
+++ b/browser/locales/generic/profile/bookmarks.html.in
@@ -7,17 +7,16 @@
 <TITLE>@bookmarks_title@</TITLE>
 <H1>@bookmarks_heading@</H1>
 
 <DL><p>
     <DT><H3 PERSONAL_TOOLBAR_FOLDER="true" ID="rdf:#$FvPhC3">@bookmarks_toolbarfolder@</H3>
 <DD>@bookmarks_toolbarfolder_description@
     <DL><p>
         <DT><A HREF="http://www.mozilla.com/@AB_CD@/firefox/central/" ID="rdf:#$GvPhC3">@getting_started@</A>
-        <DT><A HREF="http://fxfeeds.mozilla.com/@AB_CD@/firefox/livebookmarks/" FEEDURL="http://fxfeeds.mozilla.com/@AB_CD@/firefox/headlines.xml" ID="rdf:#$HvPhC3">@latest_headlines@</A>
     </DL><p>
     <DT><H3 ID="rdf:#$ZvPhC3">@firefox_heading@</H3>
     <DL><p>
         <DT><A HREF="http://www.mozilla.com/@AB_CD@/firefox/help/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$22iCK1">@firefox_help@</A>
         <DT><A HREF="http://www.mozilla.com/@AB_CD@/firefox/customize/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$32iCK1">@firefox_customize@</A>
         <DT><A HREF="http://www.mozilla.com/@AB_CD@/firefox/community/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$42iCK1">@firefox_community@</A>
         <DT><A HREF="http://www.mozilla.com/@AB_CD@/about/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$52iCK1">@firefox_about@</A>
     </DL><p>
--- a/browser/themes/gnomestripe/browser/browser.css
+++ b/browser/themes/gnomestripe/browser/browser.css
@@ -52,16 +52,18 @@
 
 %include ../../browserShared.inc
 %filter substitution
 %define toolbarHighlight rgba(255,255,255,.3)
 %define selectedTabHighlight rgba(255,255,255,.8) 1px, rgba(255,255,255,.5) 3px
 %define forwardTransitionLength 150ms
 %define conditionalForwardWithUrlbar       window:not([chromehidden~=toolbar]) :-moz-any(#nav-bar[currentset*="unified-back-forward-button,urlbar-container"][mode=icons],                #nav-bar:not([currentset])[mode=icons])                 > #unified-back-forward-button
 %define conditionalForwardWithUrlbar_small window:not([chromehidden~=toolbar]) :-moz-any(#nav-bar[currentset*="unified-back-forward-button,urlbar-container"][mode=icons][iconsize=small],#nav-bar:not([currentset])[mode=icons][iconsize=small]) > #unified-back-forward-button
+%define conditionalForwardWithUrlbarWidth 32
+%define conditionalForwardWithUrlbarWidth_small 24
 
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
 }
 
 #main-menubar {
   -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
 }
@@ -625,28 +627,16 @@ toolbar[mode="full"] .toolbarbutton-1 > 
   -moz-transition: @forwardTransitionLength@ ease-out;
 }
 
 @conditionalForwardWithUrlbar@ > #forward-button[disabled] {
   -moz-transform: scale(0);
   opacity: 0;
   pointer-events: none;
 }
-@conditionalForwardWithUrlbar@ > #forward-button[disabled]:-moz-locale-dir(ltr) {
-  margin-left: -36px;
-}
-@conditionalForwardWithUrlbar@ > #forward-button[disabled]:-moz-locale-dir(rtl) {
-  margin-right: -36px;
-}
-@conditionalForwardWithUrlbar_small@ > #forward-button[disabled]:-moz-locale-dir(ltr) {
-  margin-left: -28px;
-}
-@conditionalForwardWithUrlbar_small@ > #forward-button[disabled]:-moz-locale-dir(rtl) {
-  margin-right: -28px;
-}
 
 #reload-button {
   list-style-image: url("moz-icon://stock/gtk-refresh?size=toolbar");
 }
 #reload-button[disabled="true"] {
   list-style-image: url("moz-icon://stock/gtk-refresh?size=toolbar&state=disabled");
 }
 
@@ -960,16 +950,50 @@ toolbar[iconsize="small"] #feed-button {
   -moz-appearance: toolbarbutton-dropdown;
 }
 
 #urlbar-container {
   -moz-box-orient: horizontal;
   -moz-box-align: stretch;
 }
 
+@conditionalForwardWithUrlbar@ + #urlbar-container {
+  -moz-padding-start: @conditionalForwardWithUrlbarWidth@px;
+  -moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
+  position: relative;
+  pointer-events: none;
+}
+
+@conditionalForwardWithUrlbar_small@ + #urlbar-container {
+  -moz-padding-start: @conditionalForwardWithUrlbarWidth_small@px;
+  -moz-margin-start: -@conditionalForwardWithUrlbarWidth_small@px;
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar {
+  pointer-events: all;
+}
+
+@conditionalForwardWithUrlbar@:not([switchingtabs]) + #urlbar-container > #urlbar {
+  -moz-transition: margin-left @forwardTransitionLength@ ease-out,
+                   margin-right @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled] + #urlbar-container > #urlbar:-moz-locale-dir(ltr) {
+  margin-left: -@conditionalForwardWithUrlbarWidth@px;
+}
+@conditionalForwardWithUrlbar@[forwarddisabled] + #urlbar-container > #urlbar:-moz-locale-dir(rtl) {
+  margin-right: -@conditionalForwardWithUrlbarWidth@px;
+}
+@conditionalForwardWithUrlbar_small@[forwarddisabled] + #urlbar-container > #urlbar:-moz-locale-dir(ltr) {
+  margin-left: -@conditionalForwardWithUrlbarWidth_small@px;
+}
+@conditionalForwardWithUrlbar_small@[forwarddisabled] + #urlbar-container > #urlbar:-moz-locale-dir(rtl) {
+  margin-right: -@conditionalForwardWithUrlbarWidth_small@px;
+}
+
 #urlbar-icons {
   -moz-box-align: center;
 }
 
 .urlbar-icon {
   cursor: pointer;
   padding: 0 3px;
 }
@@ -1922,26 +1946,26 @@ panel[dimmed="true"] {
 }
 
 /* Highlighter */
 
 .highlighter-veil {
   background-color: rgba(0, 0, 0, 0.5);
 }
 
-#highlighter-close-button {
-  list-style-image: url("chrome://browser/skin/KUI-close.png");
-  top: 12px;
-  right: 12px;
-  cursor: pointer;
+#highlighter-closebutton {
+  list-style-image: url("moz-icon://stock/gtk-close?size=menu");
+  margin-top: 0;
+  margin-bottom: 0;
 }
 
-#highlighter-close-button:-moz-locale-dir(rtl)  {
-  right: auto;
-  left: 12px;
+#highlighter-closebutton > .toolbarbutton-icon {
+  /* XXX Buttons have padding in widget/ that we don't want here but can't override with good CSS, so we must
+     use evil CSS to give the impression of smaller content */
+  margin: -4px;
 }
 
 #highlighter-veil-transparentbox {
   box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
   outline: 1px dashed rgba(255,255,255,0.5);
   outline-offset: -1px;
 }
 
@@ -1949,22 +1973,27 @@ panel[dimmed="true"] {
   box-shadow: 0 0 0 1px black;
   outline-color: white;
 }
 
 /* Highlighter toolbar */
 
 #inspector-toolbar {
   -moz-appearance: none;
-  padding: 0 3px 4px;
+  padding: 4px 3px;
   border-top: 1px solid hsla(210, 8%, 5%, .65);
   box-shadow: 0 1px 0 0 hsla(210, 16%, 76%, .2) inset;
   background-image: -moz-linear-gradient(top, hsl(210,11%,36%), hsl(210,11%,18%));
 }
 
+#inspector-toolbar[treepanel-open] {
+  padding-top: 0;
+  -moz-padding-end: 0;
+}
+
 #inspector-inspect-toolbutton,
 #inspector-tools > toolbarbutton {
   -moz-appearance: none;
   min-width: 78px;
   min-height: 22px;
   color: hsl(210,30%,85%);
   text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
   border: 1px solid hsla(210,8%,5%,.45);
@@ -2000,20 +2029,16 @@ panel[dimmed="true"] {
 
 /* Highlighter - toolbar resizers */
 
 .inspector-resizer {
   -moz-appearance: none;
   cursor: n-resize;
 }
 
-.inspector-resizer[disabled] {
-  visibility: hidden;
-}
-
 #inspector-top-resizer {
   background: none;
   height: 4px;
 }
 
 #inspector-end-resizer {
   width: 12px;
   height: 8px;
@@ -2082,16 +2107,36 @@ panel[dimmed="true"] {
 #highlighter-nodeinfobar-container[position="overlap"] > #highlighter-nodeinfobar {
   box-shadow: 0 1px 0 hsla(0, 0%, 100%, .1) inset;
 }
 
 #highlighter-nodeinfobar-container[hide-arrow] > #highlighter-nodeinfobar {
   margin: 7px 0;
 }
 
+#full-screen-warning-message {
+  background-color: hsl(0,0%,15%);
+  color: white;
+  font-size: 32px;
+  border-radius: 8px;
+  margin-top: 30px;
+  padding: 30px 50px;
+  box-shadow: 0 0 2px white;
+}
+
+#full-screen-warning-container[obscure-browser] {
+  background-color: rgba(0,0,0,0.75);
+}
+
+#full-screen-warning-container[stop-obscuring-browser] {
+  -moz-transition-property: background-color;
+  -moz-transition-duration: 500ms;
+  background-color: rgba(0,0,0,0);
+}
+
 /* Highlighter toolbar - breadcrumbs */
 
 #inspector-breadcrumbs {
   padding: 0 6px;
   /* A fake 1px-shadow is included in the border-images of the
      inspector-breadcrumbs-buttons, to match toolbar-buttons style.
      This negative margin compensate the extra row of pixels created
      by the shadow.*/
--- a/browser/themes/gnomestripe/browser/devtools/csshtmltree.css
+++ b/browser/themes/gnomestripe/browser/devtools/csshtmltree.css
@@ -182,9 +182,94 @@ a.link:visited {
 .userStyles {
   position: relative;
   top: 3px;
 }
 
 .userStyles,
 .userStylesLabel {
   cursor: pointer;
-}
\ No newline at end of file
+}
+
+/**
+ * CSS Rule View
+ */
+
+.ruleview {
+  background-color: #FFF;
+}
+
+.ruleview-rule-source {
+  background-color: -moz-dialog;
+  padding: 2px;
+}
+
+.ruleview-code {
+  padding: 2px;
+}
+
+.ruleview-propertylist {
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+
+.ruleview-enableproperty {
+  height: 10px;
+  width: 10px;
+  -moz-margin-start: 2px;
+  -moz-margin-end: 0;
+}
+
+.ruleview-expander {
+  display: inline-block;
+  width: 8px;
+  height: 8px;
+  background: url("chrome://browser/skin/devtools/arrows.png") 24px 0;
+  cursor: pointer;
+  -moz-margin-start: 2px;
+  -moz-margin-end: 5px;
+}
+
+.ruleview-expander[dir="rtl"] {
+  background-position: 16px 0;
+}
+
+.ruleview-expander.styleinspector-open {
+  background-position: 8px 0;
+}
+
+.ruleview-newproperty {
+  /* (enable checkbox width: 12px) + (expander width: 15px) */
+  -moz-margin-start: 27px;
+}
+
+.ruleview-propertyname {
+  display: inline-block;
+  padding: 1px 0;
+  cursor: text;
+  color: #0060C0;
+  text-decoration: inherit;
+}
+
+.ruleview-propertyvalue {
+  cursor: text;
+  text-decoration: inherit;
+}
+
+.ruleview-computedlist {
+  list-style: none;
+  padding: 0;
+}
+
+.ruleview-computed {
+  -moz-margin-start: 4em;
+}
+
+.ruleview-overridden {
+  text-decoration: line-through;
+}
+
+.styleinspector-propertyeditor {
+  border: 1px solid #CCC;
+  padding: 0;
+  -moz-box-shadow: 2px 2px 2px #CCC;
+}
--- a/browser/themes/pinstripe/browser/browser.css
+++ b/browser/themes/pinstripe/browser/browser.css
@@ -2517,26 +2517,34 @@ panel[dimmed="true"] {
 
 
 /* Highlighter */
 
 .highlighter-veil {
   background-color: rgba(0, 0, 0, 0.5);
 }
 
-#highlighter-close-button {
-  list-style-image: url("chrome://browser/skin/KUI-close.png");
-  top: 12px;
-  right: 12px;
-  cursor: pointer;
-}
-
-#highlighter-close-button:-moz-locale-dir(rtl)  {
-  right: auto;
-  left: 12px;
+#highlighter-closebutton {
+  list-style-image: url("chrome://browser/skin/devtools/toolbarbutton-close.png");
+  -moz-image-region: rect(0, 16px, 16px, 0);
+  min-width: 16px;
+  width: 16px;
+  margin: 0 4px;
+}
+
+#highlighter-closebutton > .toolbarbutton-text {
+  display: none;
+}
+
+#highlighter-closebutton:hover {
+  -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#highlighter-closebutton:active {
+  -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
 #highlighter-veil-transparentbox {
   box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
   outline: 1px dashed rgba(255,255,255,0.5);
   outline-offset: -1px;
 }
 
@@ -2544,20 +2552,24 @@ panel[dimmed="true"] {
   box-shadow: 0 0 0 1px black;
   outline-color: white;
 }
 
 /* Highlighter toolbar */
 
 #inspector-toolbar {
   -moz-appearance: none;
-  padding: 0 3px 4px;
   border-top: 1px solid hsla(210, 8%, 5%, .65);
   box-shadow: 0 1px 0 0 hsla(210, 16%, 76%, .2) inset;
   background-image: -moz-linear-gradient(top, hsl(210,11%,36%), hsl(210,11%,18%));
+  padding: 4px 16px 4px 0; /* use -moz-padding-end: 16px when/if bug 631729 gets fixed */
+}
+
+#inspector-toolbar[treepanel-open] {
+  padding: 0 0 4px;
 }
 
 #inspector-inspect-toolbutton,
 #inspector-tools > toolbarbutton {
   -moz-appearance: none;
   min-width: 78px;
   min-height: 22px;
   color: hsl(210,30%,85%);
@@ -2595,20 +2607,16 @@ panel[dimmed="true"] {
 
 /* Highlighter - toolbar resizers */
 
 .inspector-resizer {
   -moz-appearance: none;
   cursor: n-resize;
 }
 
-.inspector-resizer[disabled] {
-  visibility: hidden;
-}
-
 #inspector-top-resizer {
   background: none;
   height: 4px;
 }
 
 #inspector-end-resizer {
   width: 12px;
   height: 8px;
@@ -2677,16 +2685,36 @@ panel[dimmed="true"] {
 #highlighter-nodeinfobar-container[position="overlap"] > #highlighter-nodeinfobar {
   box-shadow: 0 1px 0 hsla(0, 0%, 100%, .1) inset;
 }
 
 #highlighter-nodeinfobar-container[hide-arrow] > #highlighter-nodeinfobar {
   margin: 7px 0;
 }
 
+#full-screen-warning-message {
+  background-color: hsl(0,0%,15%);
+  color: white;
+  font-size: 32px;
+  border-radius: 8px;
+  margin-top: 30px;
+  padding: 30px 50px;
+  box-shadow: 0 0 2px white;
+}
+
+#full-screen-warning-container[obscure-browser] {
+  background-color: rgba(0,0,0,0.75);
+}
+
+#full-screen-warning-container[stop-obscuring-browser] {
+  -moz-transition-property: background-color;
+  -moz-transition-duration: 500ms;
+  background-color: rgba(0,0,0,0);
+}
+
 /* Highlighter toolbar - breadcrumbs */
 
 #inspector-breadcrumbs {
   padding: 0 6px;
   /* A fake 1px-shadow is included in the border-images of the
      inspector-breadcrumbs-buttons, to match toolbar-buttons style.
      This negative margin compensate the extra row of pixels created
      by the shadow.*/
--- a/browser/themes/pinstripe/browser/devtools/csshtmltree.css
+++ b/browser/themes/pinstripe/browser/devtools/csshtmltree.css
@@ -182,9 +182,94 @@ a.link:visited {
 .userStyles {
   position: relative;
   top: 3px;
 }
 
 .userStyles,
 .userStylesLabel {
   cursor: pointer;
-}
\ No newline at end of file
+}
+
+/**
+ * CSS Rule View
+ */
+
+.ruleview {
+  background-color: #FFF;
+}
+
+.ruleview-rule-source {
+  background-color: -moz-dialog;
+  padding: 2px;
+}
+
+.ruleview-code {
+  padding: 2px;
+}
+
+.ruleview-propertylist {
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+
+.ruleview-enableproperty {
+  height: 10px;
+  width: 10px;
+  -moz-margin-start: 2px;
+  -moz-margin-end: 0;
+}
+
+.ruleview-expander {
+  display: inline-block;
+  width: 8px;
+  height: 8px;
+  background: url("chrome://browser/skin/devtools/arrows.png") 24px 0;
+  cursor: pointer;
+  -moz-margin-start: 2px;
+  -moz-margin-end: 5px;
+}
+
+.ruleview-expander[dir="rtl"] {
+  background-position: 16px 0;
+}
+
+.ruleview-expander.styleinspector-open {
+  background-position: 8px 0;
+}
+
+.ruleview-newproperty {
+  /* (enable checkbox width: 12px) + (expander width: 15px) */
+  -moz-margin-start: 27px;
+}
+
+.ruleview-propertyname {
+  display: inline-block;
+  padding: 1px 0;
+  cursor: text;
+  color: #0060C0;
+  text-decoration: inherit;
+}
+
+.ruleview-propertyvalue {
+  cursor: text;
+  text-decoration: inherit;
+}
+
+.ruleview-computedlist {
+  list-style: none;
+  padding: 0;
+}
+
+.ruleview-computed {
+  -moz-margin-start: 4em;
+}
+
+.ruleview-overridden {
+  text-decoration: line-through;
+}
+
+.styleinspector-propertyeditor {
+  border: 1px solid #CCC;
+  padding: 0;
+  -moz-box-shadow: 2px 2px 2px #CCC;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..887daf052a6eda0b463bd75ca65dd998449cd330
GIT binary patch
literal 1031
zc$@(T1o-=jP)<h;3K|Lk000e1NJLTq001xm000mO1^@s6P_F#3000BdNkl<Zc-qyL
z`%6;+6vrzxC|2N<)IIeV(`|0ri^4K3dk{pC6+J16%F13^Nf9Jj8A_#<W<vDRirxho
zK3ZkYHudh_HIt>573nYalmCEyzi@?1xZPj;#UDO+IOlt|^L6KJcihm>(3oaaUPSC3
z7!Sh0B%lFPf}o*>TJ|XV$pjD%CaORUwWDevZ2rQ<nMQMbO>~UzJ#Yr$JvG#_N6|mQ
znw*lM(;Mrc=?eul)Uszp0}zpPFqN^424sL>1}5W$U!r~sRDcJd3Y5uQ2M<}JhFbP0
z`om*%1}|z|;IlR|s(V^=tRJyN7$eui8a33iCzydnyY?M^ot!fBqHIEAu{lci<mSId
zyflb`DX2XL7r_18L-`)7&B0Hi6qF<;S)B*-j<?YSHPo_4(LWKnZXjy4Q9TKXmbY4M
zWG@DnNJM1Rx9Lf?rU-3RFE!M%XG8-|Pv<5SJ6<)lx@dyKzo>6%Vb1CHZwq0d1{9Z;
zRn+$X8T7l_B++8Ew<cO_uI5jI$Xt0v^#f|C9l<~Vh-vAWH=Q2;vi(O2j>5!u4BeM=
zAisdQ1({hlsG&Bf0Td#H!0$)HCWePc?m7NOhA?msoKH!eb*HQMSL49nL9wMx^ftQ%
zFXOL${cmT@nRk~4s10JkYcR!mj-M*ZZgfd$dk-DU+nt+tjPa?WlGP@2yoZ{Q2Ds<N
zmnseQ&B8klPVfTFXG^cfv8H0+45+l(Q{H&Iou4qcmk9oV+mSl6jx}mR7!bf`ox$YW
zx#z&P2A4RG<QzP@S#LB+tWl$8K%-n6<vQ^uR16e?%hQvr!iUz5b__3&7Pq(EVzcv&
z%c%)rpaVPa!`l4ZynT0W{YPOo*}5Yq2S@yaHEKc{n47k6rE+bQ>$D&vD_g}t5svo>
zcb;n#`2M@;OV7^+d7UJ?W5!czLVW_$qIG_(O##>DWMT2Dyc4Ih8S^HhV`BZ(sNKL?
z<<2W_0&G+<P>e&p*Z+6WhvA)An?`H0qk%WUTxIp`T9q3R(-$qNYHIbRbLVjnlHOpJ
zcoWQJW-YIvM)d|zT+iVd6PjR-vC6m5d2pxn^0oV^GiE<We3K;GQ=c)%n##A37j(^;
zJHOm$ifc#QN%Tf@2Xm~c9I+}vf5`BL9PR?R1a5$<;GE1A=2)YK+W&vZ^ahh#{_jfi
z;65M_#&S-ghFbQFrh)JcTXw9$VAUK{rV3ON#>07PsAZ4xQDWJORqJFEUleo_WsVwZ
z*)!S?a~cp25>%i@Gs45%Bwz$)6^zs>ALhn1{{Yzl!O-c}?LPnj002ovPDHLkV1g8J
B@QeTe
--- a/browser/themes/pinstripe/browser/jar.mn
+++ b/browser/themes/pinstripe/browser/jar.mn
@@ -121,16 +121,17 @@ browser.jar:
   skin/classic/browser/tabview/search.png                   (tabview/search.png)
   skin/classic/browser/tabview/stack-expander.png           (tabview/stack-expander.png)
   skin/classic/browser/tabview/tabview.png                  (tabview/tabview.png)
   skin/classic/browser/tabview/tabview.css                  (tabview/tabview.css)
   skin/classic/browser/devtools/arrows.png                  (devtools/arrows.png)
   skin/classic/browser/devtools/search.png                  (devtools/search.png)
   skin/classic/browser/devtools/csshtmltree.css             (devtools/csshtmltree.css)
   skin/classic/browser/devtools/gcli.css                    (devtools/gcli.css)
+  skin/classic/browser/devtools/toolbarbutton-close.png     (devtools/toolbarbutton-close.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png              (devtools/breadcrumbs/ltr-end-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png     (devtools/breadcrumbs/ltr-end-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-selected.png             (devtools/breadcrumbs/ltr-end-selected.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end.png                      (devtools/breadcrumbs/ltr-end.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle-pressed.png           (devtools/breadcrumbs/ltr-middle-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png  (devtools/breadcrumbs/ltr-middle-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected.png          (devtools/breadcrumbs/ltr-middle-selected.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle.png                   (devtools/breadcrumbs/ltr-middle.png)
--- a/browser/themes/winstripe/browser/browser.css
+++ b/browser/themes/winstripe/browser/browser.css
@@ -2615,26 +2615,29 @@ panel[dimmed="true"] {
 }
 
 /* Highlighter */
 
 .highlighter-veil {
   background-color: rgba(0, 0, 0, 0.5);
 }
 
-#highlighter-close-button {
-  list-style-image: url("chrome://browser/skin/KUI-close.png");
-  top: 12px;
-  right: 12px;
-  cursor: pointer;
-}
-
-#highlighter-close-button:-moz-locale-dir(rtl)  {
-  right: auto;
-  left: 12px;
+#highlighter-closebutton {
+  list-style-image: url("chrome://browser/skin/devtools/toolbarbutton-close.png");
+  -moz-image-region: rect(0, 16px, 16px, 0);
+  min-width: 16px;
+  width: 16px;
+}
+
+#highlighter-closebutton:hover {
+  -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#highlighter-closebutton:active {
+  -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
 #highlighter-veil-transparentbox {
   box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
   outline: 1px dashed rgba(255,255,255,0.5);
   outline-offset: -1px;
 }
 
@@ -2642,22 +2645,27 @@ panel[dimmed="true"] {
   box-shadow: 0 0 0 1px black;
   outline-color: white;
 }
 
 /* Highlighter toolbar */
 
 #inspector-toolbar {
   -moz-appearance: none;
-  padding: 0 3px 4px;
+  padding: 4px 3px;
   border-top: 1px solid hsla(211,68%,6%,.65) !important;
   box-shadow: 0 1px 0 hsla(209,29%,72%,.25) inset;
   background-image: -moz-linear-gradient(top, hsl(209,18%,34%), hsl(210,24%,16%));
 }
 
+#inspector-toolbar[treepanel-open] {
+  padding-top: 0;
+  -moz-padding-end: 0;
+}
+
 #inspector-inspect-toolbutton,
 #inspector-tools > toolbarbutton {
   -moz-appearance: none;
   min-width: 78px;
   min-height: 22px;
   color: hsl(210,30%,85%);
   text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
   border: 1px solid hsla(211,68%,6%,.5);
@@ -2693,20 +2701,16 @@ panel[dimmed="true"] {
 
 /* Highlighter - toolbar resizers */
 
 .inspector-resizer {
   -moz-appearance: none;
   cursor: n-resize;
 }
 
-.inspector-resizer[disabled] {
-  visibility: hidden;
-}
-
 #inspector-top-resizer {
   background: none;
   height: 4px;
 }
 
 #inspector-end-resizer {
   width: 12px;
   height: 8px;
@@ -2775,16 +2779,36 @@ panel[dimmed="true"] {
 #highlighter-nodeinfobar-container[position="overlap"] > #highlighter-nodeinfobar {
   box-shadow: 0 1px 0 hsla(0, 0%, 100%, .1) inset;
 }
 
 #highlighter-nodeinfobar-container[hide-arrow] > #highlighter-nodeinfobar {
   margin: 7px 0;
 }
 
+#full-screen-warning-message {
+  background-color: hsl(0,0%,15%);
+  color: white;
+  font-size: 32px;
+  border-radius: 8px;
+  margin-top: 30px;
+  padding: 30px 50px;
+  box-shadow: 0 0 2px white;
+}
+
+#full-screen-warning-container[obscure-browser] {
+  background-color: rgba(0,0,0,0.75);
+}
+
+#full-screen-warning-container[stop-obscuring-browser] {
+  -moz-transition-property: background-color;
+  -moz-transition-duration: 500ms;
+  background-color: rgba(0,0,0,0);
+}
+
 /* Highlighter toolbar - breadcrumbs */
 
 #inspector-breadcrumbs {
   padding: 0 6px;
   /* A fake 1px-shadow is included in the border-images of the
      inspector-breadcrumbs-buttons, to match toolbar-buttons style.
      This negative margin compensate the extra row of pixels created
      by the shadow.*/
--- a/browser/themes/winstripe/browser/devtools/csshtmltree.css
+++ b/browser/themes/winstripe/browser/devtools/csshtmltree.css
@@ -182,9 +182,94 @@ a.link:visited {
 .userStyles {
   position: relative;
   top: 3px;
 }
 
 .userStyles,
 .userStylesLabel {
   cursor: pointer;
-}
\ No newline at end of file
+}
+
+/**
+ * CSS Rule View
+ */
+
+.ruleview {
+  background-color: #FFF;
+}
+
+.ruleview-rule-source {
+  background-color: -moz-dialog;
+  padding: 2px;
+}
+
+.ruleview-code {
+  padding: 2px;
+}
+
+.ruleview-propertylist {
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+
+.ruleview-enableproperty {
+  height: 10px;
+  width: 10px;
+  -moz-margin-start: 2px;
+  -moz-margin-end: 0;
+}
+
+.ruleview-expander {
+  display: inline-block;
+  width: 8px;
+  height: 8px;
+  background: url("chrome://browser/skin/devtools/arrows.png") 24px 0;
+  cursor: pointer;
+  -moz-margin-start: 2px;
+  -moz-margin-end: 5px;
+}
+
+.ruleview-expander[dir="rtl"] {
+  background-position: 16px 0;
+}
+
+.ruleview-expander.styleinspector-open {
+  background-position: 8px 0;
+}
+
+.ruleview-newproperty {
+  /* (enable checkbox width: 12px) + (expander width: 15px) */
+  -moz-margin-start: 27px;
+}
+
+.ruleview-propertyname {
+  display: inline-block;
+  padding: 1px 0;
+  cursor: text;
+  color: #0060C0;
+  text-decoration: inherit;
+}
+
+.ruleview-propertyvalue {
+  cursor: text;
+  text-decoration: inherit;
+}
+
+.ruleview-computedlist {
+  list-style: none;
+  padding: 0;
+}
+
+.ruleview-computed {
+  -moz-margin-start: 4em;
+}
+
+.ruleview-overridden {
+  text-decoration: line-through;
+}
+
+.styleinspector-propertyeditor {
+  border: 1px solid #CCC;
+  padding: 0;
+  -moz-box-shadow: 2px 2px 2px #CCC;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4a58465b9b7ea3b3c7c14b36214524fb9b0e38b7
GIT binary patch
literal 898
zc$@)(1AY97P)<h;3K|Lk000e1NJLTq001xm000mO1^@s6P_F#30009?Nkl<Zc-qaD
z-%C?*7{<E^ys6=GX#TF*%%jfDG@>G?$RO%(D9DVE2wO>|IdyGXXbKuAMWsXumi@rW
zizxG=3lSo^$*@vs{TpxZ=io>?+i~PBx_CVA-uLse=lObewsj)1Carg5ulx8l>ikEo
z`ThB`CSjy(=l*>gv)qjuW-uASvcvAuAyldwlHG@8CuIRJG;-)bbn4h)nLJiwD;QXI
zVgb^ed4Z!F9MR(rht#I0ORduxSIl5w*-2RdPGw^6Zh7SQ%fnR!1Itb<0CjyvhP3AA
zOXt?Da^3BYD`qgT?4&fn^PSZ)tmf|fd~&a%LWcCpYeY?t8LySK1h|l$Enzj-?eR$0
zwr%G1W^plE(Sw0a7-0I->}l1E>J%SPL`{!bq5xynRi??w&KnY4h|1e=hurac5j8z#
zCZnt+Kw$G`xu)h`UyI03Wyjk>a-*mSQPX2)GO!5)=%A`8+|&1d`tt{jBWilg5(OCD
zQ7O08Y`<a1%hr%Aep{3``i6*_9y1taEdd(TRNIy<5>kUtnu9Vww;(Tq?TDHlGn0X}
z3$P-@a`Vg*1sJLH%V2rA45)fbN@QFOFQ{=uO^+FjvX%hnva%#tSSS%a@m4kYcqs&;
zrpF8h);0hd93E|&n3{QqsOi}Sc;NR*e_5IIsd^2=H2r<{2co9O%w&|c1UQ?SDa{21
z((ZPfW=ES_WbVs%L`{#G$-vqMSbp0eYI=47hI|#$Q(7wBdeW~#VLkZ;c@}I()byB{
z46NlTq^2(G6XvQK9cyVbPkyAS1yR#uW-|450sg+{(ee<90^F_eN|#}nM(5S=Q#Bs(
zcn~!`W+tPoCBPXq)u`sif`80k($hw<5X@j;?fwIms=C-ckErP}OBCR?-Wr|Tw@XML
zcR{zCE3f1Bl(ht?bGamtn=6fZd2(K_7xj8Y?{`E^j~NVXioXX4P%0hz%#N%g7-cO1
zPB@)XuOH5aoE*%?6*CxEc2fCMB5>U>76;VWty05QFtF^z0;K17E*^2DFCJH8C-qyl
z*5!&TW-zeqq%44U*TFp)O7_PXEIY9P{yjB&F_i2+EIaJ}4?m}M|5uu7lPWt&!KShM
Y8%Lp~>GYClRsaA107*qoM6N<$g8i4Sr2qf`
--- a/browser/themes/winstripe/browser/jar.mn
+++ b/browser/themes/winstripe/browser/jar.mn
@@ -105,16 +105,17 @@ browser.jar:
         skin/classic/browser/tabview/stack-expander.png             (tabview/stack-expander.png)
         skin/classic/browser/tabview/tabview.png                    (tabview/tabview.png)
         skin/classic/browser/tabview/tabview-inverted.png           (tabview/tabview-inverted.png)
         skin/classic/browser/tabview/tabview.css                    (tabview/tabview.css)
         skin/classic/browser/devtools/arrows.png                    (devtools/arrows.png)
         skin/classic/browser/devtools/search.png                    (devtools/search.png)
         skin/classic/browser/devtools/csshtmltree.css               (devtools/csshtmltree.css)
         skin/classic/browser/devtools/gcli.css                      (devtools/gcli.css)
+        skin/classic/browser/devtools/toolbarbutton-close.png       (devtools/toolbarbutton-close.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png              (devtools/breadcrumbs/ltr-end-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png     (devtools/breadcrumbs/ltr-end-selected-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-end-selected.png             (devtools/breadcrumbs/ltr-end-selected.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-end.png                      (devtools/breadcrumbs/ltr-end.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-middle-pressed.png           (devtools/breadcrumbs/ltr-middle-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png  (devtools/breadcrumbs/ltr-middle-selected-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected.png          (devtools/breadcrumbs/ltr-middle-selected.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-middle.png                   (devtools/breadcrumbs/ltr-middle.png)
@@ -256,16 +257,17 @@ browser.jar:
         skin/classic/aero/browser/tabview/stack-expander.png         (tabview/stack-expander.png)
         skin/classic/aero/browser/tabview/tabview.png                (tabview/tabview.png)
         skin/classic/aero/browser/tabview/tabview-inverted.png       (tabview/tabview-inverted.png)
         skin/classic/aero/browser/tabview/tabview.css                (tabview/tabview.css)
         skin/classic/aero/browser/devtools/arrows.png                (devtools/arrows.png)
         skin/classic/aero/browser/devtools/search.png                (devtools/search.png)
         skin/classic/aero/browser/devtools/csshtmltree.css           (devtools/csshtmltree.css)
         skin/classic/aero/browser/devtools/gcli.css                  (devtools/gcli.css)
+        skin/classic/aero/browser/devtools/toolbarbutton-close.png   (devtools/toolbarbutton-close.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-pressed.png              (devtools/breadcrumbs/ltr-end-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png     (devtools/breadcrumbs/ltr-end-selected-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-selected.png             (devtools/breadcrumbs/ltr-end-selected.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-end.png                      (devtools/breadcrumbs/ltr-end.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle-pressed.png           (devtools/breadcrumbs/ltr-middle-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png  (devtools/breadcrumbs/ltr-middle-selected-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle-selected.png          (devtools/breadcrumbs/ltr-middle-selected.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle.png                   (devtools/breadcrumbs/ltr-middle.png)
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -649,22 +649,17 @@ user_pref("camino.use_system_proxy_setti
     if crashreporter:
       env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
       env['MOZ_CRASHREPORTER'] = '1'
     else:
       env['MOZ_CRASHREPORTER_DISABLE'] = '1'
 
     env['GNOME_DISABLE_CRASH_DIALOG'] = '1'
     env['XRE_NO_WINDOWS_CRASH_DIALOG'] = '1'
-
-    # Don't do this for Mac since it makes the Mac OS X 10.5 (32-bit)
-    # trace-malloc leak test hang.  (It doesn't make the 10.6 (64-bit)
-    # leak test hang, though.)
-    if not self.IS_MAC:
-      env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1'
+    env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1'
     return env
 
   if IS_WIN32:
     PeekNamedPipe = ctypes.windll.kernel32.PeekNamedPipe
     GetLastError = ctypes.windll.kernel32.GetLastError
 
     def readWithTimeout(self, f, timeout):
       """Try to read a line of output from the file object |f|.
@@ -739,42 +734,66 @@ user_pref("camino.use_system_proxy_setti
         # and re-raise any others
         if err.errno == errno.ESRCH or err.errno == errno.ECHILD:
           return False
         raise
 
     def killPid(self, pid):
       os.kill(pid, signal.SIGKILL)
 
-  if UNIXISH:
     def dumpScreen(self, utilityPath):
       self.haveDumpedScreen = True;
 
-      screentopng = os.path.join(utilityPath, "screentopng")
+      # Need to figure out what tool and whether it write to a file or stdout
+      if self.UNIXISH:
+        utility = [os.path.join(utilityPath, "screentopng")]
+        imgoutput = 'stdout'
+      elif self.IS_MAC:
+        utility = ['/usr/sbin/screencapture', '-C', '-x', '-t', 'png']
+        imgoutput = 'file'
+      elif self.IS_WIN32:
+        self.log.info("If you fixed bug 589668, you'd get a screenshot here")
+        return
+
+      # Run the capture correctly for the type of capture
       try:
-        dumper = self.Process([screentopng], bufsize=-1,
-                              stdout=subprocess.PIPE, close_fds=True)
+        if imgoutput == 'file':
+          tmpfd, imgfilename = tempfile.mkstemp(prefix='mozilla-test-fail_')
+          os.close(tmpfd)
+          dumper = self.Process(utility + [imgfilename])
+        elif imgoutput == 'stdout':
+          dumper = self.Process(utility, bufsize=-1,
+                                stdout=subprocess.PIPE, close_fds=True)
       except OSError, err:
         self.log.info("Failed to start %s for screenshot: %s",
-                      screentopng, err.strerror)
+                      utility[0], err.strerror)
+        return
+
+      # Check whether the capture utility ran successfully
+      dumper_out, dumper_err = dumper.communicate()
+      if dumper.returncode != 0:
+        self.log.info("%s exited with code %d", utility, dumper.returncode)
         return
 
-      image = dumper.stdout.read()
-      status = dumper.wait()
-      if status != 0:
-        self.log.info("screentopng exited with code %d", status)
-        return
+      try:
+        if imgoutput == 'stdout':
+          image = dumper_out
+        elif imgoutput == 'file':
+          with open(imgfilename) as imgfile:
+            image = imgfile.read()
+      except IOError, err:
+          self.log.info("Failed to read image from %s", imgoutput)
 
       import base64
       encoded = base64.b64encode(image)
       self.log.info("SCREENSHOT: data:image/png;base64,%s", encoded)
 
   def killAndGetStack(self, proc, utilityPath, debuggerInfo):
     """Kill the process, preferrably in a way that gets us a stack trace."""
-    if self.UNIXISH and not debuggerInfo and not self.haveDumpedScreen:
+    if not debuggerInfo and not self.haveDumpedScreen:
       self.dumpScreen(utilityPath)
 
     if self.CRASHREPORTER and not debuggerInfo:
       if self.UNIXISH:
         # ABRT will get picked up by Breakpad's signal handler
         os.kill(proc.pid, signal.SIGABRT)
         return
       elif self.IS_WIN32:
@@ -820,17 +839,17 @@ user_pref("camino.use_system_proxy_setti
 
       (line, didTimeout) = self.readWithTimeout(logsource, timeout)
       while line != "" and not didTimeout:
         if "TEST-START" in line and "|" in line:
           self.lastTestSeen = line.split("|")[1].strip()
         if stackFixerFunction:
           line = stackFixerFunction(line)
         self.log.info(line.rstrip().decode("UTF-8", "ignore"))
-        if self.UNIXISH and not debuggerInfo and not self.haveDumpedScreen and "TEST-UNEXPECTED-FAIL" in line and "Test timed out" in line:
+        if not debuggerInfo and not self.haveDumpedScreen and "TEST-UNEXPECTED-FAIL" in line and "Test timed out" in line:
           self.dumpScreen(utilityPath)
 
         (line, didTimeout) = self.readWithTimeout(logsource, timeout)
         if not hitMaxTime and maxTime and datetime.now() - startTime > timedelta(seconds = maxTime):
           # Kill the application, but continue reading from stack fixer so as not to deadlock on stackFixerProcess.wait().
           hitMaxTime = True
           self.log.info("TEST-UNEXPECTED-FAIL | %s | application ran for longer than allowed maximum time of %d seconds", self.lastTestSeen, int(maxTime))
           self.killAndGetStack(proc, utilityPath, debuggerInfo)
deleted file mode 100644
--- a/build/pymake/.hg_archival.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-repo: f5ab154deef2ffa97f1b2139589ae4a1962090a4
-node: 84ced2ab192d4b2ad796110d4c62fabc9ac6968d
-branch: default
-latesttag: null
-latesttagdistance: 257
--- a/build/pymake/pymake/data.py
+++ b/build/pymake/pymake/data.py
@@ -41,17 +41,18 @@ def getmtime(path):
     try:
         s = os.stat(path)
         return s.st_mtime
     except OSError:
         return None
 
 def stripdotslash(s):
     if s.startswith('./'):
-        return s[2:]
+        st = s[2:]
+        return st if st != '' else '.'
     return s
 
 def stripdotslashes(sl):
     for s in sl:
         yield stripdotslash(s)
 
 def getindent(stack):
     return ''.ljust(len(stack) - 1)
--- a/build/pymake/pymake/functions.py
+++ b/build/pymake/pymake/functions.py
@@ -649,17 +649,17 @@ class InfoFunction(Function):
     name = 'info'
     minargs = 1
     maxargs = 1
 
     __slots__ = Function.__slots__
 
     def resolve(self, makefile, variables, fd, setting):
         v = self._arguments[0].resolvestr(makefile, variables, setting)
-        log.info(v)
+        print v
 
 functionmap = {
     'subst': SubstFunction,
     'patsubst': PatSubstFunction,
     'strip': StripFunction,
     'findstring': FindstringFunction,
     'filter': FilterFunction,
     'filter-out': FilteroutFunction,
--- a/build/pymake/pymake/process.py
+++ b/build/pymake/pymake/process.py
@@ -10,17 +10,17 @@ import subprocess, shlex, re, logging, s
 subprocess._cleanup = lambda: None
 import command, util
 if sys.platform=='win32':
     import win32process
 
 _log = logging.getLogger('pymake.process')
 
 _escapednewlines = re.compile(r'\\\n')
-_blacklist = re.compile(r'[$><;[{~`|&]')
+_blacklist = re.compile(r'[$><;[{~`|&()]')
 _needsglob = re.compile(r'[\*\?]')
 def clinetoargv(cline):
     """
     If this command line can safely skip the shell, return an argv array.
     @returns argv, badchar
     """
 
     str = _escapednewlines.sub('', cline)
new file mode 100644
--- /dev/null
+++ b/build/pymake/tests/dotslash-dir.mk
@@ -0,0 +1,8 @@
+#T grep-for: "dotslash-built"
+.PHONY: $(dir foo)
+
+all: $(dir foo)
+	@echo TEST-PASS
+
+$(dir foo):
+	@echo dotslash-built
new file mode 100644
--- /dev/null
+++ b/build/pymake/tests/dotslash-parse.mk
@@ -0,0 +1,4 @@
+./:
+
+# This is merely a test to see that pymake doesn't choke on parsing ./
+$(info TEST-PASS)
new file mode 100644
--- /dev/null
+++ b/build/pymake/tests/dotslash-phony.mk
@@ -0,0 +1,3 @@
+.PHONY: ./
+./:
+	@echo TEST-PASS
new file mode 100644
--- /dev/null
+++ b/build/pymake/tests/info.mk
@@ -0,0 +1,8 @@
+#T grep-for: "info-printed\ninfo-nth"
+all:
+
+INFO = info-printed
+
+$(info $(INFO))
+$(info $(subst second,nth,info-second))
+$(info TEST-PASS)
new file mode 100644
--- /dev/null
+++ b/build/pymake/tests/parentheses.mk
@@ -0,0 +1,2 @@
+all:
+	@(echo TEST-PASS)
--- a/build/pymake/tests/runtests.py
+++ b/build/pymake/tests/runtests.py
@@ -142,17 +142,17 @@ for makefile in makefiles:
             d['returncode'] = data
         elif key == 'returncode-on':
             if sys.platform in data:
                 d['returncode'] = data[sys.platform]
         elif key == 'environment':
             for k, v in data.iteritems():
                 d['env'][k] = v
         elif key == 'grep-for':
-            grepfor = data
+            d['grepfor'] = data
         elif key == 'fail':
             d['pass'] = False
         elif key == 'skip':
             d['skip'] = True
         else:
             print >>sys.stderr, "%s: Unexpected #T key: %s" % (makefile, key)
             sys.exit(1)
 
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -102,16 +102,17 @@ MOZ_DEBUG_DISABLE_DEFS	= @MOZ_DEBUG_DISA
 MOZ_DEBUG_FLAGS	= @MOZ_DEBUG_FLAGS@
 MOZ_DEBUG_LDFLAGS=@MOZ_DEBUG_LDFLAGS@
 MOZ_EXTENSIONS  = @MOZ_EXTENSIONS@
 MOZ_JSDEBUGGER  = @MOZ_JSDEBUGGER@
 MOZ_IPDL_TESTS 	= @MOZ_IPDL_TESTS@
 MOZ_LEAKY	= @MOZ_LEAKY@
 MOZ_MEMORY      = @MOZ_MEMORY@
 MOZ_PROFILING   = @MOZ_PROFILING@
+MOZ_ENABLE_PROFILER_SPS = @MOZ_ENABLE_PROFILER_SPS@
 MOZ_JPROF       = @MOZ_JPROF@
 MOZ_SHARK       = @MOZ_SHARK@
 MOZ_CALLGRIND   = @MOZ_CALLGRIND@
 MOZ_VTUNE       = @MOZ_VTUNE@
 MOZ_ETW         = @MOZ_ETW@
 MOZ_TRACE_JSCALLS = @MOZ_TRACE_JSCALLS@
 MOZ_TRACEVIS    = @MOZ_TRACEVIS@
 DEHYDRA_PATH    = @DEHYDRA_PATH@
@@ -464,16 +465,17 @@ PNG_CFLAGS	= @MOZ_PNG_CFLAGS@
 PNG_LIBS	= @MOZ_PNG_LIBS@
 PNG_REQUIRES	= png
 endif
 
 QCMS_LIBS	= @QCMS_LIBS@
 
 MOZ_HARFBUZZ_LIBS = @MOZ_HARFBUZZ_LIBS@
 MOZ_OTS_LIBS = @MOZ_OTS_LIBS@
+MOZ_SKIA_LIBS = @MOZ_SKIA_LIBS@
 
 MOZ_NATIVE_SQLITE = @MOZ_NATIVE_SQLITE@
 SQLITE_CFLAGS     = @SQLITE_CFLAGS@
 SQLITE_LIBS       = @SQLITE_LIBS@
 
 NSPR_CONFIG	= @NSPR_CONFIG@
 NSPR_CFLAGS	= @NSPR_CFLAGS@
 NSPR_LIBS	= @NSPR_LIBS@
--- a/config/gcc-stl-wrapper.template.h
+++ b/config/gcc-stl-wrapper.template.h
@@ -36,17 +36,19 @@
  * 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 ***** */
 
 #ifndef mozilla_${HEADER}_h
 #define mozilla_${HEADER}_h
 
-#if __EXCEPTIONS
+// For some reason, Apple's GCC refuses to honor -fno-exceptions when
+// compiling ObjC.
+#if __EXCEPTIONS && !(__OBJC__ && __GNUC__ && XP_IOS)
 #  error "STL code can only be used with -fno-exceptions"
 #endif
 
 // Silence "warning: #include_next is a GCC extension"
 #pragma GCC system_header
 
 // mozalloc.h wants <new>; break the cycle by always explicitly
 // including <new> here.  NB: this is a tad sneaky.  Sez the gcc docs:
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -766,17 +766,17 @@ endif # EXPORT_LIBRARY
 endif # LIBRARY_NAME
 
 ifneq (,$(filter-out %.$(LIB_SUFFIX),$(SHARED_LIBRARY_LIBS)))
 $(error SHARED_LIBRARY_LIBS must contain .$(LIB_SUFFIX) files only)
 endif
 
 # Create dependencies on static (and shared EXTRA_DSO_LIBS) libraries
 DO_EXPAND_LIBS = $(foreach f,$(1),$(if $(filter %.$(LIB_SUFFIX),$(f)),$(if $(wildcard $(f).$(LIBS_DESC_SUFFIX)),$(f).$(LIBS_DESC_SUFFIX),$(if $(wildcard $(f)),$(f)))))
-LIBS_DEPS = $(call DO_EXPAND_LIBS,$(filter %.$(LIB_SUFFIX),$(LIBS)))
+LIBS_DEPS = $(call DO_EXPAND_LIBS,$(filter %.$(LIB_SUFFIX),$(LIBS) $(if $(PROGRAM)$(SIMPLE_PROGRAMS),$(MOZ_UTILS_PROGRAM_LDFLAGS))))
 SHARED_LIBRARY_LIBS_DEPS = $(call DO_EXPAND_LIBS,$(SHARED_LIBRARY_LIBS))
 HOST_LIBS_DEPS = $(filter %.$(LIB_SUFFIX),$(HOST_LIBS))
 DSO_LDOPTS_DEPS = $(call DO_EXPAND_LIBS,$(EXTRA_DSO_LIBS) $(filter %.$(LIB_SUFFIX), $(EXTRA_DSO_LDOPTS)))
 
 # Dependencies which, if modified, should cause everything to rebuild
 GLOBAL_DEPS += Makefile Makefile.in $(DEPTH)/config/autoconf.mk $(topsrcdir)/config/config.mk
 
 ##############################################
@@ -1631,19 +1631,19 @@ chrome::
 	$(MAKE) realchrome
 	$(LOOP_OVER_PARALLEL_DIRS)
 	$(LOOP_OVER_DIRS)
 	$(LOOP_OVER_TOOL_DIRS)
 
 $(FINAL_TARGET)/chrome:
 	$(NSINSTALL) -D $@
 
-libs realchrome:: $(CHROME_DEPS) $(FINAL_TARGET)/chrome
 ifneq (,$(wildcard $(JAR_MANIFEST)))
 ifndef NO_DIST_INSTALL
+libs realchrome:: $(CHROME_DEPS) $(FINAL_TARGET)/chrome
 	$(PYTHON) $(MOZILLA_DIR)/config/JarMaker.py \
 	  $(QUIET) -j $(FINAL_TARGET)/chrome \
 	  $(MAKE_JARS_FLAGS) $(XULPPFLAGS) $(DEFINES) $(ACDEFINES) \
 	  $(JAR_MANIFEST)
 endif
 endif
 
 ifneq ($(DIST_FILES),)
--- a/configure.in
+++ b/configure.in
@@ -368,55 +368,25 @@ case "$target" in
     CXX="$android_toolchain"/bin/"$android_tool_prefix"-g++
     CPP="$android_toolchain"/bin/"$android_tool_prefix"-cpp
     LD="$android_toolchain"/bin/"$android_tool_prefix"-ld
     AR="$android_toolchain"/bin/"$android_tool_prefix"-ar
     RANLIB="$android_toolchain"/bin/"$android_tool_prefix"-ranlib
     STRIP="$android_toolchain"/bin/"$android_tool_prefix"-strip
     OBJCOPY="$android_toolchain"/bin/"$android_tool_prefix"-objcopy
 
-    case "${target_cpu}" in
-    arm*)
-      ANDROID_CPU_ARCH=armeabi-v7a
-      ;;
-    i?86)
-      ANDROID_CPU_ARCH=x86
-      ;;
-    esac
-
-    if test -n "$MOZ_ANDROID_LIBSTDCXX" ; then
-       if test ! -e "$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH/libstdc++.a" ; then
-          AC_MSG_ERROR([Cannot find path to libstdc++ (NDK version >= 5?)])
-       fi
-       STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/gnu-libstdc++/include -I$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH/include -D_GLIBCXX_PERMIT_BACKWARD_HASH"
-       STLPORT_LDFLAGS="-L$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH"
-       STLPORT_LIBS="-lstdc++"
-    elif test -e "$android_ndk/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/libstlport_static.a" ; then
-       STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/stlport/stlport"
-       STLPORT_LDFLAGS="-L$android_ndk/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/"
-       STLPORT_LIBS="-lstlport_static"
-    elif  test -e "$android_ndk/tmp/ndk-digit/build/install/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/libstlport_static.a" ; then
-       STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/stlport/stlport"
-       STLPORT_LDFLAGS="-L$android_ndk/tmp/ndk-digit/build/install/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH"
-       STLPORT_LIBS="-lstlport_static"
-    elif test "$target" != "arm-android-eabi"; then
-       dnl fail if we're not building with NDKr4
-       AC_MSG_ERROR([Couldn't find path to stlport in the android ndk])
-    fi
-
-    CPPFLAGS="-I$android_platform/usr/include $STLPORT_CPPFLAGS $CPPFLAGS"
+    CPPFLAGS="-I$android_platform/usr/include $CPPFLAGS"
     CFLAGS="-mandroid -I$android_platform/usr/include -fno-short-enums -fno-exceptions $CFLAGS"
     CXXFLAGS="-mandroid -I$android_platform/usr/include -fno-short-enums -fno-exceptions $CXXFLAGS"
-    LIBS="$LIBS $STLPORT_LIBS"
 
     dnl Add -llog by default, since we use it all over the place.
     dnl Add --allow-shlib-undefined, because libGLESv2 links to an
     dnl undefined symbol (present on the hardware, just not in the
     dnl NDK.)
-    LDFLAGS="-mandroid $STLPORT_LDFLAGS -L$android_platform/usr/lib -Wl,-rpath-link=$android_platform/usr/lib --sysroot=$android_platform -llog -Wl,--allow-shlib-undefined $LDFLAGS"
+    LDFLAGS="-mandroid -L$android_platform/usr/lib -Wl,-rpath-link=$android_platform/usr/lib --sysroot=$android_platform -llog -Wl,--allow-shlib-undefined $LDFLAGS"
 
     dnl prevent cross compile section from using these flags as host flags
     if test -z "$HOST_CPPFLAGS" ; then
         HOST_CPPFLAGS=" "
     fi
     if test -z "$HOST_CFLAGS" ; then
         HOST_CFLAGS=" "
     fi
@@ -1520,16 +1490,257 @@ dnl Set INTEL_ARCHITECTURE if we're comp
 dnl ===============================================================
 INTEL_ARCHITECTURE=
 case "$OS_TEST" in
     x86_64|i?86)
       INTEL_ARCHITECTURE=1
 esac
 
 dnl ========================================================
+dnl = ARM toolchain tweaks
+dnl ========================================================
+
+dnl Defaults
+case "${CPU_ARCH}-${OS_TARGET}" in
+arm-Android)
+    MOZ_THUMB=yes
+    MOZ_ARCH=armv7-a
+    MOZ_FPU=vfp
+    MOZ_FLOAT_ABI=softfp
+    ;;
+arm-*)
+    if test -n "$MOZ_PLATFORM_MAEMO"; then
+        MOZ_THUMB=no
+        MOZ_ARCH=armv7-a
+        MOZ_FLOAT_ABI=softfp
+    fi
+    if test "$MOZ_PLATFORM_MAEMO" = 6; then
+        MOZ_THUMB=yes
+    fi
+    ;;
+esac
+
+dnl Kept for compatibility with some buildbot mozconfig
+MOZ_ARG_DISABLE_BOOL(thumb2, [], MOZ_THUMB=no, MOZ_THUMB=yes)
+
+MOZ_ARG_WITH_STRING(thumb,
+[  --with-thumb[[=yes|no|toolchain-default]]]
+[                          Use Thumb instruction set (-mthumb)],
+    if test -z "$GNU_CC"; then
+        AC_MSG_ERROR([--with-thumb is not supported on non-GNU toolchains])
+    fi
+    MOZ_THUMB=$withval)
+
+MOZ_ARG_WITH_STRING(thumb-interwork,
+[  --with-thumb-interwork[[=yes|no|toolchain-default]]
+                           Use Thumb/ARM instuctions interwork (-mthumb-interwork)],
+    if test -z "$GNU_CC"; then
+        AC_MSG_ERROR([--with-thumb-interwork is not supported on non-GNU toolchains])
+    fi
+    MOZ_THUMB_INTERWORK=$withval)
+
+MOZ_ARG_WITH_STRING(arch,
+[  --with-arch=[[type|toolchain-default]]
+                           Use specific CPU features (-march=type)],
+    if test -z "$GNU_CC"; then
+        AC_MSG_ERROR([--with-arch is not supported on non-GNU toolchains])
+    fi
+    MOZ_ARCH=$withval)
+
+MOZ_ARG_WITH_STRING(fpu,
+[  --with-fpu=[[type|toolchain-default]]
+                           Use specific FPU type (-mfpu=type)],
+    if test -z "$GNU_CC"; then
+        AC_MSG_ERROR([--with-fpu is not supported on non-GNU toolchains])
+    fi
+    MOZ_FPU=$withval)
+
+MOZ_ARG_WITH_STRING(float-abi,
+[  --with-float-abi=[[type|toolchain-default]]
+                           Use specific arm float ABI (-mfloat-abi=type)],
+    if test -z "$GNU_CC"; then
+        AC_MSG_ERROR([--with-float-abi is not supported on non-GNU toolchains])
+    fi
+    MOZ_FLOAT_ABI=$withval)
+
+MOZ_ARG_WITH_STRING(soft-float,
+[  --with-soft-float[[=yes|no|toolchain-default]]
+                           Use soft float library (-msoft-float)],
+    if test -z "$GNU_CC"; then
+        AC_MSG_ERROR([--with-soft-float is not supported on non-GNU toolchains])
+    fi
+    MOZ_SOFT_FLOAT=$withval)
+
+case "$MOZ_ARCH" in
+toolchain-default|"")
+    arch_flag=""
+    ;;
+*)
+    arch_flag="-march=$MOZ_ARCH"
+    ;;
+esac
+
+case "$MOZ_THUMB" in
+yes)
+    MOZ_THUMB2=1
+    thumb_flag="-mthumb"
+    ;;
+no)
+    MOZ_THUMB2=
+    thumb_flag="-marm"
+    ;;
+*)
+    _SAVE_CFLAGS="$CFLAGS"
+    CFLAGS="$arch_flag"
+    AC_TRY_COMPILE([],[return sizeof(__thumb2__);],
+        MOZ_THUMB2=1,
+        MOZ_THUMB2=)
+    CFLAGS="$_SAVE_CFLAGS"
+    thumb_flag=""
+    ;;
+esac
+
+if test "$MOZ_THUMB2" = 1; then
+    AC_DEFINE(MOZ_THUMB2)
+fi
+
+case "$MOZ_THUMB_INTERWORK" in
+yes)
+    thumb_interwork_flag="-mthumb-interwork"
+    ;;
+no)
+    thumb_interwork_flag="-mno-thumb-interwork"
+    ;;
+*) # toolchain-default
+    thumb_interwork_flag=""
+    ;;
+esac
+
+case "$MOZ_FPU" in
+toolchain-default|"")
+    fpu_flag=""
+    ;;
+*)
+    fpu_flag="-mfpu=$MOZ_FPU"
+    ;;
+esac
+
+case "$MOZ_FLOAT_ABI" in
+toolchain-default|"")
+    float_abi_flag=""
+    ;;
+*)
+    float_abi_flag="-mfloat-abi=$MOZ_FLOAT_ABI"
+    ;;
+esac
+
+case "$MOZ_SOFT_FLOAT" in
+yes)
+    soft_float_flag="-msoft-float"
+    ;;
+no)
+    soft_float_flag="-mno-soft-float"
+    ;;
+*) # toolchain-default
+    soft_float_flag=""
+    ;;
+esac
+
+dnl Use echo to avoid accumulating space characters
+all_flags=`echo $arch_flag $thumb_flag $thumb_interwork_flag $fpu_flag $float_abi_flag $soft_float_flag`
+if test -n "$all_flags"; then
+    _SAVE_CFLAGS="$CFLAGS"
+    CFLAGS="$all_flags"
+    AC_MSG_CHECKING(whether the chosen combination of compiler flags ($all_flags) works)
+    AC_TRY_COMPILE([],[return 0;],
+        AC_MSG_RESULT([yes]),
+        AC_MSG_ERROR([no]))
+
+    CFLAGS="$_SAVE_CFLAGS $all_flags"
+    CXXFLAGS="$CXXFLAGS $all_flags"
+    ASFLAGS="$ASFLAGS $all_flags"
+    if test -n "$thumb_flag"; then
+        LDFLAGS="$LDFLAGS $thumb_flag"
+    fi
+fi
+
+AC_SUBST(MOZ_THUMB2)
+
+if test "$CPU_ARCH" = "arm"; then
+  AC_MSG_CHECKING(for ARM SIMD support in compiler)
+  # We try to link so that this also fails when
+  # building with LTO.
+  AC_TRY_LINK([],
+                 [asm("uqadd8 r1, r1, r2");],
+                 result="yes", result="no")
+  AC_MSG_RESULT("$result")
+  if test "$result" = "yes"; then
+      AC_DEFINE(HAVE_ARM_SIMD)
+      HAVE_ARM_SIMD=1
+  fi
+
+  AC_MSG_CHECKING(for ARM NEON support in compiler)
+  # We try to link so that this also fails when
+  # building with LTO.
+  AC_TRY_LINK([],
+                 [asm(".fpu neon\n vadd.i8 d0, d0, d0");],
+                 result="yes", result="no")
+  AC_MSG_RESULT("$result")
+  if test "$result" = "yes"; then
+      AC_DEFINE(HAVE_ARM_NEON)
+      HAVE_ARM_NEON=1
+  fi
+fi # CPU_ARCH = arm
+
+AC_SUBST(HAVE_ARM_SIMD)
+AC_SUBST(HAVE_ARM_NEON)
+
+dnl ========================================================
+dnl Android libstdc++, placed here so it can use MOZ_ARCH
+dnl computed above.
+dnl ========================================================
+
+if test "$OS_TARGET" = "Android"; then
+    case "${CPU_ARCH}-${MOZ_ARCH}" in
+    arm-armv7*)
+      ANDROID_CPU_ARCH=armeabi-v7a
+      ;;
+    arm-*)
+      ANDROID_CPU_ARCH=armeabi
+      ;;
+    x86-*)
+      ANDROID_CPU_ARCH=x86
+      ;;
+    esac
+
+    if test -n "$MOZ_ANDROID_LIBSTDCXX" ; then
+       if test ! -e "$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH/libstdc++.a" ; then
+          AC_MSG_ERROR([Cannot find path to libstdc++ (NDK version >= 5?)])
+       fi
+       STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/gnu-libstdc++/include -I$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH/include -D_GLIBCXX_PERMIT_BACKWARD_HASH"
+       STLPORT_LDFLAGS="-L$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH"
+       STLPORT_LIBS="-lstdc++"
+    elif test -e "$android_ndk/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/libstlport_static.a" ; then
+       STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/stlport/stlport"
+       STLPORT_LDFLAGS="-L$android_ndk/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/"
+       STLPORT_LIBS="-lstlport_static"
+    elif  test -e "$android_ndk/tmp/ndk-digit/build/install/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/libstlport_static.a" ; then
+       STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/stlport/stlport"
+       STLPORT_LDFLAGS="-L$android_ndk/tmp/ndk-digit/build/install/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH"
+       STLPORT_LIBS="-lstlport_static"
+    elif test "$target" != "arm-android-eabi"; then
+       dnl fail if we're not building with NDKr4
+       AC_MSG_ERROR([Couldn't find path to stlport in the android ndk])
+    fi
+    CPPFLAGS="$CPPFLAGS $STLPORT_CPPFLAGS"
+    LDFLAGS="$LDFLAGS $STLPORT_LDFLAGS"
+    LIBS="$LIBS $STLPORT_LIBS"
+fi
+
+dnl ========================================================
 dnl GNU specific defaults
 dnl ========================================================
 if test "$GNU_CC"; then
     # FIXME: Let us build with strict aliasing. bug 414641.
     CFLAGS="$CFLAGS -fno-strict-aliasing"
     MKSHLIB='$(CXX) $(CXXFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-h,$@ -o $@'
     MKCSHLIB='$(CC) $(CFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-h,$@ -o $@'
     DSO_LDOPTS='-shared'
@@ -1819,16 +2030,27 @@ MOZ_ARG_ENABLE_BOOL(jprof,
     MOZ_JPROF=1,
     MOZ_JPROF= )
 if test -n "$MOZ_JPROF"; then
     MOZ_PROFILING=1
     AC_DEFINE(MOZ_JPROF)
 fi
 
 dnl ========================================================
+dnl SPS Profiler
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(sps,
+[  --enable-sps          Enable sps profiling tool.],
+    MOZ_ENABLE_PROFILER_SPS=1,
+    MOZ_ENABLE_PROFILER_SPS= )
+if test -n "$MOZ_ENABLE_PROFILER_SPS"; then
+    AC_DEFINE(MOZ_ENABLE_PROFILER_SPS)
+fi
+
+dnl ========================================================
 dnl shark
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(shark,
 [  --enable-shark          Enable shark remote profiling. Implies --enable-profiling.],
     MOZ_SHARK=1,
     MOZ_SHARK= )
 if test -n "$MOZ_SHARK"; then
     MOZ_PROFILING=1
@@ -2027,20 +2249,17 @@ case "$target" in
     esac
     ;;
 
 *-darwin*)
     MKSHLIB='$(CXX) $(CXXFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -o $@'
     MKCSHLIB='$(CC) $(CFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -o $@'
     MOZ_OPTIMIZE_FLAGS="-O3"
     _PEDANTIC=
-    # Due to performance regressions, statically disable jemalloc on 10.5. See bug 414946.
-    if test "$HAVE_64BIT_OS"; then
-        MOZ_MEMORY=1
-    fi
+    MOZ_MEMORY=1
     CFLAGS="$CFLAGS -fno-common"
     CXXFLAGS="$CXXFLAGS -fno-common"
     DLL_SUFFIX=".dylib"
     DSO_LDOPTS=''
     STRIP="$STRIP -x -S"
     # Check whether we're targeting OS X or iOS
     AC_CACHE_CHECK(for iOS target,
                    ac_cv_ios_target,
@@ -4394,16 +4613,17 @@ case "${target}" in
         fi
 
         NSS_DISABLE_DBM=1
         MOZ_INSTALLER=
         NECKO_WIFI=
         MOZ_THEME_FASTSTRIPE=1
         MOZ_TREE_FREETYPE=1
         MOZ_MEMORY=1
+        MOZ_RAW=1
         ;;
 esac
 
 MOZ_ARG_ENABLE_STRING(application,
 [  --enable-application=APP
                           Options include:
                             browser (Firefox)
                             xulrunner
@@ -4643,16 +4863,17 @@ cairo-uikit)
 
 cairo-android)
     AC_DEFINE(MOZ_WIDGET_ANDROID)
     MOZ_WIDGET_TOOLKIT=android
     TK_CFLAGS='$(MOZ_CAIRO_CFLAGS)'
     TK_LIBS='$(MOZ_CAIRO_LIBS)'
     MOZ_WEBGL=1
     MOZ_PDF_PRINTING=1
+    MOZ_INSTRUMENT_EVENT_LOOP=1
     ;;
 
 esac
 
 AC_SUBST(MOZ_PDF_PRINTING)
 if test "$MOZ_PDF_PRINTING"; then
    PDF_SURFACE_FEATURE="#define CAIRO_HAS_PDF_SURFACE 1"
    AC_DEFINE(MOZ_PDF_PRINTING)
@@ -6510,215 +6731,16 @@ if test $MOZ_PLATFORM_MAEMO; then
    fi
    AC_SUBST(MOZ_ENABLE_MEEGOTOUCHSHARE)
 
    AC_SUBST(MOZ_PLATFORM_MAEMO_LIBS)
    AC_SUBST(MOZ_PLATFORM_MAEMO_CFLAGS)
 fi
 
 dnl ========================================================
-dnl = ARM toolchain tweaks
-dnl ========================================================
-
-dnl Defaults
-case "${CPU_ARCH}-${OS_TARGET}" in
-arm-Android)
-    MOZ_THUMB=yes
-    MOZ_ARCH=armv7-a
-    MOZ_FPU=vfp
-    MOZ_FLOAT_ABI=softfp
-    ;;
-arm-*)
-    if test -n "$MOZ_PLATFORM_MAEMO"; then
-        MOZ_THUMB=no
-        MOZ_ARCH=armv7-a
-        MOZ_FLOAT_ABI=softfp
-    fi
-    if test "$MOZ_PLATFORM_MAEMO" = 6; then
-        MOZ_THUMB=yes
-    fi
-    ;;
-esac
-
-dnl Kept for compatibility with some buildbot mozconfig
-MOZ_ARG_DISABLE_BOOL(thumb2, [], MOZ_THUMB=no, MOZ_THUMB=yes)
-
-MOZ_ARG_WITH_STRING(thumb,
-[  --with-thumb[[=yes|no|toolchain-default]]]
-[                          Use Thumb instruction set (-mthumb)],
-    if test -z "$GNU_CC"; then
-        AC_MSG_ERROR([--with-thumb is not supported on non-GNU toolchains])
-    fi
-    MOZ_THUMB=$withval)
-
-MOZ_ARG_WITH_STRING(thumb-interwork,
-[  --with-thumb-interwork[[=yes|no|toolchain-default]]
-                           Use Thumb/ARM instuctions interwork (-mthumb-interwork)],
-    if test -z "$GNU_CC"; then
-        AC_MSG_ERROR([--with-thumb-interwork is not supported on non-GNU toolchains])
-    fi
-    MOZ_THUMB_INTERWORK=$withval)
-
-MOZ_ARG_WITH_STRING(arch,
-[  --with-arch=[[type|toolchain-default]]
-                           Use specific CPU features (-march=type)],
-    if test -z "$GNU_CC"; then
-        AC_MSG_ERROR([--with-arch is not supported on non-GNU toolchains])
-    fi
-    MOZ_ARCH=$withval)
-
-MOZ_ARG_WITH_STRING(fpu,
-[  --with-fpu=[[type|toolchain-default]]
-                           Use specific FPU type (-mfpu=type)],
-    if test -z "$GNU_CC"; then
-        AC_MSG_ERROR([--with-fpu is not supported on non-GNU toolchains])
-    fi
-    MOZ_FPU=$withval)
-
-MOZ_ARG_WITH_STRING(float-abi,
-[  --with-float-abi=[[type|toolchain-default]]
-                           Use specific arm float ABI (-mfloat-abi=type)],
-    if test -z "$GNU_CC"; then
-        AC_MSG_ERROR([--with-float-abi is not supported on non-GNU toolchains])
-    fi
-    MOZ_FLOAT_ABI=$withval)
-
-MOZ_ARG_WITH_STRING(soft-float,
-[  --with-soft-float[[=yes|no|toolchain-default]]
-                           Use soft float library (-msoft-float)],
-    if test -z "$GNU_CC"; then
-        AC_MSG_ERROR([--with-soft-float is not supported on non-GNU toolchains])
-    fi
-    MOZ_SOFT_FLOAT=$withval)
-
-case "$MOZ_ARCH" in
-toolchain-default|"")
-    arch_flag=""
-    ;;
-*)
-    arch_flag="-march=$MOZ_ARCH"
-    ;;
-esac
-
-case "$MOZ_THUMB" in
-yes)
-    MOZ_THUMB2=1
-    thumb_flag="-mthumb"
-    ;;
-no)
-    MOZ_THUMB2=
-    thumb_flag="-marm"
-    ;;
-*)
-    _SAVE_CFLAGS="$CFLAGS"
-    CFLAGS="$arch_flag"
-    AC_TRY_COMPILE([],[return sizeof(__thumb2__);],
-        MOZ_THUMB2=1,
-        MOZ_THUMB2=)
-    CFLAGS="$_SAVE_CFLAGS"
-    thumb_flag=""
-    ;;
-esac
-
-if test "$MOZ_THUMB2" = 1; then
-    AC_DEFINE(MOZ_THUMB2)
-fi
-
-case "$MOZ_THUMB_INTERWORK" in
-yes)
-    thumb_interwork_flag="-mthumb-interwork"
-    ;;
-no)
-    thumb_interwork_flag="-mno-thumb-interwork"
-    ;;
-*) # toolchain-default
-    thumb_interwork_flag=""
-    ;;
-esac
-
-case "$MOZ_FPU" in
-toolchain-default|"")
-    fpu_flag=""
-    ;;
-*)
-    fpu_flag="-mfpu=$MOZ_FPU"
-    ;;
-esac
-
-case "$MOZ_FLOAT_ABI" in
-toolchain-default|"")
-    float_abi_flag=""
-    ;;
-*)
-    float_abi_flag="-mfloat-abi=$MOZ_FLOAT_ABI"
-    ;;
-esac
-
-case "$MOZ_SOFT_FLOAT