Merge from mozilla-central.
authorDavid Anderson <danderson@mozilla.com>
Fri, 04 Nov 2011 14:02:27 -0700
changeset 105314 89fff0ee52d5e237060a44b133a68e723dab8ae4
parent 105313 5b43306f9b174e7b02fa1bf2d708943b976f00a9 (current diff)
parent 79776 ae9e5bf847fc727fc0358bdf6619922b464f2136 (diff)
child 105315 e784f2911b5bd7956f52be6a93f010a032cf364e
push id1075
push uservporof@mozilla.com
push dateThu, 13 Sep 2012 10:46:49 +0000
treeherderfx-team@f39786e8364d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone10.0a1
Merge from mozilla-central.
accessible/src/atk/nsAccessibleWrap.cpp
accessible/src/base/AccEvent.cpp
accessible/src/base/AccEvent.h
accessible/src/base/NotificationController.cpp
accessible/src/base/NotificationController.h
accessible/src/base/nsAccessibilityService.h
accessible/src/base/nsDocAccessible.cpp
accessible/src/base/nsRootAccessible.cpp
accessible/src/html/nsHTMLSelectAccessible.cpp
accessible/src/html/nsHTMLSelectAccessible.h
accessible/src/mac/mozTextAccessible.mm
accessible/tests/mochitest/events.js
accessible/tests/mochitest/events/Makefile.in
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/highlighter.css
browser/base/content/nsContextMenu.js
browser/base/content/tabbrowser.xml
browser/base/content/test/subtst_contextmenu.html
browser/base/content/test/test_contextmenu.html
browser/components/sessionstore/src/nsSessionStore.js
browser/components/sessionstore/test/browser/Makefile.in
browser/components/shell/src/nsWindowsShellService.cpp
browser/devtools/highlighter/test/browser_inspector_initialization.js
browser/devtools/jar.mn
browser/devtools/styleinspector/CssLogic.jsm
browser/devtools/styleinspector/StyleInspector.jsm
browser/devtools/webconsole/HUDService.jsm
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/devtools/inspector.properties
browser/locales/en-US/chrome/browser/devtools/styleinspector.properties
browser/locales/en-US/chrome/browser/syncSetup.dtd
browser/themes/gnomestripe/browser/browser.css
browser/themes/pinstripe/browser/browser.css
browser/themes/winstripe/browser/browser.css
caps/src/nsPrincipal.cpp
configure.in
content/base/public/nsContentUtils.h
content/base/public/nsIDocument.h
content/base/public/nsIImageLoadingContent.idl
content/base/public/nsINameSpaceManager.h
content/base/public/nsINode.h
content/base/src/nsAttrAndChildArray.cpp
content/base/src/nsAttrAndChildArray.h
content/base/src/nsContentList.cpp
content/base/src/nsContentList.h
content/base/src/nsContentUtils.cpp
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/base/src/nsGenericElement.cpp
content/base/src/nsGkAtomList.h
content/base/src/nsImageLoadingContent.cpp
content/base/src/nsImageLoadingContent.h
content/base/src/nsObjectLoadingContent.cpp
content/base/src/nsPlainTextSerializer.cpp
content/base/src/nsRange.h
content/canvas/src/CustomQS_WebGL.h
content/canvas/src/WebGLContext.cpp
content/canvas/src/WebGLContext.h
content/canvas/src/WebGLContextGL.cpp
content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
content/canvas/test/webgl/test_webgl_conformance_test_suite.html
content/events/public/nsEventNameList.h
content/events/src/nsDOMEvent.cpp
content/events/src/nsDOMEvent.h
content/events/src/nsEventDispatcher.cpp
content/events/src/nsEventListenerManager.cpp
content/events/src/nsEventStateManager.cpp
content/events/test/Makefile.in
content/html/content/src/nsGenericHTMLElement.cpp
content/html/content/test/file_fullscreen-api.html
content/html/document/src/nsHTMLDocument.cpp
content/media/nsBuiltinDecoderStateMachine.cpp
content/media/nsBuiltinDecoderStateMachine.h
content/smil/nsSMILAnimationFunction.cpp
content/svg/content/src/SVGPathSegListSMILType.cpp
content/svg/content/src/nsSVGClass.cpp
content/svg/content/src/nsSVGClass.h
content/xbl/src/nsXBLContentSink.cpp
content/xbl/src/nsXBLDocumentInfo.cpp
content/xbl/src/nsXBLDocumentInfo.h
content/xbl/src/nsXBLProtoImpl.cpp
content/xbl/src/nsXBLProtoImpl.h
content/xbl/src/nsXBLProtoImplField.cpp
content/xbl/src/nsXBLProtoImplField.h
content/xbl/src/nsXBLProtoImplMethod.h
content/xbl/src/nsXBLProtoImplProperty.cpp
content/xbl/src/nsXBLProtoImplProperty.h
content/xbl/src/nsXBLPrototypeBinding.cpp
content/xbl/src/nsXBLPrototypeBinding.h
content/xbl/src/nsXBLPrototypeHandler.cpp
content/xbl/src/nsXBLPrototypeHandler.h
content/xbl/src/nsXBLPrototypeResources.cpp
content/xbl/src/nsXBLPrototypeResources.h
content/xbl/src/nsXBLResourceLoader.cpp
content/xbl/src/nsXBLResourceLoader.h
content/xbl/src/nsXBLService.cpp
content/xbl/src/nsXBLService.h
content/xml/document/src/nsXMLDocument.cpp
docshell/shistory/src/nsSHEntry.cpp
docshell/shistory/src/nsSHEntry.h
docshell/shistory/src/nsSHEntryShared.cpp
docshell/shistory/src/nsSHEntryShared.h
dom/Makefile.in
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsPluginArray.cpp
dom/base/nsPluginArray.h
dom/dom-config.mk
dom/indexedDB/AsyncConnectionHelper.cpp
dom/indexedDB/CheckPermissionsHelper.h
dom/indexedDB/IDBCursor.cpp
dom/indexedDB/IDBDatabase.cpp
dom/indexedDB/IDBFactory.cpp
dom/indexedDB/IDBIndex.cpp
dom/indexedDB/IDBKeyRange.cpp
dom/indexedDB/IDBKeyRange.h
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/IDBObjectStore.h
dom/indexedDB/IDBTransaction.cpp
dom/indexedDB/IndexedDatabaseManager.cpp
dom/interfaces/canvas/nsIDOMWebGLRenderingContext.idl
dom/ipc/ContentChild.cpp
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/system/NetworkGeolocationProvider.js
dom/workers/RuntimeService.cpp
dom/workers/XMLHttpRequest.cpp
dom/workers/XMLHttpRequestPrivate.cpp
embedding/android/GeckoApp.java
embedding/android/GeckoAppShell.java
embedding/android/Makefile.in
extensions/spellcheck/src/mozInlineSpellChecker.cpp
extensions/spellcheck/src/mozInlineSpellChecker.h
extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
extensions/spellcheck/src/mozInlineSpellWordUtil.h
gfx/angle/Makefile.in
gfx/angle/README.mozilla
gfx/angle/angle-intrinsic-msvc2005.patch
gfx/angle/angle-limit-identifiers-to-250-chars.patch
gfx/angle/angle-renaming-debug.patch
gfx/angle/src/common/version.h
gfx/angle/src/compiler/Compiler.cpp
gfx/angle/src/compiler/MapLongVariableNames.cpp
gfx/angle/src/compiler/MapLongVariableNames.h
gfx/angle/src/compiler/ParseHelper.cpp
gfx/angle/src/libGLESv2/Context.cpp
gfx/angle/src/libGLESv2/Context.h
gfx/angle/src/libGLESv2/Program.cpp
gfx/angle/src/libGLESv2/Program.h
gfx/angle/src/libGLESv2/libGLESv2.cpp
gfx/layers/Layers.cpp
gfx/layers/d3d10/ImageLayerD3D10.cpp
gfx/layers/d3d10/LayerManagerD3D10.cpp
gfx/layers/d3d9/CanvasLayerD3D9.cpp
gfx/layers/d3d9/ContainerLayerD3D9.cpp
gfx/layers/d3d9/ImageLayerD3D9.cpp
gfx/layers/d3d9/ThebesLayerD3D9.cpp
gfx/layers/opengl/CanvasLayerOGL.cpp
gfx/layers/opengl/CanvasLayerOGL.h
gfx/layers/opengl/ImageLayerOGL.cpp
gfx/layers/opengl/LayerManagerOGL.cpp
gfx/ots/ots-fix-gcc46.patch
gfx/thebes/GLContext.cpp
gfx/thebes/GLContextProviderCGL.mm
gfx/thebes/gfxAndroidPlatform.cpp
gfx/thebes/gfxAndroidPlatform.h
gfx/thebes/gfxPlatform.cpp
gfx/thebes/gfxPlatform.h
gfx/thebes/gfxPlatformMac.cpp
gfx/thebes/gfxPlatformMac.h
gfx/thebes/gfxWindowsPlatform.cpp
gfx/thebes/gfxWindowsPlatform.h
image/decoders/nsICODecoder.cpp
image/decoders/nsICODecoder.h
image/public/imgIContainer.idl
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/VectorImage.cpp
image/src/VectorImage.h
image/test/mochitest/Makefile.in
intl/uconv/src/nsCharsetConverterManager.cpp
intl/uconv/tests/nsTestUConv.cpp
js/src/assembler/assembler/ARMAssembler.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/jsanalyze.cpp
js/src/jsapi-tests/Makefile.in
js/src/jsarray.cpp
js/src/jsfriendapi.h
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsgcmark.h
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinterp.cpp
js/src/jsobjinlines.h
js/src/jsopcode.cpp
js/src/jsopcode.tbl
js/src/jspubtd.h
js/src/jsstr.cpp
js/src/jsstr.h
js/src/jstracer.cpp
js/src/jsxdrapi.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/FastOps.cpp
js/src/methodjit/StubCalls.cpp
js/src/methodjit/StubCalls.h
js/src/tests/js1_8_5/extensions/jstests.list
js/src/tests/js1_8_5/regress/jstests.list
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcprivate.h
layout/base/crashtests/crashtests.list
layout/base/nsBidiPresUtils.cpp
layout/base/nsImageLoader.cpp
layout/base/nsImageLoader.h
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/base/nsPresShell.cpp
layout/base/nsRefreshDriver.cpp
layout/base/nsRefreshDriver.h
layout/build/Makefile.in
layout/build/nsContentDLF.cpp
layout/build/nsLayoutStatics.cpp
layout/generic/nsBulletFrame.cpp
layout/generic/nsBulletFrame.h
layout/generic/nsIFrame.h
layout/generic/nsImageFrame.cpp
layout/generic/nsObjectFrame.cpp
layout/generic/nsVideoFrame.cpp
layout/style/ua.css
layout/svg/base/src/nsSVGImageFrame.cpp
layout/svg/base/src/nsSVGLeafFrame.cpp
layout/xul/base/src/nsImageBoxFrame.cpp
layout/xul/base/src/nsImageBoxFrame.h
layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
layout/xul/base/src/tree/src/nsTreeBodyFrame.h
layout/xul/base/src/tree/src/nsTreeImageListener.cpp
layout/xul/base/src/tree/src/nsTreeImageListener.h
memory/jemalloc/jemalloc.c
memory/jemalloc/jemalloc.h
mobile/app/mobile.js
mobile/chrome/content/aboutHome.xhtml
mobile/chrome/content/browser-scripts.js
mobile/chrome/content/browser.xul
mobile/chrome/content/sync.js
mobile/installer/package-manifest.in
mobile/locales/en-US/chrome/aboutHome.dtd
mobile/locales/en-US/chrome/sync.dtd
mobile/themes/core/aboutHome.css
mobile/themes/core/browser.css
modules/libpref/public/Preferences.h
modules/libpref/src/Preferences.cpp
modules/libpref/src/init/all.js
netwerk/base/src/nsSocketTransport2.cpp
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
nsprpub/TAG-INFO
nsprpub/config/prdepend.h
other-licenses/android/APKOpen.cpp
parser/html/nsHtml5AttributeName.cpp
parser/html/nsHtml5AttributeName.h
parser/html/nsHtml5HtmlAttributes.cpp
parser/html/nsHtml5HtmlAttributes.h
parser/html/nsHtml5MetaScanner.cpp
parser/html/nsHtml5MetaScanner.h
parser/html/nsHtml5Parser.cpp
parser/html/nsHtml5Parser.h
parser/html/nsHtml5Portability.h
parser/html/nsHtml5StackNode.cpp
parser/html/nsHtml5StackNode.h
parser/html/nsHtml5StateSnapshot.cpp
parser/html/nsHtml5StateSnapshot.h
parser/html/nsHtml5StreamParser.cpp
parser/html/nsHtml5StreamParser.h
parser/html/nsHtml5Tokenizer.cpp
parser/html/nsHtml5Tokenizer.h
parser/html/nsHtml5TreeBuilder.cpp
parser/html/nsHtml5TreeBuilder.h
parser/html/nsHtml5TreeBuilderCppSupplement.h
parser/html/nsHtml5TreeBuilderHSupplement.h
parser/html/nsHtml5TreeOpExecutor.cpp
parser/html/nsHtml5TreeOpExecutor.h
parser/html/nsHtml5TreeOperation.cpp
parser/html/nsHtml5TreeOperation.h
parser/html/nsHtml5UTF16Buffer.cpp
parser/html/nsHtml5UTF16Buffer.h
parser/htmlparser/public/nsIParser.h
parser/htmlparser/src/nsParser.cpp
parser/htmlparser/src/nsParser.h
security/manager/boot/src/nsSecureBrowserUIImpl.cpp
security/manager/boot/src/nsSecureBrowserUIImpl.h
security/manager/pki/src/nsNSSDialogs.cpp
security/manager/ssl/src/nsCRLManager.cpp
security/manager/ssl/src/nsCrypto.cpp
security/manager/ssl/src/nsIdentityChecking.cpp
security/manager/ssl/src/nsKeygenHandler.cpp
security/manager/ssl/src/nsKeygenThread.cpp
security/manager/ssl/src/nsKeygenThread.h
security/manager/ssl/src/nsNSSCallbacks.cpp
security/manager/ssl/src/nsNSSCertificate.cpp
security/manager/ssl/src/nsNSSCertificateDB.cpp
security/manager/ssl/src/nsNSSComponent.cpp
security/manager/ssl/src/nsNSSComponent.h
security/manager/ssl/src/nsNSSIOLayer.cpp
security/manager/ssl/src/nsNSSIOLayer.h
security/manager/ssl/src/nsPKCS12Blob.cpp
security/manager/ssl/src/nsProtectedAuthThread.h
security/manager/ssl/src/nsSDR.cpp
security/manager/ssl/src/nsSmartCardEvent.cpp
services/sync/modules/policies.js
services/sync/modules/resource.js
services/sync/modules/rest.js
services/sync/modules/service.js
services/sync/tests/unit/head_http_server.js
services/sync/tests/unit/test_syncscheduler.js
services/sync/tests/unit/xpcshell.ini
startupcache/StartupCacheUtils.h
testing/mochitest/ssltunnel/ssltunnel.cpp
toolkit/content/tests/browser/common/mockFilePicker.js
toolkit/content/widgets/videocontrols.xml
toolkit/mozapps/extensions/XPIProvider.jsm
toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js
toolkit/xre/nsXREDirProvider.cpp
widget/public/nsGUIEvent.h
widget/src/android/AndroidBridge.cpp
widget/src/android/AndroidBridge.h
widget/src/android/AndroidJNI.cpp
widget/src/android/GfxInfo.cpp
widget/src/android/GfxInfo.h
widget/src/cocoa/GfxInfo.h
widget/src/cocoa/GfxInfo.mm
widget/src/windows/GfxInfo.cpp
widget/src/windows/GfxInfo.h
widget/src/windows/WinTaskbar.cpp
widget/src/windows/nsAppShell.cpp
widget/src/windows/nsDataObj.cpp
widget/src/windows/nsToolkit.cpp
widget/src/windows/nsToolkit.h
widget/src/windows/nsWindow.cpp
widget/src/windows/nsWindow.h
widget/src/xpwidgets/GfxInfoX11.cpp
widget/src/xpwidgets/GfxInfoX11.h
xpcom/ds/nsExpirationTracker.h
xpcom/ds/nsHashtable.h
xpcom/io/nsStringStream.cpp
xpcom/reflect/xptcall/src/md/unix/xptcinvoke_linux_m68k.cpp
xpcom/reflect/xptcall/src/md/unix/xptcinvoke_netbsd_m68k.cpp
xpcom/reflect/xptcall/src/md/unix/xptcinvoke_pa32.cpp
xpcom/reflect/xptcall/src/md/unix/xptcinvoke_ppc_aix.cpp
xpcom/reflect/xptcall/src/md/unix/xptcinvoke_ppc_rhapsody.cpp
xpcom/reflect/xptcall/src/md/unix/xptcinvoke_sparc_netbsd.cpp
xpcom/reflect/xptcall/src/md/unix/xptcinvoke_sparc_openbsd.cpp
xpcom/reflect/xptcall/src/md/unix/xptcinvoke_sparc_solaris.cpp
xpcom/reflect/xptcall/src/md/unix/xptcinvoke_sparcv9_solaris.cpp
xpcom/reflect/xptcall/src/md/unix/xptcstubs_linux_m68k.cpp
xpcom/reflect/xptcall/src/md/unix/xptcstubs_netbsd_m68k.cpp
xpcom/reflect/xptcall/src/md/unix/xptcstubs_pa32.cpp
xpcom/reflect/xptcall/src/md/unix/xptcstubs_sparc_netbsd.cpp
xpcom/reflect/xptcall/src/md/unix/xptcstubs_sparc_openbsd.cpp
xpcom/reflect/xptcall/src/md/unix/xptcstubs_sparc_solaris.cpp
xpcom/reflect/xptcall/src/md/unix/xptcstubs_sparcv9_solaris.cpp
new file mode 100644
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+# .gitignore - List of filenames git should ignore
+
+# Filenames that should be ignored wherever they appear
+*~
+*.pyc
+*.pyo
+TAGS
+tags
+ID
+.DS_Store*
+
+# Vim swap files.
+.*.sw[a-z]
+
+# User files that may appear at the root
+.mozconfig
+mozconfig
+configure
+config.cache
+config.log
+
+# Empty marker file that's generated when we check out NSS
+security/manager/.nss.checkout
+
+# Build directories
+obj/*
+
+# Build directories for js shell
+*/_DBG.OBJ/
+*/_OPT.OBJ/
+
+# SpiderMonkey configury
+js/src/configure
+js/src/autom4te.cache
+# SpiderMonkey test result logs
+js/src/tests/results-*.html
+js/src/tests/results-*.txt
+
+# Java HTML5 parser classes
+parser/html/java/htmlparser/
+parser/html/java/javaparser/
--- a/accessible/public/nsIAccessibleEvent.idl
+++ b/accessible/public/nsIAccessibleEvent.idl
@@ -54,17 +54,17 @@ interface nsIDOMNode;
  * the event and its target. To listen to in-process accessibility invents,
  * make your object an nsIObserver, and listen for accessible-event by 
  * using code something like this:
  *   nsCOMPtr<nsIObserverService> observerService = 
  *     do_GetService("@mozilla.org/observer-service;1", &rv);
  *   if (NS_SUCCEEDED(rv)) 
  *     rv = observerService->AddObserver(this, "accessible-event", PR_TRUE);
  */
