diff --git a/content/html/document/src/nsHTMLDocument.cpp b/content/html/document/src/nsHTMLDocument.cpp index e5e2d5bb7bc..c42303e3fc1 100644 --- a/content/html/document/src/nsHTMLDocument.cpp +++ b/content/html/document/src/nsHTMLDocument.cpp @@ -3961,6 +3961,13 @@ static PRBool HasPresShell(nsPIDOMWindow *aWindow) return presShell != nsnull; } +nsresult +nsHTMLDocument::SetEditingState(EditingState aState) +{ + mEditingState = aState; + return NS_OK; +} + nsresult nsHTMLDocument::EditingStateChanged() { diff --git a/content/html/document/src/nsHTMLDocument.h b/content/html/document/src/nsHTMLDocument.h index a25da1cb774..8c25a4c0ed6 100644 --- a/content/html/document/src/nsHTMLDocument.h +++ b/content/html/document/src/nsHTMLDocument.h @@ -221,6 +221,8 @@ public: mFragmentParser = aParser; } + virtual nsresult SetEditingState(EditingState aState); + protected: nsresult GetBodySize(PRInt32* aWidth, PRInt32* aHeight); diff --git a/content/html/document/src/nsIHTMLDocument.h b/content/html/document/src/nsIHTMLDocument.h index 1c15f4616e2..583a44b33f3 100644 --- a/content/html/document/src/nsIHTMLDocument.h +++ b/content/html/document/src/nsIHTMLDocument.h @@ -55,11 +55,11 @@ class nsIDOMHTMLBodyElement; class nsIScriptElement; class nsIEditor; -// Update htmldocument.gqi when updating this IID! -// bfd644d6-92cc-4560-a329-f02ba0c91ca5 +// 19d63a6c-cc94-499c-892a-955add772e10 #define NS_IHTMLDOCUMENT_IID \ -{ 0xbfd644d6, 0x92cc, 0x4560, \ - { 0xa3, 0x29, 0xf0, 0x2b, 0xa0, 0xc9, 0x1c, 0xa5 } } +{ 0x19d63a6c, 0xcc94, 0x499c, \ + { 0x89, 0x2a, 0x95, 0x5a, 0xdd, 0x77, 0x2e, 0x10 } } + /** * HTML document extensions to nsIDocument. @@ -165,6 +165,13 @@ public: */ virtual EditingState GetEditingState() = 0; + /** + * Set the editing state of the document. Don't use this if you want + * to enable/disable editing, call EditingStateChanged() or + * SetDesignMode(). + */ + virtual nsresult SetEditingState(EditingState aState) = 0; + /** * Returns the result of document.all[aID] which can either be a node * or a nodelist depending on if there are multiple nodes with the same diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index b1b81d35003..78db0a9a6e5 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -120,6 +120,7 @@ #include "nsCDefaultURIFixup.h" #include "nsDocShellEnumerator.h" #include "nsSHistory.h" +#include "nsDocShellEditorData.h" // Helper Classes #include "nsDOMError.h" @@ -306,7 +307,6 @@ nsDocShell::nsDocShell(): mDefaultScrollbarPref(Scrollbar_Auto, Scrollbar_Auto), mPreviousTransIndex(-1), mLoadedTransIndex(-1), - mEditorData(nsnull), mTreeOwner(nsnull), mChromeEventHandler(nsnull) #ifdef DEBUG @@ -1013,10 +1013,10 @@ nsDocShell::FirePageHideNotification(PRBool aIsUnload) } } - // Now make sure our editor, if any, is torn down before we go + // Now make sure our editor, if any, is detached before we go // any farther. if (mEditorData && aIsUnload) { - mEditorData->TearDownEditor(); + DetachEditorFromWindow(); } return NS_OK; @@ -3352,6 +3352,12 @@ nsDocShell::Reload(PRUint32 aReloadFlags) nsnull); // No nsIRequest } + + // Need to purge detached editor here, else when we reload a page, + // the detached editor state causes SetDesignMode() to fail. + if (mOSHE) + mOSHE->SetEditorData(nsnull); + return rv; } @@ -3663,8 +3669,7 @@ nsDocShell::Destroy() // Stop any URLs that are currently being loaded... Stop(nsIWebNavigation::STOP_ALL); - delete mEditorData; - mEditorData = 0; + mEditorData = nsnull; mTransferableHookData = nsnull; @@ -5303,6 +5308,67 @@ nsDocShell::CanSavePresentation(PRUint32 aLoadType, return PR_TRUE; } +PRBool +nsDocShell::HasDetachedEditor() +{ + return (mOSHE && mOSHE->HasDetachedEditor()) || + (mLSHE && mLSHE->HasDetachedEditor()); +} + +void +nsDocShell::ReattachEditorToWindow(nsIDOMWindow *aWindow, nsISHEntry *aSHEntry) +{ + NS_ASSERTION(!mEditorData, + "Why reattach an editor when we already have one?"); + NS_ASSERTION(aWindow, + "Need a window to reattach to."); + NS_ASSERTION(HasDetachedEditor(), + "Reattaching when there's not a detached editor."); + + if (mEditorData || !aWindow || !aSHEntry) + return; + + mEditorData = aSHEntry->ForgetEditorData(); + if (mEditorData) { + nsresult res = mEditorData->ReattachToWindow(aWindow); + NS_ASSERTION(NS_SUCCEEDED(res), "Failed to reattach editing session"); + } +} + +void +nsDocShell::DetachEditorFromWindow(nsISHEntry *aSHEntry) +{ + if (!aSHEntry || !mEditorData) + return; + + NS_ASSERTION(!aSHEntry->HasDetachedEditor(), + "Why detach an editor twice?"); + + nsresult res = mEditorData->DetachFromWindow(); + NS_ASSERTION(NS_SUCCEEDED(res), "Failed to detach editor"); + + if (NS_SUCCEEDED(res)) { + // Make aSHEntry hold the owning ref to the editor data. + aSHEntry->SetEditorData(mEditorData.forget()); + } + +#ifdef DEBUG + { + PRBool isEditable; + GetEditable(&isEditable); + NS_ASSERTION(!isEditable, + "Window is still editable after detaching editor."); + } +#endif // DEBUG + +} + +void +nsDocShell::DetachEditorFromWindow() +{ + DetachEditorFromWindow(mOSHE); +} + nsresult nsDocShell::CaptureState() { @@ -5920,6 +5986,11 @@ nsDocShell::RestoreFromHistory() } } + if (HasDetachedEditor()) { + nsCOMPtr domWin = do_QueryInterface(privWin); + ReattachEditorToWindow(domWin, mLSHE); + } + // Simulate the completion of the load. nsDocShell::FinishRestore(); @@ -7045,7 +7116,11 @@ nsDocShell::InternalLoad(nsIURI * aURI, } mLoadType = aLoadType; - + + // Detach the current editor so that it can be restored from the + // bfcache later. + DetachEditorFromWindow(); + // mLSHE should be assigned to aSHEntry, only after Stop() has // been called. But when loading an error page, do not clear the // mLSHE for the real page. @@ -7110,6 +7185,22 @@ nsDocShell::InternalLoad(nsIURI * aURI, DisplayLoadError(rv, aURI, nsnull, chan); } + if (aSHEntry) { + if (aLoadType & LOAD_CMD_HISTORY) { + // We've just loaded a page from session history. Reattach + // its editing session if it has one. + nsCOMPtr domWin; + CallGetInterface(this, static_cast(getter_AddRefs(domWin))); + ReattachEditorToWindow(domWin, aSHEntry); + } else { + // This is a non-history load from a session history entry. Purge any + // previous editing sessions, so that the the editing session will + // be recreated. This can happen when we reload something that's + // in the bfcache. + aSHEntry->SetEditorData(nsnull); + } + } + return rv; } @@ -8916,10 +9007,10 @@ nsDocShell::EnsureScriptEnvironment() NS_IMETHODIMP nsDocShell::EnsureEditorData() { - if (!mEditorData && !mIsBeingDestroyed) - { + NS_ASSERTION(!HasDetachedEditor(), "EnsureEditorData() called when detached.\n"); + + if (!mEditorData && !mIsBeingDestroyed && !HasDetachedEditor()) { mEditorData = new nsDocShellEditorData(this); - if (!mEditorData) return NS_ERROR_OUT_OF_MEMORY; } return mEditorData ? NS_OK : NS_ERROR_NOT_AVAILABLE; diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index 7a419b76224..fc23d3d7284 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -525,6 +525,9 @@ protected: // we are it's still OK to load this URI. PRBool IsOKToLoadURI(nsIURI* aURI); + void ReattachEditorToWindow(nsIDOMWindow *aWindow, nsISHEntry *aSHEntry); + void DetachEditorFromWindow(nsISHEntry *aSHEntry); + protected: // Override the parent setter from nsDocLoader virtual nsresult SetDocLoaderParent(nsDocLoader * aLoader); @@ -642,8 +645,8 @@ protected: PRInt32 mPreviousTransIndex; PRInt32 mLoadedTransIndex; - // Editor stuff - nsDocShellEditorData* mEditorData; // editor data, if any + // Editor data, if this document is designMode or contentEditable. + nsAutoPtr mEditorData; // Transferable hooks/callbacks nsCOMPtr mTransferableHookData; @@ -674,6 +677,9 @@ protected: static nsIURIFixup *sURIFixup; + // Returns true when the currently open document has a detached editor + // waiting to be reattached. + PRBool HasDetachedEditor(); public: class InterfaceRequestorProxy : public nsIInterfaceRequestor { diff --git a/docshell/base/nsDocShellEditorData.cpp b/docshell/base/nsDocShellEditorData.cpp index 9b69dce9887..f0e0832bc8a 100644 --- a/docshell/base/nsDocShellEditorData.cpp +++ b/docshell/base/nsDocShellEditorData.cpp @@ -40,13 +40,11 @@ #include "nsIComponentManager.h" #include "nsIInterfaceRequestorUtils.h" - #include "nsIDOMWindow.h" #include "nsIDocShellTreeItem.h" - +#include "nsIDOMDocument.h" #include "nsDocShellEditorData.h" - /*--------------------------------------------------------------------------- nsDocShellEditorData @@ -56,6 +54,7 @@ nsDocShellEditorData::nsDocShellEditorData(nsIDocShell* inOwningDocShell) : mDocShell(inOwningDocShell) , mMakeEditable(PR_FALSE) +, mIsDetached(PR_FALSE) { NS_ASSERTION(mDocShell, "Where is my docShell?"); } @@ -74,18 +73,12 @@ nsDocShellEditorData::~nsDocShellEditorData() void nsDocShellEditorData::TearDownEditor() { - if (mEditingSession) - { - nsCOMPtr domWindow = do_GetInterface(mDocShell); - // This will eventually call nsDocShellEditorData::SetEditor(nsnull) - // which will call mEditorPreDestroy() and delete the editor - mEditingSession->TearDownEditorOnWindow(domWindow); - } - else if (mEditor) // Should never have this w/o nsEditingSession! - { + NS_ASSERTION(mIsDetached, "We should be detached before tearing down"); + if (mEditor) { mEditor->PreDestroy(); - mEditor = nsnull; // explicit clear to make destruction order predictable + mEditor = nsnull; } + mEditingSession = nsnull; } @@ -95,7 +88,7 @@ nsDocShellEditorData::TearDownEditor() ----------------------------------------------------------------------------*/ nsresult -nsDocShellEditorData::MakeEditable(PRBool inWaitForUriLoad /*, PRBool inEditable */) +nsDocShellEditorData::MakeEditable(PRBool inWaitForUriLoad) { if (mMakeEditable) return NS_OK; @@ -218,6 +211,7 @@ nsresult nsDocShellEditorData::EnsureEditingSession() { NS_ASSERTION(mDocShell, "Should have docShell here"); + NS_ASSERTION(!mIsDetached, "This will stomp editing session!"); nsresult rv = NS_OK; @@ -230,3 +224,44 @@ nsDocShellEditorData::EnsureEditingSession() return rv; } +nsresult +nsDocShellEditorData::DetachFromWindow() +{ + NS_ASSERTION(mEditingSession, + "Can't detach when we don't have a session to detach!"); + + nsCOMPtr domWindow = do_GetInterface(mDocShell); + nsresult rv = mEditingSession->DetachFromWindow(domWindow); + NS_ENSURE_SUCCESS(rv, rv); + + mIsDetached = PR_TRUE; + mDetachedMakeEditable = mMakeEditable; + mMakeEditable = PR_FALSE; + + nsCOMPtr domDoc; + domWindow->GetDocument(getter_AddRefs(domDoc)); + nsCOMPtr htmlDoc = do_QueryInterface(domDoc); + if (htmlDoc) + mDetachedEditingState = htmlDoc->GetEditingState(); + + return NS_OK; +} + +nsresult +nsDocShellEditorData::ReattachToWindow(nsIDOMWindow *aWindow) +{ + nsCOMPtr domWindow = do_GetInterface(mDocShell); + nsresult rv = mEditingSession->ReattachToWindow(domWindow); + NS_ENSURE_SUCCESS(rv, rv); + + mIsDetached = PR_FALSE; + mMakeEditable = mDetachedMakeEditable; + + nsCOMPtr domDoc; + domWindow->GetDocument(getter_AddRefs(domDoc)); + nsCOMPtr htmlDoc = do_QueryInterface(domDoc); + if (htmlDoc) + htmlDoc->SetEditingState(mDetachedEditingState); + + return NS_OK; +} diff --git a/docshell/base/nsDocShellEditorData.h b/docshell/base/nsDocShellEditorData.h index 80077963b4f..df34ff0e89b 100644 --- a/docshell/base/nsDocShellEditorData.h +++ b/docshell/base/nsDocShellEditorData.h @@ -52,53 +52,54 @@ #endif +#include "nsIHTMLDocument.h" #include "nsIEditor.h" - -// a non-XPCOM class that is used to store per-docshell editor-related -// data. +class nsIDOMWindow; class nsDocShellEditorData { public: - nsDocShellEditorData(nsIDocShell* inOwningDocShell); - ~nsDocShellEditorData(); - + nsDocShellEditorData(nsIDocShell* inOwningDocShell); + ~nsDocShellEditorData(); - // set a flag to say this frame should be editable when the next url loads - nsresult MakeEditable(PRBool inWaitForUriLoad); - - PRBool GetEditable(); - - // actually create the editor for this docShell - nsresult CreateEditor(); - - // get the editing session. The editing session always lives on the content - // root docShell; this call may crawl up the frame tree to find it. - nsresult GetEditingSession(nsIEditingSession **outEditingSession); - - // get the editor for this docShell. May return null but NS_OK - nsresult GetEditor(nsIEditor **outEditor); - - // set the editor on this docShell - nsresult SetEditor(nsIEditor *inEditor); - - // Tear down the editor on this docshell, if any. - void TearDownEditor(); - -protected: - - nsresult EnsureEditingSession(); + nsresult MakeEditable(PRBool inWaitForUriLoad); + PRBool GetEditable(); + nsresult CreateEditor(); + nsresult GetEditingSession(nsIEditingSession **outEditingSession); + nsresult GetEditor(nsIEditor **outEditor); + nsresult SetEditor(nsIEditor *inEditor); + void TearDownEditor(); + nsresult DetachFromWindow(); + nsresult ReattachToWindow(nsIDOMWindow *aWindow); protected: - nsIDocShell* mDocShell; // the doc shell that owns us. Weak ref, since it always outlives us. - - nsCOMPtr mEditingSession; // only present for the content root docShell. Session is owned here + nsresult EnsureEditingSession(); - PRBool mMakeEditable; // indicates whether to make an editor after a url load - nsCOMPtr mEditor; // if this frame is editable, store editor here. Editor is owned here. + // The doc shell that owns us. Weak ref, since it always outlives us. + nsIDocShell* mDocShell; + + // Only present for the content root docShell. Session is owned here. + nsCOMPtr mEditingSession; + + // Indicates whether to make an editor after a url load. + PRBool mMakeEditable; + + // If this frame is editable, store editor here. Editor is owned here. + nsCOMPtr mEditor; + + // Denotes if the editor is detached from its window. The editor is detached + // while it's stored in the session history bfcache. + PRBool mIsDetached; + + // Backup for mMakeEditable while the editor is detached. + PRBool mDetachedMakeEditable; + + // Backup for the corresponding nsIHTMLDocument's editing state while + // the editor is detached. + nsIHTMLDocument::EditingState mDetachedEditingState; }; diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl index 36f31898648..dfae54e4c6d 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -457,5 +457,11 @@ interface nsIDocShell : nsISupports * known JAR type). */ readonly attribute boolean channelIsUnsafe; + + /** + * Disconnects this docshell's editor from its window, and stores the + * editor data in the open document's session history entry. + */ + [noscript, notxpcom] void DetachEditorFromWindow(); }; diff --git a/docshell/shistory/public/nsISHEntry.idl b/docshell/shistory/public/nsISHEntry.idl index 064d172aef7..b729de82c0c 100644 --- a/docshell/shistory/public/nsISHEntry.idl +++ b/docshell/shistory/public/nsISHEntry.idl @@ -52,8 +52,11 @@ interface nsIDocShellTreeItem; interface nsISupportsArray; %{C++ struct nsRect; +class nsDocShellEditorData; %} [ref] native nsRect(nsRect); +[ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData); + [scriptable, uuid(abe54136-49e5-44ca-a749-290038c6b85d)] interface nsISHEntry : nsIHistoryEntry @@ -188,6 +191,23 @@ interface nsISHEntry : nsIHistoryEntry * came from. */ attribute nsISupports owner; + + /** + * Gets the owning pointer to the editor data assosicated with + * this shistory entry. This forgets its pointer, so free it when + * you're done. + */ + [noscript, notxpcom] nsDocShellEditorDataPtr forgetEditorData(); + + /** + * Sets the owning pointer to the editor data assosicated with + * this shistory entry. Unless forgetEditorData() is called, this + * shentry will destroy the editor data when it's destroyed. + */ + [noscript, notxpcom] void setEditorData(in nsDocShellEditorDataPtr aData); + + /** Returns true if this shistory entry is storing a detached editor. */ + [noscript, notxpcom] boolean hasDetachedEditor(); }; diff --git a/docshell/shistory/src/Makefile.in b/docshell/shistory/src/Makefile.in index b98ff16a54c..5a6c9a9bd5e 100644 --- a/docshell/shistory/src/Makefile.in +++ b/docshell/shistory/src/Makefile.in @@ -59,6 +59,8 @@ REQUIRES = xpcom \ content \ widget \ nkcache \ + editor \ + composer \ $(NULL) CPPSRCS = nsSHEntry.cpp \ @@ -72,3 +74,4 @@ EXTRA_DSO_LDOPTS = \ include $(topsrcdir)/config/rules.mk +LOCAL_INCLUDES += -I$(srcdir)/../../base diff --git a/docshell/shistory/src/nsSHEntry.cpp b/docshell/shistory/src/nsSHEntry.cpp index 3550d7a9826..7d807bc48a1 100644 --- a/docshell/shistory/src/nsSHEntry.cpp +++ b/docshell/shistory/src/nsSHEntry.cpp @@ -54,6 +54,8 @@ #include "nsIWebNavigation.h" #include "nsISHistory.h" #include "nsISHistoryInternal.h" +#include "nsDocShellEditorData.h" +#include "nsIDocShell.h" // Hardcode this to time out unused content viewers after 30 minutes #define CONTENT_VIEWER_TIMEOUT_SECONDS 30*60 @@ -161,6 +163,8 @@ nsSHEntry::~nsSHEntry() viewer->Destroy(); } + mEditorData = nsnull; + #ifdef DEBUG // This is not happening as far as I can tell from breakpad as of early November 2007 nsExpirationTracker::Iterator iterator(gHistoryTracker); @@ -833,3 +837,25 @@ nsSHEntry::DocumentMutated() // Warning! The call to DropPresentationState could have dropped the last // reference to this nsSHEntry, so no accessing members beyond here. } + +nsDocShellEditorData* +nsSHEntry::ForgetEditorData() +{ + return mEditorData.forget(); +} + +void +nsSHEntry::SetEditorData(nsDocShellEditorData* aData) +{ + NS_ASSERTION(!(aData && mEditorData), + "We're going to overwrite an owning ref!"); + if (mEditorData != aData) + mEditorData = aData; +} + +PRBool +nsSHEntry::HasDetachedEditor() +{ + return mEditorData != nsnull; +} + diff --git a/docshell/shistory/src/nsSHEntry.h b/docshell/shistory/src/nsSHEntry.h index 95de45337fd..e87c6f4a99f 100644 --- a/docshell/shistory/src/nsSHEntry.h +++ b/docshell/shistory/src/nsSHEntry.h @@ -45,6 +45,7 @@ #include "nsCOMArray.h" #include "nsString.h" #include "nsVoidArray.h" +#include "nsAutoPtr.h" // Interfaces needed #include "nsIContentViewer.h" @@ -59,6 +60,7 @@ #include "nsSupportsArray.h" #include "nsIMutationObserver.h" #include "nsExpirationTracker.h" +#include "nsDocShellEditorData.h" class nsSHEntry : public nsISHEntry, public nsISHContainer, @@ -113,6 +115,7 @@ private: nsCOMPtr mRefreshURIList; nsCOMPtr mOwner; nsExpirationState mExpirationState; + nsAutoPtr mEditorData; }; #endif /* nsSHEntry_h */ diff --git a/docshell/test/navigation/Makefile.in b/docshell/test/navigation/Makefile.in index 69817c8055a..bc8cbf436df 100644 --- a/docshell/test/navigation/Makefile.in +++ b/docshell/test/navigation/Makefile.in @@ -49,6 +49,7 @@ _TEST_FILES = \ test_bug270414.html \ test_bug278916.html \ test_bug279495.html \ + test_bug386782.html \ test_child.html \ test_grandchild.html \ test_sibling-off-domain.html \ diff --git a/docshell/test/navigation/test_bug386782.html b/docshell/test/navigation/test_bug386782.html new file mode 100644 index 00000000000..f255a857a78 --- /dev/null +++ b/docshell/test/navigation/test_bug386782.html @@ -0,0 +1,136 @@ + + + + + Test for Bug 386782 + + + + + + + + + +Mozilla Bug 386782 +

