Bug 386782. save and restore editor state so that it isn't lost when a page goes into the bfcache and then comes back. patch by Chris Pearce, r+sr=peterv,a=beltzner

This commit is contained in:
roc+@cs.cmu.edu 2008-04-23 14:36:17 -07:00
parent 7f0ad58213
commit e39f69c653
17 changed files with 660 additions and 186 deletions

View File

@ -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()
{

View File

@ -221,6 +221,8 @@ public:
mFragmentParser = aParser;
}
virtual nsresult SetEditingState(EditingState aState);
protected:
nsresult GetBodySize(PRInt32* aWidth,
PRInt32* aHeight);

View File

@ -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

View File

@ -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<nsIDOMWindow> 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<nsIDOMWindow> domWin;
CallGetInterface(this, static_cast<nsIDOMWindow**>(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;

View File

@ -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<nsDocShellEditorData> mEditorData;
// Transferable hooks/callbacks
nsCOMPtr<nsIClipboardDragDropHookList> 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 {

View File

@ -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<nsIDOMWindow> 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<nsIDOMWindow> domWindow = do_GetInterface(mDocShell);
nsresult rv = mEditingSession->DetachFromWindow(domWindow);
NS_ENSURE_SUCCESS(rv, rv);
mIsDetached = PR_TRUE;
mDetachedMakeEditable = mMakeEditable;
mMakeEditable = PR_FALSE;
nsCOMPtr<nsIDOMDocument> domDoc;
domWindow->GetDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(domDoc);
if (htmlDoc)
mDetachedEditingState = htmlDoc->GetEditingState();
return NS_OK;
}
nsresult
nsDocShellEditorData::ReattachToWindow(nsIDOMWindow *aWindow)
{
nsCOMPtr<nsIDOMWindow> domWindow = do_GetInterface(mDocShell);
nsresult rv = mEditingSession->ReattachToWindow(domWindow);
NS_ENSURE_SUCCESS(rv, rv);
mIsDetached = PR_FALSE;
mMakeEditable = mDetachedMakeEditable;
nsCOMPtr<nsIDOMDocument> domDoc;
domWindow->GetDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(domDoc);
if (htmlDoc)
htmlDoc->SetEditingState(mDetachedEditingState);
return NS_OK;
}

View File

@ -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<nsIEditingSession> 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<nsIEditor> 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<nsIEditingSession> 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<nsIEditor> 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;
};

View File

@ -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();
};

View File

@ -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();
};

View File

@ -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

View File

@ -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<nsSHEntry,3>::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;
}

View File

@ -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<nsISupportsArray> mRefreshURIList;
nsCOMPtr<nsISupports> mOwner;
nsExpirationState mExpirationState;
nsAutoPtr<nsDocShellEditorData> mEditorData;
};
#endif /* nsSHEntry_h */

View File

@ -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 \

View File

@ -0,0 +1,136 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=386782
-->
<head>
<title>Test for Bug 386782</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script>
// This tests if we can load a document whose root is in designMode,
// edit it, navigate to a new page, navigate back, still edit, and still
// undo/redo. Note that this is different from the case where the
// designMode document is in a frame inside the window, as this means
// the editable region is not in the root docshell (a less complicated case).
//var test.window = null;
var testInterval = 2000;
var gTests = [
{
// <html><body onload="document.designMode='on'"><p>designModeDocument</p></body></html>
url: 'data:text/html;charset=utf-8;base64,PGh0bWw%2BPGJvZHkgb25sb2FkPSJkb2N1bWVudC5kZXNpZ25Nb2RlPSdvbiciPjxwPmRlc2lnbk1vZGVEb2N1bWVudDwvcD48L2JvZHk%2BPC9odG1sPg0K',
name: 'designModeNavigate',
expectedBodyBeforeEdit: '<p>designModeDocument</p>',
expectedBodyAfterEdit: '<p>EDITED designModeDocument</p>',
expectedBodyAfterSecondEdit: '<p>EDITED TWICE designModeDocument</p>',
},
{
// <html><body contentEditable="true"><p>contentEditable</p></body></html>
url: 'data:text/html;charset=utf-8;base64,PGh0bWw%2BPGJvZHkgY29udGVudEVkaXRhYmxlPSJ0cnVlIj48cD5jb250ZW50RWRpdGFibGU8L3A%2BPC9ib2R5PjwvaHRtbD4NCg0KDQo%3D',
name: 'contentEditableNavigate',
expectedBodyBeforeEdit: '<br><p>contentEditable</p>',
expectedBodyAfterEdit: 'EDITED <br><p>contentEditable</p>',
expectedBodyAfterSecondEdit: 'EDITED TWICE <br><p>contentEditable</p>',
}
];
var gTestNum = -1;
var gTest = null;
window.onload = goNext();
function goNext() {
gTestNum++;
if (gTestNum >= gTests.length) {
SimpleTest.finish();
return;
}
gTest = gTests[gTestNum];
gTest.window = window.open(gTest.url, gTest.name);
setTimeout(beginTest, testInterval);
}
function beginTest() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
gTest.window.document.body.focus();
// WARNING: If the following test fails, give the setTimeout() in the onload()
// a bit longer; the doc hasn't had enough time to setup its editor.
is(gTest.window.document.body.innerHTML, gTest.expectedBodyBeforeEdit, "Is doc setup yet");
sendChar('E', gTest.window.document.body);
sendChar('D', gTest.window.document.body);
sendChar('I', gTest.window.document.body);
sendChar('T', gTest.window.document.body);
sendChar('E', gTest.window.document.body);
sendChar('D', gTest.window.document.body);
sendChar(' ', gTest.window.document.body);
is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterEdit, "Editing failed.");
gTest.window.location = 'data:text/html;charset=utf-8,SomeOtherDocument';
setTimeout(goBack, testInterval);
}
function goBack() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
gTest.window.history.back();
setTimeout(checkStillEditable, testInterval);
}
function checkStillEditable() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
// Check that the contents are correct.
is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterEdit, "Edited contents still correct?");
// Check that we can undo/redo and the contents are correct.
gTest.window.document.execCommand("undo", false, null);
is(gTest.window.document.body.innerHTML, gTest.expectedBodyBeforeEdit, "Can we undo?");
gTest.window.document.execCommand("redo", false, null);
is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterEdit, "Can we redo?");
// Check that we can still edit the page.
gTest.window.document.body.focus();
sendChar('T', gTest.window.document.body);
sendChar('W', gTest.window.document.body);
sendChar('I', gTest.window.document.body);
sendChar('C', gTest.window.document.body);
sendChar('E', gTest.window.document.body);
sendChar(' ', gTest.window.document.body);
is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterSecondEdit, "Can we still edit?");
gTest.window.close();
goNext();
}
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=386782">Mozilla Bug 386782</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Bug 386782 **/
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

