Fixes for bug 192569 (allow foreign transactions to be added to the queue)

mozilla/editor/idl/nsIEditor.idl
mozilla/editor/libeditor/base/nsEditor.cpp
mozilla/editor/libeditor/base/PlaceholderTxn.cpp
mozilla/editor/libeditor/html/nsHTMLCSSUtils.cpp
mozilla/editor/libeditor/html/nsHTMLEditor.cpp
mozilla/editor/libeditor/text/nsTextEditRules.cpp

  - Renamed nsIEditor::Do() to nsIEditor::DoTransaction() so that it
    can be called from JavaScript.

  - Cleaned up nsEditor::Begin/EndUpdateViewBatch() so that
    nothing happens outside the check of mUpdateCount.

  - Modified PlaceholderTxn.cpp so that it checks to see
    if a merged transaction implements nsPIEditorTransaction
    before attempting to cast it to (EditorTxn*).

mozilla/editor/ui/composer/content/EditorCommandsDebug.js
mozilla/editor/ui/composer/content/editorOverlay.xul
mozilla/editor/ui/composer/locale/en-US/editorOverlay.dtd

  - Added debug menu items to test execution of foreign transactions
    via the txnmgr and editor.

r=jfrancis@netscape.com  sr=sfraser@netscape.com
This commit is contained in:
kin%netscape.com 2003-04-04 20:50:25 +00:00
Родитель d18471017d
Коммит 086e9f0622
9 изменённых файлов: 252 добавлений и 88 удалений

Просмотреть файл

