Fix for bug 471126 (leak content nodes (and sometimes dom windows) after clicking on nytimes.com articles). r=bent, sr=bz.
☠☠ backed out by c573ff777cc4 ☠ ☠
authorPeter Van der Beken <peterv@propagandism.org>
Wed, 14 Jan 2009 13:24:10 +0100
changeset 23651 9fd8740decb8c166955527a0c3cd199de45f0cd9
parent 23650 7b6d9acfc4a446e442a953ce74a05c078b464b3e
child 23652 b11d1f574c328d473787443d6bc0a1837c172d7d
child 23657 c573ff777cc4311b0b57bb909fd1eeac274e9e5f
push id4661
push userpvanderbeken@mozilla.com
push dateWed, 14 Jan 2009 12:24:23 +0000
treeherdermozilla-central@9fd8740decb8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent, bz
bugs471126
milestone1.9.2a1pre
Fix for bug 471126 (leak content nodes (and sometimes dom windows) after clicking on nytimes.com articles). r=bent, sr=bz.
editor/composer/src/nsComposerCommandsUpdater.cpp
editor/composer/src/nsComposerCommandsUpdater.h
layout/base/nsDocumentViewer.cpp
layout/base/tests/Makefile.in
layout/base/tests/test_bug471126.html
layout/generic/nsFrameSelection.h
layout/generic/nsSelection.cpp
--- a/editor/composer/src/nsComposerCommandsUpdater.cpp
+++ b/editor/composer/src/nsComposerCommandsUpdater.cpp
@@ -49,25 +49,29 @@
 #include "nsString.h"
 
 #include "nsICommandManager.h"
 
 #include "nsIDocShell.h"
 #include "nsITransactionManager.h"
 
 nsComposerCommandsUpdater::nsComposerCommandsUpdater()
-:  mDOMWindow(nsnull)
-,  mDirtyState(eStateUninitialized)
+:  mDirtyState(eStateUninitialized)
 ,  mSelectionCollapsed(eStateUninitialized)
 ,  mFirstDoOfFirstUndo(PR_TRUE)
 {
 }
 
 nsComposerCommandsUpdater::~nsComposerCommandsUpdater()
 {
+  // cancel any outstanding update timer
+  if (mUpdateTimer)
+  {
+    mUpdateTimer->Cancel();
+  }
 }
 
 NS_IMPL_ISUPPORTS4(nsComposerCommandsUpdater, nsISelectionListener,
                    nsIDocumentStateListener, nsITransactionListener, nsITimerCallback)
 
 #if 0
 #pragma mark -
 #endif
@@ -236,17 +240,17 @@ nsComposerCommandsUpdater::DidMerge(nsIT
 #if 0
 #pragma mark -
 #endif
 
 nsresult
 nsComposerCommandsUpdater::Init(nsIDOMWindow* aDOMWindow)
 {
   NS_ENSURE_ARG(aDOMWindow);
-  mDOMWindow = aDOMWindow;
+  mDOMWindow = do_GetWeakReference(aDOMWindow);
 
   nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aDOMWindow));
   if (window)
   {
     mDocShell = do_GetWeakReference(window->GetDocShell());
   }
   return NS_OK;
 }
@@ -359,20 +363,22 @@ nsComposerCommandsUpdater::UpdateOneComm
   commandUpdater->CommandStatusChanged(aCommand);
 
   return NS_OK;  
 }
 
 PRBool
 nsComposerCommandsUpdater::SelectionIsCollapsed()
 {
-  if (!mDOMWindow) return PR_TRUE;
+  nsCOMPtr<nsIDOMWindow> domWindow = do_QueryReferent(mDOMWindow);
+  if (!domWindow)
+    return PR_TRUE;
 
   nsCOMPtr<nsISelection> domSelection;
-  if (NS_SUCCEEDED(mDOMWindow->GetSelection(getter_AddRefs(domSelection))) && domSelection)
+  if (NS_SUCCEEDED(domWindow->GetSelection(getter_AddRefs(domSelection))) && domSelection)
   {
     PRBool selectionCollapsed = PR_FALSE;
     domSelection->GetIsCollapsed(&selectionCollapsed);
     return selectionCollapsed;
   }
 
   NS_WARNING("nsComposerCommandsUpdater::SelectionIsCollapsed - no domSelection");
 
--- a/editor/composer/src/nsComposerCommandsUpdater.h
+++ b/editor/composer/src/nsComposerCommandsUpdater.h
@@ -114,17 +114,17 @@ protected:
   nsresult      UpdateCommandGroup(const nsAString& aCommandGroup);
 
   already_AddRefed<nsPICommandUpdater> GetCommandUpdater();
   
   nsresult      PrimeUpdateTimer();
   void          TimerCallback();
   nsCOMPtr<nsITimer>  mUpdateTimer;
 
-  nsIDOMWindow* mDOMWindow;  // Weak reference
+  nsWeakPtr     mDOMWindow;
   nsWeakPtr     mDocShell;
   PRInt8        mDirtyState;  
   PRInt8        mSelectionCollapsed;  
   PRPackedBool  mFirstDoOfFirstUndo;
     
 
 };
 
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -388,16 +388,18 @@ private:
 
   nsresult SyncParentSubDocMap();
 
   nsresult GetDocumentSelection(nsISelection **aSelection);
 
   nsresult GetClipboardEventTarget(nsIDOMNode **aEventTarget);
   nsresult FireClipboardEvent(PRUint32 msg, PRBool* aPreventDefault);
 