View File

@ -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);
};

View File

@ -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<nsISelectionListener*>(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<nsIDocumentStateListener> 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<nsIDocumentStateListener*>(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<nsISelectionPrivate> 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<nsITransactionManager> txnMgr;
editor->GetTransactionManager(getter_AddRefs(txnMgr));
if (txnMgr)
txnMgr->AddListener(static_cast<nsITransactionListener*>
(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<nsISelection> selection;
aEditor->GetSelection(getter_AddRefs(selection));
nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection);
if (selPriv)
selPriv->RemoveSelectionListener(mStateMaintainer);
aEditor->RemoveDocumentStateListener(mStateMaintainer);
nsCOMPtr<nsITransactionManager> 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<nsIHTMLDocument> htmlDoc = do_QueryInterface(domDoc);
PRBool stopEditing = htmlDoc && htmlDoc->IsEditingOn();
if (stopEditing) {
nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
if (webProgress) {
webProgress->RemoveProgressListener(this);
mProgressListenerRegistered = PR_FALSE;
}
}
if (stopEditing)
RemoveWebProgressListener(aWindow);
nsCOMPtr<nsIEditorDocShell> 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<nsISelection> selection;
editor->GetSelection(getter_AddRefs(selection));
nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection);
if (selPriv)
{
nsCOMPtr<nsISelectionListener> listener =
do_QueryInterface(mStateMaintainer);
selPriv->RemoveSelectionListener(listener);
}
nsCOMPtr<nsIDocumentStateListener> docListener =
do_QueryInterface(mStateMaintainer);
editor->RemoveDocumentStateListener(docListener);
nsCOMPtr<nsITransactionManager> txnMgr;
editor->GetTransactionManager(getter_AddRefs(txnMgr));
if (txnMgr)
{
nsCOMPtr<nsITransactionListener> 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<nsIDOMWindowInternal> domWindowInt(do_QueryInterface(aWindow));
nsCOMPtr<nsIControllers> controllers;
domWindowInt->GetControllers(getter_AddRefs(controllers));
if (controllers) {
nsCOMPtr<nsIController> 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<nsIDOMWindowUtils> utils(do_GetInterface(aWindow));
if (utils)
utils->SetImageAnimationMode(mImageAnimationMode);
}
if (mMakeWholeDocumentEditable) {
nsCOMPtr<nsIDocument> 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<nsIDOMWindowInternal> domWindowInt(do_QueryInterface(aWindow));
nsCOMPtr<nsIControllers> controllers;
if (domWindowInt)
domWindowInt->GetControllers(getter_AddRefs(controllers));
if (controllers)
{
nsCOMPtr<nsIController> 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<nsIWebProgress> webProgress = do_GetInterface(docShell);
if (webProgress)
{
webProgress->RemoveProgressListener(this);
mProgressListenerRegistered = PR_FALSE;
}
}
void
nsEditingSession::RestoreAnimationMode(nsIDOMWindow *aWindow)
{
if (!mInteractive)
{
nsCOMPtr<nsIDOMWindowUtils> 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<nsIEditingSession*>(this),
&mBaseCommandControllerId);
NS_ENSURE_SUCCESS(rv, rv);
rv = SetupEditorCommandController("@mozilla.org/editor/editordocstatecontroller;1",
aWindow,
static_cast<nsIEditingSession*>(this),
&mDocStateControllerId);
NS_ENSURE_SUCCESS(rv, rv);
if (mStateMaintainer)
mStateMaintainer->Init(aWindow);
// Get editor
nsCOMPtr<nsIEditor> editor;
rv = GetEditorForWindow(aWindow, getter_AddRefs(editor));
if (!editor)
return NS_ERROR_FAILURE;
if (!mInteractive)
{
// Disable animation of images in this document:
nsCOMPtr<nsIDOMWindowUtils> 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;
}

View File

@ -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<nsISupports> mStateMaintainer; // we hold the owning ref to this
nsRefPtr<nsComposerCommandsUpdater> mStateMaintainer;
nsWeakPtr mWindowToBeEdited;