-[scriptable, uuid(fd1378c5-c606-4a5e-a321-8e7fc107e5cf)]
+[scriptable, uuid(7f66a33a-9ed7-4fd4-87a8-e431b0f43368)]
 interface nsIAccessibleEvent : nsISupports
 {
   /**
    * An object has been created.
    */
   const unsigned long EVENT_SHOW = 0x0001;
 
   /**
@@ -275,17 +275,22 @@ interface nsIAccessibleEvent : nsISuppor
   const unsigned long EVENT_DOCUMENT_ATTRIBUTES_CHANGED = 0x002A;
 
   /**
    * The contents of the document have changed.
    */
   const unsigned long EVENT_DOCUMENT_CONTENT_CHANGED = 0x002B;
 
   const unsigned long EVENT_PROPERTY_CHANGED = 0x002C;
-  const unsigned long EVENT_SELECTION_CHANGED = 0x002D;
+
+  /**
+   * A slide changed in a presentation document or a page boundary was
+   * crossed in a word processing document.
+   */
+  const unsigned long EVENT_PAGE_CHANGED = 0x002D;
 
   /**
    * A text object's attributes changed.
    * Also see EVENT_OBJECT_ATTRIBUTE_CHANGED.
    */
   const unsigned long EVENT_TEXT_ATTRIBUTE_CHANGED = 0x002E;
 
   /**
@@ -432,25 +437,19 @@ interface nsIAccessibleEvent : nsISuppor
   const unsigned long EVENT_HYPERTEXT_NLINKS_CHANGED = 0x0054;
 
   /**
    * An object's attributes changed. Also see EVENT_TEXT_ATTRIBUTE_CHANGED.
    */
   const unsigned long EVENT_OBJECT_ATTRIBUTE_CHANGED = 0x0055;
 
   /**
-   * A slide changed in a presentation document or a page boundary was
-   * crossed in a word processing document.
-   */
-  const unsigned long EVENT_PAGE_CHANGED = 0x0056;
-
-  /**
    * Help make sure event map does not get out-of-line.
    */
-  const unsigned long EVENT_LAST_ENTRY = 0x0057;
+  const unsigned long EVENT_LAST_ENTRY = 0x0056;
 
   /**
    * The type of event, based on the enumerated event values
    * defined in this interface.
    */
   readonly attribute unsigned long eventType;
   
   /**
--- a/accessible/src/atk/nsAccessibleWrap.cpp
+++ b/accessible/src/atk/nsAccessibleWrap.cpp
@@ -1084,20 +1084,34 @@ nsAccessibleWrap::FirePlatformEvent(AccE
         nsCOMPtr<nsIAccessibleValue> value(do_QueryObject(accessible));
         if (value) {    // Make sure this is a numeric value
             // Don't fire for MSAA string value changes (e.g. text editing)
             // ATK values are always numeric
             g_object_notify( (GObject*)atkObj, "accessible-value" );
         }
       } break;
 
-    case nsIAccessibleEvent::EVENT_SELECTION_CHANGED:
-        MAI_LOG_DEBUG(("\n\nReceived: EVENT_SELECTION_CHANGED\n"));
-        g_signal_emit_by_name(atkObj, "selection_changed");
-        break;
+    case nsIAccessibleEvent::EVENT_SELECTION:
+    case nsIAccessibleEvent::EVENT_SELECTION_ADD:
+    case nsIAccessibleEvent::EVENT_SELECTION_REMOVE:
+    {
+      // XXX: dupe events may be fired
+      MAI_LOG_DEBUG(("\n\nReceived: EVENT_SELECTION_CHANGED\n"));
+      AccSelChangeEvent* selChangeEvent = downcast_accEvent(aEvent);
+      g_signal_emit_by_name(nsAccessibleWrap::GetAtkObject(selChangeEvent->Widget()),
+                            "selection_changed");
+      break;
+    }
+
+    case nsIAccessibleEvent::EVENT_SELECTION_WITHIN:
+    {
+      MAI_LOG_DEBUG(("\n\nReceived: EVENT_SELECTION_CHANGED\n"));
+      g_signal_emit_by_name(atkObj, "selection_changed");
+      break;
+    }
 
     case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
         MAI_LOG_DEBUG(("\n\nReceived: EVENT_TEXT_SELECTION_CHANGED\n"));
         g_signal_emit_by_name(atkObj, "text_selection_changed");
         break;
 
     case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED:
       {
--- a/accessible/src/base/AccEvent.cpp
+++ b/accessible/src/base/AccEvent.cpp
@@ -326,16 +326,38 @@ AccCaretMoveEvent::CreateXPCOMObject()
 {
   nsAccEvent* event = new nsAccCaretMoveEvent(this);
   NS_IF_ADDREF(event);
   return event;
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
+// AccSelChangeEvent
+////////////////////////////////////////////////////////////////////////////////
+
+AccSelChangeEvent::
+  AccSelChangeEvent(nsAccessible* aWidget, nsAccessible* aItem,
+                    SelChangeType aSelChangeType) :
+    AccEvent(0, aItem, eAutoDetect, eCoalesceSelectionChange),
+    mWidget(aWidget), mItem(aItem), mSelChangeType(aSelChangeType),
+    mPreceedingCount(0), mPackedEvent(nsnull)
+{
+  if (aSelChangeType == eSelectionAdd) {
+    if (mWidget->GetSelectedItem(1))
+      mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
+    else
+      mEventType = nsIAccessibleEvent::EVENT_SELECTION;
+  } else {
+    mEventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
 // AccTableChangeEvent
 ////////////////////////////////////////////////////////////////////////////////
 
 AccTableChangeEvent::
   AccTableChangeEvent(nsAccessible* aAccessible, PRUint32 aEventType,
                       PRInt32 aRowOrColIndex, PRInt32 aNumRowsOrCols) :
   AccEvent(aEventType, aAccessible),
   mRowOrColIndex(aRowOrColIndex), mNumRowsOrCols(aNumRowsOrCols)
--- a/accessible/src/base/AccEvent.h
+++ b/accessible/src/base/AccEvent.h
@@ -77,16 +77,19 @@ public:
      //    subtree or the same node, only the umbrella event on the ancestor
      //    will be emitted.
      eCoalesceFromSameSubtree,
 
     // eCoalesceOfSameType : For events of the same type, only the newest event
     // will be processed.
     eCoalesceOfSameType,
 
+    // eCoalesceSelectionChange: coalescence of selection change events.
+    eCoalesceSelectionChange,
+
      // eRemoveDupes : For repeat events, only the newest event in queue
      //    will be emitted.
      eRemoveDupes,
 
      // eDoNotEmit : This event is confirmed as a duplicate, do not emit it.
      eDoNotEmit
   };
 
@@ -120,16 +123,17 @@ public:
   enum EventGroup {
     eGenericEvent,
     eStateChangeEvent,
     eTextChangeEvent,
     eMutationEvent,
     eHideEvent,
     eShowEvent,
     eCaretMoveEvent,
+    eSelectionChangeEvent,
     eTableChangeEvent
   };
 
   static const EventGroup kEventGroup = eGenericEvent;
   virtual unsigned int GetEventGroups() const
   {
     return 1U << eGenericEvent;
   }
@@ -322,20 +326,47 @@ public:
 private:
   PRInt32 mCaretOffset;
 };
 
 
 /**
  * Accessible widget selection change event.
  */
-class AccSelectionChangeEvent : public AccEvent
+class AccSelChangeEvent : public AccEvent
 {
 public:
+  enum SelChangeType {
+    eSelectionAdd,
+    eSelectionRemove
+  };
 
+  AccSelChangeEvent(nsAccessible* aWidget, nsAccessible* aItem,
+                    SelChangeType aSelChangeType);
+
+  virtual ~AccSelChangeEvent() { }
+
+  // AccEvent
+  static const EventGroup kEventGroup = eSelectionChangeEvent;
+  virtual unsigned int GetEventGroups() const
+  {
+    return AccEvent::GetEventGroups() | (1U << eSelectionChangeEvent);
+  }
+
+  // AccSelChangeEvent
+  nsAccessible* Widget() const { return mWidget; }
+
+private:
+  nsRefPtr<nsAccessible> mWidget;
+  nsRefPtr<nsAccessible> mItem;
+  SelChangeType mSelChangeType;
+  PRUint32 mPreceedingCount;
+  AccSelChangeEvent* mPackedEvent;
+
+  friend class NotificationController;
 };
 
 
 /**
  * Accessible table change event.
  */
 class AccTableChangeEvent : public AccEvent
 {
--- a/accessible/src/base/NotificationController.cpp
+++ b/accessible/src/base/NotificationController.cpp
@@ -46,16 +46,20 @@
 #include "nsTextAccessible.h"
 #include "FocusManager.h"
 #include "TextUpdater.h"
 
 #include "mozilla/dom/Element.h"
 
 using namespace mozilla::a11y;
 
+// Defines the number of selection add/remove events in the queue when they
+// aren't packed into single selection within event.
+const unsigned int kSelChangeCountToPack = 5;
+
 ////////////////////////////////////////////////////////////////////////////////
 // NotificationCollector
 ////////////////////////////////////////////////////////////////////////////////
 
 NotificationController::NotificationController(nsDocAccessible* aDocument,
                                                nsIPresShell* aPresShell) :
   mObservingState(eNotObservingRefresh), mDocument(aDocument),
   mPresShell(aPresShell)
@@ -486,16 +490,36 @@ NotificationController::CoalesceEvents()
             accEvent->mEventRule == tailEvent->mEventRule &&
             accEvent->mNode == tailEvent->mNode) {
           tailEvent->mEventRule = AccEvent::eDoNotEmit;
           return;
         }
       }
     } break; // case eRemoveDupes
 
+    case AccEvent::eCoalesceSelectionChange:
+    {
+      AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent);
+      PRInt32 index = tail - 1;
+      for (; index >= 0; index--) {
+        AccEvent* thisEvent = mEvents[index];
+        if (thisEvent->mEventRule == tailEvent->mEventRule) {
+          AccSelChangeEvent* thisSelChangeEvent =
+            downcast_accEvent(thisEvent);
+
+          // Coalesce selection change events within same control.
+          if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) {
+            CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index);
+            return;
+          }
+        }
+      }
+
+    } break; // eCoalesceSelectionChange
+
     default:
       break; // case eAllowDupes, eDoNotEmit
   } // switch
 }
 
 void
 NotificationController::ApplyToSiblings(PRUint32 aStart, PRUint32 aEnd,
                                         PRUint32 aEventType, nsINode* aNode,
@@ -507,16 +531,96 @@ NotificationController::ApplyToSiblings(
         accEvent->mEventRule != AccEvent::eDoNotEmit && accEvent->mNode &&
         accEvent->mNode->GetNodeParent() == aNode->GetNodeParent()) {
       accEvent->mEventRule = aEventRule;
     }
   }
 }
 
 void
+NotificationController::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
+                                                AccSelChangeEvent* aThisEvent,
+                                                PRInt32 aThisIndex)
+{
+  aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1;
+
+  // Pack all preceding events into single selection within event
+  // when we receive too much selection add/remove events.
+  if (aTailEvent->mPreceedingCount >= kSelChangeCountToPack) {
+    aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_WITHIN;
+    aTailEvent->mAccessible = aTailEvent->mWidget;
+    aThisEvent->mEventRule = AccEvent::eDoNotEmit;
+
+    // Do not emit any preceding selection events for same widget if they
+    // weren't coalesced yet.
+    if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) {
+      for (PRInt32 jdx = aThisIndex - 1; jdx >= 0; jdx--) {
+        AccEvent* prevEvent = mEvents[jdx];
+        if (prevEvent->mEventRule == aTailEvent->mEventRule) {
+          AccSelChangeEvent* prevSelChangeEvent =
+            downcast_accEvent(prevEvent);
+          if (prevSelChangeEvent->mWidget == aTailEvent->mWidget)
+            prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit;
+        }
+      }
+    }
+    return;
+  }
+
+  // Pack sequential selection remove and selection add events into
+  // single selection change event.
+  if (aTailEvent->mPreceedingCount == 1 &&
+      aTailEvent->mItem != aThisEvent->mItem) {
+    if (aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
+        aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
+      aThisEvent->mEventRule = AccEvent::eDoNotEmit;
+      aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
+      aTailEvent->mPackedEvent = aThisEvent;
+      return;
+    }
+
+    if (aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
+        aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
+      aTailEvent->mEventRule = AccEvent::eDoNotEmit;
+      aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
+      aThisEvent->mPackedEvent = aThisEvent;
+      return;
+    }
+  }
+
+  // Unpack the packed selection change event because we've got one
+  // more selection add/remove.
+  if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
+    if (aThisEvent->mPackedEvent) {
+      aThisEvent->mPackedEvent->mEventType =
+        aThisEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
+          nsIAccessibleEvent::EVENT_SELECTION_ADD :
+          nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
+
+      aThisEvent->mPackedEvent->mEventRule =
+        AccEvent::eCoalesceSelectionChange;
+
+      aThisEvent->mPackedEvent = nsnull;
+    }
+
+    aThisEvent->mEventType =
+      aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
+        nsIAccessibleEvent::EVENT_SELECTION_ADD :
+        nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
+
+    return;
+  }
+
+  // Convert into selection add since control has single selection but other
+  // selection events for this control are queued.
+  if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION)
+    aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
+}
+
+void
 NotificationController::CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent,
                                                     AccHideEvent* aThisEvent)
 {
   // XXX: we need a way to ignore SplitNode and JoinNode() when they do not
   // affect the text within the hypertext.
 
   AccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent;
   if (!textEvent)
--- a/accessible/src/base/NotificationController.h
+++ b/accessible/src/base/NotificationController.h
@@ -246,21 +246,21 @@ private:
    * @param aEventRule       the event rule to be applied
    *                         (should be eDoNotEmit or eAllowDupes)
    */
   void ApplyToSiblings(PRUint32 aStart, PRUint32 aEnd,
                        PRUint32 aEventType, nsINode* aNode,
                        AccEvent::EEventRule aEventRule);
 
   /**
-   * Do not emit one of two given reorder events fired for DOM nodes in the case
-   * when one DOM node is in parent chain of second one.
+   * Coalesce two selection change events within the same select control.
    */
-  void CoalesceReorderEventsFromSameTree(AccEvent* aAccEvent,
-                                         AccEvent* aDescendantAccEvent);
+  void CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
+                               AccSelChangeEvent* aThisEvent,
+                               PRInt32 aThisIndex);
 
   /**
    * Coalesce text change events caused by sibling hide events.
    */
   void CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent,
                                    AccHideEvent* aThisEvent);
   void CoalesceTextChangeEventsFor(AccShowEvent* aTailEvent,
                                    AccShowEvent* aThisEvent);
--- a/accessible/src/base/nsAccessibilityService.h
+++ b/accessible/src/base/nsAccessibilityService.h
@@ -468,17 +468,17 @@ static const char kEventTypeNames[][40] 
   "minimize start",                          // EVENT_MINIMIZE_START
   "minimize end",                            // EVENT_MINIMIZE_END
   "document load complete",                  // EVENT_DOCUMENT_LOAD_COMPLETE
   "document reload",                         // EVENT_DOCUMENT_RELOAD
   "document load stopped",                   // EVENT_DOCUMENT_LOAD_STOPPED
   "document attributes changed",             // EVENT_DOCUMENT_ATTRIBUTES_CHANGED
   "document content changed",                // EVENT_DOCUMENT_CONTENT_CHANGED
   "property changed",                        // EVENT_PROPERTY_CHANGED
-  "selection changed",                       // EVENT_SELECTION_CHANGED
+  "page changed",                           // EVENT_PAGE_CHANGED
   "text attribute changed",                  // EVENT_TEXT_ATTRIBUTE_CHANGED
   "text caret moved",                        // EVENT_TEXT_CARET_MOVED
   "text changed",                            // EVENT_TEXT_CHANGED
   "text inserted",                           // EVENT_TEXT_INSERTED
   "text removed",                            // EVENT_TEXT_REMOVED
   "text updated",                            // EVENT_TEXT_UPDATED
   "text selection changed",                  // EVENT_TEXT_SELECTION_CHANGED
   "visible data changed",                    // EVENT_VISIBLE_DATA_CHANGED
@@ -509,17 +509,16 @@ static const char kEventTypeNames[][40] 
   "hyperlink number of anchors changed",     // EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED
   "hyperlink selected link changed",         // EVENT_HYPERLINK_SELECTED_LINK_CHANGED
   "hypertext link activated",                // EVENT_HYPERTEXT_LINK_ACTIVATED
   "hypertext link selected",                 // EVENT_HYPERTEXT_LINK_SELECTED
   "hyperlink start index changed",           // EVENT_HYPERLINK_START_INDEX_CHANGED
   "hypertext changed",                       // EVENT_HYPERTEXT_CHANGED
   "hypertext links count changed",           // EVENT_HYPERTEXT_NLINKS_CHANGED
   "object attribute changed",                // EVENT_OBJECT_ATTRIBUTE_CHANGED
-  "page changed"                             // EVENT_PAGE_CHANGED
 };
 
 /**
  * Map nsIAccessibleRelation constants to strings. Used by
  * nsIAccessibleRetrieval::getStringRelationType() method.
  */
 static const char kRelationTypeNames[][20] = {
   "unknown",             // RELATION_NUL
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -1037,42 +1037,33 @@ nsDocAccessible::AttributeChangedImpl(ns
   if (aAttribute == nsGkAtoms::aria_busy) {
     bool isOn = aContent->AttrValueIs(aNameSpaceID, aAttribute,
                                         nsGkAtoms::_true, eCaseMatters);
     nsRefPtr<AccEvent> event = new AccStateChangeEvent(aContent, states::BUSY, isOn);
     FireDelayedAccessibleEvent(event);
     return;
   }
 
-  if (aAttribute == nsGkAtoms::selected ||
+  // ARIA or XUL selection
+  if ((aContent->IsXUL() && aAttribute == nsGkAtoms::selected) ||
       aAttribute == nsGkAtoms::aria_selected) {
-    // ARIA or XUL selection
+    nsAccessible* item = GetAccessible(aContent);
+    nsAccessible* widget =
+      nsAccUtils::GetSelectableContainer(item, item->State());
+    if (widget) {
+      AccSelChangeEvent::SelChangeType selChangeType =
+        aContent->AttrValueIs(aNameSpaceID, aAttribute,
+                              nsGkAtoms::_true, eCaseMatters) ?
+          AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
 
-    nsAccessible *multiSelect =
-      nsAccUtils::GetMultiSelectableContainer(aContent);
-    // XXX: Multi selects are handled here only (bug 414302).
-    if (multiSelect) {
-      // Need to find the right event to use here, SELECTION_WITHIN would
-      // seem right but we had started using it for something else
-      FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
-                                 multiSelect->GetNode(),
-                                 AccEvent::eAllowDupes);
-
-      static nsIContent::AttrValuesArray strings[] =
-        {&nsGkAtoms::_empty, &nsGkAtoms::_false, nsnull};
-      if (aContent->FindAttrValueIn(kNameSpaceID_None, aAttribute,
-                                    strings, eCaseMatters) >= 0) {
-        FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_REMOVE,
-                                   aContent);
-        return;
-      }
-
-      FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_ADD,
-                                 aContent);
+      nsRefPtr<AccEvent> event =
+        new AccSelChangeEvent(widget, item, selChangeType);
+      FireDelayedAccessibleEvent(event);
     }
+    return;
   }
 
   if (aAttribute == nsGkAtoms::contenteditable) {
     nsRefPtr<AccEvent> editableChangeEvent =
       new AccStateChangeEvent(aContent, states::EDITABLE);
     FireDelayedAccessibleEvent(editableChangeEvent);
     return;
   }
@@ -1204,17 +1195,28 @@ void nsDocAccessible::ContentAppended(ns
 {
 }
 
 void nsDocAccessible::ContentStateChanged(nsIDocument* aDocument,
                                           nsIContent* aContent,
                                           nsEventStates aStateMask)
 {
   if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) {
-    nsHTMLSelectOptionAccessible::SelectionChangedIfOption(aContent);
+    nsAccessible* item = GetAccessible(aContent);
+    if (item) {
+      nsAccessible* widget = item->ContainerWidget();
+      if (widget && widget->IsSelect()) {
+        AccSelChangeEvent::SelChangeType selChangeType =
+          aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED) ?
+            AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
+        nsRefPtr<AccEvent> event = new AccSelChangeEvent(widget, item,
+                                                         selChangeType);
+        FireDelayedAccessibleEvent(event);
+      }
+    }
   }
 
   if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) {
     nsRefPtr<AccEvent> event =
       new AccStateChangeEvent(aContent, states::INVALID, true);
     FireDelayedAccessibleEvent(event);
    }
 }
