diff --git a/mozilla/editor/idl/nsIEditor.idl b/mozilla/editor/idl/nsIEditor.idl index 4650fe7993b..4a1a091e2ba 100644 --- a/mozilla/editor/idl/nsIEditor.idl +++ b/mozilla/editor/idl/nsIEditor.idl @@ -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 diff --git a/mozilla/editor/libeditor/base/PlaceholderTxn.cpp b/mozilla/editor/libeditor/base/PlaceholderTxn.cpp index 40e20aad136..ee1f18e9ed6 100644 --- a/mozilla/editor/libeditor/base/PlaceholderTxn.cpp +++ b/mozilla/editor/libeditor/base/PlaceholderTxn.cpp @@ -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 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 plcTxn;// = do_QueryInterface(editTxn); diff --git a/mozilla/editor/libeditor/base/nsEditor.cpp b/mozilla/editor/libeditor/base/nsEditor.cpp index a0aaff226fe..3796338ba01 100644 --- a/mozilla/editor/libeditor/base/nsEditor.cpp +++ b/mozilla/editor/libeditor/base/nsEditor.cpp @@ -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 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 nsCOMPtrselection; result = GetSelection(getter_AddRefs(selection)); @@ -517,6 +536,7 @@ nsEditor::Do(nsITransaction *aTxn) nsCOMPtrselPrivate(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"); - nsCOMPtrselection; - nsresult rv = GetSelection(getter_AddRefs(selection)); - if (NS_SUCCEEDED(rv) && selection) - { - nsCOMPtrselPrivate(do_QueryInterface(selection)); - selPrivate->StartBatchChanges(); - } - if (nsnull!=mViewManager) + if (0 == mUpdateCount) { - if (0==mUpdateCount) + // Turn off selection updates and notifications. + + nsCOMPtr selection; + GetSelection(getter_AddRefs(selection)); + + if (selection) { - mViewManager->BeginUpdateViewBatch(); - nsCOMPtr presShell; - rv = GetPresShell(getter_AddRefs(presShell)); - if (NS_SUCCEEDED(rv) && presShell) - presShell->BeginReflowBatching(); + nsCOMPtr selPrivate(do_QueryInterface(selection)); + selPrivate->StartBatchChanges(); } - mUpdateCount++; + + // Turn off view updating. + + if (mViewManager) + mViewManager->BeginUpdateViewBatch(); + + // Turn off reflow. + + nsCOMPtr 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 selCon = do_QueryReferent(mSelConWeak,&rv); - if (NS_FAILED(rv)) - return rv; - if (!selCon) - return NS_ERROR_FAILURE; - - nsCOMPtr ps = do_QueryReferent(mPresShellWeak); - nsCOMPtr 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 caret; + nsCOMPtr 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 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); } - } - nsCOMPtrselection; - nsresult selectionResult = GetSelection(getter_AddRefs(selection)); - if (NS_SUCCEEDED(selectionResult) && selection) { - nsCOMPtrselPrivate(do_QueryInterface(selection)); - selPrivate->EndBatchChanges(); + // Turn selection updating and notifications back on. + + nsCOMPtrselection; + GetSelection(getter_AddRefs(selection)); + + if (selection) { + nsCOMPtrselPrivate(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) { diff --git a/mozilla/editor/libeditor/html/nsHTMLCSSUtils.cpp b/mozilla/editor/libeditor/html/nsHTMLCSSUtils.cpp index baf7a3c90f1..09ff300f6db 100644 --- a/mozilla/editor/libeditor/html/nsHTMLCSSUtils.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLCSSUtils.cpp @@ -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 diff --git a/mozilla/editor/libeditor/html/nsHTMLEditor.cpp b/mozilla/editor/libeditor/html/nsHTMLEditor.cpp index 374d74464c4..02d6614aafe 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditor.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLEditor.cpp @@ -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 diff --git a/mozilla/editor/libeditor/text/nsTextEditRules.cpp b/mozilla/editor/libeditor/text/nsTextEditRules.cpp index 9d0db34d622..fd605744b21 100644 --- a/mozilla/editor/libeditor/text/nsTextEditRules.cpp +++ b/mozilla/editor/libeditor/text/nsTextEditRules.cpp @@ -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); diff --git a/mozilla/editor/ui/composer/content/EditorCommandsDebug.js b/mozilla/editor/ui/composer/content/EditorCommandsDebug.js index 16a47a1d12f..e75be25337d 100644 --- a/mozilla/editor/ui/composer/content/EditorCommandsDebug.js +++ b/mozilla/editor/ui/composer/content/EditorCommandsDebug.js @@ -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!"); + } +} + diff --git a/mozilla/editor/ui/composer/content/editorOverlay.xul b/mozilla/editor/ui/composer/content/editorOverlay.xul index c2992952eeb..af9d9bb3961 100644 --- a/mozilla/editor/ui/composer/content/editorOverlay.xul +++ b/mozilla/editor/ui/composer/content/editorOverlay.xul @@ -913,6 +913,10 @@ oncommand="DumpUndoStack()"/> + + diff --git a/mozilla/editor/ui/composer/locale/en-US/editorOverlay.dtd b/mozilla/editor/ui/composer/locale/en-US/editorOverlay.dtd index 4d149f8abe3..a45dbc85255 100644 --- a/mozilla/editor/ui/composer/locale/en-US/editorOverlay.dtd +++ b/mozilla/editor/ui/composer/locale/en-US/editorOverlay.dtd @@ -482,6 +482,8 @@ + +