+  void DestroyPresShell();
+
 #ifdef NS_PRINTING
   // Called when the DocViewer is notified that the state
   // of Printing or PP has changed
   void SetIsPrintingInDocShellTree(nsIDocShellTreeNode* aParentNode, 
                                    PRBool               aIsPrintingOrPP, 
                                    PRBool               aStartAtTop);
 #endif // NS_PRINTING
 
@@ -669,16 +671,19 @@ DocumentViewerImpl::Init(nsIWidget* aPar
   NS_ENSURE_SUCCESS(rv, rv);
   
   return InitInternal(aParentWidget, nsnull, aBounds, PR_TRUE, PR_FALSE);
 }
 
 nsresult
 DocumentViewerImpl::InitPresentationStuff(PRBool aDoInitialReflow, PRBool aReenableRefresh)
 {
+  NS_ASSERTION(!mPresShell,
+               "Someone should have destroyed the presshell!");
+
   // Create the style set...
   nsStyleSet *styleSet;
   nsresult rv = CreateStyleSet(mDocument, &styleSet);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Now make the shell for the document
   rv = mDocument->CreateShell(mPresContext, mViewManager, styleSet,
                               getter_AddRefs(mPresShell));
@@ -742,24 +747,26 @@ DocumentViewerImpl::InitPresentationStuf
 
   // Now trigger a refresh
   if (aReenableRefresh && mEnableRendering && mViewManager) {
     mViewManager->EnableRefresh(NS_VMREFRESH_IMMEDIATE);
   }
 
   // now register ourselves as a selection listener, so that we get
   // called when the selection changes in the window
-  nsDocViewerSelectionListener *selectionListener =
-    new nsDocViewerSelectionListener();
-  NS_ENSURE_TRUE(selectionListener, NS_ERROR_OUT_OF_MEMORY);
-
-  selectionListener->Init(this);
-
-  // mSelectionListener is a owning reference
-  mSelectionListener = selectionListener;
+  if (!mSelectionListener) {
+    nsDocViewerSelectionListener *selectionListener =
+      new nsDocViewerSelectionListener();
+    NS_ENSURE_TRUE(selectionListener, NS_ERROR_OUT_OF_MEMORY);
+
+    selectionListener->Init(this);
+
+    // mSelectionListener is a owning reference
+    mSelectionListener = selectionListener;
+  }
 
   nsCOMPtr<nsISelection> selection;
   rv = GetDocumentSelection(getter_AddRefs(selection));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(selection));
   rv = selPrivate->AddSelectionListener(mSelectionListener);
   if (NS_FAILED(rv))
@@ -1510,29 +1517,17 @@ DocumentViewerImpl::Destroy()
   if (mPreviousViewer) {
     mPreviousViewer->Destroy();
     mPreviousViewer = nsnull;
   }
 
   mDeviceContext = nsnull;
 
   if (mPresShell) {
-    // Break circular reference (or something)
-    mPresShell->EndObservingDocument();
-
-    nsCOMPtr<nsISelection> selection;
-    GetDocumentSelection(getter_AddRefs(selection));
-
-    nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(selection));
-
-    if (selPrivate && mSelectionListener)
-      selPrivate->RemoveSelectionListener(mSelectionListener);
-
-    mPresShell->Destroy();
-    mPresShell = nsnull;
+    DestroyPresShell();
   }
 
   if (mPresContext) {
     mPresContext->SetContainer(nsnull);
     mPresContext->SetLinkHandler(nsnull);
     mPresContext = nsnull;
   }
 
@@ -1635,20 +1630,17 @@ DocumentViewerImpl::SetDOMDocument(nsIDO
     }
 
     if (mPresContext) {
       // Save the linkhandler (nsPresShell::Destroy removes it from
       // mPresContext).
       linkHandler = mPresContext->GetLinkHandler();
     }
 
-    mPresShell->EndObservingDocument();
-    mPresShell->Destroy();
-
-    mPresShell = nsnull;
+    DestroyPresShell();
 
     // This destroys the root view because it was associated with the root frame,
     // which has been torn down. Recreate the viewmanager and root view.
     MakeWindow(currentSize);
   }
 
   // And if we're already given a prescontext...
   if (mPresContext) {
@@ -1949,40 +1941,28 @@ DocumentViewerImpl::Hide(void)
   if (mIsSticky) {
     // This window is sticky, that means that it might be shown again
     // and we don't want the presshell n' all that to be thrown away
     // just because the window is hidden.
 
     return NS_OK;
   }
 
-  // Break circular reference (or something)
-  mPresShell->EndObservingDocument();
-  nsCOMPtr<nsISelection> selection;
-
-  GetDocumentSelection(getter_AddRefs(selection));
-
-  nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(selection));
-
-  if (selPrivate && mSelectionListener) {
-    selPrivate->RemoveSelectionListener(mSelectionListener);
-  }
-
   nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mContainer));
   if (docShell) {
     nsCOMPtr<nsILayoutHistoryState> layoutState;
     mPresShell->CaptureHistoryState(getter_AddRefs(layoutState), PR_TRUE);
   }
 