--- a/accessible/src/base/nsRootAccessible.cpp
+++ b/accessible/src/base/nsRootAccessible.cpp
@@ -468,16 +468,18 @@ nsRootAccessible::ProcessDOMEvent(nsIDOM
 
     nsRefPtr<AccEvent> accEvent =
       new AccStateChangeEvent(accessible, states::EXPANDED, isEnabled);
     nsEventShell::FireEvent(accEvent);
     return;
   }
 
   if (treeItemAccessible && eventType.EqualsLiteral("select")) {
+    // XXX: We shouldn't be based on DOM select event which doesn't provide us
+    // any context info. We should integrate into nsTreeSelection instead.
     // If multiselect tree, we should fire selectionadd or selection removed
     if (FocusMgr()->HasDOMFocus(targetNode)) {
       nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel =
         do_QueryInterface(targetNode);
       nsAutoString selType;
       multiSel->GetSelType(selType);
       if (selType.IsEmpty() || !selType.EqualsLiteral("single")) {
         // XXX: We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE
--- a/accessible/src/html/nsHTMLSelectAccessible.cpp
+++ b/accessible/src/html/nsHTMLSelectAccessible.cpp
@@ -411,69 +411,17 @@ nsHTMLSelectOptionAccessible::SetSelecte
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsHTMLSelectOptionAccessible: Widgets
 
 nsAccessible*
 nsHTMLSelectOptionAccessible::ContainerWidget() const
 {
-  if (mParent && mParent->IsListControl()) {
-    nsAccessible* grandParent = mParent->Parent();
-    if (grandParent && grandParent->IsCombobox())
-      return grandParent;
-
-    return mParent;
-  }
-  return nsnull;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// nsHTMLSelectOptionAccessible: static methods
-
-void
-nsHTMLSelectOptionAccessible::SelectionChangedIfOption(nsIContent *aPossibleOptionNode)
-{
-  if (!aPossibleOptionNode ||
-      aPossibleOptionNode->Tag() != nsGkAtoms::option ||
-      !aPossibleOptionNode->IsHTML()) {
-    return;
-  }
-
-  nsAccessible *multiSelect =
-    nsAccUtils::GetMultiSelectableContainer(aPossibleOptionNode);
-  if (!multiSelect)
-    return;
-
-  nsAccessible *option = GetAccService()->GetAccessible(aPossibleOptionNode);
-  if (!option)
-    return;
-
-
-  nsRefPtr<AccEvent> selWithinEvent =
-    new AccEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, multiSelect);
-
-  if (!selWithinEvent)
-    return;
-
-  option->GetDocAccessible()->FireDelayedAccessibleEvent(selWithinEvent);
-
-  PRUint64 state = option->State();
-  PRUint32 eventType;
-  if (state & states::SELECTED) {
-    eventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
-  }
-  else {
-    eventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
-  }
-
-  nsRefPtr<AccEvent> selAddRemoveEvent = new AccEvent(eventType, option);
-
-  if (selAddRemoveEvent)
-    option->GetDocAccessible()->FireDelayedAccessibleEvent(selAddRemoveEvent);
+  return mParent && mParent->IsListControl() ? mParent : nsnull;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsHTMLSelectOptionAccessible: private methods
 
 nsIContent*
 nsHTMLSelectOptionAccessible::GetSelectState(PRUint64* aState)
 {
@@ -733,36 +681,32 @@ nsHTMLComboboxAccessible::AreItemsOperab
 {
   nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(GetFrame());
   return comboboxFrame && comboboxFrame->IsDroppedDown();
 }
 
 nsAccessible*
 nsHTMLComboboxAccessible::CurrentItem()
 {
-  // No current item for collapsed combobox.
-  return SelectedOption(true);
+  return AreItemsOperable() ? mListAccessible->CurrentItem() : nsnull;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsHTMLComboboxAccessible: protected
 
 nsAccessible*
-nsHTMLComboboxAccessible::SelectedOption(bool aIgnoreIfCollapsed) const
+nsHTMLComboboxAccessible::SelectedOption() const
 {
   nsIFrame* frame = GetFrame();
   nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(frame);
-  if (comboboxFrame) {
-    if (aIgnoreIfCollapsed && !comboboxFrame->IsDroppedDown())
-      return nsnull;
+  if (!comboboxFrame)
+    return nsnull;
 
-    frame = comboboxFrame->GetDropDown();
-  }
-
-  nsIListControlFrame* listControlFrame = do_QueryFrame(frame);
+  nsIListControlFrame* listControlFrame =
+    do_QueryFrame(comboboxFrame->GetDropDown());
   if (listControlFrame) {
     nsCOMPtr<nsIContent> activeOptionNode = listControlFrame->GetCurrentOption();
     if (activeOptionNode) {
       nsDocAccessible* document = GetDocAccessible();
       if (document)
         return document->GetAccessible(activeOptionNode);
     }
   }
@@ -853,8 +797,24 @@ void nsHTMLComboboxListAccessible::GetBo
   if (!frame) {
     *aBoundingFrame = nsnull;
     return;
   }
 
   *aBoundingFrame = frame->GetParent();
   aBounds = (*aBoundingFrame)->GetRect();
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// nsHTMLComboboxListAccessible: Widgets
+
+bool
+nsHTMLComboboxListAccessible::IsActiveWidget() const
+{
+  return mParent && mParent->IsActiveWidget();
+}
+
+bool
+nsHTMLComboboxListAccessible::AreItemsOperable() const
+{
+  return mParent && mParent->AreItemsOperable();
+}
+
--- a/accessible/src/html/nsHTMLSelectAccessible.h
+++ b/accessible/src/html/nsHTMLSelectAccessible.h
@@ -125,18 +125,16 @@ public:
                                           PRInt32 *aSetSize);
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
   // Widgets
   virtual nsAccessible* ContainerWidget() const;
 
-  static void SelectionChangedIfOption(nsIContent *aPossibleOption);
-
 protected:
   // nsAccessible
   virtual nsIFrame* GetBoundsFrame();
 
 private:
   
   /**
    * Get Select element's accessible state
@@ -214,17 +212,17 @@ public:
 
 protected:
   // nsAccessible
   virtual void CacheChildren();
 
   /**
    * Return selected option.
    */
-  nsAccessible* SelectedOption(bool aIgnoreIfCollapsed = false) const;
+  nsAccessible* SelectedOption() const;
 
 private:
   nsRefPtr<nsHTMLComboboxListAccessible> mListAccessible;
 };
 
 /*
  * A class that represents the window that lives to the right
  * of the drop down button inside the Select. This is the window
@@ -241,11 +239,15 @@ public:
 
   // nsAccessNode
   virtual nsIFrame* GetFrame() const;
   virtual bool IsPrimaryForNode() const;
 
   // nsAccessible
   virtual PRUint64 NativeState();
   virtual void GetBoundsRect(nsRect& aBounds, nsIFrame** aBoundingFrame);
+
+  // Widgets
+  virtual bool IsActiveWidget() const;
+  virtual bool AreItemsOperable() const;
 };
 
 #endif
--- a/accessible/src/mac/mozAccessible.mm
+++ b/accessible/src/mac/mozAccessible.mm
@@ -185,19 +185,17 @@ GetNativeFromGeckoAccessible(nsIAccessib
   if (!generalAttributes) {
     // standard attributes that are shared and supported by all generic elements.
     generalAttributes = [[NSArray alloc] initWithObjects:  NSAccessibilityChildrenAttribute, 
                                                            NSAccessibilityParentAttribute,
                                                            NSAccessibilityRoleAttribute,
                                                            NSAccessibilityTitleAttribute,
                                                            NSAccessibilityValueAttribute,
                                                            NSAccessibilitySubroleAttribute,
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
                                                            NSAccessibilityRoleDescriptionAttribute,
-#endif
                                                            NSAccessibilityPositionAttribute,
                                                            NSAccessibilityEnabledAttribute,
                                                            NSAccessibilitySizeAttribute,
                                                            NSAccessibilityWindowAttribute,
                                                            NSAccessibilityFocusedAttribute,
                                                            NSAccessibilityHelpAttribute,
                                                            NSAccessibilityTitleUIElementAttribute,
                                                            kTopLevelUIElementAttribute,
@@ -231,20 +229,18 @@ GetNativeFromGeckoAccessible(nsIAccessib
   if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) 
     return [self position];
   if ([attribute isEqualToString:NSAccessibilitySubroleAttribute])
     return [self subrole];
   if ([attribute isEqualToString:NSAccessibilityEnabledAttribute])
     return [NSNumber numberWithBool:[self isEnabled]];
   if ([attribute isEqualToString:NSAccessibilityValueAttribute])
     return [self value];
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
   if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute])
     return NSAccessibilityRoleDescription([self role], nil);
-#endif
   if ([attribute isEqualToString: (NSString*) kInstanceDescriptionAttribute])
     return [self customDescription];
   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
     return [NSNumber numberWithBool:[self isFocused]];
   if ([attribute isEqualToString:NSAccessibilitySizeAttribute])
     return [self size];
   if ([attribute isEqualToString:NSAccessibilityWindowAttribute])
     return [self window];
--- a/accessible/src/mac/mozActionElements.mm
+++ b/accessible/src/mac/mozActionElements.mm
@@ -221,34 +221,30 @@ enum CheckboxValue {
 }
 
 - (NSArray *)accessibilityActionNames
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if ([self isEnabled]) {
     return [NSArray arrayWithObjects:NSAccessibilityPressAction,
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
                                      NSAccessibilityShowMenuAction,
-#endif
                                      nil];
   }
   return nil;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (NSString *)accessibilityActionDescription:(NSString *)action
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
   if ([action isEqualToString:NSAccessibilityShowMenuAction])
     return @"show menu";
-#endif
   return [super accessibilityActionDescription:action];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (void)accessibilityPerformAction:(NSString *)action
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
--- a/accessible/src/mac/mozTextAccessible.mm
+++ b/accessible/src/mac/mozTextAccessible.mm
@@ -44,19 +44,17 @@ extern const NSString *kTopLevelUIElemen
   static NSArray *supportedAttributes = nil;
   if (!supportedAttributes) {
     // standard attributes that are shared and supported by all generic elements.
     supportedAttributes = [[NSArray alloc] initWithObjects:NSAccessibilityParentAttribute, // required
                                                            NSAccessibilityRoleAttribute,   // required
                                                            NSAccessibilityTitleAttribute,
                                                            NSAccessibilityValueAttribute, // required
                                                            NSAccessibilitySubroleAttribute,
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
                                                            NSAccessibilityRoleDescriptionAttribute,
-#endif
                                                            NSAccessibilityPositionAttribute, // required
                                                            NSAccessibilitySizeAttribute, // required
                                                            NSAccessibilityWindowAttribute, // required
                                                            NSAccessibilityFocusedAttribute, // required
                                                            NSAccessibilityEnabledAttribute, // required
                                                            kTopLevelUIElementAttribute, // required (on OS X 10.4+)
                                                            kInstanceDescriptionAttribute, // required (on OS X 10.4+)
                                                            /* text-specific attributes */
@@ -244,19 +242,17 @@ extern const NSString *kTopLevelUIElemen
   static NSArray *supportedAttributes = nil;
   if (!supportedAttributes) {
     // standard attributes that are shared and supported by all generic elements.
     supportedAttributes = [[NSArray alloc] initWithObjects:NSAccessibilityParentAttribute, // required
                                                            NSAccessibilityRoleAttribute,   // required
                                                            NSAccessibilityTitleAttribute,
                                                            NSAccessibilityValueAttribute, // required
                                                            NSAccessibilityHelpAttribute,
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
                                                            NSAccessibilityRoleDescriptionAttribute,
-#endif
                                                            NSAccessibilityPositionAttribute, // required
                                                            NSAccessibilitySizeAttribute, // required
                                                            NSAccessibilityWindowAttribute, // required
                                                            NSAccessibilityFocusedAttribute, // required
                                                            NSAccessibilityEnabledAttribute, // required
                                                            NSAccessibilityChildrenAttribute, // required
                                                            NSAccessibilityHelpAttribute,
                                                            // NSAccessibilityExpandedAttribute, // required
@@ -276,52 +272,46 @@ extern const NSString *kTopLevelUIElemen
 }
 
 - (NSArray *)accessibilityActionNames
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if ([self isEnabled]) {
     return [NSArray arrayWithObjects:NSAccessibilityConfirmAction,
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
                                      NSAccessibilityShowMenuAction,
-#endif
                                      nil];
   }
   return nil;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (NSString *)accessibilityActionDescription:(NSString *)action
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
   if ([action isEqualToString:NSAccessibilityShowMenuAction])
     return @"show menu";
-#endif
   if ([action isEqualToString:NSAccessibilityConfirmAction])
     return @"confirm";
     
   return [super accessibilityActionDescription:action];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (void)accessibilityPerformAction:(NSString *)action
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   // both the ShowMenu and Click action do the same thing.
   if ([self isEnabled]) {
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
     if ([action isEqualToString:NSAccessibilityShowMenuAction])
       [self showMenu];
-#endif
     if ([action isEqualToString:NSAccessibilityConfirmAction])
       [self confirm];
   }
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)showMenu
--- a/accessible/src/msaa/nsEventMap.h
+++ b/accessible/src/msaa/nsEventMap.h
@@ -85,17 +85,17 @@ static const PRUint32 gWinEventMap[] = {
   kEVENT_WIN_UNKNOWN,                                // nsIAccessibleEvent::EVENT_MINIMIZE_START
   kEVENT_WIN_UNKNOWN,                                // nsIAccessibleEvent::EVENT_MINIMIZE_END
   IA2_EVENT_DOCUMENT_LOAD_COMPLETE,                  // nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE
   IA2_EVENT_DOCUMENT_RELOAD,                         // nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD
   IA2_EVENT_DOCUMENT_LOAD_STOPPED,                   // nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED
   IA2_EVENT_DOCUMENT_ATTRIBUTE_CHANGED,              // nsIAccessibleEvent::EVENT_DOCUMENT_ATTRIBUTES_CHANGED
   IA2_EVENT_DOCUMENT_CONTENT_CHANGED,                // nsIAccessibleEvent::EVENT_DOCUMENT_CONTENT_CHANGED
   kEVENT_WIN_UNKNOWN,                                // nsIAccessibleEvent::EVENT_PROPERTY_CHANGED
-  kEVENT_WIN_UNKNOWN,                                // nsIAccessibleEvent::EVENT_SELECTION_CHANGED
+  IA2_EVENT_PAGE_CHANGED,                            // nsIAccessibleEvent::IA2_EVENT_PAGE_CHANGED
   IA2_EVENT_TEXT_ATTRIBUTE_CHANGED,                  // nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED
   IA2_EVENT_TEXT_CARET_MOVED,                        // nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED
   IA2_EVENT_TEXT_CHANGED,                            // nsIAccessibleEvent::EVENT_TEXT_CHANGED
   IA2_EVENT_TEXT_INSERTED,                           // nsIAccessibleEvent::EVENT_TEXT_INSERTED
   IA2_EVENT_TEXT_REMOVED,                            // nsIAccessibleEvent::EVENT_TEXT_REMOVED
   IA2_EVENT_TEXT_UPDATED,                            // nsIAccessibleEvent::EVENT_TEXT_UPDATED
   IA2_EVENT_TEXT_SELECTION_CHANGED,                  // nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED
   IA2_EVENT_VISIBLE_DATA_CHANGED,                    // nsIAccessibleEvent::EVENT_VISIBLE_DATA_CHANGED
@@ -126,12 +126,11 @@ static const PRUint32 gWinEventMap[] = {
   IA2_EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED,     // nsIAccessibleEvent::EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED
   IA2_EVENT_HYPERLINK_SELECTED_LINK_CHANGED,         // nsIAccessibleEvent::EVENT_HYPERLINK_SELECTED_LINK_CHANGED
   IA2_EVENT_HYPERTEXT_LINK_ACTIVATED,                // nsIAccessibleEvent::EVENT_HYPERTEXT_LINK_ACTIVATED
   IA2_EVENT_HYPERTEXT_LINK_SELECTED,                 // nsIAccessibleEvent::EVENT_HYPERTEXT_LINK_SELECTED
   IA2_EVENT_HYPERLINK_START_INDEX_CHANGED,           // nsIAccessibleEvent::EVENT_HYPERLINK_START_INDEX_CHANGED
   IA2_EVENT_HYPERTEXT_CHANGED,                       // nsIAccessibleEvent::EVENT_HYPERTEXT_CHANGED
   IA2_EVENT_HYPERTEXT_NLINKS_CHANGED,                // nsIAccessibleEvent::EVENT_HYPERTEXT_NLINKS_CHANGED
   IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED,                // nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED
-  IA2_EVENT_PAGE_CHANGED,                            // nsIAccessibleEvent::EVENT_PAGE_CHANGED
   kEVENT_LAST_ENTRY                                  // nsIAccessibleEvent::EVENT_LAST_ENTRY
 };
 
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -10,17 +10,19 @@ const EVENT_FOCUS = nsIAccessibleEvent.E
 const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
 const EVENT_MENU_START = nsIAccessibleEvent.EVENT_MENU_START;
 const EVENT_MENU_END = nsIAccessibleEvent.EVENT_MENU_END;
 const EVENT_MENUPOPUP_START = nsIAccessibleEvent.EVENT_MENUPOPUP_START;
 const EVENT_MENUPOPUP_END = nsIAccessibleEvent.EVENT_MENUPOPUP_END;
 const EVENT_OBJECT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED;
 const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
 const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START;
+const EVENT_SELECTION = nsIAccessibleEvent.EVENT_SELECTION;
 const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD;
+const EVENT_SELECTION_REMOVE = nsIAccessibleEvent.EVENT_SELECTION_REMOVE;
 const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN;
 const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
 const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
 const EVENT_TEXT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED;
 const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
 const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
 const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
 const EVENT_TEXT_SELECTION_CHANGED = nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED;
--- a/accessible/tests/mochitest/events/Makefile.in
+++ b/accessible/tests/mochitest/events/Makefile.in
@@ -77,17 +77,19 @@ include $(topsrcdir)/config/rules.mk
 		test_focus_name.html \
 		test_focus_selects.html \
 		test_focus_tabbox.xul \
 		test_focus_tree.xul \
 		test_menu.xul \
 		test_mutation.html \
 		test_mutation.xhtml \
 		test_scroll.xul \
+		test_selection_aria.html \
 		test_selection.html \
+		test_selection.xul \
 		test_statechange.html \
 		test_text_alg.html \
 		test_text.html \
 		test_textattrchange.html \
 		test_tree.xul \
 		test_valuechange.html \
 		$(NULL)
 
--- a/accessible/tests/mochitest/events/test_selection.html
+++ b/accessible/tests/mochitest/events/test_selection.html
@@ -17,78 +17,97 @@
           src="../events.js"></script>
   <script type="application/javascript"
           src="../states.js"></script>
 
   <script type="application/javascript">
     ////////////////////////////////////////////////////////////////////////////
     // Invokers
 
-    function addSelection(aNode, aOption)
-    {
-      this.DOMNode = aNode;
-      this.optionNode = aOption;
-
-      this.eventSeq = [
-        new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.DOMNode)),
-        new invokerChecker(EVENT_SELECTION_ADD, getAccessible(this.optionNode))
-      ];
-
-      this.invoke = function addselection_invoke() {
-        synthesizeMouse(this.optionNode, 1, 1, {});
-      };
-
-      this.getID = function addselection_getID() {
-        return prettyName(this.optionNode) + " added to selection";
-      };
-    }
-
     ////////////////////////////////////////////////////////////////////////////
     // Do tests
 
-    var gQueue = null;
+    //gA11yEventDumpToConsole = true; // debuggin
 
-    //var gA11yEventDumpID = "eventdump"; // debug stuff
-
+    var gQueue = null;
     function doTests()
     {
       gQueue = new eventQueue();
 
-      var select = document.getElementById("toppings");
-      var option = document.getElementById("onions");
-      gQueue.push(new addSelection(select, option));
+      // open combobox
+      gQueue.push(new synthClick("combobox",
+                                 new invokerChecker(EVENT_FOCUS, "cb1_item1")));
+      gQueue.push(new synthDownKey("cb1_item1",
+                                   new invokerChecker(EVENT_SELECTION, "cb1_item2")));
+
+      // closed combobox
+      gQueue.push(new synthEscapeKey("combobox",
+                                     new invokerChecker(EVENT_FOCUS, "combobox")));
+      gQueue.push(new synthDownKey("cb1_item2",
+                                   new invokerChecker(EVENT_SELECTION, "cb1_item3")));
+
+      // listbox
+      gQueue.push(new synthClick("lb1_item1",
+                                 new invokerChecker(EVENT_SELECTION, "lb1_item1")));
+      gQueue.push(new synthDownKey("lb1_item1",
+                                   new invokerChecker(EVENT_SELECTION, "lb1_item2")));
+
+      // multiselectable listbox
+      gQueue.push(new synthClick("lb2_item1",
+                                 new invokerChecker(EVENT_SELECTION, "lb2_item1")));
+      gQueue.push(new synthDownKey("lb2_item1",
+                                   new invokerChecker(EVENT_SELECTION_ADD, "lb2_item2"),
+                                   { shiftKey: true }));
+      gQueue.push(new synthUpKey("lb2_item2",
+                                 new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item2"),
+                                 { shiftKey: true }));
+      gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true },
+                               new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item1")));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
 
 <body>
 
   <a target="_blank"
-     href="https://bugzilla.mozilla.org/show_bug.cgi?id=569653"
-     title="Make selection events async">
-    Mozilla Bug 569653
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302"
+     title="Incorrect selection events in HTML, XUL and ARIA">
+    Mozilla Bug 414302
   </a>
 
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
-  <p>Pizza</p>
-  <select id="toppings" name="toppings" multiple size=5>
-    <option value="mushrooms">mushrooms
-    <option value="greenpeppers">green peppers
-    <option value="onions" id="onions">onions
-    <option value="tomatoes">tomatoes
-    <option value="olives">olives
+  <select id="combobox">
+    <option id="cb1_item1" value="mushrooms">mushrooms
+    <option id="cb1_item2" value="greenpeppers">green peppers
+    <option id="cb1_item3" value="onions" id="onions">onions
+    <option id="cb1_item4" value="tomatoes">tomatoes
+    <option id="cb1_item5" value="olives">olives
   </select>
 
-  <div id="testContainer">
-    <iframe id="iframe"></iframe>
-  </div>
+  <select id="listbox" size=5>
+    <option id="lb1_item1" value="mushrooms">mushrooms
+    <option id="lb1_item2" value="greenpeppers">green peppers
+    <option id="lb1_item3" value="onions" id="onions">onions
+    <option id="lb1_item4" value="tomatoes">tomatoes
+    <option id="lb1_item5" value="olives">olives
+  </select>
+
+  <p>Pizza</p>
+  <select id="listbox2" multiple size=5>
+    <option id="lb2_item1" value="mushrooms">mushrooms
+    <option id="lb2_item2" value="greenpeppers">green peppers
+    <option id="lb2_item3" value="onions" id="onions">onions
+    <option id="lb2_item4" value="tomatoes">tomatoes
+    <option id="lb2_item5" value="olives">olives
+  </select>
+
   <div id="eventdump"></div>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_selection.xul
@@ -0,0 +1,244 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        title="Selection event tests">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js" />
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+  <script type="application/javascript"
+          src="../common.js" />
+  <script type="application/javascript"
+          src="../states.js" />
+  <script type="application/javascript"
+          src="../events.js" />
+
+  <script type="application/javascript">
+    function advanceTab(aTabsID, aDirection, aNextTabID)
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_SELECTION, aNextTabID)
+      ];
+
+      this.invoke = function advanceTab_invoke()
+      {
+        getNode(aTabsID).advanceSelectedTab(aDirection, true);
+      }
+
+      this.getID = function synthFocus_getID()
+      {
+        return "advanceTab on " + prettyName(aTabsID) + " to " + prettyName(aNextTabID);
+      }
+    }
+
+    function select4FirstItems(aID)
+    {
+      this.listboxNode = getNode(aID);
+      this.eventSeq = [
+        new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0)),
+        new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(1)),
+        new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(2)),
+        new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(3))
+      ];
+
+      this.invoke = function select4FirstItems_invoke()
+      {
+        synthesizeKey("VK_DOWN", { shiftKey: true }); // selects two items
+        synthesizeKey("VK_DOWN", { shiftKey: true });
+        synthesizeKey("VK_DOWN", { shiftKey: true });
+      }
+
+      this.getID = function select4FirstItems_getID()
+      {
+        return "select 4 first items for " + prettyName(aID);
+      }
+    }
+
+    function unselect4FirstItems(aID)
+    {
+      this.listboxNode = getNode(aID);
+      this.eventSeq = [
+        new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(3)),
+        new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(2)),
+        new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(1)),
+        new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0))
+      ];
+
+      this.invoke = function unselect4FirstItems_invoke()
+      {
+        synthesizeKey("VK_UP", { shiftKey: true });
+        synthesizeKey("VK_UP", { shiftKey: true });
+        synthesizeKey("VK_UP", { shiftKey: true });
+        synthesizeKey(" ", { ctrlKey: true }); // unselect first item
+      }
+
+      this.getID = function unselect4FirstItems_getID()
+      {
+        return "unselect 4 first items for " + prettyName(aID);
+      }
+    }
+
+    function selectAllItems(aID)
+    {
+      this.listboxNode = getNode(aID);
+      this.eventSeq = [
+        new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode))
+      ];
+
+      this.invoke = function selectAllItems_invoke()
+      {
+        synthesizeKey("VK_END", { shiftKey: true });
+      }
+
+      this.getID = function selectAllItems_getID()
+      {
+        return "select all items for " + prettyName(aID);
+      }
+    }
+
+    function unselectAllItemsButFirst(aID)
+    {
+      this.listboxNode = getNode(aID);
+      this.eventSeq = [
+        new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode))
+      ];
+
+      this.invoke = function unselectAllItemsButFirst_invoke()
+      {
+        synthesizeKey("VK_HOME", { shiftKey: true });
+      }
+
+      this.getID = function unselectAllItemsButFirst_getID()
+      {
+        return "unselect all items for " + prettyName(aID);
+      }
+    }
+
+    function unselectSelectItem(aID)
+    {
+      this.listboxNode = getNode(aID);
+      this.eventSeq = [
+        new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0)),
+        new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0))
+      ];
+
+      this.invoke = function unselectSelectItem_invoke()
+      {
+        synthesizeKey(" ", { ctrlKey: true }); // select item
+        synthesizeKey(" ", { ctrlKey: true }); // unselect item
+      }
+
+      this.getID = function unselectSelectItem_getID()
+      {
+        return "unselect and then select first item for " + prettyName(aID);
+      }
+    }
+
+    /**
+     * Do tests.
+     */
+    var gQueue = null;
+
+    //gA11yEventDumpToConsole = true; // debuggin
+
+    function doTests()
+    {
+      gQueue = new eventQueue();
+
+      //////////////////////////////////////////////////////////////////////////
+      // tabbox
+      gQueue.push(new advanceTab("tabs", 1, "tab3"));
+
+      //////////////////////////////////////////////////////////////////////////
+      // listbox
+      gQueue.push(new synthClick("lb1_item1",
+                                 new invokerChecker(EVENT_SELECTION, "lb1_item1")));
+      gQueue.push(new synthDownKey("lb1_item1",
+                                   new invokerChecker(EVENT_SELECTION, "lb1_item2")));
+
+      //////////////////////////////////////////////////////////////////////////
+      // multiselectable listbox
+      gQueue.push(new synthClick("lb2_item1",
+                                 new invokerChecker(EVENT_SELECTION, "lb2_item1")));
+      gQueue.push(new synthDownKey("lb2_item1",
+                                   new invokerChecker(EVENT_SELECTION_ADD, "lb2_item2"),
+                                   { shiftKey: true }));
+      gQueue.push(new synthUpKey("lb2_item2",
+                                 new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item2"),
+                                 { shiftKey: true }));
+      gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true },
+                               new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item1")));
+
+      //////////////////////////////////////////////////////////////////////////
+      // selection event coalescence
+
+      // fire 4 selection_add events
+      gQueue.push(new select4FirstItems("listbox2"));
+      // fire 4 selection_remove events
+      gQueue.push(new unselect4FirstItems("listbox2"));
+      // fire selection_within event
+      gQueue.push(new selectAllItems("listbox2"));
+      // fire selection_within event
+      gQueue.push(new unselectAllItemsButFirst("listbox2"));
+      // fire selection_remove/add events
+      gQueue.push(new unselectSelectItem("listbox2"));
+
+      gQueue.invoke(); // Will call SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTests);
+  </script>
+
+  <hbox flex="1" style="overflow: auto;">
+    <body xmlns="http://www.w3.org/1999/xhtml">
+      <a target="_blank"
+         href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302"
+         title="Incorrect selection events in HTML, XUL and ARIA">
+        Mozilla Bug 414302
+      </a>
+      <p id="display"></p>
+      <div id="content" style="display: none"></div>
+      <pre id="test">
+      </pre>
+    </body>
+
+    <tabbox id="tabbox" selectedIndex="1">
+      <tabs id="tabs">
+        <tab id="tab1" label="tab1"/>
+        <tab id="tab2" label="tab2"/>
+        <tab id="tab3" label="tab3"/>
+        <tab id="tab4" label="tab4"/>
+      </tabs>
+      <tabpanels>
+        <tabpanel><!-- tabpanel First elements go here --></tabpanel>
+        <tabpanel><button id="b1" label="b1"/></tabpanel>
+        <tabpanel><button id="b2" label="b2"/></tabpanel>
+        <tabpanel></tabpanel>
+      </tabpanels>
+    </tabbox>
+
+    <listbox id="listbox">
+      <listitem id="lb1_item1" label="item1"/>
+      <listitem id="lb1_item2" label="item2"/>
+    </listbox>
+
+    <listbox id="listbox2" seltype="multiple">
+      <listitem id="lb2_item1" label="item1"/>
+      <listitem id="lb2_item2" label="item2"/>
+      <listitem id="lb2_item3" label="item3"/>
+      <listitem id="lb2_item4" label="item4"/>
+      <listitem id="lb2_item5" label="item5"/>
+      <listitem id="lb2_item6" label="item6"/>
+      <listitem id="lb2_item7" label="item7"/>
+    </listbox>
+
+  </hbox>
+</window>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_selection_aria.html
@@ -0,0 +1,112 @@
+<html>
+
+<head>
+  <title>ARIA selection event testing</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../events.js"></script>
+  <script type="application/javascript"
+          src="../states.js"></script>
+
+  <script type="application/javascript">
+    ////////////////////////////////////////////////////////////////////////////
+    // Invokers
+
+    function selectTreeItem(aTreeID, aItemID)
+    {
+      this.treeNode = getNode(aTreeID);
+      this.itemNode = getNode(aItemID);
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_SELECTION, aItemID)
+      ];
+
+      this.invoke = function selectTreeItem_invoke() {
+        var itemNode = this.treeNode.querySelector("*[aria-selected='true']");
+        if (itemNode)
+          itemNode.removeAttribute("aria-selected", "true");
+
+        this.itemNode.setAttribute("aria-selected", "true");
+      }
+
+      this.getID = function selectTreeItem_getID()
+      {
+        return "selectTreeItem " + prettyName(aItemID);
+      }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Do tests
+
+    var gQueue = null;
+
+    //var gA11yEventDumpID = "eventdump"; // debug stuff
+
+    function doTests()
+    {
+      gQueue = new eventQueue();
+
+      gQueue.push(new selectTreeItem("tree", "treeitem1"));
+      gQueue.push(new selectTreeItem("tree", "treeitem1a"));
+      gQueue.push(new selectTreeItem("tree", "treeitem1a1"));
+
+      gQueue.push(new selectTreeItem("tree2", "tree2item1"));
+      gQueue.push(new selectTreeItem("tree2", "tree2item1a"));
+      gQueue.push(new selectTreeItem("tree2", "tree2item1a1"));
+
+      gQueue.invoke(); // Will call SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTests);
+  </script>
+</head>
+
+<body>
+
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=569653"
+     title="Make selection events async">
+    Mozilla Bug 569653
+  </a>
+
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <div id="tree" role="tree">
+    <div id="treeitem1" role="treeitem">Canada
+      <div id="treeitem1a" role="treeitem">- Ontario
+        <div id="treeitem1a1" role="treeitem">-- Toronto</div>
+      </div>
+      <div id="treeitem1b" role="treeitem">- Manitoba</div>
+    </div>
+    <div id="treeitem2" role="treeitem">Germany</div>
+    <div id="treeitem3" role="treeitem">Russia</div>
+  </div>
+
+  <div id="tree2" role="tree" aria-multiselectable="true">
+    <div id="tree2item1" role="treeitem">Canada
+      <div id="tree2item1a" role="treeitem">- Ontario
+        <div id="tree2item1a1" role="treeitem">-- Toronto</div>
+      </div>
+      <div id="tree2item1b" role="treeitem">- Manitoba</div>
+    </div>
+    <div id="tree2item2" role="treeitem">Germany</div>
+    <div id="tree2item3" role="treeitem">Russia</div>
+  </div>
+
+  <div id="eventdump"></div>
+</body>
+</html>
--- 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/aboutHome.css
+++ b/browser/base/content/aboutHome.css
@@ -364,13 +364,21 @@ body[dir=rtl] #restorePreviousSession::b
   position: absolute;
   color: rgb(150,150,150);
   font-size: .8em;
   width: 100%;
   text-align: center;
   bottom: 2%;
 }
 