@ -177,14 +177,14 @@ interface nsIEditor : nsISupports
*/
readonly attribute nsITransactionManager transactionManager;
/** do() fires a transaction.
/** doTransaction() fires a transaction.
* It is provided here so clients can create their own transactions.
* If a transaction manager is present, it is used.
* Otherwise, the transaction is just executed directly.
*
* @param aTxn the transaction to execute
*/
void do(in nsITransaction txn);
void doTransaction(in nsITransaction txn);
/** turn the undo system on or off

Просмотреть файл

@ -152,6 +152,14 @@ NS_IMETHODIMP PlaceholderTxn::Merge(nsITransaction *aTransaction, PRBool *aDidMe
return NS_ERROR_FAILURE;
}
// check to see if aTransaction is one of the editor's
// private transactions. If not, we want to avoid merging
// the foreign transaction into our placeholder since we
// don't know what it does.
nsCOMPtr<nsPIEditorTransaction> pTxn = do_QueryInterface(aTransaction);
if (!pTxn) return NS_OK; // it's foreign so just bail!
EditTxn *editTxn = (EditTxn*)aTransaction; //XXX: hack, not safe! need nsIEditTransaction!
// determine if this incoming txn is a placeholder txn
nsCOMPtr<nsIAbsorbingTransaction> plcTxn;// = do_QueryInterface(editTxn);

Просмотреть файл

@ -452,9 +452,9 @@ nsEditor::GetSelection(nsISelection **aSelection)
}
NS_IMETHODIMP
nsEditor::Do(nsITransaction *aTxn)
nsEditor::DoTransaction(nsITransaction *aTxn)
{
if (gNoisy) { printf("Editor::Do ----------\n"); }
if (gNoisy) { printf("Editor::DoTransaction ----------\n"); }
nsresult result = NS_OK;
@ -480,9 +480,9 @@ nsEditor::Do(nsITransaction *aTxn)
plcTxn->Init(mPlaceHolderName, mSelState, this);
mSelState = nsnull; // placeholder txn took ownership of this pointer
// finally we QI to an nsITransaction since that's what Do() expects
// finally we QI to an nsITransaction since that's what DoTransaction() expects
nsCOMPtr<nsITransaction> theTxn = do_QueryInterface(plcTxn);
Do(theTxn); // we will recurse, but will not hit this case in the nested call
DoTransaction(theTxn); // we will recurse, but will not hit this case in the nested call
if (mTxnMgr)
{
@ -509,6 +509,25 @@ nsEditor::Do(nsITransaction *aTxn)
if (aTxn)
{
// XXX: Why are we doing selection specific batching stuff here?
// XXX: Most entry points into the editor have auto variables that
// XXX: should trigger Begin/EndUpdateViewBatch() calls that will make
// XXX: these selection batch calls no-ops.
// XXX:
// XXX: I suspect that this was placed here to avoid multiple
// XXX: selection changed notifications from happening until after
// XXX: the transaction was done. I suppose that can still happen
// XXX: if an embedding application called DoTransaction() directly
// XXX: to pump its own transactions through the system, but in that
// XXX: case, wouldn't we want to use Begin/EndUpdateViewBatch() or
// XXX: its auto equivalent nsAutoUpdateViewBatch to ensure that
// XXX: selection listeners have access to accurate frame data?
// XXX:
// XXX: Note that if we did add Begin/EndUpdateViewBatch() calls
// XXX: we will need to make sure that they are disabled during
// XXX: the init of the editor for text widgets to avoid layout
// XXX: re-entry during initial reflow. - kin
// get the selection and start a batch change
nsCOMPtr<nsISelection>selection;
result = GetSelection(getter_AddRefs(selection));
@ -517,6 +536,7 @@ nsEditor::Do(nsITransaction *aTxn)
nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(selection));
selPrivate->StartBatchChanges();
if (mTxnMgr) {
result = mTxnMgr->DoTransaction(aTxn);
}
@ -526,7 +546,7 @@ nsEditor::Do(nsITransaction *aTxn)
if (NS_SUCCEEDED(result)) {
result = DoAfterDoTransaction(aTxn);
}
selPrivate->EndBatchChanges(); // no need to check result here, don't lose result of operation
}
@ -1051,7 +1071,7 @@ nsEditor::SetAttribute(nsIDOMElement *aElement, const nsAString & aAttribute, co
ChangeAttributeTxn *txn;
nsresult result = CreateTxnForSetAttribute(aElement, aAttribute, aValue, &txn);
if (NS_SUCCEEDED(result)) {
result = Do(txn);
result = DoTransaction(txn);
}
// The transaction system (if any) has taken ownwership of txn
NS_IF_RELEASE(txn);
@ -1087,7 +1107,7 @@ nsEditor::RemoveAttribute(nsIDOMElement *aElement, const nsAString& aAttribute)
ChangeAttributeTxn *txn;
nsresult result = CreateTxnForRemoveAttribute(aElement, aAttribute, &txn);
if (NS_SUCCEEDED(result)) {
result = Do(txn);
result = DoTransaction(txn);
}
// The transaction system (if any) has taken ownwership of txn
NS_IF_RELEASE(txn);
@ -1136,7 +1156,7 @@ NS_IMETHODIMP nsEditor::CreateNode(const nsAString& aTag,
nsresult result = CreateTxnForCreateElement(aTag, aParent, aPosition, &txn);
if (NS_SUCCEEDED(result))
{
result = Do(txn);
result = DoTransaction(txn);
if (NS_SUCCEEDED(result))
{
result = txn->GetNewNode(aNewNode);
@ -1183,7 +1203,7 @@ NS_IMETHODIMP nsEditor::InsertNode(nsIDOMNode * aNode,
InsertElementTxn *txn;
nsresult result = CreateTxnForInsertElement(aNode, aParent, aPosition, &txn);
if (NS_SUCCEEDED(result)) {
result = Do(txn);
result = DoTransaction(txn);
}
// The transaction system (if any) has taken ownwership of txn
NS_IF_RELEASE(txn);
@ -1227,7 +1247,7 @@ nsEditor::SplitNode(nsIDOMNode * aNode,
nsresult result = CreateTxnForSplitNode(aNode, aOffset, &txn);
if (NS_SUCCEEDED(result))
{
result = Do(txn);
result = DoTransaction(txn);
if (NS_SUCCEEDED(result))
{
result = txn->GetNewNode(aNewLeftNode);
@ -1288,7 +1308,7 @@ nsEditor::JoinNodes(nsIDOMNode * aLeftNode,
JoinElementTxn *txn;
result = CreateTxnForJoinNode(aLeftNode, aRightNode, &txn);
if (NS_SUCCEEDED(result)) {
result = Do(txn);
result = DoTransaction(txn);
}
// The transaction system (if any) has taken ownwership of txn
@ -1334,7 +1354,7 @@ NS_IMETHODIMP nsEditor::DeleteNode(nsIDOMNode * aElement)
DeleteElementTxn *txn;
result = CreateTxnForDeleteElement(aElement, &txn);
if (NS_SUCCEEDED(result)) {
result = Do(txn);
result = DoTransaction(txn);
}
// The transaction system (if any) has taken ownwership of txn
@ -2493,7 +2513,7 @@ NS_IMETHODIMP nsEditor::InsertTextIntoTextNodeImpl(const nsAString& aStringToIns
// XXX we may not need these view batches anymore. This is handled at a higher level now I believe
BeginUpdateViewBatch();
result = Do(txn);
result = DoTransaction(txn);
EndUpdateViewBatch();
mRangeUpdater.SelAdjInsertText(aTextNode, aOffset, aStringToInsert);
@ -2713,7 +2733,7 @@ NS_IMETHODIMP nsEditor::DeleteText(nsIDOMCharacterData *aElement,
}
}
result = Do(txn);
result = DoTransaction(txn);
// let listeners know what happened
if (mActionListeners)
@ -4300,85 +4320,96 @@ nsEditor::JoinNodeDeep(nsIDOMNode *aLeftNode,
nsresult nsEditor::BeginUpdateViewBatch()
{
NS_PRECONDITION(mUpdateCount>=0, "bad state");
NS_PRECONDITION(mUpdateCount >= 0, "bad state");
nsCOMPtr<nsISelection>selection;
nsresult rv = GetSelection(getter_AddRefs(selection));
if (NS_SUCCEEDED(rv) && selection)
{
nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(selection));
selPrivate->StartBatchChanges();
}
if (nsnull!=mViewManager)
if (0 == mUpdateCount)
{
if (0==mUpdateCount)
// Turn off selection updates and notifications.
nsCOMPtr<nsISelection> selection;
GetSelection(getter_AddRefs(selection));
if (selection)
{
mViewManager->BeginUpdateViewBatch();
nsCOMPtr<nsIPresShell> presShell;
rv = GetPresShell(getter_AddRefs(presShell));
if (NS_SUCCEEDED(rv) && presShell)
presShell->BeginReflowBatching();
nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(selection));
selPrivate->StartBatchChanges();
}
mUpdateCount++;
// Turn off view updating.
if (mViewManager)
mViewManager->BeginUpdateViewBatch();
// Turn off reflow.
nsCOMPtr<nsIPresShell> presShell;
GetPresShell(getter_AddRefs(presShell));
if (presShell)
presShell->BeginReflowBatching();
}
mUpdateCount++;
return NS_OK;
}
nsresult nsEditor::EndUpdateViewBatch()
{
NS_PRECONDITION(mUpdateCount>0, "bad state");
NS_PRECONDITION(mUpdateCount > 0, "bad state");
nsresult rv;
nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mSelConWeak,&rv);
if (NS_FAILED(rv))
return rv;
if (!selCon)
return NS_ERROR_FAILURE;
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
nsCOMPtr<nsICaret> caret;
if (!ps)
return NS_ERROR_FAILURE;
rv = ps->GetCaret(getter_AddRefs(caret));
if (NS_FAILED(rv))
return rv;
if (!caret)
return NS_ERROR_FAILURE;
if (mViewManager)
if (mUpdateCount <= 0)
{
mUpdateCount--;
if (0==mUpdateCount)
mUpdateCount = 0;
return NS_ERROR_FAILURE;
}
mUpdateCount--;
if (0 == mUpdateCount)
{
// Hide the caret with an StCaretHider. By the time it goes out
// of scope and tries to show the caret, reflow and selection changed
// notifications should've happened so the caret should have enough info
// to draw at the correct position.
nsCOMPtr<nsICaret> caret;
nsCOMPtr<nsIPresShell> presShell;
GetPresShell(getter_AddRefs(presShell));
if (presShell)
presShell->GetCaret(getter_AddRefs(caret));
StCaretHider caretHider(caret);
PRUint32 flags = 0;
GetFlags(&flags);
// Turn reflow back on.
//
// Make sure we enable reflowing before we call
// mViewManager->EndUpdateViewBatch(). This will make sure that any
// new updates caused by a reflow, that may happen during the
// EndReflowBatching(), get included if we force a refresh during
// the mViewManager->EndUpdateViewBatch() call.
if (presShell)
{
PRUint32 flags = 0;
rv = GetFlags(&flags);
if (NS_FAILED(rv))
return rv;
StCaretHider caretHider(caret);
// Make sure we enable reflowing before we call
// mViewManager->EndUpdateViewBatch(). This will make sure that any
// new updates caused by a reflow, that may happen during the
// EndReflowBatching(), get included if we force a refresh during
// the mViewManager->EndUpdateViewBatch() call.
PRBool forceReflow = PR_TRUE;
if (flags & nsIPlaintextEditor::eEditorUseAsyncUpdatesMask)
forceReflow = PR_FALSE;
nsCOMPtr<nsIPresShell> presShell;
rv = GetPresShell(getter_AddRefs(presShell));
if (NS_SUCCEEDED(rv) && presShell)
presShell->EndReflowBatching(forceReflow);
presShell->EndReflowBatching(forceReflow);
}
// Turn view updating back on.
if (mViewManager)
{
PRUint32 updateFlag = NS_VMREFRESH_IMMEDIATE;
if (flags & nsIPlaintextEditor::eEditorUseAsyncUpdatesMask)
@ -4386,13 +4417,16 @@ nsresult nsEditor::EndUpdateViewBatch()
mViewManager->EndUpdateViewBatch(updateFlag);
}
}
nsCOMPtr<nsISelection>selection;
nsresult selectionResult = GetSelection(getter_AddRefs(selection));
if (NS_SUCCEEDED(selectionResult) && selection) {
nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(selection));
selPrivate->EndBatchChanges();
// Turn selection updating and notifications back on.
nsCOMPtr<nsISelection>selection;
GetSelection(getter_AddRefs(selection));
if (selection) {
nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(selection));
selPrivate->EndBatchChanges();
}
}
return NS_OK;
@ -4439,7 +4473,7 @@ nsEditor::DeleteSelectionImpl(nsIEditor::EDirection aAction)
}
}
res = Do(txn);
res = DoTransaction(txn);
if (mActionListeners)
{

Просмотреть файл

@ -470,7 +470,7 @@ nsHTMLCSSUtils::SetCSSProperty(nsIDOMElement *aElement, nsIAtom * aProperty, con
result = txn->DoTransaction();
}
else {
result = mHTMLEditor->Do(txn);
result = mHTMLEditor->DoTransaction(txn);
}
}
// The transaction system (if any) has taken ownwership of txn
@ -492,7 +492,7 @@ nsHTMLCSSUtils::RemoveCSSProperty(nsIDOMElement *aElement, nsIAtom * aProperty,
result = txn->DoTransaction();
}
else {
result = mHTMLEditor->Do(txn);
result = mHTMLEditor->DoTransaction(txn);
}
}
// The transaction system (if any) has taken ownwership of txn

Просмотреть файл

@ -768,7 +768,7 @@ nsHTMLEditor::SetDocumentTitle(const nsAString &aTitle)
//Don't let Rules System change the selection
nsAutoTxnsConserveSelection dontChangeSelection(this);
result = nsEditor::Do(txn);
result = nsEditor::DoTransaction(txn);
}
// The transaction system (if any) has taken ownwership of txn
NS_IF_RELEASE(txn);
@ -3614,7 +3614,7 @@ nsHTMLEditor::RemoveStyleSheet(const nsAString &aURL)
if (!txn) rv = NS_ERROR_NULL_POINTER;
if (NS_SUCCEEDED(rv))
{
rv = Do(txn);
rv = DoTransaction(txn);
if (NS_SUCCEEDED(rv))
mLastStyleSheetURL.Truncate(); // forget it
@ -4293,7 +4293,7 @@ nsHTMLEditor::StyleSheetLoaded(nsICSSStyleSheet* aSheet, PRBool aNotify)
if (!txn) rv = NS_ERROR_NULL_POINTER;
if (NS_SUCCEEDED(rv))
{
rv = Do(txn);
rv = DoTransaction(txn);
if (NS_SUCCEEDED(rv))
{
// Get the URI, then url spec from the sheet

Просмотреть файл

@ -1189,7 +1189,7 @@ nsTextEditRules::ReplaceNewlines(nsIDOMRange *aRange)
res = mEditor->CreateTxnForDeleteText(textNode, offset, 1, (DeleteTextTxn**)&txn);
if (NS_FAILED(res)) return res;
if (!txn) return NS_ERROR_OUT_OF_MEMORY;
res = mEditor->Do(txn);
res = mEditor->DoTransaction(txn);
if (NS_FAILED(res)) return res;
// The transaction system (if any) has taken ownwership of txn
NS_IF_RELEASE(txn);

Просмотреть файл

@ -443,11 +443,127 @@ function PrintTxnList(txnList, prefixStr)
if (txn)
{
txn = txn.QueryInterface(Components.interfaces.nsPIEditorTransaction);
desc = txn.txnDescription;
try {
txn = txn.QueryInterface(Components.interfaces.nsPIEditorTransaction);
desc = txn.txnDescription;
} catch(e) {
desc = "UnknownTxnType";
}
}
dump(prefixStr + "+ " + desc + "\n");
PrintTxnList(txnList.getChildListForItem(i), prefixStr + "| ");
}
}
// ------------------------ 3rd Party Transaction Test ------------------------
function sampleJSTransaction()
{
this.wrappedJSObject = this;
}
sampleJSTransaction.prototype = {
isTransient: false,
mStrData: "[Sample-JS-Transaction-Content]",
mObject: null,
mContainer: null,
mOffset: null,
doTransaction: function()
{
if (this.mContainer.nodeName != "#text")
{
// We're not in a text node, so create one and
// we'll just insert it at (mContainer, mOffset).
this.mObject = this.mContainer.ownerDocument.createTextNode(this.mStrData);
}
this.redoTransaction();
},
undoTransaction: function()
{
if (!this.mObject)
this.mContainer.deleteData(this.mOffset, this.mStrData.length);
else
this.mContainer.removeChild(this.mObject);
},
redoTransaction: function()
{
if (!this.mObject)
this.mContainer.insertData(this.mOffset, this.mStrData);
else
this.insert_node_at_point(this.mObject, this.mContainer, this.mOffset);
},
merge: function(aTxn)
{
// We don't do any merging!
return false;
},
QueryInterface: function(theUID, theResult)
{
if (theUID == Components.interfaces.nsITransaction ||
theUID == Components.interfaces.nsISupports)
return this;
return nsnull;
},
insert_node_at_point: function(node, container, offset)
{
var childList = container.childNodes;
if (childList.length == 0 || offset >= childList.length)
container.appendChild(node);
else
container.insertBefore(node, childList.item(offset));
}
}
function ExecuteJSTransactionViaTxmgr()
{
try {
var editor = GetCurrentEditor();
var txmgr = editor.transactionManager;
txmgr = txmgr.QueryInterface(Components.interfaces.nsITransactionManager);
var selection = editor.selection;
var range = selection.getRangeAt(0);
var txn = new sampleJSTransaction();
txn.mContainer = range.startContainer;
txn.mOffset = range.startOffset;
txmgr.doTransaction(txn);
} catch (e) {
dump("ExecuteJSTransactionViaTxmgr() failed!");
}
}
function ExecuteJSTransactionViaEditor()
{
try {
var editor = GetCurrentEditor();
var selection = editor.selection;
var range = selection.getRangeAt(0);
var txn = new sampleJSTransaction();
txn.mContainer = range.startContainer;
txn.mOffset = range.startOffset;
editor.doTransaction(txn);
} catch (e) {
dump("ExecuteJSTransactionViaEditor() failed!");
}
}

Просмотреть файл

@ -913,6 +913,10 @@
oncommand="DumpUndoStack()"/>
<menuitem label="&dumpRedoStack.label;"
oncommand="DumpRedoStack()"/>
<menuitem label="&executeJSTransactionViaTxmgr.label;"
oncommand="ExecuteJSTransactionViaTxmgr()"/>
<menuitem label="&executeJSTransactionViaEditor.label;"
oncommand="ExecuteJSTransactionViaEditor()"/>
<menuseparator />
<menuitem label="&startLogCmd.label;"
oncommand="EditorStartLog()"/>

Просмотреть файл

@ -482,6 +482,8 @@
<!ENTITY runUnitTestsCmd.label "Run Unit Tests">
<!ENTITY dumpUndoStack.label "Dump Undo Stack">
<!ENTITY dumpRedoStack.label "Dump Redo Stack">
<!ENTITY executeJSTransactionViaTxmgr.label "Execute JS Transaction Via Transaction Manager">
<!ENTITY executeJSTransactionViaEditor.label "Execute JS Transaction Via Editor">
<!ENTITY startLogCmd.label "Start Log">
<!ENTITY stopLogCmd.label "Stop Log">
<!ENTITY runLogCmd.label "Run Log">