-  mPresShell->Destroy();
+  DestroyPresShell();
+
   // Clear weak refs
   mPresContext->SetContainer(nsnull);
   mPresContext->SetLinkHandler(nsnull);                             
 
-  mPresShell     = nsnull;
   mPresContext   = nsnull;
   mViewManager   = nsnull;
   mWindow        = nsnull;
   mDeviceContext = nsnull;
   mParentWidget  = nsnull;
 
   nsCOMPtr<nsIBaseWindow> base_win(do_QueryReferent(mContainer));
 
@@ -4161,24 +4141,17 @@ NS_IMETHODIMP DocumentViewerImpl::SetPag
   // XXX Page mode is only partially working; it's currently used for
   // reftests that require a paginated context
   mIsPageMode = aPageMode;
   // Get the current size of what is being viewed
   nsRect bounds;
   mWindow->GetBounds(bounds);
 
   if (mPresShell) {
-    // Break circular reference (or something)
-    mPresShell->EndObservingDocument();
-    nsCOMPtr<nsISelection> selection;
-    nsresult rv = GetDocumentSelection(getter_AddRefs(selection));
-    nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(selection));
-    if (NS_SUCCEEDED(rv) && selPrivate && mSelectionListener)
-      selPrivate->RemoveSelectionListener(mSelectionListener);
-    mPresShell->Destroy();
+    DestroyPresShell();
   }
 
   if (mPresContext) {
     mPresContext->SetContainer(nsnull);
     mPresContext->SetLinkHandler(nsnull);
   }
 
   mPresShell    = nsnull;
@@ -4205,8 +4178,24 @@ NS_IMETHODIMP DocumentViewerImpl::SetPag
 }
 
 NS_IMETHODIMP
 DocumentViewerImpl::GetHistoryEntry(nsISHEntry **aHistoryEntry)
 {
   NS_IF_ADDREF(*aHistoryEntry = mSHEntry);
   return NS_OK;
 }
+
+void
+DocumentViewerImpl::DestroyPresShell()
+{
+  // Break circular reference (or something)
+  mPresShell->EndObservingDocument();
+
+  nsCOMPtr<nsISelection> selection;
+  GetDocumentSelection(getter_AddRefs(selection));
+  nsCOMPtr<nsISelectionPrivate> selPrivate = do_QueryInterface(selection);
+  if (selPrivate && mSelectionListener)
+    selPrivate->RemoveSelectionListener(mSelectionListener);
+
+  mPresShell->Destroy();
+  mPresShell = nsnull;
+}
--- a/layout/base/tests/Makefile.in
+++ b/layout/base/tests/Makefile.in
@@ -60,16 +60,17 @@ DEFINES += -D_IMPL_NS_LAYOUT
 		test_bug416896.html \
 		test_bug420499.xul \
 		test_bug423523.html \
 		test_bug445810.html \
 		test_bug449781.html \
 		test_bug450930.xhtml \
 		test_bug458898.html \
 		test_bug465448.xul \
+		test_bug471126.html \
 		$(NULL)
 # test_bug396024.html is currently disabled because it interacts badly with
 # the "You can't print-preview while the page is loading" dialog.
 # (See bug 407080)
 
 # Tests for bug 441782 don't pass reliably on Windows, because of bug 469208
 ifneq (,$(filter windows,$(MOZ_WIDGET_TOOLKIT)))
 _TEST_FILES += \
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/test_bug471126.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=471126
+-->
+<head>
+  <title>Test for Bug 471126</title>
+  <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=471126">Mozilla Bug 471126</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 471126 **/
+
+function test()
+{
+  var selection = window.getSelection();
+  selection.collapse(document.documentElement, 0);
+  document.documentElement.addEventListener("click", function(){ var foo = window; }, false);
+}
+test();
+ok(true, "Shoudn't leak");
+
+</script>
+</pre>
+</body>
+</html>
--- a/layout/generic/nsFrameSelection.h
+++ b/layout/generic/nsFrameSelection.h
@@ -209,17 +209,18 @@ class nsIScrollableView;
  */
 
 class nsFrameSelection : public nsISupports {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_FRAME_SELECTION_IID)
   enum HINT { HINTLEFT = 0, HINTRIGHT = 1};  //end of this line or beginning of next
   /*interfaces for addref and release and queryinterface*/
   