+#syncLinksContainer {
+  padding-top: 1em;
+}
+
+.sync-link {
+  padding: 1em;
+}
+
 @media all and (max-height: 370px) {
   #bottomSection {
     visibility: hidden;
   }
 }
--- a/browser/base/content/aboutHome.xhtml
+++ b/browser/base/content/aboutHome.xhtml
@@ -97,11 +97,15 @@
         <button id="restorePreviousSession">&historyRestoreLastSession.label;</button>
       </div>
     </div>
 
     <div id="bottomSection">
       <div id="aboutMozilla">
         <a href="http://www.mozilla.com/about/">&abouthome.aboutMozilla;</a>
       </div>
+      <div id="syncLinksContainer">
+        <a href="javascript:void(0);" class="sync-link" id="setupSyncLink">&abouthome.syncSetup.label;</a>
+        <a href="javascript:void(0);" class="sync-link" id="pairDeviceLink">&abouthome.pairDevice.label;</a>
+      </div>
     </div>
   </body>
 </html>
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -140,22 +140,18 @@
              oncommand="PlacesCommandHook.showPlacesOrganizer('AllBookmarks');"/>
     <command id="Browser:ShowAllHistory"
              oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/>
   </commandset>
 
   <commandset id="inspectorCommands">
     <command id="Inspector:Inspect"
              oncommand="InspectorUI.toggleInspection();"/>
-    <command id="Inspector:Previous"
-             oncommand="InspectorUI.inspectPrevious();"
-             disabled="true"/>
-    <command id="Inspector:Next"
-             oncommand="InspectorUI.inspectNext();"
-             disabled="true"/>
+    <command id="Inspector:Sidebar"
+             oncommand="InspectorUI.toggleSidebar();"/>
   </commandset>
 
   <broadcasterset id="mainBroadcasterSet">
     <broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
                  type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul"
                  oncommand="toggleSidebar('viewBookmarksSidebar');"/>
 
     <!-- for both places and non-places, the sidebar lives at
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -206,16 +206,34 @@ let gSyncUI = {
   },
 
   onLoginFinish: function SUI_onLoginFinish() {
     // Clear out any login failure notifications
     let title = this._stringBundle.GetStringFromName("error.login.title");
     this.clearError(title);
   },
 
