Bug 617532 - Implement UndoManager. r=ehsan

This commit is contained in:
William Chen 2013-01-03 22:54:26 -08:00
parent ba821b29a2
commit 7cd5470029
42 changed files with 2684 additions and 101 deletions

View File

@ -64,7 +64,7 @@ var StarUI = {
this._restoreCommandsState();
this._itemId = -1;
if (this._batching) {
PlacesUtils.transactionManager.endBatch();
PlacesUtils.transactionManager.endBatch(false);
this._batching = false;
}
@ -76,13 +76,13 @@ var StarUI = {
case "remove": {
// Remove all bookmarks for the bookmark's url, this also removes
// the tags for the url.
PlacesUtils.transactionManager.beginBatch();
PlacesUtils.transactionManager.beginBatch(null);
let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
for (let i = 0; i < itemIds.length; i++) {
let txn = new PlacesRemoveItemTransaction(itemIds[i]);
PlacesUtils.transactionManager.doTransaction(txn);
}
PlacesUtils.transactionManager.endBatch();
PlacesUtils.transactionManager.endBatch(false);
break;
}
}
@ -235,7 +235,7 @@ var StarUI = {
beginBatch: function SU_beginBatch() {
if (!this._batching) {
PlacesUtils.transactionManager.beginBatch();
PlacesUtils.transactionManager.beginBatch(null);
this._batching = true;
}
}

View File

@ -405,7 +405,7 @@ var BookmarkPropertiesPanel = {
if (this._batching)
return;
PlacesUtils.transactionManager.beginBatch();
PlacesUtils.transactionManager.beginBatch(null);
this._batching = true;
},
@ -413,7 +413,7 @@ var BookmarkPropertiesPanel = {
if (!this._batching)
return;
PlacesUtils.transactionManager.endBatch();
PlacesUtils.transactionManager.endBatch(false);
this._batching = false;
},

View File

@ -1153,10 +1153,10 @@ XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ptm", function() {
//// nsITransactionManager forwarders.
beginBatch: function()
PlacesUtils.transactionManager.beginBatch(),
PlacesUtils.transactionManager.beginBatch(null),
endBatch: function()
PlacesUtils.transactionManager.endBatch(),
PlacesUtils.transactionManager.endBatch(false),
doTransaction: function(txn)
PlacesUtils.transactionManager.doTransaction(txn),

View File

@ -407,6 +407,7 @@ user_pref("app.update.enabled", false);
user_pref("app.update.staging.enabled", false);
user_pref("browser.panorama.experienced_first_run", true); // Assume experienced
user_pref("dom.w3c_touch_events.enabled", 1);
user_pref("dom.undo_manager.enabled", true);
// Set a future policy version to avoid the telemetry prompt.
user_pref("toolkit.telemetry.prompted", 999);
user_pref("toolkit.telemetry.notifiedOptOut", 999);

View File

@ -116,6 +116,7 @@ namespace mozilla {
namespace dom {
class Link;
class UndoManager;
// IID for the dom::Element interface
#define NS_ELEMENT_IID \
@ -695,6 +696,21 @@ public:
nsPresContext::AppUnitsToIntCSSPixels(sf->GetScrollRange().XMost()) :
0;
}
virtual already_AddRefed<mozilla::dom::UndoManager> GetUndoManager()
{
return nullptr;
}
virtual bool UndoScope()
{
return false;
}
virtual void SetUndoScope(bool aUndoScope, mozilla::ErrorResult& aError)
{
}
virtual void GetInnerHTML(nsAString& aInnerHTML,
mozilla::ErrorResult& aError);
virtual void SetInnerHTML(const nsAString& aInnerHTML,

View File

@ -166,6 +166,8 @@ class nsInlineEventHandlersTearoff;
namespace mozilla {
namespace dom {
class UndoManager;
class FragmentOrElement : public nsIContent
{
public:
@ -312,6 +314,12 @@ public:
*/
nsDOMStringMap* mDataset; // [Weak]
/**
* The .undoManager property.
* @see nsGenericHTMLElement::GetUndoManager
*/
nsRefPtr<UndoManager> mUndoManager;
/**
* SMIL Overridde style rules (for SMIL animation of CSS properties)
* @see nsIContent::GetSMILOverrideStyle

View File

@ -90,6 +90,7 @@ class DocumentType;
class DOMImplementation;
class Element;
class Link;
class UndoManager;
template<typename> class Sequence;
} // namespace dom
} // namespace mozilla
@ -1647,6 +1648,8 @@ public:
*/
virtual Element* LookupImageElement(const nsAString& aElementId) = 0;
virtual already_AddRefed<mozilla::dom::UndoManager> GetUndoManager() = 0;
nsresult ScheduleFrameRequestCallback(nsIFrameRequestCallback* aCallback,
int32_t *aHandle);
void CancelFrameRequestCallback(int32_t aHandle);

View File

@ -101,6 +101,7 @@
#include "nsAsyncDOMEvent.h"
#include "nsTextNode.h"
#include "mozilla/dom/NodeListBinding.h"
#include "mozilla/dom/UndoManager.h"
#ifdef MOZ_XUL
#include "nsIXULDocument.h"
@ -539,6 +540,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(nsInlineEventHandlersTearoff)
FragmentOrElement::nsDOMSlots::nsDOMSlots()
: nsINode::nsSlots(),
mDataset(nullptr),
mUndoManager(nullptr),
mBindingParent(nullptr)
{
}
@ -566,6 +568,9 @@ FragmentOrElement::nsDOMSlots::Traverse(nsCycleCollectionTraversalCallback &cb,
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mAttributeMap");
cb.NoteXPCOMChild(mAttributeMap.get());
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mUndoManager");
cb.NoteXPCOMChild(mUndoManager.get());
if (aIsXUL) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mControllers");
cb.NoteXPCOMChild(mControllers);
@ -590,6 +595,7 @@ FragmentOrElement::nsDOMSlots::Unlink(bool aIsXUL)
if (aIsXUL)
NS_IF_RELEASE(mControllers);
mChildrenList = nullptr;
mUndoManager = nullptr;
if (mClassList) {
mClassList->DropReference();
mClassList = nullptr;

View File

@ -184,6 +184,7 @@
#include "nsIAppsService.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/UndoManager.h"
#include "nsFrame.h"
#include "nsDOMCaretPosition.h"
#include "nsIDOMHTMLTextAreaElement.h"
@ -1679,6 +1680,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStateObjectCached)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUndoManager)
// Traverse all our nsCOMArrays.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets)
@ -1736,6 +1738,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mUndoManager)
tmp->mParentDocument = nullptr;
@ -2640,6 +2643,22 @@ nsDocument::GetAllowPlugins(bool * aAllowPlugins)
return NS_OK;
}
already_AddRefed<UndoManager>
nsDocument::GetUndoManager()
{
Element* rootElement = GetRootElement();
if (!rootElement) {
return nullptr;
}
if (!mUndoManager) {
mUndoManager = new UndoManager(rootElement);
}
nsRefPtr<UndoManager> undoManager = mUndoManager;
return undoManager.forget();
}
/* Return true if the document is in the focused top-level window, and is an
* ancestor of the focused DOMWindow. */
NS_IMETHODIMP

View File

@ -94,6 +94,12 @@ class nsDOMNavigationTiming;
class nsWindowSizes;
class nsHtml5TreeOpExecutor;
namespace mozilla {
namespace dom {
class UndoManager;
}
}
/**
* Right now our identifier map entries contain information for 'name'
* and 'id' mappings of a given string. This is so that
@ -569,6 +575,8 @@ public:
virtual nsresult GetAllowPlugins(bool* aAllowPlugins);
virtual already_AddRefed<mozilla::dom::UndoManager> GetUndoManager();
virtual nsresult SetSubDocumentFor(Element* aContent,
nsIDocument* aSubDoc);
virtual nsIDocument* GetSubDocumentFor(nsIContent* aContent) const;
@ -1349,6 +1357,8 @@ private:
// Tracking for plugins in the document.
nsTHashtable< nsPtrHashKey<nsIObjectLoadingContent> > mPlugins;
nsRefPtr<mozilla::dom::UndoManager> mUndoManager;
#ifdef DEBUG
protected:
bool mWillReparent;

View File

@ -1090,6 +1090,7 @@ GK_ATOM(u, "u")
GK_ATOM(ul, "ul")
GK_ATOM(underflow, "underflow")
GK_ATOM(undetermined, "undetermined")
GK_ATOM(undoscope, "undoscope")
GK_ATOM(unload, "unload")
GK_ATOM(unparsedEntityUri, "unparsed-entity-uri")
GK_ATOM(upperFirst, "upper-first")

View File

@ -881,6 +881,8 @@ nsEventDispatcher::CreateEvent(nsPresContext* aPresContext,
return NS_NewDOMBeforeUnloadEvent(aDOMEvent, aPresContext, nullptr);
if (aEventType.LowerCaseEqualsLiteral("pagetransition"))
return NS_NewDOMPageTransitionEvent(aDOMEvent, aPresContext, nullptr);
if (aEventType.LowerCaseEqualsLiteral("domtransaction"))
return NS_NewDOMDOMTransactionEvent(aDOMEvent, aPresContext, nullptr);
if (aEventType.LowerCaseEqualsLiteral("scrollareaevent"))
return NS_NewDOMScrollAreaEvent(aDOMEvent, aPresContext, nullptr);
// FIXME: Should get spec to say what the right string is here! This

View File

@ -45,7 +45,9 @@ EXPORTS_mozilla/dom = \
HTMLSharedListElement.h \
HTMLSpanElement.h \
HTMLTitleElement.h \
HTMLUnknownElement.h
HTMLUnknownElement.h \
UndoManager.h \
$(NULL)
CPPSRCS = \
HTMLPropertiesCollection.cpp \
@ -111,6 +113,7 @@ CPPSRCS = \
nsIConstraintValidation.cpp \
nsRadioVisitor.cpp \
nsDOMStringMap.cpp \
UndoManager.cpp \
$(NULL)
ifdef MOZ_MEDIA

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,104 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_UndoManager_h
#define mozilla_dom_UndoManager_h
#include "mozilla/dom/UndoManagerBinding.h"
#include "nsCycleCollectionParticipant.h"
#include "nsCOMPtr.h"
#include "nsCOMArray.h"
#include "nsTArray.h"
#include "nsWrapperCache.h"
#include "mozilla/dom/Nullable.h"
#include "nsIContent.h"
class nsIUndoManagerTransaction;
class nsITransactionManager;
class nsIMutationObserver;
namespace mozilla {
class ErrorResult;
namespace dom {
class UndoManager : public nsISupports,
public nsWrapperCache
{
friend class TxnScopeGuard;
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(UndoManager)
explicit UndoManager(nsIContent* aNode);
void Transact(JSContext* aCx, nsIUndoManagerTransaction& aTransaction,
bool aMerge, ErrorResult& aRv);
void Undo(JSContext* aCx, ErrorResult& aRv);
void Redo(JSContext* acx, ErrorResult& aRv);
void Item(uint32_t aIndex,
Nullable<nsTArray<nsRefPtr<nsIUndoManagerTransaction>>>& aItems,
ErrorResult& aRv);
uint32_t GetLength(ErrorResult& aRv);
uint32_t GetPosition(ErrorResult& aRv);
void ClearUndo(ErrorResult& aRv);
void ClearRedo(ErrorResult& aRv);
static bool PrefEnabled();
void Disconnect();
nsISupports* GetParentObject() const
{
return mHostNode;
}
JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap)
{
return mozilla::dom::UndoManagerBinding::Wrap(aCx, aScope, this,
aTriedToWrap);
}
nsITransactionManager* GetTransactionManager();
protected:
virtual ~UndoManager();
nsCOMPtr<nsITransactionManager> mTxnManager;
nsCOMPtr<nsIContent> mHostNode;
/**
* Executes |aTransaction| as a manual transaction.
*/
void ManualTransact(nsIUndoManagerTransaction* aTransaction,
ErrorResult& aRv);
/**
* Executes |aTransaction| as an automatic transaction.
*/
void AutomaticTransact(nsIUndoManagerTransaction* aTransaction,
ErrorResult& aRv);
/**
* Appends the transactions in the undo transaction history at |aIndex|
* to the array |aItems|.
*/
void ItemInternal(uint32_t aIndex,
nsTArray<nsIUndoManagerTransaction*>& aItems,
ErrorResult& aRv);
/**
* Dispatches an event to the undo scope host.
* @param aPreviousPosition The index of the transaction that
* triggered the event.
*/
void DispatchTransactionEvent(JSContext* aCx, const nsAString& aType,
uint32_t aPreviousPosition,
ErrorResult& aRv);
bool mInTransaction;
bool mIsDisconnected;
};
} // namespace dom
} // namespace mozilla
#endif

View File

@ -87,6 +87,7 @@
#include "nsDOMMutationObserver.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/FromParser.h"
#include "mozilla/dom/UndoManager.h"
#include "mozilla/BloomFilter.h"
#include "HTMLPropertiesCollection.h"
@ -981,6 +982,8 @@ nsGenericHTMLElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
{
bool contentEditable = aNameSpaceID == kNameSpaceID_None &&
aName == nsGkAtoms::contenteditable;
bool undoScope = aNameSpaceID == kNameSpaceID_None &&
aName == nsGkAtoms::undoscope;
bool accessKey = aName == nsGkAtoms::accesskey &&
aNameSpaceID == kNameSpaceID_None;
@ -1006,6 +1009,11 @@ nsGenericHTMLElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
ChangeEditableState(change);
}
if (undoScope) {
rv = SetUndoScopeInternal(true);
NS_ENSURE_SUCCESS(rv, rv);
}
if (accessKey && !aValue.IsEmpty()) {
SetFlags(NODE_HAS_ACCESSKEY);
RegAccessKey();
@ -1032,6 +1040,10 @@ nsGenericHTMLElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
contentEditable = true;
contentEditableChange = GetContentEditableValue() == eTrue ? -1 : 0;
}
else if (aAttribute == nsGkAtoms::undoscope) {
nsresult rv = SetUndoScopeInternal(false);
NS_ENSURE_SUCCESS(rv, rv);
}
else if (aAttribute == nsGkAtoms::accesskey) {
// Have to unregister before clearing flag. See UnregAccessKey
UnregAccessKey();
@ -2065,6 +2077,78 @@ nsGenericHTMLElement::IsLabelable() const
Tag() == nsGkAtoms::meter;
}
already_AddRefed<UndoManager>
nsGenericHTMLElement::GetUndoManager()
{
nsDOMSlots* slots = GetExistingDOMSlots();
if (slots && slots->mUndoManager) {
nsRefPtr<UndoManager> undoManager = slots->mUndoManager;
return undoManager.forget();
} else {
return nullptr;
}
}
bool
nsGenericHTMLElement::UndoScope()
{
nsDOMSlots* slots = GetExistingDOMSlots();
return slots && slots->mUndoManager;
}
void
nsGenericHTMLElement::SetUndoScope(bool aUndoScope, mozilla::ErrorResult& aError)
{
nsresult rv = SetUndoScopeInternal(aUndoScope);
if (NS_FAILED(rv)) {
aError.Throw(rv);
return;
}
// The undoScope property must reflect the undoscope boolean attribute.
if (aUndoScope) {
rv = SetAttr(kNameSpaceID_None, nsGkAtoms::undoscope,
NS_LITERAL_STRING(""), true);
} else {
rv = UnsetAttr(kNameSpaceID_None, nsGkAtoms::undoscope, true);
}
if (NS_FAILED(rv)) {
aError.Throw(rv);
return;
}
}
nsresult
nsGenericHTMLElement::SetUndoScopeInternal(bool aUndoScope)
{
if (aUndoScope) {
nsDOMSlots* slots = DOMSlots();
if (!slots->mUndoManager) {
slots->mUndoManager = new UndoManager(this);
}
} else {
nsDOMSlots* slots = GetExistingDOMSlots();
if (slots && slots->mUndoManager) {
// Clear transaction history and disconnect.
ErrorResult rv;
slots->mUndoManager->ClearRedo(rv);
if (rv.Failed()) {
return rv.ErrorCode();
}
slots->mUndoManager->ClearUndo(rv);
if (rv.Failed()) {
return rv.ErrorCode();
}
slots->mUndoManager->Disconnect();
slots->mUndoManager = nullptr;
}
}
return NS_OK;
}
// static
bool
nsGenericHTMLElement::PrefEnabled()

View File

@ -323,6 +323,9 @@ protected:
nsresult SetTokenList(nsIAtom* aAtom, nsIVariant* aValue);
public:
nsresult SetContentEditable(const nsAString &aContentEditable);
virtual already_AddRefed<mozilla::dom::UndoManager> GetUndoManager();
virtual bool UndoScope();
virtual void SetUndoScope(bool aUndoScope, mozilla::ErrorResult& aError);
nsresult GetDataset(nsISupports** aDataset);
// Callback for destructor of of dataset to ensure to null out weak pointer.
nsresult ClearDataset();
@ -1022,6 +1025,8 @@ protected:
*/
bool IsEditableRoot() const;
nsresult SetUndoScopeInternal(bool aUndoScope);
private:
void ChangeEditableState(int32_t aChange);
};

View File

@ -242,6 +242,7 @@ MOCHITEST_FILES = \
test_bug677463.html \
test_bug682886.html \
test_bug717819.html \
test_undoManager.html \
file_fullscreen-utils.js \
file_fullscreen-api.html \
file_fullscreen-api-keys.html \

View File

@ -0,0 +1,852 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=617532
-->
<head>
<title>Test for Bug 617532</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=617532">Mozilla Bug 617532</a>
<p id="display"></p>
<div id="content"></div>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Bug 617532 **/
/**
* Tests for typical use cases.
*/
var content = document.getElementById("content");
// Test appending text to a text node.
var textAppend = {
label: "textAppend",
executeAutomatic: function() {
var box = document.getElementById("box");
box.firstChild.appendData(" and more text");
}
};
testTransaction([textAppend],
[getTextMatchFunction("box with text"),
getTextMatchFunction("box with text and more text")]);
// Test replacing text in a text node.
var textReplace = {
label: "textReplace",
executeAutomatic: function() {
var box = document.getElementById("box");
box.firstChild.replaceData(0, 3, "div");
}
};
testTransaction([textReplace],
[getTextMatchFunction("box with text"),
getTextMatchFunction("div with text")]);
// Test inserting text in a text node.
var textInsert = {
label: "textInsert",
executeAutomatic: function() {
var box = document.getElementById("box");
box.firstChild.insertData(0, "a ");
}
};
testTransaction([textInsert],
[getTextMatchFunction("box with text"),
getTextMatchFunction("a box with text")]);
// Test deleting text in a text node.
var textDelete = {
label: "textDelete",
executeAutomatic: function() {
var box = document.getElementById("box");
box.firstChild.deleteData(3, 10);
}
};
testTransaction([textDelete],
[getTextMatchFunction("box with text"),
getTextMatchFunction("box")]);
// Test creating a new attribute.
var createAttribute = {
label: "createAttribute",
executeAutomatic: function() {
var box = document.getElementById("box");
box.setAttribute("data-new", "new");
}
};
testTransaction([createAttribute],
[getAttrAbsentFunction("data-new"),
getAttrIsFunction("data-new", "new")]);
// Test changing an attribute.
var changeAttribute = {
label: "changeAttribute",
executeAutomatic: function() {
var box = document.getElementById("box");
box.setAttribute("data-test", "change");
}
};
testTransaction([changeAttribute],
[getAttrIsFunction("data-test", "test"),
getAttrIsFunction("data-test", "change")]);
// Test remove an attribute.
var removeAttribute = {
label: "removeAttribute",
executeAutomatic: function() {
var box = document.getElementById("box");
box.removeAttribute("data-test");
}
};
testTransaction([removeAttribute],
[getAttrIsFunction("data-test", "test"),
getAttrAbsentFunction("data-test")]);
// Test remove an attribute.
var removeAttribute = {
label: "removeAttribute",
executeAutomatic: function() {
var box = document.getElementById("box");
box.removeAttribute("data-test");
}
};
testTransaction([removeAttribute],
[getAttrIsFunction("data-test", "test"),
getAttrAbsentFunction("data-test")]);
// Test appending child element to box.
var appendChild = {
label: "appendChild",
executeAutomatic: function() {
var box = document.getElementById("box");
box.appendChild(document.createElement("div"));
}
};
testTransaction([appendChild],
[getChildCountFunction(1),
getChildCountFunction(2)]);
// Test appending document fragment with multiple elements to box.
var appendFragment = {
label: "appendFragment",
executeAutomatic: function() {
var box = document.getElementById("box");
var fragment = document.createDocumentFragment();
fragment.appendChild(document.createElement("div"));
fragment.appendChild(document.createElement("div"));
fragment.appendChild(document.createElement("div"));
box.appendChild(fragment);
}
};
testTransaction([appendFragment],
[getChildCountFunction(1),
getChildCountFunction(4)]);
// Test appending a child then removing a child.
var removeChild = {
label: "removeChild",
executeAutomatic: function() {
var box = document.getElementById("box");
box.removeChild(box.firstChild);
}
};
testTransaction([removeChild],
[getChildCountFunction(1),
getChildCountFunction(0)]);
// Test appending a child then removing a child.
var applyInnerHTML = {
label: "applyInnerHTML",
executeAutomatic: function() {
var box = document.getElementById("box");
box.innerHTML = "<div>one</div><div>two</div><div>three</div>";
}
};
testTransaction([applyInnerHTML],
[getChildCountFunction(1),
getChildCountFunction(3)]);
// Test multiple append children and multiple remove children.
testTransaction([appendChild, appendChild, appendChild, appendChild,
removeChild, removeChild, removeChild],
[getChildCountFunction(1),
getChildCountFunction(2),
getChildCountFunction(3),
getChildCountFunction(4),
getChildCountFunction(5),
getChildCountFunction(4),
getChildCountFunction(3),
getChildCountFunction(2)]);
/**
* Creates a div element containing the text "box with text".
* Has an id of "box". Has an attribute "data-test" with the value of "test".
*/
function createTextBoxElement() {
var box = document.createElement("div");
box.setAttribute("id", "box");
box.setAttribute("data-test", "test");
box.textContent = "box with text";
return box;
}
function getTextMatchFunction(matchText) {
return function(box) {
is(box.textContent, matchText, "Text content did not match expected content");
}
}
function getAttrAbsentFunction(attrName) {
return function(box) {
ok(!box.hasAttribute(attrName), "Element should not have attribute");
}
}
function getAttrIsFunction(attrName, attrValue) {
return function(box) {
is(box.getAttribute(attrName), attrValue, "Element attribute is not the expected value");
}
}
function getChildCountFunction(count) {
return function(box) {
is(box.childNodes.length, count, "Element has incorrect number of children");
}
}
/**
* Test transactions by applying, undoing and redoing a sequence of transactions.
* At each step the state of the host node is evaluated for the expected state.
* @param transactions An array of transactions to apply.
* @param evaluators An array of functions which take an element as its only argument
* and evaluates the state of the element for correctness. The
* nth transaction in the transactions array is evaluated by the
* n+1th function in the evaluators array. The first element of
* the evaluators array evaluates the initial state of the host
* node. Thus there should always be one more element in the
* evaluators array than the transactions array.
* @param merges An optional set of integers indicating which indices in the transactions
* array should be merged with the previous transaction.
*/
function testTransaction(transactions, evaluators, merges) {
if (!merges) {
merges = [];
}
var box = createTextBoxElement();
box.undoScope = true;
content.appendChild(box);
var manager = box.undoManager;
is(manager.length, 0, "Initial lenght of UndoManager should be 0");
is(manager.position, 0, "Initial position of UndoManager should be 0");
// Apply transactions and ensure the contents of the box have the
// expected values.
var expectedLength = 0;
for (var i = 0; i < transactions.length; i++) {
var merge = merges.indexOf(i) == -1 ? false : true;
if (!merge || i == 0) {
expectedLength++;
}
manager.transact(transactions[i], merge);
evaluators[i+1](box);
// Length and position should increase by 1.
is(manager.length, expectedLength, "UndoManager length is incorrect after transaction");
is(manager.position, 0, "UndoManager position is incorrect after transaction");
}
// Undo all the transactions and make sure the content of box is the
// expected content.
var expectedPosition = 0;
for (var i = transactions.length - 1; i >= 0; i--) {
// Don't evaluate if the transaction was merged.
if (merges.indexOf(i) == -1) {
expectedPosition++;
manager.undo();
evaluators[i](box);
// Length and position should decrease by one.
is(manager.length, expectedLength, "UndoManager length should not change after undo");
is(manager.position, expectedPosition, "UndoManager position is incorrect after undo");
}
}
// Redo all the transactions and make sure the content of the box is
// the expected content.
for (var i = 0; i < transactions.length; i++) {
// Don't evaluate if the next transaction was merged into the current transaction.
if (merges.indexOf(i+1) == -1) {
expectedPosition--;
manager.redo();
evaluators[i+1](box);
// Length and position should increase by 1.
is(manager.length, expectedLength, "UndoManager length should not change after redo");
is(manager.position, expectedPosition, "UndoManager position is incorrect after redo");
}
}
content.removeChild(box);
}
testUndoRedoOnEmptyManager(0, 1);
testUndoRedoOnEmptyManager(1, 0);
testUndoRedoOnEmptyManager(2, 1);
testUndoRedoOnEmptyManager(1, 2);
testUndoRedoOnEmptyManager(2, 2);
/**
* Test undo/redo when there is no undo transactions.
* Makes sure that nothing bad happens (eg. crash) and that position and length
* don't change.
*/
function testUndoRedoOnEmptyManager(numUndo, numRedo) {
var box = createTextBoxElement();
content.appendChild(box);
box.undoScope = true;
var manager = box.undoManager;
for (var i = 0; i < numUndo; i++) {
manager.undo();
is(manager.length, 0, "Undo should not change number of transacstions");
is(manager.position, 0, "Undo should not change position");
}
for (var i = 0; i < numRedo; i++) {
manager.redo();
is(manager.length, 0, "Undo should not change number of transacstions");
is(manager.position, 0, "Undo should not change position");
}
content.removeChild(box);
}
// Test item()
testItem(10, 0);
testItem(10, 1);
testItem(10, 10);
testItem(10, 5);
testItem(1, 1);
/**
* Create a UndoManager with numTxns transactions and calls undo numUndo
* times. Ensures that items are in the correct order.
*/
function testItem(numTxns, numUndo) {
var box = createTextBoxElement();
content.appendChild(box);
box.undoScope = true;
var manager = box.undoManager;
for (var i = 0; i < numTxns; i++) {
var dummyTxn = {label: null, execute: function(){}, value: i};
manager.transact(dummyTxn, false);
}
for (var i = 0; i < numUndo; i++) {
manager.undo();
}
for (var i = 0; i < manager.length; i++) {
var txns = manager.item(i);
is(txns[0].value, numTxns - i - 1, "item() returned the incorrect transaction");
}
content.removeChild(box);
}
testClearUndoRedoGetItem(10, 10, true);
testClearUndoRedoGetItem(1, 1, true);
testClearUndoRedoGetItem(1, 0, true);
testClearUndoRedoGetItem(10, 0, true);
testClearUndoRedoGetItem(10, 6, true);
testClearUndoRedoGetItem(10, 10, false);
testClearUndoRedoGetItem(1, 1, false);
testClearUndoRedoGetItem(1, 0, false);
testClearUndoRedoGetItem(10, 0, false);
testClearUndoRedoGetItem(10, 6, false);
/**
* Test clear undo/redo and ensure that item() returns the correct transaction.
*/
function testClearUndoRedoGetItem(numTxns, numUndo, clearRedo) {
var box = createTextBoxElement();
content.appendChild(box);
box.undoScope = true;
var manager = box.undoManager;
for (var i = 0; i < numTxns; i++) {
var dummyTxn = {label: null, execute: function(){}, value: i};
manager.transact(dummyTxn, false);
}
for (var i = 0; i < numUndo; i++) {
manager.undo();
}
is(manager.length, numTxns, "UndoManager has incorrect number of transactions");
is(manager.position, numUndo, "UndoManager has incorrect position");
// Check for the correct length and position.
if (clearRedo) {
manager.clearRedo();
is(manager.position, 0, "UndoManager is incorrect position after clearRedo")
is(manager.length, numTxns - numUndo, "UndoManager has incorrect number of transactions after clearRedo");
} else {
manager.clearUndo();
is(manager.position, numUndo, "UndoManager should be at position 0 after clearUndo");
is(manager.length, numUndo, "UndoManager has incorrect number of transactions after clearUndo");
}
// Check that the most recent transaction (if one exists) is the correct one.
if (manager.length > 0) {
var newestTxn = manager.item(0)[0];
if (clearRedo) {
is(newestTxn.value + 1, numTxns - numUndo, "item() returned the incorrect transaction after clearRedo");
} else {
is(newestTxn.value + 1, numTxns, "item() returned the incorrect transaction after clearUndo");
}
}
content.removeChild(box);
}
testOutOfBoundsItem(0, -1);
testOutOfBoundsItem(0, 0);
testOutOfBoundsItem(0, 1);
testOutOfBoundsItem(1, -1);
testOutOfBoundsItem(1, 1);
testOutOfBoundsItem(2, 2);
testOutOfBoundsItem(2, 3);
/**
* Out of bound access to item.
*/
function testOutOfBoundsItem(numTxns, itemNum) {
var box = createTextBoxElement();
content.appendChild(box);
box.undoScope = true;
var manager = box.undoManager;
for (var i = 0; i < numTxns; i++) {
var dummyTxn = {label: null, execute: function(){}};
manager.transact(dummyTxn, false);
}
is(null, manager.item(itemNum), "Accessing out of bounds item should return null.");
content.removeChild(box);
}
/**
* Should not be able to access undoManager from within a transaction.
*/
var accessUndoManagerLength = {
label: "accessUndoManagerLength",
executeAutomatic: function() {
var box = document.getElementById("box");
is(0, box.undoManager.length, "Length should not change until after transaction is executed.");
}
};
transactExpectNoException(accessUndoManagerLength);
var accessUndoManagerPosition = {
label: "accessUndoManagerPosition",
executeAutomatic: function() {
var box = document.getElementById("box");
is(0, box.undoManager.position, "Position should not change until after transaction is executed.");
}
};
transactExpectNoException(accessUndoManagerPosition);
var accessUndoManagerUndo = {
label: "accessUndoManagerUndo",
executeAutomatic: function() {
var box = document.getElementById("box");
box.undoManager.undo();
}
};
transactExpectException(accessUndoManagerUndo);
var accessUndoManagerRedo = {
label: "accessUndoManagerRedo",
executeAutomatic: function() {
var box = document.getElementById("box");
box.undoManager.redo();
}
};
transactExpectException(accessUndoManagerRedo);
function transactExpectNoException(transaction) {
var box = createTextBoxElement();
content.appendChild(box);
box.undoScope = true;
try {
box.undoManager.transact(transaction, false);
ok(true, "Transaction should not cause UndoManager to throw an exception.");
} catch (ex) {
ok(false, "Transaction should not cause UndoManager to throw an exception.");
}
content.removeChild(box);
}
function transactExpectException(transaction) {
var box = createTextBoxElement();
content.appendChild(box);
box.undoScope = true;
try {
box.undoManager.managedTransaction(transaction);
ok(false, "Transaction should cause UndoManager to throw an exception.");
} catch (ex) {
ok(true, "Transaction should cause UndoManager to throw an exception.");
}
content.removeChild(box);
}
/**
* Should be able to access other undoManager from within a transaction.
*/
modifyOtherManager(accessUndoManagerLength);
modifyOtherManager(accessUndoManagerPosition);
modifyOtherManager(accessUndoManagerUndo);
modifyOtherManager(accessUndoManagerRedo);
function modifyOtherManager(transaction) {
var otherElement = document.createElement('div');
var box = createTextBoxElement();
content.appendChild(box);
content.appendChild(otherElement);
otherElement.undoScope = true;
box.undoScope = true;
try {
otherElement.undoManager.transact(transaction, false);
ok(true, "Should not throw exception when UndoManager accesses other undo manager");
} catch (ex) {
ok(false, "Should not throw exception when UndoManager accesses other undo manager");
}
content.removeChild(box);
content.removeChild(otherElement);
}
/**
* Tests for usage of undo manager with modifications to the host node outside
* of the manager. Such usage is not supported but it should not cause bad
* things to happen (eg. crash).
*/
// Remove the last characters from text, so that the deleteData range is out of bounds.
function removeLastCharactersExternal(element) {
element.firstChild.deleteData(3, 10);
}
testExternalMutation(removeLastCharactersExternal, textInsert);
testExternalMutation(removeLastCharactersExternal, textAppend);
// Change attribute value.
function changeAttributeExternal(element) {
element.setAttribute("data-test", "bad");
}
testExternalMutation(changeAttributeExternal, createAttribute);
testExternalMutation(changeAttributeExternal, changeAttribute);
testExternalMutation(changeAttributeExternal, removeAttribute);
function removeAttributeExternal(element) {
element.removeAttribute("data-test");
}
testExternalMutation(removeAttributeExternal, createAttribute);
testExternalMutation(removeAttributeExternal, changeAttribute);
testExternalMutation(removeAttributeExternal, removeAttribute);
// removing children
function removeAllChildrenExternal(element) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
testExternalMutation(removeAllChildrenExternal, appendChild);
testExternalMutation(removeAllChildrenExternal, applyInnerHTML);
// Insert child
function appendChildExternal(element) {
var newElement = document.createElement("span");
element.appendChild(newElement);
}
testExternalMutation(appendChildExternal, appendChild);
testExternalMutation(appendChildExternal, applyInnerHTML);
function testExternalMutation(externalMutation, transaction) {
var box = createTextBoxElement();
box.undoScope = true;
content.appendChild(box);
box.undoManager.transact(transaction, false);
externalMutation(box);
try {
box.undoManager.undo();
ok(true, "Should not throw exception when undo is called on the transaction manager");
} catch (ex) {
ok(false, "Should not throw exception when undo is called on the transaction manager");
}
try {
box.undoManager.redo();
ok(true, "Should not throw exception when redo is called on the transaction manager");
} catch (ex) {
ok(false, "Should not throw exception when redo is called on the transaction manager");
}
content.removeChild(box);
}
// Tests for manual transactions.
var appendChildManual = {
label: "appendChild",
execute: function() {
this.element = document.createElement("div");
this.redo();
},
redo: function() {
var box = document.getElementById("box");
box.appendChild(this.element);
},
undo: function() {
var box = document.getElementById("box");
box.removeChild(this.element);
}
};
testTransaction([appendChildManual],
[getChildCountFunction(1),
getChildCountFunction(2)]);
testTransaction([appendChildManual, appendChild],
[getChildCountFunction(1),
getChildCountFunction(2),
getChildCountFunction(3)]);
testTransaction([appendChild, appendChildManual, appendChild],
[getChildCountFunction(1),
getChildCountFunction(2),
getChildCountFunction(3),
getChildCountFunction(4)]);
// Tests for merge
testTransaction([appendChild, appendChild, appendChild],
[getChildCountFunction(1),
getChildCountFunction(2),
getChildCountFunction(3),
getChildCountFunction(4)],
[1]);
// Passing invalid objects to transact.
transactExpectException({});
transactExpectException({label: "hi"});
transactExpectException({label: "hi", executeAutomatic: null});
transactExpectException({label: "hi", execute: null});
transactExpectException({label: "hi", executeAutomatic: 1});
transactExpectException({label: "hi", execute: 1});
transactExpectException({label: "hi", execute: function() {}, redo: null});
// make sure the document undomanager exists.
ok(document.undoManager, "Document should have an undo manager.");
// Make sure that the UndoManager only records transactions within scope.
var parent = document.createElement("div");
var child = document.createElement("div");
parent.appendChild(child);
parent.undoScope = true;
child.undoScope = true;
var innerHTMLToBye = {
label: "hiToBye",
executeAutomatic: function() {
child.innerHTML = "bye";
}
};
child.innerHTML = "hello";
parent.undoManager.transact(innerHTMLToBye, false);
parent.undoManager.undo();
// The parent undo manager should not have recorded transactions for the children.
// Thus the innerHTML should not have been undone.
is(child.innerHTML, "bye", "Inner HTML of child should not be undone because it is not in parent scope.");
// Disconnect test.
var dummyNode = document.createElement("div");
dummyNode.undoScope = true;
var dummyManager = dummyNode.undoManager;
dummyManager.transact({label: null, execute: function() {}}, false);
dummyNode.undoScope = false;
is(dummyManager.length, 0, "All transactions should be cleared.");
try {
dummyManager.transact({label: null, execute: function() {}}, false);
ok(false, "Should not be able to transact in disconnected UndoManager.");
} catch (ex) {
ok(true, "Should not be able to transact in disconnected UndoManager.");
}
// Undo call before and after undo/redo.
dummyNode = document.createElement("div");
dummyNode.undoScope = true;
dummyNode.innerHTML = "hello";
var undoRedoTransaction = {
label: "undoRedoTransaction",
undoCall: 0,
redoCall: 0,
executeAutomatic: function () {
dummyNode.innerHTML = "bye";
},
undo: function() {
is(dummyNode.innerHTML, "hello", "redo should be called after transaction is undone.");
this.undoCall++;
},
redo: function() {
is(dummyNode.innerHTML, "bye", "redo should be called after transaction is redone.");
this.redoCall++;
}
};
dummyNode.undoManager.transact(undoRedoTransaction, false);
is(undoRedoTransaction.undoCall, 0, "undo should not have been called yet.");
is(undoRedoTransaction.redoCall, 0, "redo should not have been called yet.");
dummyNode.undoManager.undo();
is(undoRedoTransaction.undoCall, 1, "undo should be called once.");
is(undoRedoTransaction.redoCall, 0, "redo should not have been called yet.");
dummyNode.undoManager.redo();
is(undoRedoTransaction.undoCall, 1, "undo should be called once.");
is(undoRedoTransaction.redoCall, 1, "redo should be called once.");
// Attribute setting.
var dummyNode = document.createElement("div");
ok(!dummyNode.undoManager, "UndoManager should not exist for element without undoscope attribute.");
dummyNode.setAttribute("undoscope", "");
ok(dummyNode.undoManager, "UndoManager should exist for element with undoscope attribute.");
dummyNode.removeAttribute("undoscope");
ok(!dummyNode.undoManager, "UndoManager should not exist for element without undoscope attribute.");
dummyNode = document.createElement("div");
dummyNode.undoScope = true;
is(dummyNode.getAttribute("undoscope"), "", "undoscope attribute should reflect undoScope property.");
dummyNode.undoScope = false;
ok(!dummyNode.hasAttribute("undoscope"), "undoscope attribute should not exist if undoScope property is false.");
// Event test
dummyNode = document.createElement("div");
dummyNode.undoScope = true;
var transactionOne = {
transactCount: 0,
undoCount: 0,
redoCount: 0,
label: "transactionOne",
execute: function() {},
};
var transactionTwo = {
transactCount: 0,
undoCount: 0,
redoCount: 0,
label: "transactionTwo",
execute: function() {},
};
var transactionThree = {
transactCount: 0,
undoCount: 0,
redoCount: 0,
label: "transactionThree",
execute: function() {},
};
dummyNode.addEventListener("DOMTransaction", function(e) { e.transactions[0].transactCount++; });
dummyNode.addEventListener("undo", function(e) { e.transactions[0].undoCount++; });
dummyNode.addEventListener("redo", function(e) { e.transactions[0].redoCount++; });
dummyNode.undoManager.transact(transactionOne, false);
checkTransactionEvents(transactionOne, 1, 0, 0);
dummyNode.undoManager.transact(transactionTwo, false);
checkTransactionEvents(transactionTwo, 1, 0, 0);
dummyNode.undoManager.transact(transactionThree, false);
checkTransactionEvents(transactionThree, 1, 0, 0);
// Should do nothing, no events dispatched.
dummyNode.undoManager.redo();
checkTransactionEvents(transactionThree, 1, 0, 0);
dummyNode.undoManager.undo();
checkTransactionEvents(transactionThree, 1, 1, 0);
dummyNode.undoManager.redo();
checkTransactionEvents(transactionThree, 1, 1, 1);
dummyNode.undoManager.undo();
checkTransactionEvents(transactionThree, 1, 2, 1);
dummyNode.undoManager.undo();
checkTransactionEvents(transactionTwo, 1, 1, 0);
dummyNode.undoManager.undo();
checkTransactionEvents(transactionOne, 1, 1, 0);
// Should do nothing, no events dispatched.
dummyNode.undoManager.undo();
checkTransactionEvents(transactionOne, 1, 1, 0);
function checkTransactionEvents(transaction, expectedTransact, expectedUndo, expectedRedo) {
is(transaction.transactCount, expectedTransact, "DOMTransaction event was dispatched an unexpected number of times.");
is(transaction.undoCount, expectedUndo, "undo event was dispatched an unexpected number of times.");
is(transaction.redoCount, expectedRedo, "redo event was dispatched an unexpected number of times.");
}
</script>
</pre>
</body>
</html>

View File

@ -293,6 +293,16 @@ DOMInterfaces = {
'workers': True,
},
'DOMTransaction': [
{
'nativeType': 'nsIUndoManagerTransaction',
}],
'UndoManager': [
{
'implicitJSContext' : [ 'undo', 'redo', 'transact' ]
}],
'FormData': [
{
'nativeType': 'nsFormData'

View File

@ -31,6 +31,7 @@ XPIDLSRCS = \
nsIDOMMutationEvent.idl \
nsIDOMDragEvent.idl \
nsIDOMDataTransfer.idl \
nsIDOMDOMTransactionEvent.idl \
nsIDOMPopupBlockedEvent.idl \
nsIDOMBeforeUnloadEvent.idl \
nsIDOMSmartCardEvent.idl \

View File

@ -0,0 +1,25 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIDOMEvent.idl"
interface nsIVariant;
[scriptable, builtinclass, uuid(f57f7c46-d420-4f32-a61b-0eb585d30ee1)]
interface nsIDOMDOMTransactionEvent : nsIDOMEvent
{
readonly attribute nsIVariant transactions;
void initDOMTransactionEvent(in DOMString typeArg,
in boolean canBubbleArg,
in boolean canCancelArg,
in nsIVariant transactions);
};
dictionary DOMTransactionEventInit : EventInit
{
nsIVariant transactions;
};

View File

@ -84,6 +84,7 @@ SDK_XPIDLSRCS = \
nsIDOMHTMLVideoElement.idl \
nsIDOMHTMLAudioElement.idl \
nsIDOMValidityState.idl \
nsIUndoManagerTransaction.idl \
nsIDOMMozBrowserFrame.idl \
$(NULL)

View File

@ -0,0 +1,17 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
[scriptable, uuid(3802f155-e0f5-49ba-9972-e1aadcf2fdf0)]
interface nsIUndoManagerTransaction : nsISupports
{
attribute DOMString label;
void executeAutomatic();
void execute();
void undo();
void redo();
};

View File

@ -513,6 +513,8 @@ var interfaceNamesInGlobalScope =
"CameraManager",
"CSSSupportsRule",
"MozMobileCellInfo",
"UndoManager",
"DOMTransactionEvent",
"MozCanvasPrintState",
"TCPSocket",
"MozTimeManager",

View File

@ -0,0 +1,20 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* http://dvcs.w3.org/hg/undomanager/raw-file/tip/undomanager.html
*
* Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/
callback interface DOMTransaction {
attribute DOMString label;
void executeAutomatic();
void execute();
void undo();
void redo();
};

View File

@ -377,7 +377,16 @@ partial interface Document {
*/
Element? elementFromPoint (float x, float y);
//(Not implemented)CaretPosition? caretPositionFromPoint (float x, float y);
/*};
/*
};
http://dvcs.w3.org/hg/undomanager/raw-file/tip/undomanager.html
partial interface Document {
*/
[Pref="dom.undo_manager.enabled"]
readonly attribute UndoManager? undoManager;
/*
};
http://dev.w3.org/2006/webapi/selectors-api2/#interface-definitions
partial interface Document {

View File

@ -169,6 +169,16 @@ partial interface Element {
/*
};
// http://dvcs.w3.org/hg/undomanager/raw-file/tip/undomanager.html
partial interface Element {
*/
[Pref="dom.undo_manager.enabled"]
readonly attribute UndoManager? undoManager;
[SetterThrows,Pref="dom.undo_manager.enabled"]
attribute boolean undoScope;
/*
};
// http://domparsing.spec.whatwg.org/#extensions-to-the-element-interface
partial interface Element {
*/

View File

@ -0,0 +1,26 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* http://dvcs.w3.org/hg/undomanager/raw-file/tip/undomanager.html
*
* Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/
interface DOMTransaction;
[PrefControlled]
interface UndoManager {
[Throws] void transact(DOMTransaction transaction, boolean merge);
[Throws] void undo();
[Throws] void redo();
[Throws] sequence<DOMTransaction>? item(unsigned long index);
[Throws] readonly attribute unsigned long length;
[Throws] readonly attribute unsigned long position;
[Throws] void clearUndo();
[Throws] void clearRedo();
};

View File

@ -38,6 +38,7 @@ webidl_files = \
DOMSettableTokenList.webidl \
DOMStringMap.webidl \
DOMTokenList.webidl \
DOMTransaction.webidl \
DynamicsCompressorNode.webidl \
Element.webidl \
EventHandler.webidl \
@ -111,6 +112,7 @@ webidl_files = \
TextEncoder.webidl \
URL.webidl \
WebSocket.webidl \
UndoManager.webidl \
XMLHttpRequest.webidl \
XMLHttpRequestEventTarget.webidl \
XMLHttpRequestUpload.webidl \

View File

@ -869,7 +869,7 @@ nsEditor::BeginTransaction()
BeginUpdateViewBatch();
if (mTxnMgr) {
mTxnMgr->BeginBatch();
mTxnMgr->BeginBatch(nullptr);
}
return NS_OK;
@ -879,7 +879,7 @@ NS_IMETHODIMP
nsEditor::EndTransaction()
{
if (mTxnMgr) {
mTxnMgr->EndBatch();
mTxnMgr->EndBatch(false);
}
EndUpdateViewBatch();

View File

@ -55,10 +55,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=471319
// Test 3: Undoing a batched transaction where both end points of the
// transaction are the bogus node - the bogus node should still be
// recognized as bogus
t1Editor.transactionManager.beginBatch();
t1Editor.transactionManager.beginBatch(null);
t1.value = "mozilla";
t1.value = "";
t1Editor.transactionManager.endBatch();
t1Editor.transactionManager.endBatch(false);
t1Editor.undo(1);
ok(!t1.value,
"recreated <br> from undo transaction recognized as bogus");

View File

@ -4,6 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsITransaction;
/*
@ -14,7 +15,7 @@ interface nsITransaction;
* Undo or Redo stacks as well as any auto-aggregated children that a
* transaction may have.
*/
[scriptable, uuid(97f863f3-f886-11d4-9d39-0060b0f8baff)]
[scriptable, uuid(d007ceff-c978-486a-b697-384ca01997be)]
interface nsITransactionList : nsISupports
{
@ -43,6 +44,13 @@ interface nsITransactionList : nsISupports
*/
nsITransaction getItem(in long aIndex);
/**
* getData() returns the data (of type nsISupports array) associated with
* the transaction list.
*/
void getData(in long aIndex, [optional] out unsigned long aLength,
[array, size_is(aLength), retval] out nsISupports aData);
/**
* getNumChildrenForItem() returns the number of child (auto-aggreated)
* transactions the item at aIndex has.

View File

@ -20,7 +20,7 @@
* This interface is implemented by an object that wants to
* manage/track transactions.
*/
[scriptable, builtinclass, uuid(58e330c2-7b48-11d2-98b9-00805f297d89)]
[scriptable, builtinclass, uuid(c77763df-0fb9-41a8-8074-8e882f605755)]
interface nsITransactionManager : nsISupports
{
/**
@ -51,19 +51,34 @@ interface nsITransactionManager : nsISupports
*/
void clear();
/**
* Clears the undo stack only.
*/
void clearUndoStack();
/**
* Clears the redo stack only.
*/
void clearRedoStack();
/**
* Turns on the transaction manager's batch mode, forcing all transactions
* executed by the transaction manager's doTransaction() method to be
* aggregated together until EndBatch() is called. This mode allows an
* application to execute and group together several independent transactions
* so they can be undone with a single call to undoTransaction().
* @param aData An arbitrary nsISupports object that is associated with the
* batch. Can be retrieved from nsITransactionList.
*/
void beginBatch();
void beginBatch(in nsISupports aData);
/**
* Turns off the transaction manager's batch mode.
* @param aAllowEmpty If true, a batch containing no children will be
* pushed onto the undo stack. Otherwise, ending a batch with no
* children will result in no transactions being pushed on the undo stack.
*/
void endBatch();
void endBatch(in boolean aAllowEmpty);
/**
* The number of items on the undo stack.
@ -90,6 +105,19 @@ interface nsITransactionManager : nsISupports
*/
attribute long maxTransactionCount;
/**
* Combines the transaction at the top of the undo stack (if any) with the
* preceding undo transaction (if any) into a batch transaction. Thus,
* a call to undoTransaction() will undo both transactions.
*/
void batchTopUndo();
/**
* Removes the transaction at the top of the undo stack (if any) without
* transacting.
*/
void removeTopUndo();
/**
* Returns an AddRef'd pointer to the transaction at the top of the
* undo stack. Callers should be aware that this method could return

View File

@ -32,6 +32,7 @@ NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(nsTransactionItem)
NS_IMPL_CYCLE_COLLECTION_NATIVE_CLASS(nsTransactionItem)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTransactionItem)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mData)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransaction)
if (tmp->mRedoStack) {
tmp->mRedoStack->DoUnlink();
@ -42,6 +43,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTransactionItem)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTransactionItem)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mData)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
if (tmp->mRedoStack) {
tmp->mRedoStack->DoTraverse(cb);

View File

@ -7,6 +7,7 @@
#define nsTransactionItem_h__
#include "nsCOMPtr.h"
#include "nsCOMArray.h"
#include "nsCycleCollectionParticipant.h"
#include "nsISupportsImpl.h"
#include "nscore.h"
@ -17,6 +18,7 @@ class nsTransactionStack;
class nsTransactionItem
{
nsCOMArray<nsISupports> mData;
nsCOMPtr<nsITransaction> mTransaction;
nsTransactionStack *mUndoStack;
nsTransactionStack *mRedoStack;
@ -40,6 +42,11 @@ public:
virtual nsresult UndoTransaction(nsTransactionManager *aTxMgr);
virtual nsresult RedoTransaction(nsTransactionManager *aTxMgr);
nsCOMArray<nsISupports>& GetData()
{
return mData;
}
private:
virtual nsresult UndoChildren(nsTransactionManager *aTxMgr);

View File

@ -87,6 +87,42 @@ NS_IMETHODIMP nsTransactionList::ItemIsBatch(int32_t aIndex, bool *aIsBatch)
return item->GetIsBatch(aIsBatch);
}
/* void getData (in long aIndex,
[optional] out unsigned long aLength,
[array, size_is (aLength), retval]
out nsISupports aData); */
NS_IMETHODIMP nsTransactionList::GetData(int32_t aIndex,
uint32_t *aLength,
nsISupports ***aData)
{
nsCOMPtr<nsITransactionManager> txMgr = do_QueryReferent(mTxnMgr);
NS_ENSURE_TRUE(txMgr, NS_ERROR_FAILURE);
nsRefPtr<nsTransactionItem> item;
if (mTxnStack) {
item = mTxnStack->GetItem(aIndex);
} else if (mTxnItem) {
nsresult result = mTxnItem->GetChild(aIndex, getter_AddRefs(item));
NS_ENSURE_SUCCESS(result, result);
}
nsCOMArray<nsISupports>& data = item->GetData();
nsISupports** ret = static_cast<nsISupports**>(NS_Alloc(data.Count() *
sizeof(nsISupports*)));
for (int32_t i = 0; i < data.Count(); i++) {
NS_ADDREF(ret[i] = data[i]);
}
*aLength = data.Count();
*aData = ret;
return NS_OK;
}
/* nsITransaction getItem (in long aIndex); */
NS_IMETHODIMP nsTransactionList::GetItem(int32_t aIndex, nsITransaction **aItem)
{

View File

@ -76,14 +76,14 @@ nsTransactionManager::DoTransaction(nsITransaction *aTransaction)
return NS_OK;
}
result = BeginTransaction(aTransaction);
result = BeginTransaction(aTransaction, nullptr);
if (NS_FAILED(result)) {
DidDoNotify(aTransaction, result);
return result;
}
result = EndTransaction();
result = EndTransaction(false);
nsresult result2 = DidDoNotify(aTransaction, result);
@ -216,7 +216,7 @@ nsTransactionManager::Clear()
}
NS_IMETHODIMP
nsTransactionManager::BeginBatch()
nsTransactionManager::BeginBatch(nsISupports* aData)
{
nsresult result;
@ -237,7 +237,7 @@ nsTransactionManager::BeginBatch()
return NS_OK;
}
result = BeginTransaction(0);
result = BeginTransaction(0, aData);
nsresult result2 = DidBeginBatchNotify(result);
@ -248,7 +248,7 @@ nsTransactionManager::BeginBatch()
}
NS_IMETHODIMP
nsTransactionManager::EndBatch()
nsTransactionManager::EndBatch(bool aAllowEmpty)
{
nsCOMPtr<nsITransaction> ti;
nsresult result;
@ -286,7 +286,7 @@ nsTransactionManager::EndBatch()
return NS_OK;
}
result = EndTransaction();
result = EndTransaction(aAllowEmpty);
nsresult result2 = DidEndBatchNotify(result);
@ -455,6 +455,50 @@ nsTransactionManager::GetRedoList(nsITransactionList **aTransactionList)
return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK;
}
nsresult
nsTransactionManager::BatchTopUndo()
{
if (mUndoStack.GetSize() < 2) {
// Not enough transactions to merge into one batch.
return NS_OK;
}
nsRefPtr<nsTransactionItem> lastUndo;
nsRefPtr<nsTransactionItem> previousUndo;
lastUndo = mUndoStack.Pop();
MOZ_ASSERT(lastUndo, "There should be at least two transactions.");
previousUndo = mUndoStack.Peek();
MOZ_ASSERT(previousUndo, "There should be at least two transactions.");
nsresult result = previousUndo->AddChild(lastUndo);
// Transfer data from the transactions that is going to be
// merged to the transaction that it is being merged with.
nsCOMArray<nsISupports>& lastData = lastUndo->GetData();
nsCOMArray<nsISupports>& previousData = previousUndo->GetData();
NS_ENSURE_TRUE(previousData.AppendObjects(lastData), NS_ERROR_UNEXPECTED);
lastData.Clear();
return result;
}
nsresult
nsTransactionManager::RemoveTopUndo()
{
nsRefPtr<nsTransactionItem> lastUndo;
lastUndo = mUndoStack.Peek();
if (!lastUndo) {
return NS_OK;
}
lastUndo = mUndoStack.Pop();
return NS_OK;
}
NS_IMETHODIMP
nsTransactionManager::AddListener(nsITransactionListener *aListener)
{
@ -471,14 +515,14 @@ nsTransactionManager::RemoveListener(nsITransactionListener *aListener)
return mListeners.RemoveObject(aListener) ? NS_OK : NS_ERROR_FAILURE;
}
nsresult
NS_IMETHODIMP
nsTransactionManager::ClearUndoStack()
{
mUndoStack.Clear();
return NS_OK;
}
nsresult
NS_IMETHODIMP
nsTransactionManager::ClearRedoStack()
{
mRedoStack.Clear();
@ -717,7 +761,8 @@ nsTransactionManager::DidMergeNotify(nsITransaction *aTop,
}
nsresult
nsTransactionManager::BeginTransaction(nsITransaction *aTransaction)
nsTransactionManager::BeginTransaction(nsITransaction *aTransaction,
nsISupports *aData)
{
nsresult result = NS_OK;
@ -725,6 +770,11 @@ nsTransactionManager::BeginTransaction(nsITransaction *aTransaction)
// We could use a factory that pre-allocates/recycles transaction items.
nsRefPtr<nsTransactionItem> tx = new nsTransactionItem(aTransaction);
if (aData) {
nsCOMArray<nsISupports>& data = tx->GetData();
data.AppendObject(aData);
}
if (!tx) {
return NS_ERROR_OUT_OF_MEMORY;
}
@ -742,7 +792,7 @@ nsTransactionManager::BeginTransaction(nsITransaction *aTransaction)
}
nsresult
nsTransactionManager::EndTransaction()
nsTransactionManager::EndTransaction(bool aAllowEmpty)
{
nsresult result = NS_OK;
@ -753,7 +803,7 @@ nsTransactionManager::EndTransaction()
nsCOMPtr<nsITransaction> tint = tx->GetTransaction();
if (!tint) {
if (!tint && !aAllowEmpty) {
int32_t nc = 0;
// If we get here, the transaction must be a dummy batch transaction

View File

@ -9,6 +9,7 @@
#include "nsCOMArray.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsTransactionStack.h"
#include "nsISupportsImpl.h"
#include "nsITransactionManager.h"
#include "nsTransactionStack.h"
@ -51,9 +52,6 @@ public:
/* nsITransactionManager method implementations. */
NS_DECL_NSITRANSACTIONMANAGER
/* nsTransactionManager specific methods. */
virtual nsresult ClearUndoStack(void);
virtual nsresult ClearRedoStack(void);
already_AddRefed<nsITransaction> PeekUndoStack();
already_AddRefed<nsITransaction> PeekRedoStack();
@ -78,8 +76,9 @@ public:
private:
/* nsTransactionManager specific private methods. */
virtual nsresult BeginTransaction(nsITransaction *aTransaction);
virtual nsresult EndTransaction(void);
virtual nsresult BeginTransaction(nsITransaction *aTransaction,
nsISupports *aData);
virtual nsresult EndTransaction(bool aAllowEmpty);
};
#endif // nsTransactionManager_h__

View File

@ -613,7 +613,7 @@ public:
return NS_OK;
if (mFlags & BATCH_FLAG) {
result = mTXMgr->BeginBatch();
result = mTXMgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
return result;
}
@ -647,7 +647,7 @@ public:
i, mLevel, result);
if (mFlags & BATCH_FLAG)
mTXMgr->EndBatch();
mTXMgr->EndBatch(false);
return NS_ERROR_OUT_OF_MEMORY;
}
@ -659,7 +659,7 @@ public:
i, mLevel, result);
if (mFlags & BATCH_FLAG)
mTXMgr->EndBatch();
mTXMgr->EndBatch(false);
return result;
}
@ -672,7 +672,7 @@ public:
tx->Release();
if (mFlags & BATCH_FLAG)
mTXMgr->EndBatch();
mTXMgr->EndBatch(false);
return result;
}
@ -681,7 +681,7 @@ public:
}
if (mFlags & BATCH_FLAG)
mTXMgr->EndBatch();
mTXMgr->EndBatch(false);
return result;
}
@ -2700,7 +2700,7 @@ quick_batch_test(TestTransactionFactory *factory)
/*******************************************************************
*
* Make sure an unbalanced call to EndBatch() with empty undo stack
* Make sure an unbalanced call to EndBatch(false) with empty undo stack
* throws an error!
*
*******************************************************************/
@ -2719,10 +2719,10 @@ quick_batch_test(TestTransactionFactory *factory)
return NS_ERROR_FAILURE;
}
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (result != NS_ERROR_FAILURE) {
fail("EndBatch() returned unexpected status. (%d)\n", result);
fail("EndBatch(false) returned unexpected status. (%d)\n", result);
return result;
}
@ -2740,7 +2740,7 @@ quick_batch_test(TestTransactionFactory *factory)
return NS_ERROR_FAILURE;
}
passed("Test unbalanced EndBatch() with empty undo stack");
passed("Test unbalanced EndBatch(false) with empty undo stack");
/*******************************************************************
*
@ -2763,10 +2763,10 @@ quick_batch_test(TestTransactionFactory *factory)
return NS_ERROR_FAILURE;
}
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -2784,10 +2784,10 @@ quick_batch_test(TestTransactionFactory *factory)
return NS_ERROR_FAILURE;
}
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}
@ -2818,10 +2818,10 @@ quick_batch_test(TestTransactionFactory *factory)
*
*******************************************************************/
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -2864,10 +2864,10 @@ quick_batch_test(TestTransactionFactory *factory)
return NS_ERROR_FAILURE;
}
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}
@ -2917,10 +2917,10 @@ quick_batch_test(TestTransactionFactory *factory)
return result;
}
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -2949,10 +2949,10 @@ quick_batch_test(TestTransactionFactory *factory)
tx->Release();
}
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}
@ -3021,10 +3021,10 @@ quick_batch_test(TestTransactionFactory *factory)
*
*******************************************************************/
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -3064,10 +3064,10 @@ quick_batch_test(TestTransactionFactory *factory)
return NS_ERROR_FAILURE;
}
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -3107,10 +3107,10 @@ quick_batch_test(TestTransactionFactory *factory)
return NS_ERROR_FAILURE;
}
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -3150,24 +3150,24 @@ quick_batch_test(TestTransactionFactory *factory)
return NS_ERROR_FAILURE;
}
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}
@ -3323,15 +3323,15 @@ quick_batch_test(TestTransactionFactory *factory)
/*******************************************************************
*
* Make sure an unbalanced call to EndBatch() throws an error and
* Make sure an unbalanced call to EndBatch(false) throws an error and
* doesn't affect the undo and redo stacks!
*
*******************************************************************/
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (result != NS_ERROR_FAILURE) {
fail("EndBatch() returned unexpected status. (%d)\n", result);
fail("EndBatch(false) returned unexpected status. (%d)\n", result);
return result;
}
@ -3363,7 +3363,7 @@ quick_batch_test(TestTransactionFactory *factory)
return NS_ERROR_FAILURE;
}
passed("Test effect of unbalanced EndBatch() on undo and redo stacks");
passed("Test effect of unbalanced EndBatch(false) on undo and redo stacks");
/*******************************************************************
*
@ -3373,10 +3373,10 @@ quick_batch_test(TestTransactionFactory *factory)
*
*******************************************************************/
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -3408,10 +3408,10 @@ quick_batch_test(TestTransactionFactory *factory)
return NS_ERROR_FAILURE;
}
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}
@ -3451,10 +3451,10 @@ quick_batch_test(TestTransactionFactory *factory)
*
*******************************************************************/
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -3511,10 +3511,10 @@ quick_batch_test(TestTransactionFactory *factory)
return NS_ERROR_FAILURE;
}
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}
@ -3636,10 +3636,10 @@ quick_batch_test(TestTransactionFactory *factory)
return result;
}
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -3652,10 +3652,10 @@ quick_batch_test(TestTransactionFactory *factory)
tx->Release();
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}
@ -3739,10 +3739,10 @@ quick_batch_test(TestTransactionFactory *factory)
return result;
}
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -3755,10 +3755,10 @@ quick_batch_test(TestTransactionFactory *factory)
tx->Release();
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}
@ -3870,10 +3870,10 @@ quick_batch_test(TestTransactionFactory *factory)
return result;
}
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -3886,10 +3886,10 @@ quick_batch_test(TestTransactionFactory *factory)
tx->Release();
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}
@ -4082,10 +4082,10 @@ quick_batch_test(TestTransactionFactory *factory)
return result;
}
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -4097,10 +4097,10 @@ quick_batch_test(TestTransactionFactory *factory)
tx->Release();
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}
@ -4167,10 +4167,10 @@ quick_batch_test(TestTransactionFactory *factory)
return result;
}
result = mgr->BeginBatch();
result = mgr->BeginBatch(nullptr);
if (NS_FAILED(result)) {
fail("BeginBatch() failed. (%d)\n", result);
fail("BeginBatch(nullptr) failed. (%d)\n", result);
return result;
}
@ -4182,10 +4182,10 @@ quick_batch_test(TestTransactionFactory *factory)
tx->Release();
result = mgr->EndBatch();
result = mgr->EndBatch(false);
if (NS_FAILED(result)) {
fail("EndBatch() failed. (%d)\n", result);
fail("EndBatch(false) failed. (%d)\n", result);
return result;
}

View File

@ -15,6 +15,7 @@ simple_events = [
'UserProximityEvent',
'CustomEvent',
'PageTransitionEvent',
'DOMTransactionEvent',
'PopStateEvent',
'HashChangeEvent',
'CloseEvent',

View File

@ -119,8 +119,8 @@ add_test(function test_create_folder_with_description() {
// This checks that calling undoTransaction on an "empty batch" doesn't
// undo the previous transaction (getItemTitle will fail)
txnManager.beginBatch();
txnManager.endBatch();
txnManager.beginBatch(null);
txnManager.endBatch(false);
txnManager.undoTransaction();
let folderId = observer._itemAddedId;