-  NS_DECL_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(nsFrameSelection)
 
   /** Init will initialize the frame selector with the necessary pres shell to 
    *  be used by most of the methods
    *  @param aShell is the parameter to be used for most of the other calls for callbacks etc
    *  @param aLimiter limits the selection to nodes with aLimiter parents
    */
   void Init(nsIPresShell *aShell, nsIContent *aLimiter);
 
@@ -546,17 +547,16 @@ public:
    * Primary use: double click selecting then dragging on second click
    * @param aAmount the initial amount of text selected (word, line or paragraph).
    *                For "line", use eSelectBeginLine.
    */
   nsresult MaintainSelection(nsSelectionAmount aAmount = eSelectNoAmount);
 
 
   nsFrameSelection();
-  virtual ~nsFrameSelection();
 
   void StartBatchChanges();
   void EndBatchChanges();
   /*unsafe*/
   nsresult DeleteFromDocument();
 
   nsIPresShell *GetShell()const  { return mShell; }
 
@@ -610,17 +610,17 @@ private:
   PRUint32     GetBatching() const {return mBatching; }
   PRBool       GetNotifyFrames() const { return mNotifyFrames; }
   void         SetDirty(PRBool aDirty=PR_TRUE){if (mBatching) mChangesDuringBatching = aDirty;}
 
   // nsFrameSelection may get deleted when calling this,
   // so remember to use nsCOMPtr when needed.
   nsresult     NotifySelectionListeners(SelectionType aType);     // add parameters to say collapsed etc?
 
-  nsTypedSelection *mDomSelections[nsISelectionController::NUM_SELECTIONTYPES];
+  nsRefPtr<nsTypedSelection> mDomSelections[nsISelectionController::NUM_SELECTIONTYPES];
 
   // Table selection support.
   // Interfaces that let us get info based on cellmap locations
   nsITableLayout* GetTableLayout(nsIContent *aTableContent) const;
   nsITableCellLayout* GetCellLayout(nsIContent *aCellContent) const;
 
   nsresult SelectBlockOfCells(nsIContent *aStartNode, nsIContent *aEndNode);
   nsresult SelectRowOrColumn(nsIContent *aCellContent, PRUint32 aTarget);
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -191,17 +191,18 @@ class nsTypedSelection : public nsISelec
                          public nsISelectionPrivate,
                          public nsSupportsWeakReference
 {
 public:
   nsTypedSelection();
   nsTypedSelection(nsFrameSelection *aList);
   virtual ~nsTypedSelection();
   
-  NS_DECL_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTypedSelection, nsISelection)
   NS_DECL_NSISELECTION
   NS_DECL_NSISELECTION2
   NS_DECL_NSISELECTIONPRIVATE
 
   // utility methods for scrolling the selection into view
   nsresult      GetPresContext(nsPresContext **aPresContext);
   nsresult      GetPresShell(nsIPresShell **aPresShell);
   nsresult      GetRootScrollableView(nsIScrollableView **aScrollableView);
@@ -352,17 +353,17 @@ private:
                                    PRBool aUseBeginning);
   PRInt32 FindRangeGivenPoint(nsIDOMNode* aBeginNode, PRInt32 aBeginOffset,
                               nsIDOMNode* aEndNode, PRInt32 aEndOffset,
                               PRInt32 aStartSearchingHere);
 
   nsTArray<RangeData> mRanges;
   nsTArray<PRInt32> mRangeEndings;    // references info mRanges
   nsCOMPtr<nsIDOMRange> mAnchorFocusRange;
-  nsFrameSelection *mFrameSelection;
+  nsRefPtr<nsFrameSelection> mFrameSelection;
   nsWeakPtr mPresShellWeak;
   nsRefPtr<nsAutoScrollTimer> mAutoScrollTimer;
   nsCOMArray<nsISelectionListener> mSelectionListeners;
   nsRevocableEventPtr<ScrollSelectionIntoViewEvent> mScrollEvent;
   CachedOffsetForFrame *mCachedOffsetForFrame;
   nsDirection mDirection;
   SelectionType mType;
 };
@@ -805,22 +806,19 @@ nsSelectionIterator::IsDone()
 ////////////BEGIN nsFrameSelection methods
 
 nsFrameSelection::nsFrameSelection()
   : mScrollableViewProvider(nsnull),
     mDelayedMouseEvent(PR_FALSE, 0, nsnull, nsMouseEvent::eReal)
 {
   PRInt32 i;
   for (i = 0;i<nsISelectionController::NUM_SELECTIONTYPES;i++){
-    mDomSelections[i] = nsnull;
-  }
-  for (i = 0;i<nsISelectionController::NUM_SELECTIONTYPES;i++){
     mDomSelections[i] = new nsTypedSelection(this);
     if (!mDomSelections[i])
-      return;
+      break;
     mDomSelections[i]->SetType(GetSelectionTypeFromIndex(i));
   }
   mBatching = 0;
   mChangesDuringBatching = PR_FALSE;
   mNotifyFrames = PR_TRUE;
   mLimiter = nsnull; //no default limiter.
   mAncestorLimiter = nsnull;
   
@@ -850,28 +848,53 @@ nsFrameSelection::nsFrameSelection()
 
   mDisplaySelection = nsISelectionController::SELECTION_OFF;
 
   mDelayedMouseEventValid = PR_FALSE;
   mSelectionChangeReason = nsISelectionListener::NO_REASON;
 }
 
 