+ +
+
+
+ + + diff --git a/editor/composer/public/nsIEditingSession.idl b/editor/composer/public/nsIEditingSession.idl index dd1a3181d31..d27715b851e 100644 --- a/editor/composer/public/nsIEditingSession.idl +++ b/editor/composer/public/nsIEditingSession.idl @@ -116,5 +116,17 @@ interface nsIEditingSession : nsISupports * were before the last call to disableJSAndPlugins. */ void restoreJSAndPlugins(in nsIDOMWindow aWindow); + + /** + * Removes all the editor's controllers/listeners etc and makes the window + * uneditable. + */ + void detachFromWindow(in nsIDOMWindow aWindow); + + /** + * Undos detachFromWindow(), reattaches this editing session/editor + * to the window. + */ + void reattachToWindow(in nsIDOMWindow aWindow); }; diff --git a/editor/composer/src/nsEditingSession.cpp b/editor/composer/src/nsEditingSession.cpp index 3110ba142c4..c00bed5a953 100644 --- a/editor/composer/src/nsEditingSession.cpp +++ b/editor/composer/src/nsEditingSession.cpp @@ -248,6 +248,9 @@ nsEditingSession::DisableJSAndPlugins(nsIDOMWindow *aWindow) NS_IMETHODIMP nsEditingSession::RestoreJSAndPlugins(nsIDOMWindow *aWindow) { + if (!mDisabledJSAndPlugins) + return NS_OK; + mDisabledJSAndPlugins = PR_FALSE; nsIDocShell *docShell = GetDocShellFromWindow(aWindow); @@ -410,26 +413,17 @@ nsEditingSession::SetupEditorOnWindow(nsIDOMWindow *aWindow) } // make the UI state maintainer - nsComposerCommandsUpdater *stateMaintainer; - NS_NEWXPCOM(stateMaintainer, nsComposerCommandsUpdater); - mStateMaintainer = static_cast(stateMaintainer); - - if (!mStateMaintainer) return NS_ERROR_OUT_OF_MEMORY; + mStateMaintainer = new nsComposerCommandsUpdater(); // now init the state maintainer // This allows notification of error state // even if we don't create an editor - rv = stateMaintainer->Init(aWindow); + rv = mStateMaintainer->Init(aWindow); if (NS_FAILED(rv)) return rv; if (mEditorStatus != eEditorCreationInProgress) { - // We had an earlier error -- force notification of document creation - nsCOMPtr docListener = - do_QueryInterface(mStateMaintainer); - if (docListener) - docListener->NotifyDocumentCreated(); - + mStateMaintainer->NotifyDocumentCreated(); return NS_ERROR_FAILURE; } @@ -484,8 +478,7 @@ nsEditingSession::SetupEditorOnWindow(nsIDOMWindow *aWindow) // Set up as a doc state listener // Important! We must have this to broadcast the "obs_documentCreated" message - rv = editor->AddDocumentStateListener( - static_cast(stateMaintainer)); + rv = editor->AddDocumentStateListener(mStateMaintainer); if (NS_FAILED(rv)) return rv; // XXXbz we really shouldn't need a presShell here! @@ -504,15 +497,14 @@ nsEditingSession::SetupEditorOnWindow(nsIDOMWindow *aWindow) nsCOMPtr selPriv = do_QueryInterface(selection); if (!selPriv) return NS_ERROR_FAILURE; - rv = selPriv->AddSelectionListener(stateMaintainer); + rv = selPriv->AddSelectionListener(mStateMaintainer); if (NS_FAILED(rv)) return rv; // and as a transaction listener nsCOMPtr txnMgr; editor->GetTransactionManager(getter_AddRefs(txnMgr)); if (txnMgr) - txnMgr->AddListener(static_cast - (stateMaintainer)); + txnMgr->AddListener(mStateMaintainer); // Set context on all controllers to be the editor rv = SetEditorOnControllers(aWindow, editor); @@ -521,11 +513,37 @@ nsEditingSession::SetupEditorOnWindow(nsIDOMWindow *aWindow) // Everything went fine! mEditorStatus = eEditorOK; - // This will trigger documentCreation notification return editor->PostCreate(); } +// Removes all listeners and controllers from aWindow and aEditor. +void +nsEditingSession::RemoveListenersAndControllers(nsIDOMWindow *aWindow, + nsIEditor *aEditor) +{ + if (!mStateMaintainer || !aEditor) + return; + + // Remove all the listeners + nsCOMPtr selection; + aEditor->GetSelection(getter_AddRefs(selection)); + nsCOMPtr selPriv = do_QueryInterface(selection); + if (selPriv) + selPriv->RemoveSelectionListener(mStateMaintainer); + + aEditor->RemoveDocumentStateListener(mStateMaintainer); + + nsCOMPtr txnMgr; + aEditor->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) + txnMgr->RemoveListener(mStateMaintainer); + + // Remove editor controllers from the window now that we're not + // editing in that window any more. + RemoveEditorControllers(aWindow); +} + /*--------------------------------------------------------------------------- TearDownEditorOnWindow @@ -538,6 +556,9 @@ nsEditingSession::TearDownEditorOnWindow(nsIDOMWindow *aWindow) if (!mDoneSetup) return NS_OK; + if (!aWindow) + return NS_ERROR_NULL_POINTER; + nsresult rv; // Kill any existing reload timer @@ -547,8 +568,6 @@ nsEditingSession::TearDownEditorOnWindow(nsIDOMWindow *aWindow) mLoadBlankDocTimer = nsnull; } - nsIDocShell *docShell = GetDocShellFromWindow(aWindow); - mDoneSetup = PR_FALSE; // Check if we're turning off editing (from contentEditable or designMode). @@ -556,14 +575,8 @@ nsEditingSession::TearDownEditorOnWindow(nsIDOMWindow *aWindow) aWindow->GetDocument(getter_AddRefs(domDoc)); nsCOMPtr htmlDoc = do_QueryInterface(domDoc); PRBool stopEditing = htmlDoc && htmlDoc->IsEditingOn(); - if (stopEditing) { - nsCOMPtr webProgress = do_GetInterface(docShell); - if (webProgress) { - webProgress->RemoveProgressListener(this); - - mProgressListenerRegistered = PR_FALSE; - } - } + if (stopEditing) + RemoveWebProgressListener(aWindow); nsCOMPtr editorDocShell; rv = GetEditorDocShellFromWindow(aWindow, getter_AddRefs(editorDocShell)); @@ -576,107 +589,27 @@ nsEditingSession::TearDownEditorOnWindow(nsIDOMWindow *aWindow) if (stopEditing) htmlDoc->TearingDownEditor(editor); - // null out the editor on the controllers first to prevent their weak - // references from pointing to a destroyed editor if (mStateMaintainer && editor) { - // null out the editor on the controllers + // Null out the editor on the controllers first to prevent their weak + // references from pointing to a destroyed editor. SetEditorOnControllers(aWindow, nsnull); } - // null out the editor on the docShell to trigger PreDestroy which - // needs to happen before document state listeners are removed below + // Null out the editor on the docShell to trigger PreDestroy which + // needs to happen before document state listeners are removed below. editorDocShell->SetEditor(nsnull); - if (mStateMaintainer) + RemoveListenersAndControllers(aWindow, editor); + + if (stopEditing) { - if (editor) + // Make things the way they were before we started editing. + RestoreJSAndPlugins(aWindow); + RestoreAnimationMode(aWindow); + + if (mMakeWholeDocumentEditable) { - // If we had an editor -- we are loading a new URL into existing window - - // Remove all the listeners - nsCOMPtr selection; - editor->GetSelection(getter_AddRefs(selection)); - nsCOMPtr selPriv = do_QueryInterface(selection); - if (selPriv) - { - nsCOMPtr listener = - do_QueryInterface(mStateMaintainer); - selPriv->RemoveSelectionListener(listener); - } - - nsCOMPtr docListener = - do_QueryInterface(mStateMaintainer); - editor->RemoveDocumentStateListener(docListener); - - nsCOMPtr txnMgr; - editor->GetTransactionManager(getter_AddRefs(txnMgr)); - if (txnMgr) - { - nsCOMPtr transactionListener = - do_QueryInterface(mStateMaintainer); - txnMgr->RemoveListener(transactionListener); - } - } - - // Remove editor controllers from the window now that we're not - // editing in that window any more. - - nsCOMPtr domWindowInt(do_QueryInterface(aWindow)); - - nsCOMPtr controllers; - domWindowInt->GetControllers(getter_AddRefs(controllers)); - - if (controllers) { - nsCOMPtr controller; - - if (mBaseCommandControllerId) { - controllers->GetControllerById(mBaseCommandControllerId, - getter_AddRefs(controller)); - - if (controller) { - controllers->RemoveController(controller); - } - } - - if (mDocStateControllerId) { - controllers->GetControllerById(mDocStateControllerId, - getter_AddRefs(controller)); - - if (controller) { - controllers->RemoveController(controller); - } - } - - if (mHTMLCommandControllerId) { - controllers->GetControllerById(mHTMLCommandControllerId, - getter_AddRefs(controller)); - - if (controller) { - controllers->RemoveController(controller); - } - } - } - - // Clear IDs to trigger creation of new controllers - mBaseCommandControllerId = 0; - mDocStateControllerId = 0; - mHTMLCommandControllerId = 0; - } - - if (stopEditing) { - if (mDisabledJSAndPlugins) { - // Make things the way they were before we started editing. - RestoreJSAndPlugins(aWindow); - } - - if (!mInteractive) { - nsCOMPtr utils(do_GetInterface(aWindow)); - if (utils) - utils->SetImageAnimationMode(mImageAnimationMode); - } - - if (mMakeWholeDocumentEditable) { nsCOMPtr doc = do_QueryInterface(domDoc, &rv); NS_ENSURE_SUCCESS(rv, rv); @@ -1020,7 +953,9 @@ nsEditingSession::StartDocumentLoad(nsIWebProgress *aWebProgress, aWebProgress->GetDOMWindow(getter_AddRefs(domWindow)); if (domWindow) { - TearDownEditorOnWindow(domWindow); + nsIDocShell *docShell = GetDocShellFromWindow(domWindow); + if (!docShell) return NS_ERROR_FAILURE; + docShell->DetachEditorFromWindow(); } if (aIsToBeMadeEditable) @@ -1387,3 +1322,179 @@ nsEditingSession::SetContextOnControllerById(nsIControllers* aControllers, return editorController->SetCommandContext(aContext); } + +void +nsEditingSession::RemoveEditorControllers(nsIDOMWindow *aWindow) +{ + // Remove editor controllers from the aWindow, call when we're + // tearing down/detaching editor. + nsCOMPtr domWindowInt(do_QueryInterface(aWindow)); + + nsCOMPtr controllers; + if (domWindowInt) + domWindowInt->GetControllers(getter_AddRefs(controllers)); + + if (controllers) + { + nsCOMPtr controller; + if (mBaseCommandControllerId) + { + controllers->GetControllerById(mBaseCommandControllerId, + getter_AddRefs(controller)); + if (controller) + controllers->RemoveController(controller); + } + + if (mDocStateControllerId) + { + controllers->GetControllerById(mDocStateControllerId, + getter_AddRefs(controller)); + if (controller) + controllers->RemoveController(controller); + } + + if (mHTMLCommandControllerId) + { + controllers->GetControllerById(mHTMLCommandControllerId, + getter_AddRefs(controller)); + if (controller) + controllers->RemoveController(controller); + } + } + + // Clear IDs to trigger creation of new controllers. + mBaseCommandControllerId = 0; + mDocStateControllerId = 0; + mHTMLCommandControllerId = 0; +} + +void +nsEditingSession::RemoveWebProgressListener(nsIDOMWindow *aWindow) +{ + nsIDocShell *docShell = GetDocShellFromWindow(aWindow); + nsCOMPtr webProgress = do_GetInterface(docShell); + if (webProgress) + { + webProgress->RemoveProgressListener(this); + mProgressListenerRegistered = PR_FALSE; + } +} + +void +nsEditingSession::RestoreAnimationMode(nsIDOMWindow *aWindow) +{ + if (!mInteractive) + { + nsCOMPtr utils(do_GetInterface(aWindow)); + if (utils) + utils->SetImageAnimationMode(mImageAnimationMode); + } +} + +nsresult +nsEditingSession::DetachFromWindow(nsIDOMWindow* aWindow) +{ + NS_ASSERTION(mEditorFlags != 0, "mEditorFlags should not be 0"); + NS_ASSERTION(mStateMaintainer, "mStateMaintainer should exist."); + + // Kill any existing reload timer + if (mLoadBlankDocTimer) + { + mLoadBlankDocTimer->Cancel(); + mLoadBlankDocTimer = nsnull; + } + + // Remove controllers, webprogress listener, and otherwise + // make things the way they were before we started editing. + RemoveEditorControllers(aWindow); + RemoveWebProgressListener(aWindow); + RestoreJSAndPlugins(aWindow); + RestoreAnimationMode(aWindow); + + // Kill our weak reference to our original window, in case + // it changes on restore, or otherwise dies. + mWindowToBeEdited = nsnull; + + return NS_OK; +} + +nsresult +nsEditingSession::ReattachToWindow(nsIDOMWindow* aWindow) +{ + NS_ASSERTION(mEditorFlags != 0, "mEditorFlags should still be valid..."); + NS_ASSERTION(mStateMaintainer, "mStateMaintainer should exist."); + + // Imitate nsEditorDocShell::MakeEditable() to reattach the + // old editor ot the window. + nsresult rv; + + mWindowToBeEdited = do_GetWeakReference(aWindow); + + // Disable plugins. + if (!mInteractive) + { + rv = DisableJSAndPlugins(aWindow); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Tells embedder that startup is in progress. + mEditorStatus = eEditorCreationInProgress; + + // Adds back web progress listener. + rv = PrepareForEditing(aWindow); + NS_ENSURE_SUCCESS(rv, rv); + + // Setup the command controllers again. + rv = SetupEditorCommandController("@mozilla.org/editor/editorcontroller;1", + aWindow, + static_cast(this), + &mBaseCommandControllerId); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetupEditorCommandController("@mozilla.org/editor/editordocstatecontroller;1", + aWindow, + static_cast(this), + &mDocStateControllerId); + NS_ENSURE_SUCCESS(rv, rv); + + if (mStateMaintainer) + mStateMaintainer->Init(aWindow); + + // Get editor + nsCOMPtr editor; + rv = GetEditorForWindow(aWindow, getter_AddRefs(editor)); + if (!editor) + return NS_ERROR_FAILURE; + + if (!mInteractive) + { + // Disable animation of images in this document: + nsCOMPtr utils(do_GetInterface(aWindow)); + if (!utils) return NS_ERROR_FAILURE; + + rv = utils->GetImageAnimationMode(&mImageAnimationMode); + NS_ENSURE_SUCCESS(rv, rv); + utils->SetImageAnimationMode(imgIContainer::kDontAnimMode); + } + + // The third controller takes an nsIEditor as the context + rv = SetupEditorCommandController("@mozilla.org/editor/htmleditorcontroller;1", + aWindow, editor, + &mHTMLCommandControllerId); + NS_ENSURE_SUCCESS(rv, rv); + + // Set context on all controllers to be the editor + rv = SetEditorOnControllers(aWindow, editor); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef DEBUG + { + PRBool isEditable; + rv = WindowIsEditable(aWindow, &isEditable); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(isEditable, "Window is not editable after reattaching editor."); + } +#endif // DEBUG + + return NS_OK; +} diff --git a/editor/composer/src/nsEditingSession.h b/editor/composer/src/nsEditingSession.h index fa4e48c2a8c..dc870d7ddba 100644 --- a/editor/composer/src/nsEditingSession.h +++ b/editor/composer/src/nsEditingSession.h @@ -45,6 +45,7 @@ #endif #include "nsITimer.h" +#include "nsAutoPtr.h" #ifndef __gen_nsIWebProgressListener_h__ #include "nsIWebProgressListener.h" @@ -119,6 +120,12 @@ protected: PRBool IsProgressForTargetDocument(nsIWebProgress *aWebProgress); + void RemoveEditorControllers(nsIDOMWindow *aWindow); + void RemoveWebProgressListener(nsIDOMWindow *aWindow); + void RestoreAnimationMode(nsIDOMWindow *aWindow); + void RemoveListenersAndControllers(nsIDOMWindow *aWindow, + nsIEditor *aEditor); + protected: PRPackedBool mDoneSetup; // have we prepared for editing yet? @@ -149,7 +156,7 @@ protected: // THE REMAINING MEMBER VARIABLES WILL BECOME A SET WHEN WE EDIT // MORE THAN ONE EDITOR PER EDITING SESSION - nsCOMPtr mStateMaintainer; // we hold the owning ref to this + nsRefPtr mStateMaintainer; nsWeakPtr mWindowToBeEdited;