+  // Set visibility of "Setup Sync" link
+  showSetupSyncAboutHome: function SUI_showSetupSyncAboutHome(toShow) {
+    let browsers = gBrowser.browsers;
+    for (let i = 0; i < browsers.length; i++) {
+      let b = browsers[i];
+      if ("about:home" == b.currentURI.spec) {
+        b.contentDocument.getElementById("setupSyncLink").hidden = !toShow;
+      }
+    }
+  },
+
+  onSetupComplete: function SUI_onSetupComplete() {
+    // Remove "setup sync" link in about:home if it is open. 
+    this.showSetupSyncAboutHome(false);
+
+    onLoginFinish();
+  },
+
   onLoginError: function SUI_onLoginError() {
     // if login fails, any other notifications are essentially moot
     Weave.Notifications.removeAll();
 
     // if we haven't set up the client, don't show errors
     if (this._needsSetup()) {
       this.updateUI();
       return;
@@ -250,16 +268,18 @@ let gSyncUI = {
   },
 
   onLogout: function SUI_onLogout() {
     this.updateUI();
   },
 
   onStartOver: function SUI_onStartOver() {
     this.clearError();
+    // Make "setup sync" link visible in about:home if it is open. 
+    this.showSetupSyncAboutHome(true);
   },
 
   onQuotaNotice: function onQuotaNotice(subject, data) {
     let title = this._stringBundle.GetStringFromName("warning.sync.quota.label");
     let description = this._stringBundle.GetStringFromName("warning.sync.quota.description");
     let buttons = [];
     buttons.push(new Weave.NotificationButton(
       this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.label"),
@@ -286,26 +306,50 @@ let gSyncUI = {
     if (this._needsSetup())
       this.openSetup();
     else
       this.doSync();
   },
 
   //XXXzpao should be part of syncCommon.js - which we might want to make a module...
   //        To be fixed in a followup (bug 583366)
-  openSetup: function SUI_openSetup() {
+
+  /**
+   * Invoke the Sync setup wizard.
+   *
+   * @param wizardType
+   *        Indicates type of wizard to launch:
+   *          null    -- regular set up wizard
+   *          "pair"  -- pair a device first
+   *          "reset" -- reset sync
+   */
+
+  openSetup: function SUI_openSetup(wizardType) {
     let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
     if (win)
       win.focus();
     else {
       window.openDialog("chrome://browser/content/syncSetup.xul",
-                        "weaveSetup", "centerscreen,chrome,resizable=no");
+                        "weaveSetup", "centerscreen,chrome,resizable=no",
+                        wizardType);
     }
   },
 
+  openAddDevice: function () {
+    if (!Weave.Utils.ensureMPUnlocked())
+      return;
+
+    let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
+    if (win)
+      win.focus();
+    else
+      window.openDialog("chrome://browser/content/syncAddDevice.xul",
+                        "syncAddDevice", "centerscreen,chrome,resizable=no");
+  },
+
   openQuotaDialog: function SUI_openQuotaDialog() {
     let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
     if (win)
       win.focus();
     else
       Services.ww.activeWindow.openDialog(
         "chrome://browser/content/syncQuota.xul", "",
         "centerscreen,chrome,dialog,modal");
@@ -457,17 +501,17 @@ let gSyncUI = {
         break;
       case "weave:service:sync:delayed":
         this.onSyncDelay();
         break;
       case "weave:service:quota:remaining":
         this.onQuotaNotice();
         break;
       case "weave:service:setup-complete":
-        this.onLoginFinish();
+        this.onSetupComplete();
         break;
       case "weave:service:login:start":
         this.onActivityStart();
         break;
       case "weave:service:login:finish":
         this.onLoginFinish();
         break;
       case "weave:ui:login:error":
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2670,25 +2670,31 @@ function PageProxyClickHandler(aEvent)
  *  to the DOM for unprivileged pages.
  */
 function BrowserOnAboutPageLoad(document) {
   if (/^about:home$/i.test(document.documentURI)) {
     let ss = Components.classes["@mozilla.org/browser/sessionstore;1"].
              getService(Components.interfaces.nsISessionStore);
     if (!ss.canRestoreLastSession)
       document.getElementById("sessionRestoreContainer").hidden = true;
+    // Sync-related links
+    if (Services.prefs.prefHasUserValue("services.sync.username")) {
+      document.getElementById("setupSyncLink").hidden = true;
+    }
   }
 }
 
 /**
  * Handle command events bubbling up from error page content
  */
 function BrowserOnClick(event) {
     // Don't trust synthetic events
-    if (!event.isTrusted || event.target.localName != "button")
+    if (!event.isTrusted ||
+        (event.target.localName != "button" &&
+         event.target.className != "sync-link"))
       return;
 
     var ot = event.originalTarget;
     var errorDoc = ot.ownerDocument;
 
     // If the event came from an ssl error page, it is probably either the "Add
     // Exception…" or "Get me out of here!" button
     if (/^about:certerror/.test(errorDoc.documentURI)) {
@@ -2808,16 +2814,26 @@ function BrowserOnClick(event) {
     else if (/^about:home$/i.test(errorDoc.documentURI)) {
       if (ot == errorDoc.getElementById("restorePreviousSession")) {
         let ss = Cc["@mozilla.org/browser/sessionstore;1"].
                  getService(Ci.nsISessionStore);
         if (ss.canRestoreLastSession)
           ss.restoreLastSession();
         errorDoc.getElementById("sessionRestoreContainer").hidden = true;
       }
+      else if (ot == errorDoc.getElementById("pairDeviceLink")) {
+        if (Services.prefs.prefHasUserValue("services.sync.username")) {
+          gSyncUI.openAddDevice();
+        } else {
+          gSyncUI.openSetup("pair");
+        }
+      }
+      else if (ot == errorDoc.getElementById("setupSyncLink")) {
+        gSyncUI.openSetup(null);
+      }
     }
 }
 
 /**
  * Re-direct the browser to a known-safe page.  This function is
  * used when, for example, the user browses to a known malware page
  * and is presented with about:blocked.  The "Get me out of here!"
  * button should take the user to the default start page so that even
@@ -3887,22 +3903,39 @@ var FullScreen = {
       this._isAnimating = false;
       // This is needed if they use the context menu to quit fullscreen
       this._isPopupOpen = false;
 
       this.cleanup();
     }
   },
 
+  exitDomFullScreen : function(e) {
+    document.mozCancelFullScreen();
+  },
+
   enterDomFullScreen : function(event) {
-    if (!document.mozFullScreen) {
+    // We receive "mozfullscreenchange" events for each subdocument which
+    // is an ancestor of the document containing the element which requested
+    // full-screen. Only add listeners and show warning etc when the event we
+    // receive is targeted at the chrome document, i.e. only once every time
+    // we enter DOM full-screen mode.
+    if (!document.mozFullScreen || event.target.ownerDocument != document) {
       return;
     }
     this.showWarning(true);
 
+    // Exit DOM full-screen mode upon open, close, or change tab.
+    gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
+    gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen);
+    gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen);
+
+    // Exit DOM full-screen mode when the browser window loses focus (ALT+TAB, etc).
+    window.addEventListener("deactivate", this.exitDomFullScreen, 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);
 
@@ -3925,16 +3958,20 @@ var FullScreen = {
       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();
+      gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
+      gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
+      gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
+      window.removeEventListener("deactivate", this.exitDomFullScreen, true);
     }
   },
 
   observe: function(aSubject, aTopic, aData)
   {
     if (aData == "browser.fullscreen.autohide") {
       if (gPrefService.getBoolPref("browser.fullscreen.autohide")) {
         gBrowser.mPanelContainer.addEventListener("mousemove",
@@ -8895,24 +8932,26 @@ function toggleAddonBar() {
   let addonBar = document.getElementById("addon-bar");
   setToolbarVisibility(addonBar, addonBar.collapsed);
 }
 
 var Scratchpad = {
   prefEnabledName: "devtools.scratchpad.enabled",
 
   openScratchpad: function SP_openScratchpad() {
-    const SCRATCHPAD_WINDOW_URL = "chrome://browser/content/scratchpad.xul";
-    const SCRATCHPAD_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
-
-    return Services.ww.openWindow(null, SCRATCHPAD_WINDOW_URL, "_blank",
-                                  SCRATCHPAD_WINDOW_FEATURES, null);
-  },
+    return this.ScratchpadManager.openScratchpad();
+  }
 };
 
+XPCOMUtils.defineLazyGetter(Scratchpad, "ScratchpadManager", function() {
+  let tmp = {};
+  Cu.import("resource:///modules/devtools/scratchpad-manager.jsm", tmp);
+  return tmp.ScratchpadManager;
+});
+
 
 XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () {
 #ifdef XP_WIN
   // Only show resizers on Windows 2000 and XP
   let sysInfo = Components.classes["@mozilla.org/system-info;1"]
                           .getService(Components.interfaces.nsIPropertyBag2);
   return parseFloat(sysInfo.getProperty("version")) < 6;
 #else
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -961,16 +961,22 @@
       <tabbrowser id="content" disablehistory="true"
                   flex="1" contenttooltip="aHTMLTooltip"
                   tabcontainer="tabbrowser-tabs"
                   contentcontextmenu="contentAreaContextMenu"
                   autocompletepopup="PopupAutoComplete"
                   onclick="return contentAreaClick(event, false);"/>
       <statuspanel id="statusbar-display" inactive="true"/>
     </vbox>
+    <splitter id="devtools-side-splitter" hidden="true"/>
+    <vbox id="devtools-sidebar-box" hidden="true" flex="1"
+          style="min-width: 18em; width: 22em; max-width: 42em;" persist="width">
+      <toolbar id="devtools-sidebar-toolbar" nowindowdrag="true"/>
+      <deck id="devtools-sidebar-deck" flex="1"/>
+    </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>
@@ -982,26 +988,40 @@
              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">
+            <toolbarbutton id="inspector-style-button"
+                           label="&inspectStyleButton.label;"
+                           accesskey="&inspectStyleButton.accesskey;"
+                           command="Inspector:Sidebar"/>
             <!-- 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
@@ -272,19 +272,20 @@ nsContextMenu.prototype = {
     }
 
     // Reload image depends on an image that's not fully loaded
     this.showItem("context-reloadimage", (this.onImage && !this.onCompletedImage));
 
     // View image depends on having an image that's not standalone
     // (or is in a frame), or a canvas.
     this.showItem("context-viewimage", (this.onImage &&
-                  (!this.onStandaloneImage || this.inFrame)) || this.onCanvas);
+                  (!this.inSyntheticDoc || this.inFrame)) || this.onCanvas);
 
-    this.showItem("context-viewvideo", this.onVideo);
+    // View video depends on not having a standalone video.
+    this.showItem("context-viewvideo", this.onVideo && (!this.inSyntheticDoc || this.inFrame));
     this.setItemAttr("context-viewvideo",  "disabled", !this.mediaURL);
 
     // View background image depends on whether there is one.
     this.showItem("context-viewbgimage", shouldShow && !this._hasMultipleBGImages);
     this.showItem("context-sep-viewbgimage", shouldShow && !this._hasMultipleBGImages);
     document.getElementById("context-viewbgimage")
             .disabled = !this.hasBGImage;
 
@@ -461,32 +462,32 @@ nsContextMenu.prototype = {
       this.shouldDisplay = false;
       return;
     }
 
     // Initialize contextual info.
     this.onImage           = false;
     this.onLoadedImage     = false;
     this.onCompletedImage  = false;
-    this.onStandaloneImage = false;
     this.onCanvas          = false;
     this.onVideo           = false;
     this.onAudio           = false;
     this.onTextInput       = false;
     this.onKeywordField    = false;
     this.mediaURL          = "";
     this.onLink            = false;
     this.onMailtoLink      = false;
     this.onSaveableLink    = false;
     this.link              = null;
     this.linkURL           = "";
     this.linkURI           = null;
     this.linkProtocol      = "";
     this.onMathML          = false;
     this.inFrame           = false;
+    this.inSyntheticDoc    = false;
     this.hasBGImage        = false;
     this.bgImageURL        = "";
     this.onEditableArea    = false;
     this.isDesignMode      = false;
 
     // Clear any old spellchecking items from the menu, this used to
     // be in the menu hiding code but wasn't getting called in all
     // situations. Here, we can ensure it gets cleaned up any time the
@@ -495,33 +496,33 @@ nsContextMenu.prototype = {
     InlineSpellCheckerUI.clearSuggestionsFromMenu();
     InlineSpellCheckerUI.clearDictionaryListFromMenu();
 
     InlineSpellCheckerUI.uninit();
 
     // Remember the node that was clicked.
     this.target = aNode;
 
+    // Check if we are in a synthetic document (stand alone image, video, etc.).
+    this.inSyntheticDoc =  this.target.ownerDocument.mozSyntheticDocument;
     // First, do checks for nodes that never have children.
     if (this.target.nodeType == Node.ELEMENT_NODE) {
       // See if the user clicked on an image.
       if (this.target instanceof Ci.nsIImageLoadingContent &&
           this.target.currentURI) {
         this.onImage = true;
 
         var request =
           this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
         if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
           this.onLoadedImage = true;
         if (request && (request.imageStatus & request.STATUS_LOAD_COMPLETE))
           this.onCompletedImage = true;
 
         this.mediaURL = this.target.currentURI.spec;
-        if (this.target.ownerDocument instanceof ImageDocument)
-          this.onStandaloneImage = true;
       }
       else if (this.target instanceof HTMLCanvasElement) {
         this.onCanvas = true;
       }
       else if (this.target instanceof HTMLVideoElement) {
         this.onVideo = true;
         this.mediaURL = this.target.currentSrc || this.target.src;
       }
--- a/browser/base/content/syncProgress.js
+++ b/browser/base/content/syncProgress.js
@@ -39,34 +39,67 @@
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://services-sync/main.js");
 
 let gProgressBar;
 let gCounter = 0;
 
 function onLoad(event) {
-  Services.obs.addObserver(increaseProgressBar, "weave:engine:sync:finish", false);
-  Services.obs.addObserver(increaseProgressBar, "weave:engine:sync:error", false);
+  Services.obs.addObserver(onEngineSync, "weave:engine:sync:finish", false);
+  Services.obs.addObserver(onEngineSync, "weave:engine:sync:error", false);
+  Services.obs.addObserver(onServiceSync, "weave:service:sync:finish", false);
+  Services.obs.addObserver(onServiceSync, "weave:service:sync:error", false);
+
   gProgressBar = document.getElementById('uploadProgressBar');
 
   if (Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
-    gProgressBar.max = Weave.Engines.getEnabled().length;
     gProgressBar.style.display = "inline";
   }
   else {
     gProgressBar.style.display = "none";
   }
 }
 
 function onUnload(event) {
-  Services.obs.removeObserver(increaseProgressBar, "weave:engine:sync:finish");
-  Services.obs.removeObserver(increaseProgressBar, "weave:engine:sync:error");
+  cleanUpObservers();
+}
+
+function cleanUpObservers() {
+  try {
+    Services.obs.removeObserver(onEngineSync, "weave:engine:sync:finish", false);
+    Services.obs.removeObserver(onEngineSync, "weave:engine:sync:error", false);
+    Services.obs.removeObserver(onServiceSync, "weave:service:sync:finish", false);
+    Services.obs.removeObserver(onServiceSync, "weave:service:sync:error", false);
+  }
+  catch (e) {
+    // may be double called by unload & exit. Ignore.
+  }
 }
 
-function increaseProgressBar(){
+function onEngineSync(subject, topic, data) {
+  // The Clients engine syncs first. At this point we don't necessarily know
+  // yet how many engines will be enabled, so we'll ignore the Clients engine
+  // and evaluate how many engines are enabled when the first "real" engine
+  // syncs.
+  if (data == "clients") {
+    return;
+  }
+
+  if (!gCounter &&
+      Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
+    gProgressBar.max = Weave.Engines.getEnabled().length;
+  }
+
   gCounter += 1;
   gProgressBar.setAttribute("value", gCounter);
 }
 
+function onServiceSync(subject, topic, data) {
+  // To address the case where 0 engines are synced, we will fill the
+  // progress bar so the user knows that the sync has finished.
+  gProgressBar.setAttribute("value", gProgressBar.max);
+  cleanUpObservers();
+}
+
 function closeTab() {
   window.close();
 }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2427,17 +2427,17 @@
                 offset *= -1;
               this.tabContainer.advanceSelectedTab(offset, true);
               aEvent.stopPropagation();
               aEvent.preventDefault();
           }
 #else
           if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
               aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
-              this.mTabBox.handleCtrlPageUpDown) {
+              !this.mCurrentTab.pinned) {
             this.removeCurrentTab({animate: true});
             aEvent.stopPropagation();
             aEvent.preventDefault();
           }
 #endif
         ]]></body>
       </method>
 
--- a/browser/base/content/test/browser_aboutHome.js
+++ b/browser/base/content/test/browser_aboutHome.js
@@ -5,16 +5,19 @@
 registerCleanupFunction(function() {
   // Ensure we don't pollute prefs for next tests.
   try {
     Services.prefs.clearUserPref("network.cookies.cookieBehavior");
   } catch (ex) {}
   try {
     Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
   } catch (ex) {}
+  try {
+    Services.prefs.clearUserPref("services.sync.username");
+  } catch (ex) {}
 });
 
 let gTests = [
 
 {
   desc: "Check that rejecting cookies does not prevent page from working",
   setup: function ()
   {
@@ -109,16 +112,126 @@ let gTests = [
     ok(snippetsElt, "Found snippets element");
     is(snippetsElt.getElementsByTagName("span").length, 1,
        "A default snippet is visible.");
 
     executeSoon(runNextTest);
   }
 },
 
+{
+  desc: "Check sync links visibility before and after Sync setup",
+  setup: function ()
+  {
+    try {
+      Services.prefs.clearUserPref("services.sync.username");
+    } catch (ex) {}
+    Services.obs.notifyObservers(null, "weave:service:ready", null);
+  },
+  run: function ()
+  {
+    let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+    let pairLink = doc.getElementById("pairDeviceLink");
+    let setupLink = doc.getElementById("setupSyncLink");
+
+    ok(pairLink, "Found 'Pair Device' link");
+    ok(setupLink, "Found 'Set Up Sync' link");
+    ok(!pairLink.hidden, "'Pair' link is visible before setup");
+    ok(!setupLink.hidden, "'Set Up' link is visible before setup");
+
+    Services.obs.notifyObservers(null, "weave:service:setup-complete", null);
+
+    executeSoon(function () {
+      setupLink = doc.getElementById("setupSyncLink");
+      ok(setupLink.hidden, "'Set Up' link is hidden after setup");
+      ok(!pairLink.hidden, "'Pair' link is visible after setup");
+
+      executeSoon(runNextTest);
+    });
+  }
+},
+
+{
+  desc: "Check sync links visibility before and after Sync unlink",
+  setup: function ()
+  {
+    Services.prefs.setCharPref("services.sync.username", "someuser@domain.com");
+    Services.obs.notifyObservers(null, "weave:service:ready", null);
+  },
+  run: function ()
+  {
+    let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+    let pairLink = doc.getElementById("pairDeviceLink");
+    let setupLink = doc.getElementById("setupSyncLink");
+
+    ok(!pairLink.hidden, "'Pair' link is visible before unlink");
+    ok(setupLink.hidden, "'Set Up' link is hidden before unlink");
+
+    Services.obs.notifyObservers(null, "weave:service:start-over", null);
+
+    executeSoon(function () {
+      setupLink = doc.getElementById("setupSyncLink");
+      ok(!setupLink.hidden, "'Set Up' link is visible after unlink");
+      ok(!pairLink.hidden, "'Pair' link is visible after unlink");
+      executeSoon(runNextTest);
+    });
+  }
+},
+
+{
+  desc: "Check Pair Device link opens correct dialog with Sync account ",
+  setup: function ()
+  {
+    Services.prefs.setCharPref("services.sync.username", "someuser@domain.com");
+    Services.obs.notifyObservers(null, "weave:service:ready", null);
+  },
+  run: function ()
+  {
+    expectDialogWindow("Sync:AddDevice");
+    let browser = gBrowser.selectedTab.linkedBrowser;
+    let button = browser.contentDocument.getElementById("pairDeviceLink");
+    EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow);
+  }
+},
+
+{
+  desc: "Check Pair Device link opens correct dialog without Sync account",
+  setup: function ()
+  {
+    try {
+      Services.prefs.clearUserPref("services.sync.username");
+    } catch (ex) {}
+    Services.obs.notifyObservers(null, "weave:service:ready", null);
+  },
+  run: function ()
+  {
+    expectDialogWindow("Weave:AccountSetup");
+    let browser = gBrowser.selectedTab.linkedBrowser;
+    let button = browser.contentDocument.getElementById("pairDeviceLink");
+    EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow);
+  }
+},
+
+{
+  desc: "Check Sync Setup link opens correct dialog (without Sync account)",
+  setup: function ()
+  {
+    try {
+      Services.prefs.clearUserPref("services.sync.username");
+    } catch (ex) {}
+    Services.obs.notifyObservers(null, "weave:service:ready", null);
+  },
+  run: function ()
+  {
+    expectDialogWindow("Weave:AccountSetup");
+    let browser = gBrowser.selectedTab.linkedBrowser;
+    let button = browser.contentDocument.getElementById("setupSyncLink");
+    EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow);
+  }
+},
 ];
 
 function test()
 {
   waitForExplicitFinish();
 
   // browser-chrome test harness inits browser specifying an hardcoded page
   // and this causes nsIBrowserHandler.defaultArgs to not be evaluated since
@@ -154,16 +267,32 @@ function runNextTest()
       executeSoon(test.run);
     }, true);
   }
   else {
     finish();
   }
 }
 
+function expectDialogWindow(expectedDialog) {
+  Services.ww.registerNotification(function onWindow(subject, topic) {
+    let win = subject.QueryInterface(Components.interfaces.nsIDOMWindow);
+    win.addEventListener("load", function onLoad() {
+      win.removeEventListener("load", onLoad, false);
+      let wintype = win.document.documentElement.getAttribute("windowtype");
+      if (topic == "domwindowopened" && wintype == expectedDialog) {
+        Services.ww.unregisterNotification(onWindow);
+        // Clean up dialog.
+        win.close();
+        executeSoon(runNextTest);
+      }
+    }, false);
+  });
+}
+
 function getStorage()
 {
   let aboutHomeURI = Services.io.newURI("moz-safe-about:home", null, null);
   let principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"].
                   getService(Components.interfaces.nsIScriptSecurityManager).
                   getCodebasePrincipal(Services.io.newURI("about:home", null, null));
   let dsm = Components.classes["@mozilla.org/dom/storagemanager;1"].
             getService(Components.interfaces.nsIDOMStorageManager);
--- 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_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/base/content/test/subtst_contextmenu.html
+++ b/browser/base/content/test/subtst_contextmenu.html
@@ -13,16 +13,18 @@ Browser context menu subtest.
 <img id="test-image" src="ctxmenu-image.png">
 <canvas id="test-canvas" width="100" height="100" style="background-color: blue"></canvas>
 <video controls id="test-video-ok"  src="video.ogg" width="100" height="100" style="background-color: green"></video>
 <video controls id="test-video-bad" src="bogus.duh" width="100" height="100" style="background-color: orange"></video>
 <video controls id="test-video-bad2" width="100" height="100" style="background-color: yellow">
   <source src="bogus.duh" type="video/durrrr;">
 </video>
 <iframe id="test-iframe" width="98"  height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-video-in-iframe" src="video.ogg" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-image-in-iframe" src="ctxmenu-image.png" width="98" height="98" style="border: 1px solid black"></iframe>
 <textarea id="test-textarea">chssseesbbbie</textarea> <!-- a weird word which generates only one suggestion -->
 <div id="test-contenteditable" contenteditable="true">chssseefsbbbie</div> <!-- a more weird word which generates no suggestions -->
 <input id="test-input-spellcheck" type="text" spellcheck="true" autofocus value="prodkjfgigrty"> <!-- this one also generates one suggestion -->
 <div contextmenu="myMenu">
   <p id="test-pagemenu" hopeless="true">I've got a context menu!</p>
   <menu id="myMenu" type="context">
     <menuitem label="Plain item" onclick="document.getElementById('test-pagemenu').removeAttribute('hopeless');"></menuitem>
     <menuitem label="Disabled item" disabled></menuitem>
--- a/browser/base/content/test/test_contextmenu.html
+++ b/browser/base/content/test/test_contextmenu.html
@@ -428,20 +428,78 @@ function runTest(testNum) {
                                "---",                       null,
                                "context-viewframesource",   true,
                                "context-viewframeinfo",     true], null,
                           "---",                  null,
                           "context-viewsource",   true,
                           "context-viewinfo",     true
                          ].concat(inspectItems));
         closeContextMenu();
+        openContextMenuFor(video_in_iframe); // Invoke context menu for next test.
+        break;
+
+    case 12:
+        // Context menu for a video in an iframe
+        checkContextMenu(["context-media-play",         true,
+                          "context-media-mute",         true,
+                          "context-media-hidecontrols", true,
+                          "context-video-showstats",    true,
+                          "context-video-fullscreen",   true,
+                          "---",                        null,
+                          "context-viewvideo",          true,
+                          "context-copyvideourl",       true,
+                          "---",                        null,
+                          "context-savevideo",          true,
+                          "context-video-saveimage",    true,
+                          "context-sendvideo",          true,
+                          "frame",                null,
+                              ["context-showonlythisframe", true,
+                               "context-openframeintab",    true,
+                               "context-openframe",         true,
+                               "---",                       null,
+                               "context-reloadframe",       true,
+                               "---",                       null,
+                               "context-bookmarkframe",     true,
+                               "context-saveframe",         true,
+                               "---",                       null,
+                               "context-printframe",        true,
+                               "---",                       null,
+                               "context-viewframeinfo",     true], null].concat(inspectItems));
+        closeContextMenu();
+        openContextMenuFor(image_in_iframe); // Invoke context menu for next test.
+        break;
+
+    case 13:
+        // Context menu for an image in an iframe
+        checkContextMenu(["context-viewimage",            true,
+                          "context-copyimage-contents",   true,
+                          "context-copyimage",            true,
+                          "---",                          null,
+                          "context-saveimage",            true,
+                          "context-sendimage",            true,
+                          "context-setDesktopBackground", true,
+                          "context-viewimageinfo",        true,
+                          "frame",                null,
+                              ["context-showonlythisframe", true,
+                               "context-openframeintab",    true,
+                               "context-openframe",         true,
+                               "---",                       null,
+                               "context-reloadframe",       true,
+                               "---",                       null,
+                               "context-bookmarkframe",     true,
+                               "context-saveframe",         true,
+                               "---",                       null,
+                               "context-printframe",        true,
+                               "---",                       null,
+                               "context-viewframeinfo",     true], null].concat(inspectItems));
+        closeContextMenu();
         openContextMenuFor(textarea, false, true); // Invoke context menu for next test, but wait for the spellcheck.
         break;
 
-    case 12:
+    case 14:
         // Context menu for textarea
         checkContextMenu(["*chubbiness",         true, // spelling suggestion
                           "spell-add-to-dictionary", true,
                           "---",                 null,
                           "context-undo",        false,
                           "---",                 null,
                           "context-cut",         false,
                           "context-copy",        false,
@@ -456,17 +514,17 @@ function runTest(testNum) {
                                "---",                          null,
                                "spell-add-dictionaries",       true], null,
                          ].concat(inspectItems));
 
         closeContextMenu();
         openContextMenuFor(contenteditable); // Invoke context menu for next test.
         break;
 
-    case 13:
+    case 15:
         // Context menu for contenteditable
         checkContextMenu(["spell-no-suggestions", false,
                           "spell-add-to-dictionary", true,
                           "---",                 null,
                           "context-undo",        false,
                           "---",                 null,
                           "context-cut",         false,
                           "context-copy",        false,
@@ -481,17 +539,17 @@ function runTest(testNum) {
                                "---",                          null,
                                "spell-add-dictionaries",       true], null
                          ].concat(inspectItems));
 
         closeContextMenu();
         openContextMenuFor(inputspell); // Invoke context menu for next test.
         break;
 
-    case 14:
+    case 16:
         // Context menu for spell-check input
         checkContextMenu(["*prodigality",        true, // spelling suggestion
                           "spell-add-to-dictionary", true,
                           "---",                 null,
                           "context-undo",        false,
                           "---",                 null,
                           "context-cut",         false,
                           "context-copy",        false,
@@ -506,23 +564,23 @@ function runTest(testNum) {
                                "---",                          null,
                                "spell-add-dictionaries",       true], null
                          ].concat(inspectItems));
 
         closeContextMenu();
         openContextMenuFor(link); // Invoke context menu for next test.
         break;
 
-    case 15:
+    case 17:
         executeCopyCommand("cmd_copyLink", "http://mozilla.com/");
         closeContextMenu();
         openContextMenuFor(pagemenu); // Invoke context menu for next test.
         break;
 
-    case 16:
+    case 18:
         // Context menu for element with assigned content context menu
         checkContextMenu(["+Plain item",          {type: "", icon: "", checked: false, disabled: false},
                           "+Disabled item",       {type: "", icon: "", checked: false, disabled: true},
                           "+Item w/ textContent", {type: "", icon: "", checked: false, disabled: false},
                           "---",                  null,
                           "+Checkbox",            {type: "checkbox", icon: "", checked: true, disabled: false},
                           "---",                  null,
                           "+Radio1",              {type: "checkbox", icon: "", checked: true, disabled: false},
@@ -555,17 +613,17 @@ function runTest(testNum) {
                           "context-viewinfo",     true
                          ].concat(inspectItems));
 
         invokeItemAction("0");
         closeContextMenu();
         openContextMenuFor(pagemenu, true); // Invoke context menu for next test.
         break;
 
-    case 17:
+    case 19:
         // Context menu for element with assigned content context menu
         // The shift key should bypass content context menu processing
         checkContextMenu(["context-back",         false,
                           "context-forward",      false,
                           "context-reload",       true,
                           "context-stop",         false,
                           "---",                  null,
                           "context-bookmarkpage", true,
@@ -599,17 +657,17 @@ function runTest(testNum) {
   }
 
 }
 
 
 var testNum = 1;
 var subwindow, chromeWin, contextMenu, lastElement;
 var text, link, mailto, input, img, canvas, video_ok, video_bad, video_bad2,
-    iframe, textarea, contenteditable, inputspell, pagemenu;
+    iframe, video_in_iframe, image_in_iframe, textarea, contenteditable, inputspell, pagemenu;
 
 function startTest() {
     netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
     chromeWin = subwindow
                     .QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIWebNavigation)
                     .QueryInterface(Ci.nsIDocShellTreeItem)
                     .rootTreeItem
@@ -631,16 +689,19 @@ function startTest() {
     mailto = subwindow.document.getElementById("test-mailto");
     input  = subwindow.document.getElementById("test-input");
     img    = subwindow.document.getElementById("test-image");
     canvas = subwindow.document.getElementById("test-canvas");
     video_ok   = subwindow.document.getElementById("test-video-ok");
     video_bad  = subwindow.document.getElementById("test-video-bad");
     video_bad2 = subwindow.document.getElementById("test-video-bad2");
     iframe = subwindow.document.getElementById("test-iframe");
+    video_in_iframe = subwindow.document.getElementById("test-video-in-iframe").contentDocument.getElementsByTagName("video")[0];
+    video_in_iframe.pause();
+    image_in_iframe = subwindow.document.getElementById("test-image-in-iframe").contentDocument.getElementsByTagName("img")[0];
     textarea = subwindow.document.getElementById("test-textarea");
     contenteditable = subwindow.document.getElementById("test-contenteditable");
     contenteditable.focus(); // content editable needs to be focused to enable spellcheck
     inputspell = subwindow.document.getElementById("test-input-spellcheck");
     pagemenu = subwindow.document.getElementById("test-pagemenu");
 
     contextMenu.addEventListener("popupshown", function() { runTest(++testNum); }, false);
     runTest(1);
--- 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/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -135,16 +135,21 @@ Cu.import("resource://gre/modules/Servic
 // debug.js adds NS_ASSERT. cf. bug 669196
 Cu.import("resource://gre/modules/debug.js");
 
 XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
   Cu.import("resource://gre/modules/NetUtil.jsm");
   return NetUtil;
 });
 
+XPCOMUtils.defineLazyGetter(this, "ScratchpadManager", function() {
+  Cu.import("resource:///modules/devtools/scratchpad-manager.jsm");
+  return ScratchpadManager;
+});
+
 XPCOMUtils.defineLazyServiceGetter(this, "CookieSvc",
   "@mozilla.org/cookiemanager;1", "nsICookieManager2");
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
   "@mozilla.org/xre/app-info;1", "nsICrashReporter");
 #endif
 
@@ -1577,16 +1582,20 @@ SessionStoreService.prototype = {
     }
 
     // Merge closed windows from this session with ones from last session
     if (lastSessionState._closedWindows) {
       this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows);
       this._capClosedWindows();
     }
 
+    if (lastSessionState.scratchpads) {
+      ScratchpadManager.restoreSession(lastSessionState.scratchpads);
+    }
+
     // Set data that persists between sessions
     this._recentCrashes = lastSessionState.session &&
                           lastSessionState.session.recentCrashes || 0;
     this._sessionStartTime = lastSessionState.session &&
                              lastSessionState.session.startTime ||
                              this._sessionStartTime;
 
     this._lastSessionState = null;
@@ -2249,27 +2258,43 @@ SessionStoreService.prototype = {
    *        should we check the privacy level for https
    * @param aIsPinned
    *        is the entry we're evaluating for a pinned tab; used only if
    *        aCheckPrivacy
    */
   _extractHostsForCookies:
     function sss__extractHostsForCookies(aEntry, aHosts, aCheckPrivacy, aIsPinned) {
 
-    // _host and _scheme may not be set (for about: urls for example), in which
-    // case testing _scheme will be sufficient.
-    if (/https?/.test(aEntry._scheme) && !aHosts[aEntry._host] &&
+    let host = aEntry._host,
+        scheme = aEntry._scheme;
+
+    // If host & scheme aren't defined, then we are likely here in the startup
+    // process via _splitCookiesFromWindow. In that case, we'll turn aEntry.url
+    // into an nsIURI and get host/scheme from that. This will throw for about:
+    // urls in which case we don't need to do anything.
+    if (!host && !scheme) {
+      try {
+        let uri = this._getURIFromString(aEntry.url);
+        host = uri.host;
+        scheme = uri.scheme;
+      }
+      catch(ex) { }
+    }
+
+    // host and scheme may not be set (for about: urls for example), in which
+    // case testing scheme will be sufficient.
+    if (/https?/.test(scheme) && !aHosts[host] &&
         (!aCheckPrivacy ||
-         this._checkPrivacyLevel(aEntry._scheme == "https", aIsPinned))) {
+         this._checkPrivacyLevel(scheme == "https", aIsPinned))) {
       // By setting this to true or false, we can determine when looking at
       // the host in _updateCookies if we should check for privacy.
-      aHosts[aEntry._host] = aIsPinned;
+      aHosts[host] = aIsPinned;
     }
-    else if (aEntry._scheme == "file") {
-      aHosts[aEntry._host] = true;
+    else if (scheme == "file") {
+      aHosts[host] = true;
     }
 
     if (aEntry.children) {
       aEntry.children.forEach(function(entry) {
         this._extractHostsForCookies(entry, aHosts, aCheckPrivacy, aIsPinned);
       }, this);
     }
   },
@@ -2482,22 +2507,26 @@ SessionStoreService.prototype = {
       ix = -1;
 
     let session = {
       state: this._loadState == STATE_RUNNING ? STATE_RUNNING_STR : STATE_STOPPED_STR,
       lastUpdate: Date.now(),
       startTime: this._sessionStartTime,
       recentCrashes: this._recentCrashes
     };
+    
+    // get open Scratchpad window states too
+    var scratchpads = ScratchpadManager.getSessionState();
 
     return {
       windows: total,
       selectedWindow: ix + 1,
       _closedWindows: lastClosedWindowsCopy,
-      session: session
+      session: session,
+      scratchpads: scratchpads
     };
   },
 
   /**
    * serialize session data for a window 
    * @param aWindow
    *        Window reference
    * @returns string
@@ -2695,16 +2724,20 @@ SessionStoreService.prototype = {
     }
     if (aOverwriteTabs || root._firstTabs) {
       this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs || [];
     }
     
     this.restoreHistoryPrecursor(aWindow, tabs, winData.tabs,
       (aOverwriteTabs ? (parseInt(winData.selected) || 1) : 0), 0, 0);
 
+    if (aState.scratchpads) {
+      ScratchpadManager.restoreSession(aState.scratchpads);
+    }
+
     // This will force the keypress listener that Panorama has to attach if it
     // isn't already. This will be the case if tab view wasn't entered or there
     // were only visible tabs when TabView.init was first called.
     aWindow.TabView.init();
 
     // set smoothScroll back to the original value
     tabstrip.smoothScroll = smoothScroll;
 
@@ -3187,27 +3220,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 +3255,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"
@@ -3981,16 +4033,19 @@ SessionStoreService.prototype = {
       tab.entries.forEach(function(entry) {
         this._extractHostsForCookies(entry, cookieHosts, false)
       }, this);
     }, this);
 
     // By creating a regex we reduce overhead and there is only one loop pass
     // through either array (cookieHosts and aWinState.cookies).
     let hosts = Object.keys(cookieHosts).join("|").replace("\\.", "\\.", "g");
+    // If we don't actually have any hosts, then we don't want to do anything.
+    if (!hosts.length)
+      return;
     let cookieRegex = new RegExp(".*(" + hosts + ")");
     for (let cIndex = 0; cIndex < aWinState.cookies.length;) {
       if (cookieRegex.test(aWinState.cookies[cIndex].host)) {
         aTargetWinState.cookies =
           aTargetWinState.cookies.concat(aWinState.cookies.splice(cIndex, 1));
         continue;
       }
       cIndex++;
--- a/browser/components/sessionstore/test/browser/Makefile.in
+++ b/browser/components/sessionstore/test/browser/Makefile.in
@@ -144,21 +144,24 @@ include $(topsrcdir)/config/rules.mk
 	browser_615394-SSWindowState_events.js \
 	browser_618151.js \
 	browser_623779.js \
 	browser_624727.js \
 	browser_625257.js \
 	browser_628270.js \
 	browser_635418.js \
 	browser_636279.js \
+	browser_644409-scratchpads.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_644409-scratchpads.js
@@ -0,0 +1,59 @@
+ /* Any copyright is dedicated to the Public Domain.
+    http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const testState = {
+  windows: [{
+    tabs: [
+      { entries: [{ url: "about:blank" }] },
+    ]
+  }],
+  scratchpads: [
+    { text: "text1", executionContext: 1 },
+    { text: "", executionContext: 2, filename: "test.js" }
+  ]
+};
+
+// only finish() when correct number of windows opened
+var restored = [];
+function addState(state) {
+  restored.push(state);
+
+  if (restored.length == testState.scratchpads.length) {
+    ok(statesMatch(restored, testState.scratchpads),
+      "Two scratchpad windows restored");
+
+    Services.ww.unregisterNotification(windowObserver);
+    finish();
+  }
+}
+
+function test() {
+  waitForExplicitFinish();
+
+  Services.ww.registerNotification(windowObserver);
+
+  ss.setBrowserState(JSON.stringify(testState));
+}
+
+function windowObserver(aSubject, aTopic, aData) {
+  if (aTopic == "domwindowopened") {     
+    let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+    win.addEventListener("load", function() {
+      if (win.Scratchpad) {
+        let state = win.Scratchpad.getState();
+        win.close();
+        addState(state);
+      }
+    }, false);
+  }
+}
+
+function statesMatch(restored, states) {
+  return states.every(function(state) {
+    return restored.some(function(restoredState) {
+      return state.filename == restoredState.filename &&
+             state.text == restoredState.text &&
+             state.executionContext == restoredState.executionContext;
+    })
+  });
+}
\ No newline at end of file
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 + ')');
+  }
+}
--- a/browser/components/shell/src/nsWindowsShellService.cpp
+++ b/browser/components/shell/src/nsWindowsShellService.cpp
@@ -257,16 +257,19 @@ LaunchHelper(nsAutoString& aPath)
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindowsShellService::ShortcutMaintenance()
 {
   nsresult rv;
 
+  // XXX App ids were updated to a constant install path hash,
+  // XXX this code can be removed after a few upgrade cycles.
+
   // Launch helper.exe so it can update the application user model ids on
   // shortcuts in the user's taskbar and start menu. This keeps older pinned
   // shortcuts grouped correctly after major updates. Note, we also do this
   // through the upgrade installer script, however, this is the only place we
   // have a chance to trap links created by users who do control the install/
   // update process of the browser.
 
   nsCOMPtr<nsIWinTaskbar> taskbarInfo =
--- a/browser/config/mozconfigs/macosx32/debug
+++ b/browser/config/mozconfigs/macosx32/debug
@@ -1,12 +1,9 @@
-# Don't use the standard mozconfig. We don't want universal for a debug build. 
-#. $topsrcdir/build/macosx/universal/mozconfig
-
-ac_add_options --with-macos-sdk=/Developer/SDKs/MacOSX10.5.sdk
+. $topsrcdir/build/macosx/mozconfig.leopard
 ac_add_options --enable-debug
 ac_add_options --enable-trace-malloc
 
 # Enable parallel compiling
 mk_add_options MOZ_MAKE_FLAGS="-j4"
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
--- a/browser/devtools/Makefile.in
+++ b/browser/devtools/Makefile.in
@@ -46,16 +46,13 @@ include $(DEPTH)/config/autoconf.mk
 
 include $(topsrcdir)/config/config.mk
 
 DIRS = \
   highlighter \
   webconsole \
   sourceeditor \
   styleinspector \
+  scratchpad \
   shared \
   $(NULL)
 
-ifdef ENABLE_TESTS
-DIRS += scratchpad/test
-endif
-
 include $(topsrcdir)/config/rules.mk
--- 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;
@@ -793,16 +754,67 @@ InspectorUI.prototype = {
     if (this.isInspectorOpen) {
       this.closeInspectorUI();
     } else {
       this.openInspectorUI();
     }
   },
 
   /**
+   * Show the Sidebar.
+   */
+  showSidebar: function IUI_showSidebar()
+  {
+    this.sidebarBox.removeAttribute("hidden");
+    this.sidebarSplitter.removeAttribute("hidden");
+    this.stylingButton.checked = true;
+
+    // Activate the first tool in the sidebar, only if none previously-
+    // selected. We'll want to do a followup to remember selected tool-states.
+    if (!Array.some(this.sidebarToolbar.children,
+      function(btn) btn.hasAttribute("checked"))) {
+        let firstButtonId = this.getToolbarButtonId(this.sidebarTools[0].id);
+        this.chromeDoc.getElementById(firstButtonId).click();
+    }
+  },
+
+  /**
+   * Hide the Sidebar.
+   */
+  hideSidebar: function IUI_hideSidebar()
+  {
+    this.sidebarBox.setAttribute("hidden", "true");
+    this.sidebarSplitter.setAttribute("hidden", "true");
+    this.stylingButton.checked = false;
+  },
+
+  /**
+   * Show or hide the sidebar. Called from the Styling button on the
+   * highlighter toolbar.
+   */
+  toggleSidebar: function IUI_toggleSidebar()
+  {
+    if (!this.isSidebarOpen) {
+      this.showSidebar();
+    } else {
+      this.hideSidebar();
+    }
+  },
+
+  /**
+   * Getter to test if the Sidebar is open or not.
+   */
+  get isSidebarOpen()
+  {
+    return this.stylingButton.checked &&
+          !this.sidebarBox.hidden &&
+          !this.sidebarSplitter.hidden;
+  },
+
+  /**
    * Toggle the status of the inspector, starting or stopping it. Invoked
    * from the toolbar's Inspect button.
    */
   toggleInspection: function IUI_toggleInspection()
   {
     if (this.inspecting) {
       this.stopInspecting();
     } else {
@@ -989,16 +1001,19 @@ InspectorUI.prototype = {
     this.stopInspecting();
     this.browser.removeEventListener("keypress", this, true);
 
     this.saveToolState(this.winID);
     this.toolsDo(function IUI_toolsHide(aTool) {
       this.unregisterTool(aTool);
     }.bind(this));
 
+    // close the sidebar
+    this.hideSidebar();
+
     if (this.highlighter) {
       this.highlighter.highlighterContainer.removeEventListener("keypress",
                                                                 this,
                                                                 true);
       this.highlighter.destroy();
       this.highlighter = null;
     }
 
@@ -1401,135 +1416,253 @@ InspectorUI.prototype = {
    * @returns String
    */
   getToolbarButtonId: function IUI_createButtonId(anId)
   {
     return "inspector-" + anId + "-toolbutton";
   },
 
   /**
+   * Save a registered tool's callback for a specified event.
+   * @param aWidget xul:widget
+   * @param aEvent a DOM event name
+   * @param aCallback Function the click event handler for the button
+   */
+  bindToolEvent: function IUI_bindToolEvent(aWidget, aEvent, aCallback)
+  {
+    this.toolEvents[aWidget.id + "_" + aEvent] = aCallback;
+    aWidget.addEventListener(aEvent, aCallback, false);
+  },
+
+  /**
    * Register an external tool with the inspector.
    *
    * aRegObj = {
    *   id: "toolname",
    *   context: myTool,
-   *   label: "Button label",
+   *   label: "Button or tab label",
    *   icon: "chrome://somepath.png",
    *   tooltiptext: "Button tooltip",
    *   accesskey: "S",
    *   isOpen: object.property, (getter) returning true if tool is open.
    *   onSelect: object.method,
    *   show: object.method, called to show the tool when button is pressed.
    *   hide: object.method, called to hide the tool when button is pressed.
    *   dim: object.method, called to disable a tool during highlighting.
    *   unregister: object.method, called when tool should be destroyed.
-   *   panel: myTool.panel
+   *   panel: myTool.panel, set if tool is in a separate panel, null otherwise.
+   *   sidebar: boolean, true if tool lives in sidebar tab.
    * }
    *
    * @param aRegObj Object
    *        The Registration Object used to register this tool described
    *        above. The tool should cache this object for later deregistration.
    */
   registerTool: function IUI_registerTool(aRegObj)
   {
     if (this.toolRegistered(aRegObj.id)) {
       return;
     }
 
     this.tools[aRegObj.id] = aRegObj;
 
     let buttonContainer = this.chromeDoc.getElementById("inspector-tools");
-    let btn = this.chromeDoc.createElement("toolbarbutton");
+    let btn;
+
+    // if this is a sidebar tool, create the sidebar features for it and bail.
+    if (aRegObj.sidebar) {
+      this.createSidebarTool(aRegObj);
+      return;
+    }
+
+    btn = this.chromeDoc.createElement("toolbarbutton");
     let buttonId = this.getToolbarButtonId(aRegObj.id);
     btn.setAttribute("id", buttonId);
     btn.setAttribute("label", aRegObj.label);
     btn.setAttribute("tooltiptext", aRegObj.tooltiptext);
     btn.setAttribute("accesskey", aRegObj.accesskey);
     btn.setAttribute("image", aRegObj.icon || "");
-    buttonContainer.appendChild(btn);
+    buttonContainer.insertBefore(btn, this.stylingButton);
 
-    /**
-     * Save a registered tool's callback for a specified event.
-     * @param aWidget xul:widget
-     * @param aEvent a DOM event name
-     * @param aCallback Function the click event handler for the button
-     */
-    let toolEvents = this.toolEvents;
-    function bindToolEvent(aWidget, aEvent, aCallback) {
-      toolEvents[aWidget.id + "_" + aEvent] = aCallback;
-      aWidget.addEventListener(aEvent, aCallback, false);
-    }
-
-    bindToolEvent(btn, "click",
+    this.bindToolEvent(btn, "click",
       function IUI_toolButtonClick(aEvent) {
         if (btn.checked) {
           this.toolHide(aRegObj);
         } else {
           this.toolShow(aRegObj);
         }
       }.bind(this));
 
+    // if the tool has a panel, register the popuphiding event
     if (aRegObj.panel) {
-      bindToolEvent(aRegObj.panel, "popuphiding",
+      this.bindToolEvent(aRegObj.panel, "popuphiding",
         function IUI_toolPanelHiding() {
           btn.checked = false;
         });
     }
   },
 
+  get sidebarBox()
+  {
+    return this.chromeDoc.getElementById("devtools-sidebar-box");
+  },
+
+  get sidebarToolbar()
+  {
+    return this.chromeDoc.getElementById("devtools-sidebar-toolbar");
+  },
+
+  get sidebarDeck()
+  {
+    return this.chromeDoc.getElementById("devtools-sidebar-deck");
+  },
+
+  get sidebarSplitter()
+  {
+    return this.chromeDoc.getElementById("devtools-side-splitter");
+  },
+
+  get stylingButton()
+  {
+    return this.chromeDoc.getElementById("inspector-style-button");
+  },
+
+  /**
+   * Creates a tab and tabpanel for our tool to reside in.
+   * @param {Object} aRegObj the Registration Object for our tool.
+   */
+  createSidebarTool: function IUI_createSidebarTab(aRegObj)
+  {
+    // toolbutton elements
+    let btn = this.chromeDoc.createElement("toolbarbutton");
+    let buttonId = this.getToolbarButtonId(aRegObj.id);
+
+    btn.id = buttonId;
+    btn.setAttribute("label", aRegObj.label);
+    btn.setAttribute("tooltiptext", aRegObj.tooltiptext);
+    btn.setAttribute("accesskey", aRegObj.accesskey);
+    btn.setAttribute("image", aRegObj.icon || "");
+    btn.setAttribute("type", "radio");
+    btn.setAttribute("group", "sidebar-tools");
+    this.sidebarToolbar.appendChild(btn);
+
+    // create tool iframe
+    let iframe = this.chromeDoc.createElement("iframe");
+    iframe.id = "devtools-sidebar-iframe-" + aRegObj.id;
+    iframe.setAttribute("flex", "1");
+    this.sidebarDeck.appendChild(iframe);
+
+    // wire up button to show the iframe
+    this.bindToolEvent(btn, "click", function showIframe() {
+      let visible = this.sidebarDeck.selectedPanel == iframe;
+      if (!visible) {
+        sidebarDeck.selectedPanel = iframe;
+      }
+      this.toolShow(aRegObj);
+    }.bind(this));
+  },
+
+  /**
+   * Return the registered object's iframe.
+   * @param aRegObj see registerTool function.
+   * @return iframe or null
+   */
+  getToolIframe: function IUI_getToolIFrame(aRegObj)
+  {
+    return this.chromeDoc.getElementById("devtools-sidebar-iframe-" + aRegObj.id);
+  },
+
   /**
    * Show the specified tool.
    * @param aTool Object (see comment for IUI_registerTool)
    */
   toolShow: function IUI_toolShow(aTool)
   {
     aTool.show.call(aTool.context, this.selection);
-    this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id)).checked = true;
+
+    let btn = this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id));
+    btn.setAttribute("checked", "true");
   },
 
   /**
    * Hide the specified tool.
    * @param aTool Object (see comment for IUI_registerTool)
    */
   toolHide: function IUI_toolHide(aTool)
   {
     aTool.hide.call(aTool.context);
-    this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id)).checked = false;
+
+    let btn = this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id));
+    btn.removeAttribute("checked");
+  },
+
+  /**
+   * Unregister the events associated with the registered tool's widget.
+   * @param aWidget XUL:widget (toolbarbutton|panel).
+   * @param aEvent a DOM event.
+   */
+  unbindToolEvent: function IUI_unbindToolEvent(aWidget, aEvent)
+  {
+    let toolEvent = aWidget.id + "_" + aEvent;
+    aWidget.removeEventListener(aEvent, this.toolEvents[toolEvent], false);
+    delete this.toolEvents[toolEvent]
   },
 
   /**
    * Unregister the registered tool, unbinding click events for the buttons
    * and showing and hiding events for the panel.
    * @param aRegObj Object
    *        The registration object used to register the tool.
    */
   unregisterTool: function IUI_unregisterTool(aRegObj)
   {
+    // if this is a sidebar tool, use the sidebar unregistration method
+    if (aRegObj.sidebar) {
+      this.unregisterSidebarTool(aRegObj);
+      return;
+    }
+
     let button = this.chromeDoc.getElementById(this.getToolbarButtonId(aRegObj.id));
+    let buttonContainer = this.chromeDoc.getElementById("inspector-tools");
 
-    /**
-     * Unregister the events associated with the registered tool's widget.
-     * @param aWidget XUL:widget (toolbarbutton|panel).
-     * @param aEvent a DOM event.
-     */
-    let toolEvents = this.toolEvents;
-    function unbindToolEvent(aWidget, aEvent) {
-      let toolEvent = aWidget.id + "_" + aEvent;
-      aWidget.removeEventListener(aEvent, toolEvents[toolEvent], false);
-      delete toolEvents[toolEvent]
-    };
+    // unbind click events on button
+    this.unbindToolEvent(button, "click");
 
-    let buttonContainer = this.chromeDoc.getElementById("inspector-tools");
-    unbindToolEvent(button, "click");
+    // unbind panel popuphiding events if present.
+    if (aRegObj.panel)
+      this.unbindToolEvent(aRegObj.panel, "popuphiding");
 
-    if (aRegObj.panel)
-      unbindToolEvent(aRegObj.panel, "popuphiding");
-
+    // remove the button from its container
     buttonContainer.removeChild(button);
 
+    // call unregister callback and remove from collection
+    if (aRegObj.unregister)
+      aRegObj.unregister.call(aRegObj.context);
+
+    delete this.tools[aRegObj.id];
+  },
+
+  /**
+   * Unregister the registered sidebar tool, unbinding click events for the
+   * button.
+   * @param aRegObj Object
+   *        The registration object used to register the tool.
+   */
+  unregisterSidebarTool: function IUI_unregisterSidebarTool(aRegObj)
+  {
+    // unbind tool button click event
+    let buttonId = this.getToolbarButtonId(aRegObj.id);
+    let btn = this.chromeDoc.getElementById(buttonId);
+    this.unbindToolEvent(btn, "click");
+
+    // remove sidebar buttons and tools
+    this.sidebarToolbar.removeChild(btn);
+
+    // call unregister callback and remove from collection, this also removes
+    // the iframe.
     if (aRegObj.unregister)
       aRegObj.unregister.call(aRegObj.context);
 
     delete this.tools[aRegObj.id];
   },
 
   /**
    * Save a list of open tools to the inspector store.
@@ -1554,16 +1687,19 @@ InspectorUI.prototype = {
    *               restored.
    */
   restoreToolState: function IUI_restoreToolState(aWinID)
   {
     let openTools = this.store.getValue(aWinID, "openTools");
     if (openTools) {
       this.toolsDo(function IUI_toolsOnShow(aTool) {
         if (aTool.id in openTools) {
+          if (aTool.sidebar && !this.isSidebarOpen) {
+            this.showSidebar();
+          }
           this.toolShow(aTool);
         }
       }.bind(this));
     }
   },
 
   /**
    * For each tool in the tools collection select the current node that is
@@ -1601,16 +1737,28 @@ InspectorUI.prototype = {
   toolsDo: function IUI_toolsDo(aFunction)
   {
     for each (let tool in this.tools) {
       aFunction(tool);
     }
   },
 
   /**
+   * Convenience getter to retrieve only the sidebar tools.
+   */
+  get sidebarTools()
+  {
+    let sidebarTools = [];
+    for each (let tool in this.tools)
+      if (tool.sidebar)
+        sidebarTools.push(tool);
+    return sidebarTools;
+  },
+
+  /**
    * Check if a tool is registered?
    * @param aId The id of the tool to check
    */
   toolRegistered: function IUI_toolRegistered(aId)
   {
     return aId in this.tools;
   },
 
--- 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/highlighter/test/browser_inspector_initialization.js
+++ b/browser/devtools/highlighter/test/browser_inspector_initialization.js
@@ -71,16 +71,17 @@ function runInspectorTests()
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
   Services.obs.addObserver(treePanelTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
 
   ok(InspectorUI.toolbar, "we have the toolbar.");
   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.isSidebarOpen, "Inspector Sidebar is not open");
   ok(InspectorUI.highlighter, "Highlighter is up");
   InspectorUI.inspectNode(doc.body);
   InspectorUI.stopInspecting();
 
   InspectorUI.treePanel.open();
 }
 
 function treePanelTests()
@@ -88,27 +89,27 @@ function treePanelTests()
   Services.obs.removeObserver(treePanelTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
   Services.obs.addObserver(stylePanelTests,
     "StyleInspector-opened", false);
 
   ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
 
   executeSoon(function() {
-    InspectorUI.stylePanel.open(doc.body);
+    InspectorUI.showSidebar();
   });
 }
 
 function stylePanelTests()
 {
   Services.obs.removeObserver(stylePanelTests, "StyleInspector-opened");
   Services.obs.addObserver(runContextMenuTest,
     InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
 
-  ok(InspectorUI.stylePanel.isOpen(), "Style Panel is Open");
+  ok(InspectorUI.isSidebarOpen, "Inspector Sidebar is open");
   ok(InspectorUI.stylePanel.cssHtmlTree, "Style Panel has a cssHtmlTree");
 
   executeSoon(function() {
     InspectorUI.closeInspectorUI();
   });
 
 }
 
@@ -186,16 +187,17 @@ function inspectNodesFromContextTestTrap
 function finishInspectorTests()
 {
   Services.obs.removeObserver(finishInspectorTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
 
   ok(!InspectorUI.highlighter, "Highlighter is gone");
   ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed");
   ok(!InspectorUI.inspecting, "Inspector is not inspecting");
+  ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is closed");
   ok(!InspectorUI.toolbar, "toolbar is hidden");
 
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
--- 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)
-
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/Makefile.in
@@ -0,0 +1,53 @@
+#
+# ***** 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 Scratchpad Build Code.
+#
+# 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):
+#   Rob Campbell <rcampbell@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 *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+ifdef ENABLE_TESTS
+	DIRS += test
+endif
+
+include $(topsrcdir)/config/rules.mk
+
+libs::
+	$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/scratchpad-manager.jsm
@@ -0,0 +1,174 @@
+/* vim:set ts=2 sw=2 sts=2 et 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 Scratchpad
+ *
+ * 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):
+ *   Heather Arthur <fayearthur@gmail.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";
+
+var EXPORTED_SYMBOLS = ["ScratchpadManager"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+const SCRATCHPAD_WINDOW_URL = "chrome://browser/content/scratchpad.xul";
+const SCRATCHPAD_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+/**
+ * The ScratchpadManager object opens new Scratchpad windows and manages the state
+ * of open scratchpads for session restore. There's only one ScratchpadManager in
+ * the life of the browser.
+ */
+var ScratchpadManager = {
+
+  _scratchpads: [],
+
+  /**
+   * Get the saved states of open scratchpad windows. Called by
+   * session restore.
+   *
+   * @return array
+   *         The array of scratchpad states.
+   */
+  getSessionState: function SPM_getSessionState()
+  {
+    return this._scratchpads;
+  },
+
+  /**
+   * Restore scratchpad windows from the scratchpad session store file.
+   * Called by session restore.
+   *
+   * @param function aSession
+   *        The session object with scratchpad states.
+   *
+   * @return array
+   *         The restored scratchpad windows.
+   */
+  restoreSession: function SPM_restoreSession(aSession)
+  {
+    if (!Array.isArray(aSession)) {
+      return [];
+    }
+
+    let wins = [];
+    aSession.forEach(function(state) {
+      let win = this.openScratchpad(state);
+      wins.push(win);
+    }, this);
+
+    return wins;
+  },
+
+  /**
+   * Iterate through open scratchpad windows and save their states.
+   */
+  saveOpenWindows: function SPM_saveOpenWindows() {
+    this._scratchpads = [];
+
+    let enumerator = Services.wm.getEnumerator("devtools:scratchpad");
+    while (enumerator.hasMoreElements()) {
+      let win = enumerator.getNext();
+      if (!win.closed) {
+        this._scratchpads.push(win.Scratchpad.getState());
+      }
+    }
+  },
+
+  /**
+   * Open a new scratchpad window with an optional initial state.
+   *
+   * @param object aState
+   *        Optional. The initial state of the scratchpad, an object
+   *        with properties filename, text, and executionContext.
+   *
+   * @return nsIDomWindow
+   *         The opened scratchpad window.
+   */
+  openScratchpad: function SPM_openScratchpad(aState)
+  {
+    let params = null;
+    if (aState) {
+      if (typeof aState != 'object') {
+        return;
+      }
+      params = Cc["@mozilla.org/embedcomp/dialogparam;1"]
+               .createInstance(Ci.nsIDialogParamBlock);
+      params.SetNumberStrings(1);
+      params.SetString(0, JSON.stringify(aState));
+    }
+    let win = Services.ww.openWindow(null, SCRATCHPAD_WINDOW_URL, "_blank",
+                                     SCRATCHPAD_WINDOW_FEATURES, params);
+    // Only add shutdown observer if we've opened a scratchpad window
+    ShutdownObserver.init();
+
+    return win;
+  }
+};
+
+
+/**
+ * The ShutdownObserver listens for app shutdown and saves the current state
+ * of the scratchpads for session restore.
+ */
+var ShutdownObserver = {
+  _initialized: false,
+
+  init: function SDO_init()
+  {
+    if (this._initialized) {
+      return;
+    }
+
+    Services.obs.addObserver(this, "quit-application-granted", false);
+    this._initialized = true;
+  },
+
+  observe: function SDO_observe(aMessage, aTopic, aData)
+  {
+    if (aTopic == "quit-application-granted") {
+      ScratchpadManager.saveOpenWindows();
+      this.uninit();
+    }
+  },
+
+  uninit: function SDO_uninit()
+  {
+    Services.obs.removeObserver(this, "quit-application-granted");
+  }
+};
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -54,22 +54,22 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource:///modules/PropertyPanel.jsm");
 Cu.import("resource:///modules/source-editor.jsm");
+Cu.import("resource:///modules/devtools/scratchpad-manager.jsm");
+
 
 const SCRATCHPAD_CONTEXT_CONTENT = 1;
 const SCRATCHPAD_CONTEXT_BROWSER = 2;
-const SCRATCHPAD_WINDOW_URL = "chrome://browser/content/scratchpad.xul";
 const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
-const SCRATCHPAD_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
 const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
 
 /**
  * The scratchpad object handles the Scratchpad window functionality.
  */
 var Scratchpad = {
   /**
    * The script execution context. This tells Scratchpad in which context the
@@ -128,16 +128,65 @@ var Scratchpad = {
    *        replacing text in the editor.
    */
   setText: function SP_setText(aText, aStart, aEnd)
   {
     this.editor.setText(aText, aStart, aEnd);
   },
 
   /**
+   * Set the filename in the scratchpad UI and object
+   *
+   * @param string aFilename
+   *        The new filename
+   */
+  setFilename: function SP_setFilename(aFilename)
+  {
+    document.title = this.filename = aFilename;
+  },
+
+  /**
+   * Get the current state of the scratchpad. Called by the
+   * Scratchpad Manager for session storing.
+   *
+   * @return object
+   *        An object with 3 properties: filename, text, and
+   *        executionContext.
+   */
+  getState: function SP_getState()
+  {
+    return {
+      filename: this.filename,
+      text: this.getText(),
+      executionContext: this.executionContext
+    };
+  },
+
+  /**
+   * Set the filename and execution context using the given state. Called
+   * when scratchpad is being restored from a previous session.
+   *
+   * @param object aState
+   *        An object with filename and executionContext properties.
+   */
+  setState: function SP_getState(aState)
+  {
+    if (aState.filename) {
+      this.setFilename(aState.filename);
+    }
+
+    if (aState.executionContext == SCRATCHPAD_CONTEXT_BROWSER) {
+      this.setBrowserContext();
+    }
+    else {
+      this.setContentContext();
+    }
+  },
+
+  /**
    * Get the most recent chrome window of type navigator:browser.
    */
   get browserWindow() Services.wm.getMostRecentWindow("navigator:browser"),
 
   /**
    * Reference to the last chrome window of type navigator:browser. We use this
    * to check if the chrome window changed since the last code evaluation.
    */
@@ -259,17 +308,17 @@ var Scratchpad = {
    *
    * @param string aString
    *        The script you want evaluated.
    * @return mixed
    *         The script evaluation result.
    */
   evalInContentSandbox: function SP_evalInContentSandbox(aString)
   {
-    let result;
+    let error, result;
     try {
       result = Cu.evalInSandbox(aString, this.contentSandbox, "1.8",
                                 "Scratchpad", 1);
     }
     catch (ex) {
       this.openWebConsole();
 
       let contentWindow = this.gBrowser.selectedBrowser.contentWindow;
@@ -278,43 +327,47 @@ var Scratchpad = {
                         createInstance(Ci.nsIScriptError2);
 
       scriptError.initWithWindowID(ex.message + "\n" + ex.stack, ex.fileName,
                                    "", ex.lineNumber, 0, scriptError.errorFlag,
                                    "content javascript",
                                    this.getInnerWindowId(contentWindow));
 
       Services.console.logMessage(scriptError);
+
+      error = true;
     }
 
-    return result;
+    return [error, result];
   },
 
   /**
    * Evaluate a string in the most recent navigator:browser chrome window.
    *
    * @param string aString
    *        The script you want evaluated.
    * @return mixed
    *         The script evaluation result.
    */
   evalInChromeSandbox: function SP_evalInChromeSandbox(aString)
   {
-    let result;
+    let error, result;
     try {
       result = Cu.evalInSandbox(aString, this.chromeSandbox, "1.8",
                                 "Scratchpad", 1);
     }
     catch (ex) {
       Cu.reportError(ex);
       Cu.reportError(ex.stack);
       this.openErrorConsole();
+
+      error = true;
     }
 
-    return result;
+    return [error, result];
   },
 
   /**
    * Evaluate a string in the currently desired context, that is either the
    * chrome window or the tab content window object.
    *
    * @param string aString
    *        The script you want to evaluate.
@@ -330,31 +383,31 @@ var Scratchpad = {
 
   /**
    * Execute the selected text (if any) or the entire editor content in the
    * current context.
    */
   run: function SP_run()
   {
     let selection = this.selectedText || this.getText();
-    let result = this.evalForContext(selection);
+    let [error, result] = this.evalForContext(selection);
     this.deselect();
-    return [selection, result];
+    return [selection, error, result];
   },
 
   /**
    * Execute the selected text (if any) or the entire editor content in the
    * current context. The resulting object is opened up in the Property Panel
    * for inspection.
    */
   inspect: function SP_inspect()
   {
-    let [selection, result] = this.run();
+    let [selection, error, result] = this.run();
 
-    if (result) {
+    if (!error) {
       this.openPropertyPanel(selection, result);
     }
   },
 
   /**
    * Execute the selected text (if any) or the entire editor content in the
    * current context. The evaluation result is inserted into the editor after
    * the selected text, or at the end of the editor content if there is no
@@ -362,22 +415,22 @@ var Scratchpad = {
    */
   display: function SP_display()
   {
     let selection = this.getSelectionRange();
     let insertionPoint = selection.start != selection.end ?
                          selection.end : // after selected text
                          this.editor.getCharCount(); // after text end
 
-    let [selectedText, result] = this.run();
-    if (!result) {
+    let [selectedText, error, result] = this.run();
+    if (error) {
       return;
     }
 
-    let newComment = "/*\n" + result.toString() + "\n*/";
+    let newComment = "/*\n" + result + "\n*/";
 
     this.setText(newComment, insertionPoint, insertionPoint);
 
     // Select the new comment.
     this.selectRange(insertionPoint, insertionPoint + newComment.length);
   },
 
   /**
@@ -405,24 +458,21 @@ var Scratchpad = {
     // the content of the panel.
     if (aEvalString !== null) {
       buttons.push({
         label: this.strings.
                GetStringFromName("propertyPanel.updateButton.label"),
         accesskey: this.strings.
                    GetStringFromName("propertyPanel.updateButton.accesskey"),
         oncommand: function () {
-          try {
-            let result = self.evalForContext(aEvalString);
+          let [error, result] = self.evalForContext(aEvalString);
 
-            if (result !== undefined) {
-              propPanel.treeView.data = result;
-            }
+          if (!error) {
+            propPanel.treeView.data = result;
           }
-          catch (ex) { }
         }
       });
     }
 
     let doc = this.browserWindow.document;
     let parent = doc.getElementById("mainPopupSet");
     let title = aOutputObject.toString();
     propPanel = new PropertyPanel(parent, doc, title, aOutputObject, buttons);
@@ -437,18 +487,17 @@ var Scratchpad = {
 
   // Menu Operations
 
   /**
    * Open a new Scratchpad window.
    */
   openScratchpad: function SP_openScratchpad()
   {
-    Services.ww.openWindow(null, SCRATCHPAD_WINDOW_URL, "_blank",
-                           SCRATCHPAD_WINDOW_FEATURES, null);
+    ScratchpadManager.openScratchpad();
   },
 
   /**
    * Export the textbox content to a file.
    *
    * @param nsILocalFile aFile
    *        The file where you want to save the textbox content.
    * @param boolean aNoConfirmation
@@ -536,17 +585,17 @@ var Scratchpad = {
    */
   openFile: function SP_openFile()
   {
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
     fp.init(window, this.strings.GetStringFromName("openFile.title"),
             Ci.nsIFilePicker.modeOpen);
     fp.defaultString = "";
     if (fp.show() != Ci.nsIFilePicker.returnCancel) {
-      document.title = this.filename = fp.file.path;
+      this.setFilename(fp.file.path);
       this.importFromFile(fp.file);
     }
   },
 
   /**
    * Save the textbox content to the currently open file.
    */
   saveFile: function SP_saveFile()
@@ -675,22 +724,30 @@ var Scratchpad = {
       let environmentMenu = document.getElementById("sp-environment-menu");
       let errorConsoleCommand = document.getElementById("sp-cmd-errorConsole");
       let chromeContextCommand = document.getElementById("sp-cmd-browserContext");
       environmentMenu.removeAttribute("hidden");
       chromeContextCommand.removeAttribute("disabled");
       errorConsoleCommand.removeAttribute("disabled");
     }
 
+    let initialText = this.strings.GetStringFromName("scratchpadIntro");
+    if ("arguments" in window &&
+         window.arguments[0] instanceof Ci.nsIDialogParamBlock) {
+      let state = JSON.parse(window.arguments[0].GetString(0));
+      this.setState(state);
+      initialText = state.text;
+    }
+
     this.editor = new SourceEditor();
 
     let config = {
       mode: SourceEditor.MODES.JAVASCRIPT,
       showLineNumbers: true,
-      placeholderText: this.strings.GetStringFromName("scratchpadIntro"),
+      placeholderText: initialText
     };
 
     let editorPlaceholder = document.getElementById("scratchpad-editor");
     this.editor.init(editorPlaceholder, config, this.onEditorLoad.bind(this));
   },
 
   /**
    * The load event handler for the source editor. This method does post-load
--- a/browser/devtools/scratchpad/test/Makefile.in
+++ b/browser/devtools/scratchpad/test/Makefile.in
@@ -48,11 +48,14 @@ include $(topsrcdir)/config/rules.mk
 		browser_scratchpad_contexts.js \
 		browser_scratchpad_tab_switch.js \
 		browser_scratchpad_execute_print.js \
 		browser_scratchpad_inspect.js \
 		browser_scratchpad_files.js \
 		browser_scratchpad_ui.js \
 		browser_scratchpad_bug_646070_chrome_context_pref.js \
 		browser_scratchpad_bug_660560_tab.js \
+		browser_scratchpad_open.js \
+		browser_scratchpad_restore.js \
+		browser_scratchpad_bug_679467_falsy.js \
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js
@@ -0,0 +1,64 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Reference to the Scratchpad chrome window object.
+let gScratchpadWindow;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    gScratchpadWindow = Scratchpad.openScratchpad();
+    gScratchpadWindow.addEventListener("load", testFalsy, false);
+  }, true);
+
+  content.location = "data:text/html,<p>test falsy display() values in Scratchpad";
+}
+
+function testFalsy(sp)
+{
+  gScratchpadWindow.removeEventListener("load", testFalsy, false);
+
+  let sp = gScratchpadWindow.Scratchpad;
+  verifyFalsies(sp);
+  
+  sp.setBrowserContext();
+  verifyFalsies(sp);
+
+  gScratchpadWindow.close();
+  gScratchpadWindow = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function verifyFalsies(sp)
+{
+  sp.setText("undefined");
+  sp.display();
+  is(sp.selectedText, "/*\nundefined\n*/", "'undefined' is displayed");
+
+  sp.setText("false");
+  sp.display();
+  is(sp.selectedText, "/*\nfalse\n*/", "'false' is displayed");
+
+  sp.setText("0");
+  sp.display();
+  is(sp.selectedText, "/*\n0\n*/", "'0' is displayed");
+
+  sp.setText("null");
+  sp.display();
+  is(sp.selectedText, "/*\nnull\n*/", "'null' is displayed");
+
+  sp.setText("NaN");
+  sp.display();
+  is(sp.selectedText, "/*\nNaN\n*/", "'NaN' is displayed");
+
+  sp.setText("''");
+  sp.display();
+  is(sp.selectedText, "/*\n\n*/", "empty string is displayed");
+}
--- a/browser/devtools/scratchpad/test/browser_scratchpad_contexts.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_contexts.js
@@ -83,48 +83,48 @@ function runTests()
 
   is(window.foobarBug636725, "aloha2", "window.foobarBug636725 has been set");
 
   sp.setText("gBrowser", 7);
 
   ok(sp.getText(), "window.gBrowser",
      "setText() worked with no end for the replace range");
 
-  is(typeof sp.run()[1].addTab, "function",
+  is(typeof sp.run()[2].addTab, "function",
      "chrome context has access to chrome objects");
 
   // Check that the sandbox is cached.
 
   sp.setText("typeof foobarBug636725cache;");
-  is(sp.run()[1], "undefined", "global variable does not exist");
+  is(sp.run()[2], "undefined", "global variable does not exist");
 
   sp.setText("var foobarBug636725cache = 'foo';");
   sp.run();
 
   sp.setText("typeof foobarBug636725cache;");
-  is(sp.run()[1], "string",
+  is(sp.run()[2], "string",
      "global variable exists across two different executions");
 
   sp.resetContext();
 
-  is(sp.run()[1], "undefined",
+  is(sp.run()[2], "undefined",
      "global variable no longer exists after calling resetContext()");
 
   sp.setText("var foobarBug636725cache2 = 'foo';");
   sp.run();
 
   sp.setText("typeof foobarBug636725cache2;");
-  is(sp.run()[1], "string",
+  is(sp.run()[2], "string",
      "global variable exists across two different executions");
 
   sp.setContentContext();
 
   is(sp.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_CONTENT,
      "executionContext is content");
 
-  is(sp.run()[1], "undefined",
+  is(sp.run()[2], "undefined",
      "global variable no longer exists after changing the context");
 
   gScratchpadWindow.close();
   gScratchpadWindow = null;
   gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
@@ -27,18 +27,19 @@ function runTests()
   let sp = gScratchpadWindow.Scratchpad;
 
   content.wrappedJSObject.foobarBug636725 = 1;
 
   sp.setText("++window.foobarBug636725");
 
   let exec = sp.run();
   is(exec[0], sp.getText(), "run()[0] is correct");
-  is(exec[1], content.wrappedJSObject.foobarBug636725,
-     "run()[1] is correct");
+  ok(!exec[1], "run()[1] is correct");
+  is(exec[2], content.wrappedJSObject.foobarBug636725,
+     "run()[2] is correct");
 
   is(sp.getText(), "++window.foobarBug636725",
      "run() does not change the editor content");
 
   is(content.wrappedJSObject.foobarBug636725, 2,
      "run() updated window.foobarBug636725");
 
   sp.display();
@@ -72,18 +73,20 @@ function runTests()
 
   is(selection.start, 0, "selection.start is 0");
   is(selection.end, 29, "selection.end is 29");
 
   exec = sp.run();
 
   is(exec[0], "window.foobarBug636725 = 'a';",
      "run()[0] is correct");
-  is(exec[1], "a",
+  ok(!exec[1], 
      "run()[1] is correct");
+  is(exec[2], "a",
+     "run()[2] is correct");
 
   is(sp.getText(), "window.foobarBug636725 = 'a';\n" +
                    "window.foobarBug636725 = 'b';",
      "run() does not change the textbox value");
 
   is(content.wrappedJSObject.foobarBug636725, "a",
      "run() worked for the selected range");
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_open.js
@@ -0,0 +1,71 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var ScratchpadManager = Scratchpad.ScratchpadManager;
+
+// only finish() when correct number of tests are done
+const expected = 3;
+var count = 0;
+
+function done()
+{
+  if (++count == expected) {
+    finish();
+  }
+}
+
+
+function test()
+{
+  waitForExplicitFinish();
+  testOpen();
+  testOpenWithState();
+  testOpenInvalidState();
+}
+
+function testOpen()
+{
+  let win = ScratchpadManager.openScratchpad();
+
+  win.addEventListener("load", function() {
+    is(win.Scratchpad.filename, undefined, "Default filename is undefined");
+    is(win.Scratchpad.getText(),
+       win.Scratchpad.strings.GetStringFromName("scratchpadIntro"),
+       "Default text is loaded")
+    is(win.Scratchpad.executionContext, win.SCRATCHPAD_CONTEXT_CONTENT,
+      "Default execution context is content");
+
+    win.close();
+    done();
+  });
+}
+
+function testOpenWithState()
+{
+  let state = {
+    filename: "testfile",
+    executionContext: 2,
+    text: "test text"
+  };
+
+  let win = ScratchpadManager.openScratchpad(state);
+
+  win.addEventListener("load", function() {
+    is(win.Scratchpad.filename, state.filename, "Filename loaded from state");
+    is(win.Scratchpad.executionContext, state.executionContext, "Execution context loaded from state");
+    is(win.Scratchpad.getText(), state.text, "Content loaded from state");
+
+    win.close();
+    done();
+  });
+}
+
+function testOpenInvalidState()
+{
+  let state = 7;
+
+  let win = ScratchpadManager.openScratchpad(state);
+  ok(!win, "no scratchpad opened if state is not an object");
+  done();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_restore.js
@@ -0,0 +1,101 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var ScratchpadManager = Scratchpad.ScratchpadManager;
+
+/* Call the iterator for each item in the list,
+   calling the final callback with all the results
+   after every iterator call has sent its result */
+function asyncMap(items, iterator, callback)
+{
+  let expected = items.length;
+  let results = [];
+
+  items.forEach(function(item) {
+    iterator(item, function(result) {
+      results.push(result);
+      if (results.length == expected) {
+        callback(results);
+      }
+    });
+  });
+}
+
+function test()
+{
+  waitForExplicitFinish();
+  testRestore();
+}
+
+function testRestore()
+{
+  let states = [
+    {
+      filename: "testfile",
+      text: "test1",
+      executionContext: 2
+    },
+    {
+      text: "text2",
+      executionContext: 1
+    },
+    {
+      text: "text3",
+      executionContext: 1
+    }
+  ];
+
+  asyncMap(states, function(state, done) {
+    // Open some scratchpad windows
+    let win = ScratchpadManager.openScratchpad(state);
+    win.addEventListener("load", function() {
+      done(win);
+    })
+  }, function(wins) {
+    // Then save the windows to session store
+    ScratchpadManager.saveOpenWindows();
+
+    // Then get their states
+    let session = ScratchpadManager.getSessionState();
+
+    // Then close them
+    wins.forEach(function(win) {
+      win.close();
+    });
+
+    // Clear out session state for next tests
+    ScratchpadManager.saveOpenWindows();
+
+    // Then restore them
+    let restoredWins = ScratchpadManager.restoreSession(session);
+
+    is(restoredWins.length, 3, "Three scratchad windows restored");
+
+    asyncMap(restoredWins, function(restoredWin, done) {
+      restoredWin.addEventListener("load", function() {
+        let state = restoredWin.Scratchpad.getState();
+        restoredWin.close();
+        done(state);
+      });
+    }, function(restoredStates) {
+      // Then make sure they were restored with the right states
+      ok(statesMatch(restoredStates, states),
+        "All scratchpad window states restored correctly");
+
+      // Yay, we're done!
+      finish();
+    });
+  });
+}
+
+function statesMatch(restoredStates, states)
+{
+  return states.every(function(state) {
+    return restoredStates.some(function(restoredState) {
+      return state.filename == restoredState.filename
+        && state.text == restoredState.text
+        && state.executionContext == restoredState.executionContext;
+    })
+  });
+}
--- a/browser/devtools/scratchpad/test/browser_scratchpad_tab_switch.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_tab_switch.js
@@ -92,17 +92,17 @@ function runTests2() {
   content.location = "data:text/html,test context switch in Scratchpad location 2";
 }
 
 function runTests3() {
   gBrowser.selectedBrowser.removeEventListener("load", runTests3, true);
   // Check that the sandbox is not cached.
 
   sp.setText("typeof foosbug653108;");
-  is(sp.run()[1], "undefined", "global variable does not exist");
+  is(sp.run()[2], "undefined", "global variable does not exist");
 
   gScratchpadWindow.close();
   gScratchpadWindow = null;
   tab1 = null;
   tab2 = null;
   sp = null;
   gBrowser.removeCurrentTab();
   gBrowser.removeCurrentTab();
--- 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._maxLineInd