-nsFrameSelection::~nsFrameSelection()
-{
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection)
+  PRInt32 i;
+  for (i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; ++i) {
+    tmp->mDomSelections[i] = nsnull;
+  }
+
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCellParent)
+  tmp->mSelectingTableCellMode = 0;
+  tmp->mDragSelectingCells = PR_FALSE;
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mStartSelectedCell)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEndSelectedCell)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mAppendStartSelectedCell)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mUnselectCellOnMouseUp)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mMaintainRange)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection)
   PRInt32 i;
-  for (i = 0;i<nsISelectionController::NUM_SELECTIONTYPES;i++){
-    if (mDomSelections[i]) {
-      delete mDomSelections[i];
-    }
-  }
-}
-
-
-NS_IMPL_ISUPPORTS1(nsFrameSelection, nsFrameSelection)
+  for (i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; ++i) {
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mDomSelections[i],
+                                                         nsISelection)
+  }
+
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCellParent)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mStartSelectedCell)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mEndSelectedCell)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mAppendStartSelectedCell)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mUnselectCellOnMouseUp)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mMaintainRange)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameSelection)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameSelection)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameSelection)
+  NS_INTERFACE_MAP_ENTRY(nsFrameSelection)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
 
 
 nsresult
 nsFrameSelection::FetchDesiredX(nscoord &aDesiredX) //the x position requested by the Key Handling for up down
 {
   if (!mShell)
   {
     NS_ASSERTION(0,"fetch desired X failed\n");
@@ -1224,16 +1247,19 @@ nsFrameSelection::MoveCaret(PRUint32    
 
   nsCOMPtr<nsIDOMNode> weakNodeUsed;
   PRInt32 offsetused = 0;
 
   PRBool isCollapsed;
   nscoord desiredX = 0; //we must keep this around and revalidate it when its just UP/DOWN
 
   PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+  if (!mDomSelections[index])
+    return NS_ERROR_NULL_POINTER;
+
   nsresult result = mDomSelections[index]->GetIsCollapsed(&isCollapsed);
   if (NS_FAILED(result))
     return result;
   if (aKeycode == nsIDOMKeyEvent::DOM_VK_UP || aKeycode == nsIDOMKeyEvent::DOM_VK_DOWN)
   {
     result = FetchDesiredX(desiredX);
     if (NS_FAILED(result))
       return result;
@@ -1620,16 +1646,18 @@ nsFrameSelection::GetFrameFromLevel(nsIF
   return NS_OK;
 }
 
 
 nsresult
 nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount)
 {
   PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+  if (!mDomSelections[index])
+    return NS_ERROR_NULL_POINTER;
 
   mMaintainedAmount = aAmount;
   mMaintainRange = nsnull;
   
   nsCOMPtr<nsIDOMNode> startNode;
   nsCOMPtr<nsIDOMNode> endNode;
   PRInt32 startOffset;
   PRInt32 endOffset;
@@ -1733,16 +1761,18 @@ nsFrameSelection::AdjustForMaintainedSel
   if (!mMaintainRange)
     return PR_FALSE;
 
   nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(aContent);
   if (!domNode)
     return PR_FALSE;
   
   PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+  if (!mDomSelections[index])
+    return PR_FALSE;
 
   nsCOMPtr<nsIDOMNode> rangeStartNode, rangeEndNode;
   PRInt32 rangeStartOffset, rangeEndOffset;
   mMaintainRange->GetStartContainer(getter_AddRefs(rangeStartNode));
   mMaintainRange->GetEndContainer(getter_AddRefs(rangeEndNode));
   mMaintainRange->GetStartOffset(&rangeStartOffset);
   mMaintainRange->GetEndOffset(&rangeEndOffset);
 
@@ -1881,24 +1911,30 @@ nsFrameSelection::HandleDrag(nsIFrame *a
 
 nsresult
 nsFrameSelection::StartAutoScrollTimer(nsIView  *aView,
                                        nsPoint   aPoint,
                                        PRUint32  aDelay)
 {
   NS_ENSURE_STATE(mShell);
   PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+  if (!mDomSelections[index])
+    return NS_ERROR_NULL_POINTER;
+
   return mDomSelections[index]->StartAutoScrollTimer(mShell->GetPresContext(),
                                                      aView, aPoint, aDelay);
 }
 
 void
 nsFrameSelection::StopAutoScrollTimer()
 {
   PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+  if (!mDomSelections[index])
+    return;
+
   mDomSelections[index]->StopAutoScrollTimer();
 }
 
 /**
 hard to go from nodes to frames, easy the other way!
  */
 nsresult
 nsFrameSelection::TakeFocus(nsIContent *aNewFocus,
@@ -1926,16 +1962,19 @@ nsFrameSelection::TakeFocus(nsIContent *
   //HACKHACKHACK
   if (!aNewFocus->GetParent())
     return NS_ERROR_FAILURE;
   //END HACKHACKHACK /checking for root frames/content
 
   mHint = aHint;
   
   PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+  if (!mDomSelections[index])
+    return NS_ERROR_NULL_POINTER;
+
   nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(aNewFocus);
   //traverse through document and unselect crap here
   if (!aContinueSelection) {//single click? setting cursor down
     PRUint32 batching = mBatching;//hack to use the collapse code.
     PRBool changes = mChangesDuringBatching;
     mBatching = 1;
 
     if (aMultipleSelection) {
@@ -2403,17 +2442,17 @@ nsFrameSelection::EndBatchChanges()
   }
 }
 
 
 nsresult
 nsFrameSelection::NotifySelectionListeners(SelectionType aType)
 {
   PRInt8 index = GetIndexFromSelectionType(aType);
-  if (index >=0)
+  if (index >=0 && mDomSelections[index])
   {
     return mDomSelections[index]->NotifySelectionListeners();
   }
   return NS_ERROR_FAILURE;
 }
 
 // Start of Table Selection methods
 
@@ -2449,16 +2488,19 @@ nsFrameSelection::GetTableLayout(nsICont
   nsITableLayout *tableLayoutObject = do_QueryFrame(tableFrame);
   return tableLayoutObject;
 }
 
 nsresult
 nsFrameSelection::ClearNormalSelection()
 {
   PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+  if (!mDomSelections[index])
+    return NS_ERROR_NULL_POINTER;
+
   return mDomSelections[index]->RemoveAllRanges();
 }
 
 // Table selection support.
 // TODO: Separate table methods into a separate nsITableSelection interface
 nsresult
 nsFrameSelection::HandleTableSelection(nsIContent *aParentContent, PRInt32 aContentOffset, PRInt32 aTarget, nsMouseEvent *aMouseEvent)
 {
@@ -2482,16 +2524,19 @@ nsFrameSelection::HandleTableSelection(n
   nsCOMPtr<nsIDOMNode> childNode = do_QueryInterface(childContent);
   if (!childNode)
     return NS_ERROR_FAILURE;
 
   // When doing table selection, always set the direction to next so
   // we can be sure that anchorNode's offset always points to the
   // selected cell
   PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+  if (!mDomSelections[index])
+    return NS_ERROR_NULL_POINTER;
+
   mDomSelections[index]->SetDirection(eDirNext);
 
   // Stack-class to wrap all table selection changes in 
   //  BeginBatchChanges() / EndBatchChanges()
   nsSelectionBatcher selectionBatcher(mDomSelections[index]);
 
   PRInt32 startRowIndex, startColIndex, curRowIndex, curColIndex;
   if (mMouseDownState && mDragSelectingCells)
@@ -2811,16 +2856,18 @@ nsFrameSelection::SelectBlockOfCells(nsI
 
   PRInt32 curRowIndex, curColIndex;
 
   if (mDragSelectingCells)
   {
     // Drag selecting: remove selected cells outside of new block limits
 
     PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+    if (!mDomSelections[index])
+      return NS_ERROR_NULL_POINTER;
 
     nsCOMPtr<nsIDOMNode> cellNode;
     nsCOMPtr<nsIDOMRange> range;
     result = GetFirstSelectedCellAndRange(getter_AddRefs(cellNode), getter_AddRefs(range));
     if (NS_FAILED(result)) return result;
 
     PRInt32 minRowIndex = PR_MIN(startRowIndex, endRowIndex);
     PRInt32 maxRowIndex = PR_MAX(startRowIndex, endRowIndex);
@@ -3061,16 +3108,19 @@ nsFrameSelection::GetFirstSelectedCellAn
   *aCell = nsnull;
 
   // aRange is optional
   if (aRange)
     *aRange = nsnull;
 
   nsCOMPtr<nsIDOMRange> firstRange;
   PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+  if (!mDomSelections[index])
+    return NS_ERROR_NULL_POINTER;
+
   nsresult result = mDomSelections[index]->GetRangeAt(0, getter_AddRefs(firstRange));
   if (NS_FAILED(result)) return result;
   if (!firstRange) return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsIDOMNode> cellNode;
   result = GetFirstCellNodeInRange(firstRange, getter_AddRefs(cellNode));
   if (NS_FAILED(result)) return result;
   if (!cellNode) return NS_OK;
@@ -3097,16 +3147,19 @@ nsFrameSelection::GetNextSelectedCellAnd
   *aCell = nsnull;
 
   // aRange is optional
   if (aRange)
     *aRange = nsnull;
 
   PRInt32 rangeCount;
   PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+  if (!mDomSelections[index])
+    return NS_ERROR_NULL_POINTER;
+
   nsresult result = mDomSelections[index]->GetRangeCount(&rangeCount);
   if (NS_FAILED(result)) return result;
 
   // Don't even try if index exceeds range count
   if (mSelectedCellIndex >= rangeCount) 
   {
     // Should we reset index? 
     // Maybe better to force recalling GetFirstSelectedCell()
@@ -3403,28 +3456,34 @@ nsFrameSelection::CreateAndAddRange(nsID
 
   // Set range around child at given offset
   nsresult result = range->SetStart(aParentNode, aOffset);
   if (NS_FAILED(result)) return result;
   result = range->SetEnd(aParentNode, aOffset+1);
   if (NS_FAILED(result)) return result;
   
   PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+  if (!mDomSelections[index])
+    return NS_ERROR_NULL_POINTER;
+
   return mDomSelections[index]->AddRange(range);
 }
 
 // End of Table Selection
 
 void
 nsFrameSelection::SetAncestorLimiter(nsIContent *aLimiter)
 {
   if (mAncestorLimiter != aLimiter) {
     mAncestorLimiter = aLimiter;
     PRInt8 index =
       GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+    if (!mDomSelections[index])
+      return;
+
     if (!IsValidSelectionPoint(this, mDomSelections[index]->FetchFocusNode())) {
       ClearNormalSelection();
       if (mAncestorLimiter) {
         PostReason(nsISelectionListener::NO_REASON);
         TakeFocus(mAncestorLimiter, 0, 0, HINTLEFT, PR_FALSE, PR_FALSE);
       }
     }
   }
@@ -3442,16 +3501,19 @@ nsFrameSelection::DeleteFromDocument()
 {
   nsresult res;
 
   // If we're already collapsed, then set ourselves to include the
   // last item BEFORE the current range, rather than the range itself,
   // before we do the delete.
   PRBool isCollapsed;
   PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
+  if (!mDomSelections[index])
+    return NS_ERROR_NULL_POINTER;
+
   mDomSelections[index]->GetIsCollapsed( &isCollapsed);
   if (isCollapsed)
   {
     // If the offset is positive, then it's easy:
     if (mDomSelections[index]->FetchFocusOffset() > 0)
     {
       mDomSelections[index]->Extend(mDomSelections[index]->FetchFocusNode(), mDomSelections[index]->FetchFocusOffset() - 1);
     }
@@ -3526,18 +3588,17 @@ nsFrameSelection::GetDelayedCaretData()
 #pragma mark -
 #endif
 
 // nsTypedSelection implementation
 
 // note: this can return a nil anchor node
 
 nsTypedSelection::nsTypedSelection()
-  : mFrameSelection(nsnull)
-  , mCachedOffsetForFrame(nsnull)
+  : mCachedOffsetForFrame(nsnull)
   , mDirection(eDirNext)
   , mType(nsISelectionController::SELECTION_NORMAL)
 {
 }
 
 nsTypedSelection::nsTypedSelection(nsFrameSelection *aList)
   : mFrameSelection(aList)
   , mCachedOffsetForFrame(nsnull)
@@ -3556,62 +3617,47 @@ nsTypedSelection::~nsTypedSelection()
   }
 
   mScrollEvent.Revoke();
 
   if (mCachedOffsetForFrame) {
     delete mCachedOffsetForFrame;
     mCachedOffsetForFrame = nsnull;
   }
-
-  mFrameSelection = nsnull;
-}
-
-
-// QueryInterface implementation for nsRange
-NS_INTERFACE_MAP_BEGIN(nsTypedSelection)
+}
+
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsTypedSelection)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTypedSelection)
+  tmp->RemoveAllRanges();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFrameSelection)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTypedSelection)
+  {
+    PRUint32 i, count = tmp->mRanges.Length();
+    for (i = 0; i < count; ++i) {
+      NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRanges[i].mRange)
+    }
+  }
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mAnchorFocusRange)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFrameSelection)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// QueryInterface implementation for nsTypedSelection
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTypedSelection)
   NS_INTERFACE_MAP_ENTRY(nsISelection)
   NS_INTERFACE_MAP_ENTRY(nsISelection2)
   NS_INTERFACE_MAP_ENTRY(nsISelectionPrivate)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelection)
   NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(Selection)
 NS_INTERFACE_MAP_END
 
-
-NS_IMETHODIMP_(nsrefcnt)
-nsTypedSelection::AddRef()
-{
-  if (mFrameSelection) {
-    return mFrameSelection->AddRef();
-  }
-  NS_PRECONDITION(PRInt32(mRefCnt) >= 0, "illegal refcnt");
-  NS_ASSERT_OWNINGTHREAD(nsTypedSelection);
-  ++mRefCnt;
-  NS_LOG_ADDREF(this, mRefCnt, "nsTypedSelection", sizeof(*this));
-  return mRefCnt;
-}
-
-NS_IMETHODIMP_(nsrefcnt)
-nsTypedSelection::Release()
-{
-  if (mFrameSelection) {
-    return mFrameSelection->Release();
-  }
-  NS_PRECONDITION(0 != mRefCnt, "dup release");
-  NS_ASSERT_OWNINGTHREAD(nsTypedSelection);
-  --mRefCnt;
-  NS_LOG_RELEASE(this, mRefCnt, "nsTypedSelection");
-  if (mRefCnt == 0) {
-    mRefCnt = 1; /* stabilize */
-    NS_DELETEXPCOM(this);
-    return 0;
-  }
-  return mRefCnt;
-}
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTypedSelection)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTypedSelection)
 
 NS_IMETHODIMP
 nsTypedSelection::SetPresShell(nsIPresShell *aPresShell)
 {
   mPresShellWeak = do_GetWeakReference(aPresShell);
   return NS_OK;
 }
 
@@ -4095,17 +4141,18 @@ nsTypedSelection::Clear(nsPresContext* a
     selectFrames(aPresContext, mRanges[i].mRange, 0);
   mRanges.Clear();
   mRangeEndings.Clear();
 
   // Reset direction so for more dependable table selection range handling
   SetDirection(eDirNext);
 
   // If this was an ATTENTION selection, change it back to normal now
-  if (mFrameSelection->GetDisplaySelection() ==
+  if (mFrameSelection &&
+      mFrameSelection->GetDisplaySelection() ==
       nsISelectionController::SELECTION_ATTENTION) {
     mFrameSelection->SetDisplaySelection(nsISelectionController::SELECTION_ON);
   }
 
   return NS_OK;
 }
 
 // nsTypedSelection::MoveIndexToFirstMatch
@@ -4455,17 +4502,17 @@ nsTypedSelection::FindRangeGivenPoint(
 }
 
 //utility method to get the primary frame of node or use the offset to get frame of child node
 
 #if 0
 NS_IMETHODIMP
 nsTypedSelection::GetPrimaryFrameForRangeEndpoint(nsIDOMNode *aNode, PRInt32 aOffset, PRBool aIsEndNode, nsIFrame **aReturnFrame)
 {
-  if (!aNode || !aReturnFrame)
+  if (!aNode || !aReturnFrame || !mFrameSelection)
     return NS_ERROR_NULL_POINTER;
   
   if (aOffset < 0)
     return NS_ERROR_FAILURE;
 
   *aReturnFrame = 0;
   
   nsresult  result = NS_OK;
@@ -4925,17 +4972,18 @@ nsTypedSelection::GetFrameSelection(nsFr
   *aFrameSelection = mFrameSelection;
   NS_IF_ADDREF(*aFrameSelection);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTypedSelection::SetAncestorLimiter(nsIContent *aContent)
 {
-  mFrameSelection->SetAncestorLimiter(aContent);
+  if (mFrameSelection)
+    mFrameSelection->SetAncestorLimiter(aContent);
   return NS_OK;
 }
 
 nsresult
 nsTypedSelection::StartAutoScrollTimer(nsPresContext *aPresContext,
                                        nsIView *aView,
                                        nsPoint& aPoint,
                                        PRUint32 aDelay)
@@ -5471,18 +5519,17 @@ nsTypedSelection::Collapse(nsIDOMNode* a
     return NS_ERROR_FAILURE;
   nsresult result;
   // Delete all of the current ranges
   nsCOMPtr<nsPresContext>  presContext;
   GetPresContext(getter_AddRefs(presContext));
   Clear(presContext);
 
   // Turn off signal for table selection
-  if (mFrameSelection)
-    mFrameSelection->ClearTableCellSelection();
+  mFrameSelection->ClearTableCellSelection();
 
   nsCOMPtr<nsIDOMRange> range;
   NS_NewRange(getter_AddRefs(range));
   if (! range){
     NS_ASSERTION(PR_FALSE,"Couldn't make a range - nsFrameSelection::Collapse");
     return NS_ERROR_UNEXPECTED;
   }
   result = range->SetEnd(aParentNode, aOffset);
@@ -5510,18 +5557,16 @@ nsTypedSelection::Collapse(nsIDOMNode* a
 #endif
 
 
   result = AddItem(range);
   setAnchorFocusRange(0);
   selectFrames(presContext, range,PR_TRUE);
   if (NS_FAILED(result))
     return result;
-  if (!mFrameSelection)
-    return NS_OK;//nothing to do
   return mFrameSelection->NotifySelectionListeners(GetType());
 }
 
 /*
  * Sets the whole selection to be one point
  * at the start of the current selection
  */
 NS_IMETHODIMP
@@ -5934,18 +5979,16 @@ nsTypedSelection::Extend(nsIDOMNode* aPa
     const char *tagString;
     content->Tag()->GetUTF8String(&tagString);
     printf ("Sel. Extend to %p %s %d\n", content.get(), tagString, aOffset);
   }
   else {
     printf ("Sel. Extend set to null parent.\n");
   }
 #endif
-  if (!mFrameSelection)
-    return NS_OK;//nothing to do
   return mFrameSelection->NotifySelectionListeners(GetType());
 }
 
 static nsresult
 GetChildOffset(nsIDOMNode *aChild, nsIDOMNode *aParent, PRInt32 &aOffset)
 {
   NS_ASSERTION((aChild && aParent), "bad args");
   nsCOMPtr<nsIContent> content = do_QueryInterface(aParent);