Web Inspector: add generic support for undo-ing DOM edits.
authorpfeldman@chromium.org <pfeldman@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 7 Feb 2012 14:00:24 +0000 (14:00 +0000)
committerpfeldman@chromium.org <pfeldman@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 7 Feb 2012 14:00:24 +0000 (14:00 +0000)
https://bugs.webkit.org/show_bug.cgi?id=77875

Reviewed by Yury Semikhatsky.

Source/WebCore:

This change introduces InspectorHistory::Action that encapsulates all DOM modifications
initiated by the inspector. There is a way to undo these actions up until the undoable
state marker.

Tests: inspector/elements/undo-dom-edits-2.html
       inspector/elements/undo-dom-edits.html
       inspector/styles/undo-add-property.html
       inspector/styles/undo-change-property.html
       inspector/styles/undo-property-toggle.html

* CMakeLists.txt:
* GNUmakefile.list.am:
* Target.pri:
* WebCore.gypi:
* WebCore.vcproj/WebCore.vcproj:
* WebCore.xcodeproj/project.pbxproj:
* inspector/Inspector.json:
* inspector/InspectorAllInOne.cpp:
* inspector/InspectorCSSAgent.cpp:
(InspectorCSSAgent::StyleSheetAction):
(WebCore::InspectorCSSAgent::StyleSheetAction::StyleSheetAction):
(WebCore::InspectorCSSAgent::StyleSheetAction::perform):
(WebCore::InspectorCSSAgent::StyleSheetAction::undo):
(WebCore):
(InspectorCSSAgent::SetStyleSheetTextAction):
(WebCore::InspectorCSSAgent::SetStyleSheetTextAction::SetStyleSheetTextAction):
(WebCore::InspectorCSSAgent::SetStyleSheetTextAction::perform):
(WebCore::InspectorCSSAgent::SetStyleSheetTextAction::undo):
(InspectorCSSAgent::SetPropertyTextAction):
(WebCore::InspectorCSSAgent::SetPropertyTextAction::SetPropertyTextAction):
(WebCore::InspectorCSSAgent::SetPropertyTextAction::toString):
(WebCore::InspectorCSSAgent::SetPropertyTextAction::perform):
(WebCore::InspectorCSSAgent::SetPropertyTextAction::undo):
(WebCore::InspectorCSSAgent::SetPropertyTextAction::mergeId):
(WebCore::InspectorCSSAgent::SetPropertyTextAction::merge):
(InspectorCSSAgent::TogglePropertyAction):
(WebCore::InspectorCSSAgent::TogglePropertyAction::TogglePropertyAction):
(WebCore::InspectorCSSAgent::TogglePropertyAction::perform):
(WebCore::InspectorCSSAgent::TogglePropertyAction::undo):
(WebCore::InspectorCSSAgent::getStyleSheetText):
(WebCore::InspectorCSSAgent::setStyleSheetText):
(WebCore::InspectorCSSAgent::setPropertyText):
(WebCore::InspectorCSSAgent::toggleProperty):
* inspector/InspectorCSSAgent.h:
(InspectorCSSAgent):
* inspector/InspectorDOMAgent.cpp:
(WebCore::InspectorDOMAgent::InspectorDOMAgent):
(WebCore::InspectorDOMAgent::reset):
(WebCore::InspectorDOMAgent::setAttributeValue):
(WebCore::InspectorDOMAgent::setAttributesAsText):
(WebCore::InspectorDOMAgent::removeAttribute):
(WebCore::InspectorDOMAgent::removeNode):
(WebCore::InspectorDOMAgent::setNodeName):
(WebCore::InspectorDOMAgent::setOuterHTML):
(WebCore::InspectorDOMAgent::setNodeValue):
(WebCore::InspectorDOMAgent::moveTo):
(WebCore::InspectorDOMAgent::undo):
(WebCore):
(WebCore::InspectorDOMAgent::markUndoableState):
* inspector/InspectorDOMAgent.h:
(InspectorDOMAgent):
(WebCore::InspectorDOMAgent::history):
* inspector/InspectorHistory.cpp: Added.
(WebCore::InspectorHistory::Action::Action):
(WebCore):
(WebCore::InspectorHistory::Action::~Action):
(WebCore::InspectorHistory::Action::toString):
(WebCore::InspectorHistory::Action::isUndoableStateMark):
(WebCore::InspectorHistory::Action::mergeId):
(WebCore::InspectorHistory::Action::merge):
(WebCore::InspectorHistory::InspectorHistory):
(WebCore::InspectorHistory::~InspectorHistory):
(WebCore::InspectorHistory::perform):
(WebCore::InspectorHistory::markUndoableState):
(WebCore::InspectorHistory::undo):
(WebCore::InspectorHistory::reset):
* inspector/InspectorHistory.h: Added.
(WebCore):
(InspectorHistory):
(Action):
* inspector/InspectorStyleSheet.cpp:
(WebCore::InspectorStyle::setPropertyText):
(WebCore::InspectorStyle::styleText):
(WebCore::InspectorStyleSheet::addRule):
(WebCore::InspectorStyleSheet::buildObjectForStyleSheet):
(WebCore::InspectorStyleSheet::buildObjectForStyle):
(WebCore::InspectorStyleSheet::setPropertyText):
(WebCore::InspectorStyleSheet::getText):
(WebCore::InspectorStyleSheetForInlineStyle::getText):
* inspector/InspectorStyleSheet.h:
(InspectorStyle):
(InspectorStyleSheet):
(InspectorStyleSheetForInlineStyle):
* inspector/front-end/CSSStyleModel.js:
(WebInspector.CSSProperty.prototype.setText):
* inspector/front-end/ElementsPanel.js:
(WebInspector.ElementsPanel.prototype._selectedNodeChanged):
(WebInspector.ElementsPanel.prototype._updateSidebars):
(WebInspector.ElementsPanel.prototype.handleShortcut):
* inspector/front-end/StylesSidebarPane.js:
(WebInspector.StylePropertiesSection.prototype.onpopulate):
(WebInspector.StylePropertiesSection.prototype.addNewBlankProperty):
(WebInspector.ComputedStylePropertiesSection.prototype.onpopulate):
(WebInspector.StylePropertyTreeElement):
(WebInspector.StylePropertyTreeElement.prototype):

LayoutTests:

* http/tests/inspector/elements-test.js:
(initialize_ElementTest.InspectorTest.rangeText):
(initialize_ElementTest.InspectorTest.generateUndoTest):
* inspector/elements/undo-dom-edits-2-expected.txt: Added.
* inspector/elements/undo-dom-edits-2.html: Added.
* inspector/elements/undo-dom-edits-expected.txt: Added.
* inspector/elements/undo-dom-edits.html: Added.
* inspector/styles/undo-add-property-expected.txt: Added.
* inspector/styles/undo-add-property.html: Added.
* inspector/styles/undo-change-property-expected.txt: Added.
* inspector/styles/undo-change-property.html: Added.
* inspector/styles/undo-property-toggle-expected.txt: Added.
* inspector/styles/undo-property-toggle.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@106932 268f45cc-cd09-0410-ab3c-d52691b4dbfc

32 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/inspector/elements-test.js
LayoutTests/inspector/elements/undo-dom-edits-2-expected.txt [new file with mode: 0644]
LayoutTests/inspector/elements/undo-dom-edits-2.html [new file with mode: 0644]
LayoutTests/inspector/elements/undo-dom-edits-expected.txt [new file with mode: 0644]
LayoutTests/inspector/elements/undo-dom-edits.html [new file with mode: 0644]
LayoutTests/inspector/styles/undo-add-property-expected.txt [new file with mode: 0644]
LayoutTests/inspector/styles/undo-add-property.html [new file with mode: 0644]
LayoutTests/inspector/styles/undo-change-property-expected.txt [new file with mode: 0644]
LayoutTests/inspector/styles/undo-change-property.html [new file with mode: 0644]
LayoutTests/inspector/styles/undo-property-toggle-expected.txt [new file with mode: 0644]
LayoutTests/inspector/styles/undo-property-toggle.html [new file with mode: 0644]
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/GNUmakefile.list.am
Source/WebCore/Target.pri
Source/WebCore/WebCore.gypi
Source/WebCore/WebCore.vcproj/WebCore.vcproj
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/inspector/Inspector.json
Source/WebCore/inspector/InspectorAllInOne.cpp
Source/WebCore/inspector/InspectorCSSAgent.cpp
Source/WebCore/inspector/InspectorCSSAgent.h
Source/WebCore/inspector/InspectorDOMAgent.cpp
Source/WebCore/inspector/InspectorDOMAgent.h
Source/WebCore/inspector/InspectorHistory.cpp [new file with mode: 0644]
Source/WebCore/inspector/InspectorHistory.h [new file with mode: 0644]
Source/WebCore/inspector/InspectorStyleSheet.cpp
Source/WebCore/inspector/InspectorStyleSheet.h
Source/WebCore/inspector/front-end/CSSStyleModel.js
Source/WebCore/inspector/front-end/ElementsPanel.js
Source/WebCore/inspector/front-end/StylesSidebarPane.js

index 3492e32..186e306 100644 (file)
@@ -1,3 +1,24 @@
+2012-02-07  Pavel Feldman  <pfeldman@google.com>
+
+        Web Inspector: add generic support for undo-ing DOM edits.
+        https://bugs.webkit.org/show_bug.cgi?id=77875
+
+        Reviewed by Yury Semikhatsky.
+
+        * http/tests/inspector/elements-test.js:
+        (initialize_ElementTest.InspectorTest.rangeText):
+        (initialize_ElementTest.InspectorTest.generateUndoTest):
+        * inspector/elements/undo-dom-edits-2-expected.txt: Added.
+        * inspector/elements/undo-dom-edits-2.html: Added.
+        * inspector/elements/undo-dom-edits-expected.txt: Added.
+        * inspector/elements/undo-dom-edits.html: Added.
+        * inspector/styles/undo-add-property-expected.txt: Added.
+        * inspector/styles/undo-add-property.html: Added.
+        * inspector/styles/undo-change-property-expected.txt: Added.
+        * inspector/styles/undo-change-property.html: Added.
+        * inspector/styles/undo-property-toggle-expected.txt: Added.
+        * inspector/styles/undo-property-toggle.html: Added.
+
 2012-02-06  Yury Semikhatsky  <yurys@chromium.org>
 
         Web Inspector: don't mark object is queriable if it is only reachable by internal reference
index 2dc997c..e9aa660 100644 (file)
@@ -381,6 +381,37 @@ InspectorTest.rangeText = function(range)
     if (!range)
         return "[undefined-undefined]";
     return "[" + range.start + "-" + range.end + "]";
-};
+}
+
+InspectorTest.generateUndoTest = function(testBody)
+{
+    function result(next)
+    {
+        var testNode = InspectorTest.expandedNodeWithId(/function\s([^(]*)/.exec(testBody)[1]);
+        InspectorTest.addResult("Initial:");
+        InspectorTest.dumpElementsTree(testNode);
+
+        testBody(step1);
+
+        function step1()
+        {
+            InspectorTest.addResult("Post-action:");
+            InspectorTest.dumpElementsTree(testNode);
+            DOMAgent.undo(step2);
+        }
+
+        function step2()
+        {
+            InspectorTest.addResult("Post-undo (initial):");
+            InspectorTest.dumpElementsTree(testNode);
+            next();
+        }
+    }
+    result.toString = function()
+    {
+        return testBody.toString();
+    }
+    return result;
+}
 
 };
diff --git a/LayoutTests/inspector/elements/undo-dom-edits-2-expected.txt b/LayoutTests/inspector/elements/undo-dom-edits-2-expected.txt
new file mode 100644 (file)
index 0000000..f01aed6
--- /dev/null
@@ -0,0 +1,47 @@
+Tests that DOM modifications done in the Elements panel are undoable (Part 2).
+
+
+Running: testSetUp
+
+Running: testSetAttribute
+Initial:
+- <div id="testSetAttribute">
+      <div foo="attribute value" id="node-to-set-attribute"></div>
+  </div>
+Post-action:
+- <div id="testSetAttribute">
+      <div id="node-to-set-attribute" bar="edited attribute"></div>
+  </div>
+Post-undo (initial):
+- <div id="testSetAttribute">
+      <div id="node-to-set-attribute" foo="attribute value"></div>
+  </div>
+
+Running: testRemoveAttribute
+Initial:
+- <div id="testRemoveAttribute">
+      <div foo="attribute value" id="node-to-remove-attribute"></div>
+  </div>
+Post-action:
+- <div id="testRemoveAttribute">
+      <div id="node-to-remove-attribute"></div>
+  </div>
+Post-undo (initial):
+- <div id="testRemoveAttribute">
+      <div id="node-to-remove-attribute" foo="attribute value"></div>
+  </div>
+
+Running: testAddAttribute
+Initial:
+- <div id="testAddAttribute">
+      <div id="node-to-add-attribute"></div>
+  </div>
+Post-action:
+- <div id="testAddAttribute">
+      <div id="node-to-add-attribute" newattr="new-value"></div>
+  </div>
+Post-undo (initial):
+- <div id="testAddAttribute">
+      <div id="node-to-add-attribute"></div>
+  </div>
+
diff --git a/LayoutTests/inspector/elements/undo-dom-edits-2.html b/LayoutTests/inspector/elements/undo-dom-edits-2.html
new file mode 100644 (file)
index 0000000..40786c1
--- /dev/null
@@ -0,0 +1,68 @@
+<html>
+<head>
+<script src="../../http/tests/inspector/inspector-test.js"></script>
+<script src="../../http/tests/inspector/elements-test.js"></script>
+<script>
+
+function test()
+{
+    var testSuite = [];
+
+    function testSetUp(next)
+    {
+        InspectorTest.expandElementsTree(next);
+    }
+    testSuite.push(testSetUp);
+
+
+    function testSetAttribute(callback)
+    {
+        var node = InspectorTest.expandedNodeWithId("node-to-set-attribute"); 
+        node.setAttribute("foo", "bar=\"edited attribute\"", callback);
+    }
+    testSuite.push(InspectorTest.generateUndoTest(testSetAttribute));
+
+
+    function testRemoveAttribute(callback)
+    {
+        var node = InspectorTest.expandedNodeWithId("node-to-remove-attribute"); 
+        node.removeAttribute("foo", callback);
+    }
+    testSuite.push(InspectorTest.generateUndoTest(testRemoveAttribute));
+
+
+    function testAddAttribute(callback)
+    {
+        var node = InspectorTest.expandedNodeWithId("node-to-add-attribute"); 
+        node.setAttribute("", "newattr=\"new-value\"", callback);
+    }
+    testSuite.push(InspectorTest.generateUndoTest(testAddAttribute));
+
+
+    InspectorTest.runTestSuite(testSuite);
+}
+
+</script>
+</head>
+
+<body onload="runTest()">
+<p>
+Tests that DOM modifications done in the Elements panel are undoable (Part 2).
+</p>
+
+<div style="display:none">
+    <div id="testSetAttribute">
+        <div foo="attribute value" id="node-to-set-attribute"></div>
+    </div>
+
+    <div id="testRemoveAttribute">
+        <div foo="attribute value" id="node-to-remove-attribute"></div>
+    </div>
+
+    <div id="testAddAttribute">
+        <div id="node-to-add-attribute"></div>
+    </div>
+</div>
+
+</body>
+</html>
diff --git a/LayoutTests/inspector/elements/undo-dom-edits-expected.txt b/LayoutTests/inspector/elements/undo-dom-edits-expected.txt
new file mode 100644 (file)
index 0000000..9a61b73
--- /dev/null
@@ -0,0 +1,66 @@
+Tests that DOM modifications done in the Elements panel are undoable.
+
+
+Running: testSetUp
+
+Running: testRemove
+Initial:
+- <div id="testRemove">
+      <div id="node-to-remove"></div>
+  </div>
+Post-action:
+- <div id="testRemove">
+  </div>
+Post-undo (initial):
+- <div id="testRemove">
+      <div id="node-to-remove"></div>
+  </div>
+
+Running: testSetNodeName
+Initial:
+- <div id="testSetNodeName">
+      <div id="node-to-set-name"></div>
+  </div>
+Post-action:
+- <div id="testSetNodeName">
+      <span id="node-to-set-name"></span>
+  </div>
+Post-undo (initial):
+- <div id="testSetNodeName">
+      <div id="node-to-set-name"></div>
+  </div>
+
+Running: testSetNodeValue
+Initial:
+- <div id="testSetNodeValue">
+      <div id="node-to-set-value">Text</div>
+  </div>
+Post-action:
+- <div id="testSetNodeValue">
+      <div id="node-to-set-value">New Text</div>
+  </div>
+Post-undo (initial):
+- <div id="testSetNodeValue">
+      <div id="node-to-set-value">Text</div>
+  </div>
+
+Running: testEditAsHTML
+Initial:
+- <div id="testEditAsHTML">
+    - <div id="node-to-edit-as-html">
+          <span id="span">Text</span>
+      </div>
+  </div>
+Post-action:
+- <div id="testEditAsHTML">
+    - <div id="node-to-edit-as-html">
+          <div id="span2">Text2</div>
+      </div>
+  </div>
+Post-undo (initial):
+- <div id="testEditAsHTML">
+    - <div id="node-to-edit-as-html">
+          <span id="span">Text</span>
+      </div>
+  </div>
+
diff --git a/LayoutTests/inspector/elements/undo-dom-edits.html b/LayoutTests/inspector/elements/undo-dom-edits.html
new file mode 100644 (file)
index 0000000..ebd92de
--- /dev/null
@@ -0,0 +1,79 @@
+<html>
+<head>
+<script src="../../http/tests/inspector/inspector-test.js"></script>
+<script src="../../http/tests/inspector/elements-test.js"></script>
+<script>
+
+function test()
+{
+    var testSuite = [];
+
+    function testSetUp(next)
+    {
+        InspectorTest.expandElementsTree(next);
+    }
+    testSuite.push(testSetUp);
+
+
+    function testRemove(callback)
+    {
+        var node = InspectorTest.expandedNodeWithId("node-to-remove"); 
+        node.removeNode(callback);
+    }
+    testSuite.push(InspectorTest.generateUndoTest(testRemove));
+
+
+    function testSetNodeName(callback)
+    {
+        var node = InspectorTest.expandedNodeWithId("node-to-set-name"); 
+        node.setNodeName("span", callback);
+    }
+    testSuite.push(InspectorTest.generateUndoTest(testSetNodeName));
+
+
+    function testSetNodeValue(callback)
+    {
+        var node = InspectorTest.expandedNodeWithId("node-to-set-value"); 
+        node.firstChild.setNodeValue("New Text", callback);
+    }
+    testSuite.push(InspectorTest.generateUndoTest(testSetNodeValue));
+
+    function testEditAsHTML(callback)
+    {
+        var node = InspectorTest.expandedNodeWithId("node-to-edit-as-html"); 
+        node.setOuterHTML("<div id=\"node-to-edit-as-html\"><div id=\"span2\">Text2</div></div>", callback);
+    }
+    testSuite.push(InspectorTest.generateUndoTest(testEditAsHTML));
+
+
+    InspectorTest.runTestSuite(testSuite);
+}
+
+</script>
+</head>
+
+<body onload="runTest()">
+<p>
+Tests that DOM modifications done in the Elements panel are undoable.
+</p>
+
+<div style="display:none">
+    <div id="testRemove">
+        <div id="node-to-remove"></div>
+    </div>
+
+    <div id="testSetNodeName">
+        <div id="node-to-set-name"></div>
+    </div>
+
+    <div id="testSetNodeValue">
+        <div id="node-to-set-value">Text</div>
+    </div>
+
+    <div id="testEditAsHTML">
+        <div id="node-to-edit-as-html"><span id="span">Text</span></div>
+    </div>
+</div>
+
+</body>
+</html>
diff --git a/LayoutTests/inspector/styles/undo-add-property-expected.txt b/LayoutTests/inspector/styles/undo-add-property-expected.txt
new file mode 100644 (file)
index 0000000..92eee9a
--- /dev/null
@@ -0,0 +1,46 @@
+Tests that adding a property is undone properly.
+
+Initial value
+[expanded] 
+element.style  { ()
+
+======== Matched CSS Rules ========
+[expanded] 
+#container  { (undo-add-property.html:7)
+font-weight: bold;
+
+[expanded] 
+div  { (user agent stylesheet)
+display: block;
+
+
+After adding property
+[expanded] 
+element.style  { ()
+
+======== Matched CSS Rules ========
+[expanded] 
+#container  { (undo-add-property.html:7)
+font-weight: bold;
+margin-left: 1px;
+
+[expanded] 
+div  { (user agent stylesheet)
+display: block;
+
+
+After undo
+[expanded] 
+element.style  { ()
+
+======== Matched CSS Rules ========
+[expanded] 
+#container  { (undo-add-property.html:7)
+font-weight: bold;
+
+[expanded] 
+div  { (user agent stylesheet)
+display: block;
+
+
+
diff --git a/LayoutTests/inspector/styles/undo-add-property.html b/LayoutTests/inspector/styles/undo-add-property.html
new file mode 100644 (file)
index 0000000..f9beb59
--- /dev/null
@@ -0,0 +1,65 @@
+<html>
+<head>
+<script src="../../http/tests/inspector/inspector-test.js"></script>
+<script src="../../http/tests/inspector/elements-test.js"></script>
+
+<style>
+#container {
+  font-weight: bold
+}
+</style>
+
+<script>
+
+function test()
+{
+    InspectorTest.selectNodeAndWaitForStyles("container", step1);
+
+    function step1()
+    {
+        InspectorTest.addResult("Initial value");
+        InspectorTest.dumpSelectedElementStyles(true);
+
+        var treeItem = InspectorTest.getMatchedStylePropertyTreeItem("font-weight");
+        var treeElement = treeItem.section.addNewBlankProperty();
+        treeElement.startEditing();
+        treeElement.nameElement.textContent = "margin-left";
+        treeElement.nameElement.dispatchEvent(InspectorTest.createKeyEvent("Enter"));
+        treeElement.valueElement.textContent = "1px";
+        treeElement.nameElement.dispatchEvent(InspectorTest.createKeyEvent("Enter"));
+        InspectorTest.runAfterPendingDispatches(step2);
+    }
+
+    function step2()
+    {
+        InspectorTest.addResult("After adding property");
+        InspectorTest.dumpSelectedElementStyles(true);
+        DOMAgent.undo(step3);
+    }
+
+    function step3()
+    {
+        InspectorTest.waitForStyles("container", step4);
+        WebInspector.panels.elements._updateSidebars();
+    }
+
+    function step4()
+    {
+        InspectorTest.addResult("After undo");
+        InspectorTest.dumpSelectedElementStyles(true);
+        InspectorTest.completeTest();
+    }
+}
+</script>
+</head>
+
+<body onload="runTest()">
+<p>
+Tests that adding a property is undone properly.
+</p>
+
+<div id="container">
+</div>
+
+</body>
+</html>
diff --git a/LayoutTests/inspector/styles/undo-change-property-expected.txt b/LayoutTests/inspector/styles/undo-change-property-expected.txt
new file mode 100644 (file)
index 0000000..25dc485
--- /dev/null
@@ -0,0 +1,45 @@
+Tests that changing a property is undone properly.
+
+Initial value
+[expanded] 
+element.style  { ()
+
+======== Matched CSS Rules ========
+[expanded] 
+#container  { (undo-change-property.html:7)
+font-weight: bold;
+
+[expanded] 
+div  { (user agent stylesheet)
+display: block;
+
+
+After changing property
+[expanded] 
+element.style  { ()
+
+======== Matched CSS Rules ========
+[expanded] 
+#container  { (undo-change-property.html:7)
+font-weight: normal;
+
+[expanded] 
+div  { (user agent stylesheet)
+display: block;
+
+
+After undo
+[expanded] 
+element.style  { ()
+
+======== Matched CSS Rules ========
+[expanded] 
+#container  { (undo-change-property.html:7)
+font-weight: bold;
+
+[expanded] 
+div  { (user agent stylesheet)
+display: block;
+
+
+
diff --git a/LayoutTests/inspector/styles/undo-change-property.html b/LayoutTests/inspector/styles/undo-change-property.html
new file mode 100644 (file)
index 0000000..238272f
--- /dev/null
@@ -0,0 +1,60 @@
+<html>
+<head>
+<script src="../../http/tests/inspector/inspector-test.js"></script>
+<script src="../../http/tests/inspector/elements-test.js"></script>
+
+<style>
+#container {
+  font-weight: bold
+}
+</style>
+
+<script>
+
+function test()
+{
+    InspectorTest.selectNodeAndWaitForStyles("container", step1);
+
+    function step1()
+    {
+        InspectorTest.addResult("Initial value");
+        InspectorTest.dumpSelectedElementStyles(true);
+
+        var treeItem = InspectorTest.getMatchedStylePropertyTreeItem("font-weight");
+        treeItem.applyStyleText("font-weight: normal", true, false);
+        InspectorTest.waitForStyles("container", step2);
+    }
+
+    function step2()
+    {
+        InspectorTest.addResult("After changing property");
+        InspectorTest.dumpSelectedElementStyles(true);
+        DOMAgent.undo(step3);
+    }
+
+    function step3()
+    {
+        InspectorTest.waitForStyles("container", step4);
+        WebInspector.panels.elements._updateSidebars();
+    }
+
+    function step4()
+    {
+        InspectorTest.addResult("After undo");
+        InspectorTest.dumpSelectedElementStyles(true);
+        InspectorTest.completeTest();
+    }
+}
+</script>
+</head>
+
+<body onload="runTest()">
+<p>
+Tests that changing a property is undone properly.
+</p>
+
+<div id="container">
+</div>
+
+</body>
+</html>
diff --git a/LayoutTests/inspector/styles/undo-property-toggle-expected.txt b/LayoutTests/inspector/styles/undo-property-toggle-expected.txt
new file mode 100644 (file)
index 0000000..dc518d5
--- /dev/null
@@ -0,0 +1,9 @@
+Tests that disabling style is undone properly.
+
+Before disable
+font-weight: bold;
+After disable
+/-- overloaded --/ /-- disabled --/ font-weight: bold;
+After undo
+font-weight: bold;
+
diff --git a/LayoutTests/inspector/styles/undo-property-toggle.html b/LayoutTests/inspector/styles/undo-property-toggle.html
new file mode 100644 (file)
index 0000000..ca2cf03
--- /dev/null
@@ -0,0 +1,52 @@
+<html>
+<head>
+<script src="../../http/tests/inspector/inspector-test.js"></script>
+<script src="../../http/tests/inspector/elements-test.js"></script>
+<script>
+
+function test()
+{
+    InspectorTest.selectNodeAndWaitForStyles("container", step1);
+
+    function step1(node)
+    {
+        InspectorTest.addResult("Before disable");
+        var treeItem = InspectorTest.getElementStylePropertyTreeItem("font-weight");
+        InspectorTest.dumpStyleTreeItem(treeItem, "");
+
+        treeItem.toggleEnabled({ target: { checked: false } });
+        InspectorTest.waitForStyles("container", step2);
+    }
+
+    function step2()
+    {
+        InspectorTest.addResult("After disable");
+        var treeItem = InspectorTest.getElementStylePropertyTreeItem("font-weight");
+        InspectorTest.dumpStyleTreeItem(treeItem, "");
+
+        DOMAgent.undo();
+        InspectorTest.waitForStyles("container", step3);
+    }
+
+    function step3()
+    {
+        InspectorTest.addResult("After undo");
+        var treeItem = InspectorTest.getElementStylePropertyTreeItem("font-weight");
+        InspectorTest.dumpStyleTreeItem(treeItem, "");
+
+        InspectorTest.completeTest();
+    }
+}
+</script>
+</head>
+
+<body onload="runTest()">
+<p>
+Tests that disabling style is undone properly.
+</p>
+
+<div id="container" style="font-weight:bold">
+</div>
+
+</body>
+</html>
index 75e3ffa..dd04cf6 100644 (file)
@@ -901,6 +901,7 @@ SET(WebCore_SOURCES
     inspector/InspectorFileSystemAgent.cpp
     inspector/InspectorFrontendClientLocal.cpp
     inspector/InspectorFrontendHost.cpp
+    inspector/InspectorHistory.cpp
     inspector/InspectorIndexedDBAgent.cpp
     inspector/InspectorInstrumentation.cpp
     inspector/InspectorMemoryAgent.cpp
index 3d4040e..c0714d1 100644 (file)
@@ -1,3 +1,116 @@
+2012-02-07  Pavel Feldman  <pfeldman@google.com>
+
+        Web Inspector: add generic support for undo-ing DOM edits.
+        https://bugs.webkit.org/show_bug.cgi?id=77875
+
+        Reviewed by Yury Semikhatsky.
+
+        This change introduces InspectorHistory::Action that encapsulates all DOM modifications
+        initiated by the inspector. There is a way to undo these actions up until the undoable
+        state marker.
+
+        Tests: inspector/elements/undo-dom-edits-2.html
+               inspector/elements/undo-dom-edits.html
+               inspector/styles/undo-add-property.html
+               inspector/styles/undo-change-property.html
+               inspector/styles/undo-property-toggle.html
+
+        * CMakeLists.txt:
+        * GNUmakefile.list.am:
+        * Target.pri:
+        * WebCore.gypi:
+        * WebCore.vcproj/WebCore.vcproj:
+        * WebCore.xcodeproj/project.pbxproj:
+        * inspector/Inspector.json:
+        * inspector/InspectorAllInOne.cpp:
+        * inspector/InspectorCSSAgent.cpp:
+        (InspectorCSSAgent::StyleSheetAction):
+        (WebCore::InspectorCSSAgent::StyleSheetAction::StyleSheetAction):
+        (WebCore::InspectorCSSAgent::StyleSheetAction::perform):
+        (WebCore::InspectorCSSAgent::StyleSheetAction::undo):
+        (WebCore):
+        (InspectorCSSAgent::SetStyleSheetTextAction):
+        (WebCore::InspectorCSSAgent::SetStyleSheetTextAction::SetStyleSheetTextAction):
+        (WebCore::InspectorCSSAgent::SetStyleSheetTextAction::perform):
+        (WebCore::InspectorCSSAgent::SetStyleSheetTextAction::undo):
+        (InspectorCSSAgent::SetPropertyTextAction):
+        (WebCore::InspectorCSSAgent::SetPropertyTextAction::SetPropertyTextAction):
+        (WebCore::InspectorCSSAgent::SetPropertyTextAction::toString):
+        (WebCore::InspectorCSSAgent::SetPropertyTextAction::perform):
+        (WebCore::InspectorCSSAgent::SetPropertyTextAction::undo):
+        (WebCore::InspectorCSSAgent::SetPropertyTextAction::mergeId):
+        (WebCore::InspectorCSSAgent::SetPropertyTextAction::merge):
+        (InspectorCSSAgent::TogglePropertyAction):
+        (WebCore::InspectorCSSAgent::TogglePropertyAction::TogglePropertyAction):
+        (WebCore::InspectorCSSAgent::TogglePropertyAction::perform):
+        (WebCore::InspectorCSSAgent::TogglePropertyAction::undo):
+        (WebCore::InspectorCSSAgent::getStyleSheetText):
+        (WebCore::InspectorCSSAgent::setStyleSheetText):
+        (WebCore::InspectorCSSAgent::setPropertyText):
+        (WebCore::InspectorCSSAgent::toggleProperty):
+        * inspector/InspectorCSSAgent.h:
+        (InspectorCSSAgent):
+        * inspector/InspectorDOMAgent.cpp:
+        (WebCore::InspectorDOMAgent::InspectorDOMAgent):
+        (WebCore::InspectorDOMAgent::reset):
+        (WebCore::InspectorDOMAgent::setAttributeValue):
+        (WebCore::InspectorDOMAgent::setAttributesAsText):
+        (WebCore::InspectorDOMAgent::removeAttribute):
+        (WebCore::InspectorDOMAgent::removeNode):
+        (WebCore::InspectorDOMAgent::setNodeName):
+        (WebCore::InspectorDOMAgent::setOuterHTML):
+        (WebCore::InspectorDOMAgent::setNodeValue):
+        (WebCore::InspectorDOMAgent::moveTo):
+        (WebCore::InspectorDOMAgent::undo):
+        (WebCore):
+        (WebCore::InspectorDOMAgent::markUndoableState):
+        * inspector/InspectorDOMAgent.h:
+        (InspectorDOMAgent):
+        (WebCore::InspectorDOMAgent::history):
+        * inspector/InspectorHistory.cpp: Added.
+        (WebCore::InspectorHistory::Action::Action):
+        (WebCore):
+        (WebCore::InspectorHistory::Action::~Action):
+        (WebCore::InspectorHistory::Action::toString):
+        (WebCore::InspectorHistory::Action::isUndoableStateMark):
+        (WebCore::InspectorHistory::Action::mergeId):
+        (WebCore::InspectorHistory::Action::merge):
+        (WebCore::InspectorHistory::InspectorHistory):
+        (WebCore::InspectorHistory::~InspectorHistory):
+        (WebCore::InspectorHistory::perform):
+        (WebCore::InspectorHistory::markUndoableState):
+        (WebCore::InspectorHistory::undo):
+        (WebCore::InspectorHistory::reset):
+        * inspector/InspectorHistory.h: Added.
+        (WebCore):
+        (InspectorHistory):
+        (Action):
+        * inspector/InspectorStyleSheet.cpp:
+        (WebCore::InspectorStyle::setPropertyText):
+        (WebCore::InspectorStyle::styleText):
+        (WebCore::InspectorStyleSheet::addRule):
+        (WebCore::InspectorStyleSheet::buildObjectForStyleSheet):
+        (WebCore::InspectorStyleSheet::buildObjectForStyle):
+        (WebCore::InspectorStyleSheet::setPropertyText):
+        (WebCore::InspectorStyleSheet::getText):
+        (WebCore::InspectorStyleSheetForInlineStyle::getText):
+        * inspector/InspectorStyleSheet.h:
+        (InspectorStyle):
+        (InspectorStyleSheet):
+        (InspectorStyleSheetForInlineStyle):
+        * inspector/front-end/CSSStyleModel.js:
+        (WebInspector.CSSProperty.prototype.setText):
+        * inspector/front-end/ElementsPanel.js:
+        (WebInspector.ElementsPanel.prototype._selectedNodeChanged):
+        (WebInspector.ElementsPanel.prototype._updateSidebars):
+        (WebInspector.ElementsPanel.prototype.handleShortcut):
+        * inspector/front-end/StylesSidebarPane.js:
+        (WebInspector.StylePropertiesSection.prototype.onpopulate):
+        (WebInspector.StylePropertiesSection.prototype.addNewBlankProperty):
+        (WebInspector.ComputedStylePropertiesSection.prototype.onpopulate):
+        (WebInspector.StylePropertyTreeElement):
+        (WebInspector.StylePropertyTreeElement.prototype):
+
 2012-02-07  Chris Guan  <chris.guan@torchmobile.com.cn>
 
         [Blackberry] Clean up Networkjob and Networkmanger: remove unused variables in release build and change some public functions into be private ones
index c1ec70c..f5762df 100644 (file)
@@ -2390,6 +2390,8 @@ webcore_sources += \
        Source/WebCore/inspector/InspectorFrontendClientLocal.h \
        Source/WebCore/inspector/InspectorFrontendHost.cpp \
        Source/WebCore/inspector/InspectorFrontendHost.h \
+       Source/WebCore/inspector/InspectorHistory.cpp \
+       Source/WebCore/inspector/InspectorHistory.h \
        Source/WebCore/inspector/InspectorIndexedDBAgent.h \
        Source/WebCore/inspector/InspectorIndexedDBAgent.cpp \
        Source/WebCore/inspector/InspectorInstrumentation.cpp \
index c6a2ece..d34c6d9 100644 (file)
@@ -877,6 +877,7 @@ SOURCES += \
     inspector/InspectorDOMStorageResource.cpp \
     inspector/InspectorFrontendClientLocal.cpp \
     inspector/InspectorFrontendHost.cpp \
+    inspector/InspectorHistory.cpp \
     inspector/InspectorInstrumentation.cpp \
     inspector/InspectorMemoryAgent.cpp \
     inspector/InspectorPageAgent.cpp \
@@ -1943,6 +1944,7 @@ HEADERS += \
     inspector/InspectorFrontendClient.h \
     inspector/InspectorFrontendClientLocal.h \
     inspector/InspectorFrontendHost.h \
+    inspector/InspectorHistory.h \
     inspector/InspectorInstrumentation.h \
     inspector/InspectorMemoryAgent.h \
     inspector/InspectorPageAgent.h \
index 1959ddf..07a340f 100644 (file)
             'inspector/InspectorFrontendClientLocal.cpp',
             'inspector/InspectorFrontendHost.cpp',
             'inspector/InspectorFrontendHost.h',
+            'inspector/InspectorHistory.cpp',
+            'inspector/InspectorHistory.h',
             'inspector/InspectorIndexedDBAgent.cpp',
             'inspector/InspectorIndexedDBAgent.h',
             'inspector/InspectorInstrumentation.cpp',
index 35d46da..0f4dc47 100755 (executable)
                                >
                        </File>
                        <File
+                               RelativePath="..\inspector\InspectorHistory.cpp"
+                               >
+                               <FileConfiguration
+                                       Name="Release|Win32"
+                                       ExcludedFromBuild="true"
+                                       >
+                                       <Tool
+                                               Name="VCCLCompilerTool"
+                                       />
+                               </FileConfiguration>
+                               <FileConfiguration
+                                       Name="Production|Win32"
+                                       ExcludedFromBuild="true"
+                                       >
+                                       <Tool
+                                               Name="VCCLCompilerTool"
+                                       />
+                               </FileConfiguration>
+                       </File>
+                       <File
+                               RelativePath="..\inspector\InspectorHistory.h"
+                               >
+                       </File>
+                       <File
                                RelativePath="..\inspector\InspectorIndexedDBAgent.cpp"
                                >
                                <FileConfiguration
index dbd7f0b..4a79803 100644 (file)
                7A1F2B52126C61B20006A7E6 /* InspectorClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A1F2B51126C61B20006A7E6 /* InspectorClient.cpp */; };
                7A24587B1021EAF4000A00AA /* InspectorDOMAgent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A2458791021EAF4000A00AA /* InspectorDOMAgent.cpp */; };
                7A24587C1021EAF4000A00AA /* InspectorDOMAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A24587A1021EAF4000A00AA /* InspectorDOMAgent.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               7A54857F14E02D51006AE05A /* InspectorHistory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A54857D14E02D51006AE05A /* InspectorHistory.cpp */; };
+               7A54858014E02D51006AE05A /* InspectorHistory.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A54857E14E02D51006AE05A /* InspectorHistory.h */; };
                7A674BDB0F9EBF4E006CF099 /* PageGroupLoadDeferrer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A674BD90F9EBF4E006CF099 /* PageGroupLoadDeferrer.cpp */; };
                7A674BDC0F9EBF4E006CF099 /* PageGroupLoadDeferrer.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A674BDA0F9EBF4E006CF099 /* PageGroupLoadDeferrer.h */; };
                7A74ECBA101839A600BF939E /* InspectorDOMStorageAgent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A74ECB8101839A500BF939E /* InspectorDOMStorageAgent.cpp */; };
                7A1F2B51126C61B20006A7E6 /* InspectorClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorClient.cpp; sourceTree = "<group>"; };
                7A2458791021EAF4000A00AA /* InspectorDOMAgent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorDOMAgent.cpp; sourceTree = "<group>"; };
                7A24587A1021EAF4000A00AA /* InspectorDOMAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorDOMAgent.h; sourceTree = "<group>"; };
+               7A54857D14E02D51006AE05A /* InspectorHistory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorHistory.cpp; sourceTree = "<group>"; };
+               7A54857E14E02D51006AE05A /* InspectorHistory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorHistory.h; sourceTree = "<group>"; };
                7A563E5412DE32B000F4536D /* InjectedScriptSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InjectedScriptSource.h; sourceTree = "<group>"; };
                7A563F9512DF5C9100F4536D /* InjectedScriptSource.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = InjectedScriptSource.js; sourceTree = "<group>"; };
                7A674BD90F9EBF4E006CF099 /* PageGroupLoadDeferrer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PageGroupLoadDeferrer.cpp; sourceTree = "<group>"; };
                                7A0E770B10C00A8800A0276E /* InspectorFrontendHost.cpp */,
                                7A0E770C10C00A8800A0276E /* InspectorFrontendHost.h */,
                                7A0E770D10C00A8800A0276E /* InspectorFrontendHost.idl */,
+                               7A54857D14E02D51006AE05A /* InspectorHistory.cpp */,
+                               7A54857E14E02D51006AE05A /* InspectorHistory.h */,
                                7ACD88D114C08BD60084EDD2 /* InspectorIndexedDBAgent.cpp */,
                                7ACD88D214C08BD60084EDD2 /* InspectorIndexedDBAgent.h */,
                                20D629241253690B00081543 /* InspectorInstrumentation.cpp */,
                                1AAADDBF14DC640700AF64B3 /* ScrollingTreeState.h in Headers */,
                                1AAADDE414DC8C8F00AF64B3 /* ScrollingTreeNode.h in Headers */,
                                1AAADDE914DC8DF800AF64B3 /* ScrollingTreeNodeMac.h in Headers */,
+                               7A54858014E02D51006AE05A /* InspectorHistory.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                1AAADDDA14DC74EC00AF64B3 /* ScrollingTreeStateMac.mm in Sources */,
                                1AAADDE314DC8C8F00AF64B3 /* ScrollingTreeNode.cpp in Sources */,
                                1AAADDE814DC8DF800AF64B3 /* ScrollingTreeNodeMac.mm in Sources */,
+                               7A54857F14E02D51006AE05A /* InspectorHistory.cpp in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index 31e8d8a..e204d9c 100644 (file)
                 ],
                 "description": "Toggles mouse event-based touch event emulation.",
                 "hidden": true
+            },
+            {
+                "name": "undo",
+                "description": "Undoes the last performed action.",
+                "hidden": true
+            },
+            {
+                "name": "markUndoableState",
+                "description": "Marks last undoable state.",
+                "hidden": true
             }
         ],
         "events": [
index 789a271..35173cb 100644 (file)
@@ -42,6 +42,7 @@
 #include "InspectorFileSystemAgent.cpp"
 #include "InspectorFrontendClientLocal.cpp"
 #include "InspectorFrontendHost.cpp"
+#include "InspectorHistory.cpp"
 #include "InspectorIndexedDBAgent.cpp"
 #include "InspectorInstrumentation.cpp"
 #include "InspectorMemoryAgent.cpp"
index 013f4f3..4e42ac4 100644 (file)
@@ -39,6 +39,7 @@
 #include "DOMWindow.h"
 #include "HTMLHeadElement.h"
 #include "InspectorDOMAgent.h"
+#include "InspectorHistory.h"
 #include "InspectorState.h"
 #include "InspectorValues.h"
 #include "InstrumentingAgents.h"
@@ -217,6 +218,158 @@ PassRefPtr<InspectorObject> SelectorProfile::toInspectorObject() const
     return result.release();
 }
 
+class InspectorCSSAgent::StyleSheetAction : public InspectorHistory::Action {
+    WTF_MAKE_NONCOPYABLE(StyleSheetAction);
+public:
+    StyleSheetAction(const String& name, InspectorCSSAgent* cssAgent, const String& styleSheetId)
+        : InspectorHistory::Action(name)
+        , m_cssAgent(cssAgent)
+        , m_styleSheetId(styleSheetId)
+    {
+    }
+
+    virtual bool perform(ErrorString* errorString)
+    {
+        InspectorStyleSheet* styleSheet = m_cssAgent->assertStyleSheetForId(errorString, m_styleSheetId);
+        if (!styleSheet)
+            return false;
+        return perform(styleSheet, errorString);
+    }
+
+    virtual bool undo(ErrorString* errorString)
+    {
+        InspectorStyleSheet* styleSheet = m_cssAgent->assertStyleSheetForId(errorString, m_styleSheetId);
+        if (!styleSheet)
+            return false;
+        return undo(styleSheet, errorString);
+    }
+
+    virtual bool perform(InspectorStyleSheet*, ErrorString*) = 0;
+
+    virtual bool undo(InspectorStyleSheet*, ErrorString*) = 0;
+
+protected:
+    InspectorCSSAgent* m_cssAgent;
+    String m_styleSheetId;
+};
+
+class InspectorCSSAgent::SetStyleSheetTextAction : public InspectorCSSAgent::StyleSheetAction {
+    WTF_MAKE_NONCOPYABLE(SetStyleSheetTextAction);
+public:
+    SetStyleSheetTextAction(InspectorCSSAgent* cssAgent, const String& styleSheetId, const String& text)
+        : InspectorCSSAgent::StyleSheetAction("SetStyleSheetText", cssAgent, styleSheetId)
+        , m_text(text)
+    {
+    }
+
+    virtual bool perform(InspectorStyleSheet* inspectorStyleSheet, ErrorString*)
+    {
+        if (!inspectorStyleSheet->getText(&m_oldText))
+            return false;
+
+        if (inspectorStyleSheet->setText(m_text)) {
+            inspectorStyleSheet->reparseStyleSheet(m_text);
+            return true;
+        }
+        return false;
+    }
+
+    virtual bool undo(InspectorStyleSheet* inspectorStyleSheet, ErrorString*)
+    {
+        if (inspectorStyleSheet->setText(m_oldText)) {
+            inspectorStyleSheet->reparseStyleSheet(m_oldText);
+            return true;
+        }
+        return false;
+    }
+
+private:
+    String m_text;
+    String m_oldText;
+};
+
+class InspectorCSSAgent::SetPropertyTextAction : public InspectorCSSAgent::StyleSheetAction {
+    WTF_MAKE_NONCOPYABLE(SetPropertyTextAction);
+public:
+    SetPropertyTextAction(InspectorCSSAgent* cssAgent, const String& styleSheetId, const InspectorCSSId& cssId, unsigned propertyIndex, const String& text, bool overwrite)
+        : InspectorCSSAgent::StyleSheetAction("SetPropertyText", cssAgent, styleSheetId)
+        , m_cssId(cssId)
+        , m_propertyIndex(propertyIndex)
+        , m_text(text)
+        , m_overwrite(overwrite)
+    {
+    }
+
+    virtual String toString()
+    {
+        return mergeId() + ": " + m_oldText + " -> " + m_text;
+    }
+
+    virtual bool perform(InspectorStyleSheet* inspectorStyleSheet, ErrorString* errorString)
+    {
+        String oldText;
+        bool result = inspectorStyleSheet->setPropertyText(errorString, m_cssId, m_propertyIndex, m_text, m_overwrite, &oldText);
+        m_oldText = oldText.stripWhiteSpace();
+        // FIXME: remove this once the model handles this case.
+        if (!m_oldText.endsWith(";"))
+            m_oldText += ";";
+        return result;
+    }
+
+    virtual bool undo(InspectorStyleSheet* inspectorStyleSheet, ErrorString* errorString)
+    {
+        String placeholder;
+        return inspectorStyleSheet->setPropertyText(errorString, m_cssId, m_propertyIndex, m_overwrite ? m_oldText : "", true, &placeholder);
+    }
+
+    virtual String mergeId()
+    {
+        return String::format("SetPropertyText %s:%u:%s", m_styleSheetId.utf8().data(), m_propertyIndex, m_overwrite ? "true" : "false");
+    }
+
+    virtual void merge(PassOwnPtr<Action> action)
+    {
+        ASSERT(action->mergeId() == mergeId());
+
+        SetPropertyTextAction* other = static_cast<SetPropertyTextAction*>(action.get());
+        m_text = other->m_text;
+    }
+
+private:
+    InspectorCSSId m_cssId;
+    unsigned m_propertyIndex;
+    String m_text;
+    String m_oldText;
+    bool m_overwrite;
+};
+
+class InspectorCSSAgent::TogglePropertyAction : public InspectorCSSAgent::StyleSheetAction {
+    WTF_MAKE_NONCOPYABLE(TogglePropertyAction);
+public:
+    TogglePropertyAction(InspectorCSSAgent* cssAgent, const String& styleSheetId, const InspectorCSSId& cssId, unsigned propertyIndex, bool disable)
+        : InspectorCSSAgent::StyleSheetAction("ToggleProperty", cssAgent, styleSheetId)
+        , m_cssId(cssId)
+        , m_propertyIndex(propertyIndex)
+        , m_disable(disable)
+    {
+    }
+
+    virtual bool perform(InspectorStyleSheet* inspectorStyleSheet, ErrorString* errorString)
+    {
+        return inspectorStyleSheet->toggleProperty(errorString, m_cssId, m_propertyIndex, m_disable);
+    }
+
+    virtual bool undo(InspectorStyleSheet* inspectorStyleSheet, ErrorString* errorString)
+    {
+      return inspectorStyleSheet->toggleProperty(errorString, m_cssId, m_propertyIndex, !m_disable);
+    }
+
+private:
+    InspectorCSSId m_cssId;
+    unsigned m_propertyIndex;
+    bool m_disable;
+};
+
 // static
 CSSStyleRule* InspectorCSSAgent::asCSSStyleRule(CSSRule* rule)
 {
@@ -438,19 +591,13 @@ void InspectorCSSAgent::getStyleSheetText(ErrorString* errorString, const String
     if (!inspectorStyleSheet)
         return;
 
-    inspectorStyleSheet->text(result);
+    inspectorStyleSheet->getText(result);
 }
 
 void InspectorCSSAgent::setStyleSheetText(ErrorString* errorString, const String& styleSheetId, const String& text)
 {
-    InspectorStyleSheet* inspectorStyleSheet = assertStyleSheetForId(errorString, styleSheetId);
-    if (!inspectorStyleSheet)
-        return;
-
-    if (inspectorStyleSheet->setText(text))
-        inspectorStyleSheet->reparseStyleSheet(text);
-    else
-        *errorString = "Internal error setting style sheet text";
+    m_domAgent->history()->perform(adoptPtr(new SetStyleSheetTextAction(this, styleSheetId, text)), errorString);
+    m_domAgent->history()->markUndoableState();
 }
 
 void InspectorCSSAgent::setPropertyText(ErrorString* errorString, const RefPtr<InspectorObject>& fullStyleId, int propertyIndex, const String& text, bool overwrite, RefPtr<InspectorObject>& result)
@@ -462,7 +609,7 @@ void InspectorCSSAgent::setPropertyText(ErrorString* errorString, const RefPtr<I
     if (!inspectorStyleSheet)
         return;
 
-    bool success = inspectorStyleSheet->setPropertyText(errorString, compoundId, propertyIndex, text, overwrite);
+    bool success = m_domAgent->history()->perform(adoptPtr(new SetPropertyTextAction(this, compoundId.styleSheetId(), compoundId, propertyIndex, text, overwrite)), errorString);
     if (success)
         result = inspectorStyleSheet->buildObjectForStyle(inspectorStyleSheet->styleForId(compoundId));
 }
@@ -476,9 +623,10 @@ void InspectorCSSAgent::toggleProperty(ErrorString* errorString, const RefPtr<In
     if (!inspectorStyleSheet)
         return;
 
-    bool success = inspectorStyleSheet->toggleProperty(errorString, compoundId, propertyIndex, disable);
+    bool success = m_domAgent->history()->perform(adoptPtr(new TogglePropertyAction(this, compoundId.styleSheetId(), compoundId, propertyIndex, disable)), errorString);
     if (success)
         result = inspectorStyleSheet->buildObjectForStyle(inspectorStyleSheet->styleForId(compoundId));
+    m_domAgent->history()->markUndoableState();
 }
 
 void InspectorCSSAgent::setRuleSelector(ErrorString* errorString, const RefPtr<InspectorObject>& fullRuleId, const String& selector, RefPtr<InspectorObject>& result)
index 64e2724..ebb3136 100644 (file)
@@ -98,6 +98,11 @@ public:
     void didProcessRule();
 
 private:
+    class StyleSheetAction;
+    class SetStyleSheetTextAction;
+    class SetPropertyTextAction;
+    class TogglePropertyAction;
+
     InspectorCSSAgent(InstrumentingAgents*, InspectorState*, InspectorDOMAgent*);
 
     typedef HashMap<String, RefPtr<InspectorStyleSheet> > IdToInspectorStyleSheet;
index 490d1c9..94656c2 100644 (file)
 
 namespace WebCore {
 
+namespace {
+
+class DOMAction : public InspectorHistory::Action {
+public:
+    DOMAction(const String& name) : InspectorHistory::Action(name) { }
+
+    virtual bool perform(ErrorString* errorString)
+    {
+        ExceptionCode ec = 0;
+        bool result = perform(ec);
+        if (ec) {
+            ExceptionCodeDescription description(ec);
+            *errorString = description.name;
+        }
+        return result && !ec;
+    }
+
+    virtual bool undo(ErrorString* errorString)
+    {
+        ExceptionCode ec = 0;
+        bool result = undo(ec);
+        if (ec) {
+            ExceptionCodeDescription description(ec);
+            *errorString = description.name;
+        }
+        return result && !ec;
+    }
+
+    virtual bool perform(ExceptionCode&) = 0;
+
+    virtual bool undo(ExceptionCode&) = 0;
+
+private:
+    RefPtr<Node> m_parentNode;
+    RefPtr<Node> m_node;
+    RefPtr<Node> m_anchorNode;
+};
+
+class RemoveChildAction : public DOMAction {
+    WTF_MAKE_NONCOPYABLE(RemoveChildAction);
+public:
+    RemoveChildAction(Node* parentNode, Node* node)
+        : DOMAction("RemoveChild")
+        , m_parentNode(parentNode)
+        , m_node(node)
+    {
+    }
+
+    virtual bool perform(ExceptionCode& ec)
+    {
+        m_anchorNode = m_node->nextSibling();
+        return m_parentNode->removeChild(m_node.get(), ec);
+    }
+
+    virtual bool undo(ExceptionCode& ec)
+    {
+        return m_parentNode->insertBefore(m_node.get(), m_anchorNode.get(), ec);
+    }
+
+private:
+    RefPtr<Node> m_parentNode;
+    RefPtr<Node> m_node;
+    RefPtr<Node> m_anchorNode;
+};
+
+class InsertBeforeAction : public DOMAction {
+    WTF_MAKE_NONCOPYABLE(InsertBeforeAction);
+public:
+    InsertBeforeAction(Node* parentNode, Node* node, Node* anchorNode)
+        : DOMAction("InsertBefore")
+        , m_parentNode(parentNode)
+        , m_node(node)
+        , m_anchorNode(anchorNode)
+    {
+    }
+
+    virtual bool perform(ExceptionCode& ec)
+    {
+        if (m_node->parentNode()) {
+            m_removeChildAction = adoptPtr(new RemoveChildAction(m_node->parentNode(), m_node.get()));
+            if (!m_removeChildAction->perform(ec))
+                return false;
+        }
+        return m_parentNode->insertBefore(m_node.get(), m_anchorNode.get(), ec);
+    }
+
+    virtual bool undo(ExceptionCode& ec)
+    {
+        if (m_removeChildAction)
+            return m_removeChildAction->undo(ec);
+
+        return m_parentNode->removeChild(m_node.get(), ec);
+    }
+
+private:
+    RefPtr<Node> m_parentNode;
+    RefPtr<Node> m_node;
+    RefPtr<Node> m_anchorNode;
+    OwnPtr<RemoveChildAction> m_removeChildAction;
+};
+
+class RemoveAttributeAction : public DOMAction {
+    WTF_MAKE_NONCOPYABLE(RemoveAttributeAction);
+public:
+    RemoveAttributeAction(Element* element, const String& name)
+        : DOMAction("RemoveAttribute")
+        , m_element(element)
+        , m_name(name)
+    {
+    }
+
+    virtual bool perform(ExceptionCode&)
+    {
+        m_value = m_element->getAttribute(m_name);
+        m_element->removeAttribute(m_name);
+        return true;
+    }
+
+    virtual bool undo(ExceptionCode& ec)
+    {
+        m_element->setAttribute(m_name, m_value, ec);
+        return true;
+    }
+
+private:
+    RefPtr<Element> m_element;
+    String m_name;
+    String m_value;
+};
+
+class SetAttributeAction : public DOMAction {
+    WTF_MAKE_NONCOPYABLE(SetAttributeAction);
+public:
+    SetAttributeAction(Element* element, const String& name, const String& value)
+        : DOMAction("SetAttribute")
+        , m_element(element)
+        , m_name(name)
+        , m_value(value)
+        , m_hadAttribute(false)
+    {
+    }
+
+    virtual bool perform(ExceptionCode& ec)
+    {
+        m_hadAttribute = m_element->hasAttribute(m_name);
+        if (m_hadAttribute)
+            m_oldValue = m_element->getAttribute(m_name);
+        m_element->setAttribute(m_name, m_value, ec);
+        return !ec;
+    }
+
+    virtual bool undo(ExceptionCode& ec)
+    {
+        if (m_hadAttribute)
+            m_element->setAttribute(m_name, m_oldValue, ec);
+        else
+            m_element->removeAttribute(m_name);
+        return true;
+    }
+
+private:
+    RefPtr<Element> m_element;
+    String m_name;
+    String m_value;
+    bool m_hadAttribute;
+    String m_oldValue;
+};
+
+class SetOuterHTMLAction : public DOMAction {
+    WTF_MAKE_NONCOPYABLE(SetOuterHTMLAction);
+public:
+    SetOuterHTMLAction(Node* node, const String& html)
+        : DOMAction("SetOuterHTML")
+        , m_node(node)
+        , m_html(html)
+        , m_newNode(0)
+    {
+    }
+
+    virtual bool perform(ExceptionCode& ec)
+    {
+        m_oldHTML = createMarkup(m_node.get());
+        DOMEditor domEditor(m_node->ownerDocument());
+        m_newNode = domEditor.patchNode(m_node.get(), m_html, ec);
+        return !ec;
+    }
+
+    virtual bool undo(ExceptionCode& ec)
+    {
+        DOMEditor domEditor(m_node->ownerDocument());
+        domEditor.patchNode(m_node.get(), m_oldHTML, ec);
+        return !ec;
+    }
+
+    Node* newNode()
+    {
+        return m_newNode;
+    }
+
+private:
+    RefPtr<Node> m_node;
+    String m_html;
+    String m_oldHTML;
+    Node* m_newNode;
+};
+
+class ReplaceWholeTextAction : public DOMAction {
+    WTF_MAKE_NONCOPYABLE(ReplaceWholeTextAction);
+public:
+    ReplaceWholeTextAction(Text* textNode, const String& text)
+        : DOMAction("ReplaceWholeText")
+        , m_textNode(textNode)
+        , m_text(text)
+    {
+    }
+
+    virtual bool perform(ExceptionCode& ec)
+    {
+        m_oldText = m_textNode->wholeText();
+        m_textNode->replaceWholeText(m_text, ec);
+        return true;
+    }
+
+    virtual bool undo(ExceptionCode& ec)
+    {
+        m_textNode->replaceWholeText(m_oldText, ec);
+        return true;
+    }
+
+private:
+    RefPtr<Text> m_textNode;
+    String m_text;
+    String m_oldText;
+};
+
+}
+
 namespace DOMAgentState {
 static const char documentRequested[] = "documentRequested";
 
@@ -186,6 +423,7 @@ InspectorDOMAgent::InspectorDOMAgent(InstrumentingAgents* instrumentingAgents, I
     , m_domListener(0)
     , m_lastNodeId(1)
     , m_searchingForNode(false)
+    , m_history(adoptPtr(new InspectorHistory()))
 {
 }
 
@@ -253,7 +491,7 @@ Node* InspectorDOMAgent::highlightedNode() const
 
 void InspectorDOMAgent::reset()
 {
-    ErrorString error;
+    m_history->reset();
     m_searchResults.clear();
     discardBindings();
     if (m_revalidateStyleAttrTask)
@@ -518,10 +756,8 @@ void InspectorDOMAgent::setAttributeValue(ErrorString* errorString, int elementI
     if (!element)
         return;
 
-    ExceptionCode ec = 0;
-    element->setAttribute(name, value, ec);
-    if (ec)
-        *errorString = "Internal error: could not set attribute value";
+    m_history->perform(adoptPtr(new SetAttributeAction(element, name, value)), errorString);
+    m_history->markUndoableState();
 }
 
 void InspectorDOMAgent::setAttributesAsText(ErrorString* errorString, int elementId, const String& text, const String* const name)
@@ -551,7 +787,7 @@ void InspectorDOMAgent::setAttributesAsText(ErrorString* errorString, int elemen
 
     Element* childElement = toElement(child);
     if (!childElement->hasAttributes() && name) {
-        element->removeAttribute(*name);
+        m_history->perform(adoptPtr(new RemoveAttributeAction(element, *name)), errorString);
         return;
     }
 
@@ -561,20 +797,24 @@ void InspectorDOMAgent::setAttributesAsText(ErrorString* errorString, int elemen
         // Add attribute pair
         const Attribute* attribute = childElement->attributeItem(i);
         foundOriginalAttribute = foundOriginalAttribute || (name && attribute->name().toString() == *name);
-        element->setAttribute(attribute->name(), attribute->value());
+        if (!m_history->perform(adoptPtr(new SetAttributeAction(element, attribute->name().toString(), attribute->value())), errorString))
+            return;
     }
 
-    if (!foundOriginalAttribute && name) {
-        element->removeAttribute(*name);
-        return;
-    }
+    if (!foundOriginalAttribute && name && !name->stripWhiteSpace().isEmpty())
+        m_history->perform(adoptPtr(new RemoveAttributeAction(element, *name)), errorString);
+
+    m_history->markUndoableState();
 }
 
 void InspectorDOMAgent::removeAttribute(ErrorString* errorString, int elementId, const String& name)
 {
     Element* element = assertElement(errorString, elementId);
-    if (element)
-        element->removeAttribute(name);
+    if (!element)
+        return;
+
+    m_history->perform(adoptPtr(new RemoveAttributeAction(element, name)), errorString);
+    m_history->markUndoableState();
 }
 
 void InspectorDOMAgent::removeNode(ErrorString* errorString, int nodeId)
@@ -589,13 +829,11 @@ void InspectorDOMAgent::removeNode(ErrorString* errorString, int nodeId)
         return;
     }
 
-    ExceptionCode ec = 0;
-    parentNode->removeChild(node, ec);
-    if (ec)
-        *errorString = "Could not remove node due to DOM exception";
+    m_history->perform(adoptPtr(new RemoveChildAction(parentNode, node)), errorString);
+    m_history->markUndoableState();
 }
 
-void InspectorDOMAgent::setNodeName(ErrorString*, int nodeId, const String& tagName, int* newId)
+void InspectorDOMAgent::setNodeName(ErrorString* errorString, int nodeId, const String& tagName, int* newId)
 {
     *newId = 0;
 
@@ -613,16 +851,18 @@ void InspectorDOMAgent::setNodeName(ErrorString*, int nodeId, const String& tagN
 
     // Copy over the original node's children.
     Node* child;
-    while ((child = oldNode->firstChild()))
-        newElem->appendChild(child, ec);
+    while ((child = oldNode->firstChild())) {
+        if (!m_history->perform(adoptPtr(new InsertBeforeAction(newElem.get(), child, 0)), errorString))
+            return;
+    }
 
     // Replace the old node with the new node
     ContainerNode* parent = oldNode->parentNode();
-    parent->insertBefore(newElem, oldNode->nextSibling(), ec);
-    parent->removeChild(oldNode, ec);
-
-    if (ec)
+    if (!m_history->perform(adoptPtr(new InsertBeforeAction(parent, newElem.get(), oldNode->nextSibling())), errorString))
         return;
+    if (!m_history->perform(adoptPtr(new RemoveChildAction(parent, oldNode)), errorString))
+        return;
+    m_history->markUndoableState();
 
     *newId = pushNodePathToFrontend(newElem.get());
     if (m_childrenRequested.contains(nodeId))
@@ -658,13 +898,14 @@ void InspectorDOMAgent::setOuterHTML(ErrorString* errorString, int nodeId, const
 
     DOMEditor domEditor(document);
 
-    ExceptionCode ec = 0;
-    Node* newNode = domEditor.patchNode(node, outerHTML, ec);
-    if (ec) {
-        ExceptionCodeDescription description(ec);
-        *errorString = description.name;
+    OwnPtr<SetOuterHTMLAction> action = adoptPtr(new SetOuterHTMLAction(node, outerHTML));
+    SetOuterHTMLAction* rawAction = action.get();
+    Node* newNode = 0;
+    if (!m_history->perform(action.release(), errorString))
         return;
-    }
+    m_history->markUndoableState();
+
+    newNode = rawAction->newNode();
 
     if (!newNode) {
         // The only child node has been deleted.
@@ -689,11 +930,7 @@ void InspectorDOMAgent::setNodeValue(ErrorString* errorString, int nodeId, const
         return;
     }
 
-    Text* textNode = static_cast<Text*>(node);
-    ExceptionCode ec = 0;
-    textNode->replaceWholeText(value, ec);
-    if (ec)
-        *errorString = "DOM Error while setting the node value";
+    m_history->perform(adoptPtr(new ReplaceWholeTextAction(static_cast<Text*>(node), value)), errorString);
 }
 
 void InspectorDOMAgent::getEventListenersForNode(ErrorString*, int nodeId, RefPtr<InspectorArray>& listenersArray)
@@ -1030,33 +1267,31 @@ void InspectorDOMAgent::hideHighlight(ErrorString*)
     m_client->hideHighlight();
 }
 
-void InspectorDOMAgent::moveTo(ErrorString* error, int nodeId, int targetElementId, const int* const anchorNodeId, int* newNodeId)
+void InspectorDOMAgent::moveTo(ErrorString* errorString, int nodeId, int targetElementId, const int* const anchorNodeId, int* newNodeId)
 {
-    Node* node = assertNode(error, nodeId);
+    Node* node = assertNode(errorString, nodeId);
     if (!node)
         return;
 
-    Element* targetElement = assertElement(error, targetElementId);
+    Element* targetElement = assertElement(errorString, targetElementId);
     if (!targetElement)
         return;
 
     Node* anchorNode = 0;
     if (anchorNodeId && *anchorNodeId) {
-        anchorNode = assertNode(error, *anchorNodeId);
+        anchorNode = assertNode(errorString, *anchorNodeId);
         if (!anchorNode)
             return;
         if (anchorNode->parentNode() != targetElement) {
-            *error = "Anchor node must be child of the target element";
+            *errorString = "Anchor node must be child of the target element";
             return;
         }
     }
 
-    ExceptionCode ec = 0;
-    bool success = targetElement->insertBefore(node, anchorNode, ec);
-    if (ec || !success) {
-        *error = "Could not drop node";
+    if (!m_history->perform(adoptPtr(new InsertBeforeAction(targetElement, node, anchorNode)), errorString))
         return;
-    }
+    m_history->markUndoableState();
+
     *newNodeId = pushNodePathToFrontend(node);
 }
 
@@ -1073,6 +1308,16 @@ void InspectorDOMAgent::setTouchEmulationEnabled(ErrorString* error, bool enable
 #endif
 }
 
+void InspectorDOMAgent::undo(ErrorString* errorString)
+{
+    m_history->undo(errorString);
+}
+
+void InspectorDOMAgent::markUndoableState(ErrorString*)
+{
+    m_history->markUndoableState();
+}
+
 void InspectorDOMAgent::resolveNode(ErrorString* error, int nodeId, const String* const objectGroup, RefPtr<InspectorObject>& result)
 {
     String objectGroupName = objectGroup ? *objectGroup : "";
index 259fef6..86e3c67 100644 (file)
@@ -35,6 +35,7 @@
 #include "InjectedScriptManager.h"
 #include "InspectorBaseAgent.h"
 #include "InspectorFrontend.h"
+#include "InspectorHistory.h"
 #include "InspectorValues.h"
 #include "Timer.h"
 
@@ -143,6 +144,8 @@ public:
     virtual void highlightFrame(ErrorString*, const String& frameId, const RefPtr<InspectorObject>* color, const RefPtr<InspectorObject>* outlineColor);
     virtual void moveTo(ErrorString*, int nodeId, int targetNodeId, const int* anchorNodeId, int* newNodeId);
     virtual void setTouchEmulationEnabled(ErrorString*, bool);
+    virtual void undo(ErrorString*);
+    virtual void markUndoableState(ErrorString*);
 
     Node* highlightedNode() const;
 
@@ -176,6 +179,8 @@ public:
     void drawHighlight(GraphicsContext&) const;
     void getHighlight(Highlight*) const;
 
+    InspectorHistory* history() { return m_history.get(); }
+
     // We represent embedded doms as a part of the same hierarchy. Hence we treat children of frame owners differently.
     // We also skip whitespace text nodes conditionally. Following methods encapsulate these specifics.
     static Node* innerFirstChild(Node*);
@@ -240,6 +245,7 @@ private:
     OwnPtr<HighlightData> m_highlightData;
     RefPtr<Node> m_nodeToFocus;
     bool m_searchingForNode;
+    OwnPtr<InspectorHistory> m_history;
 };
 
 #endif // ENABLE(INSPECTOR)
diff --git a/Source/WebCore/inspector/InspectorHistory.cpp b/Source/WebCore/inspector/InspectorHistory.cpp
new file mode 100644 (file)
index 0000000..bf68c1f
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "InspectorHistory.h"
+
+#if ENABLE(INSPECTOR)
+
+#include "Node.h"
+
+namespace WebCore {
+
+namespace {
+
+class UndoableStateMark : public InspectorHistory::Action {
+public:
+    UndoableStateMark() : InspectorHistory::Action("[UndoableState]") { }
+
+    virtual bool perform(ErrorString*) { return true; }
+
+    virtual bool undo(ErrorString*) { return true; }
+
+    virtual bool isUndoableStateMark() { return true; }
+};
+
+}
+
+InspectorHistory::Action::Action(const String& name) : m_name(name)
+{
+}
+
+InspectorHistory::Action::~Action()
+{
+}
+
+String InspectorHistory::Action::toString()
+{
+    return m_name;
+}
+
+bool InspectorHistory::Action::isUndoableStateMark()
+{
+    return false;
+}
+
+String InspectorHistory::Action::mergeId()
+{
+    return "";
+}
+
+void InspectorHistory::Action::merge(PassOwnPtr<Action>)
+{
+}
+
+InspectorHistory::InspectorHistory() { }
+
+InspectorHistory::~InspectorHistory() { }
+
+bool InspectorHistory::perform(PassOwnPtr<Action> action, ErrorString* errorString)
+{
+    if (!action->perform(errorString))
+        return false;
+
+    if (!m_history.isEmpty() && !action->mergeId().isEmpty() && action->mergeId() == m_history.first()->mergeId())
+        m_history.first()->merge(action);
+    else
+        m_history.prepend(action);
+
+    return true;
+}
+
+void InspectorHistory::markUndoableState()
+{
+    m_history.prepend(adoptPtr(new UndoableStateMark()));
+}
+
+bool InspectorHistory::undo(ErrorString* errorString)
+{
+    while (!m_history.isEmpty() && m_history.first()->isUndoableStateMark())
+        m_history.removeFirst();
+
+    while (!m_history.isEmpty()) {
+        OwnPtr<Action> first = m_history.takeFirst();
+        if (!first->undo(errorString)) {
+            m_history.clear();
+            return false;
+        }
+
+        if (first->isUndoableStateMark())
+            break;
+    }
+
+    return true;
+}
+
+void InspectorHistory::reset()
+{
+    m_history.clear();
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(INSPECTOR)
diff --git a/Source/WebCore/inspector/InspectorHistory.h b/Source/WebCore/inspector/InspectorHistory.h
new file mode 100644 (file)
index 0000000..e506e05
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef InspectorHistory_h
+#define InspectorHistory_h
+
+#include "ExceptionCode.h"
+
+#include <wtf/Deque.h>
+#include <wtf/OwnPtr.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class ContainerNode;
+class Element;
+class Node;
+
+typedef String ErrorString;
+
+#if ENABLE(INSPECTOR)
+
+class InspectorHistory {
+    WTF_MAKE_NONCOPYABLE(InspectorHistory);
+public:
+    class Action {
+    public:
+        Action(const String& name);
+        virtual ~Action();
+        virtual String toString();
+
+        virtual bool isUndoableStateMark();
+
+        virtual String mergeId();
+        virtual void merge(PassOwnPtr<Action>);
+
+        virtual bool perform(ErrorString*) = 0;
+        virtual bool undo(ErrorString*) = 0;
+    private:
+        String m_name;
+    };
+
+    InspectorHistory();
+    virtual ~InspectorHistory();
+
+    bool perform(PassOwnPtr<Action>, ErrorString*);
+    void markUndoableState();
+
+    bool undo(ErrorString*);
+    void reset();
+
+private:
+    void dump();
+    Deque<OwnPtr<Action> > m_history;
+};
+
+#endif // ENABLE(INSPECTOR)
+
+} // namespace WebCore
+
+#endif // !defined(InspectorHistory_h)
index 57df992..c7c4edb 100644 (file)
@@ -280,7 +280,7 @@ PassRefPtr<InspectorArray> InspectorStyle::buildArrayForComputedStyle() const
 //
 // The propertyText (if not empty) is checked to be a valid style declaration (containing at least one property). If not,
 // the method returns false (denoting an error).
-bool InspectorStyle::setPropertyText(ErrorString* errorString, unsigned index, const String& propertyText, bool overwrite)
+bool InspectorStyle::setPropertyText(ErrorString* errorString, unsigned index, const String& propertyText, bool overwrite, String* oldText)
 {
     ASSERT(m_parentStyleSheet);
     DEFINE_STATIC_LOCAL(String, bogusPropertyName, ("-webkit-boguz-propertee"));
@@ -328,9 +328,10 @@ bool InspectorStyle::setPropertyText(ErrorString* errorString, unsigned index, c
     }
 
     InspectorStyleTextEditor editor(&allProperties, &m_disabledProperties, text, newLineAndWhitespaceDelimiters());
-    if (overwrite)
+    if (overwrite) {
+        *oldText = allProperties.at(index).rawText;
         editor.replaceProperty(index, propertyText);
-    else
+    else
         editor.insertProperty(index, propertyText, sourceData->styleSourceData->styleBodyRange.length());
 
     return applyStyleText(editor.styleText());
@@ -385,7 +386,7 @@ bool InspectorStyle::styleText(String* result) const
         return false;
 
     String styleSheetText;
-    bool success = m_parentStyleSheet->text(&styleSheetText);
+    bool success = m_parentStyleSheet->getText(&styleSheetText);
     if (!success)
         return false;
 
@@ -729,7 +730,7 @@ bool InspectorStyleSheet::setRuleSelector(const InspectorCSSId& id, const String
 CSSStyleRule* InspectorStyleSheet::addRule(const String& selector)
 {
     String styleSheetText;
-    bool success = text(&styleSheetText);
+    bool success = getText(&styleSheetText);
     if (!success)
         return 0;
 
@@ -777,7 +778,7 @@ PassRefPtr<InspectorObject> InspectorStyleSheet::buildObjectForStyleSheet()
     result->setArray("rules", cssRules.release());
 
     String styleSheetText;
-    bool success = text(&styleSheetText);
+    bool success = getText(&styleSheetText);
     if (success)
         result->setString("text", styleSheetText);
 
@@ -857,7 +858,7 @@ PassRefPtr<InspectorObject> InspectorStyleSheet::buildObjectForStyle(CSSStyleDec
     // Style text cannot be retrieved without stylesheet, so set cssText here.
     if (sourceData) {
         String sheetText;
-        bool success = text(&sheetText);
+        bool success = getText(&sheetText);
         if (success) {
             const SourceRange& bodyRange = sourceData->styleSourceData->styleBodyRange;
             result->setString("cssText", sheetText.substring(bodyRange.start, bodyRange.end - bodyRange.start));
@@ -867,7 +868,7 @@ PassRefPtr<InspectorObject> InspectorStyleSheet::buildObjectForStyle(CSSStyleDec
     return result.release();
 }
 
-bool InspectorStyleSheet::setPropertyText(ErrorString* errorString, const InspectorCSSId& id, unsigned propertyIndex, const String& text, bool overwrite)
+bool InspectorStyleSheet::setPropertyText(ErrorString* errorString, const InspectorCSSId& id, unsigned propertyIndex, const String& text, bool overwrite, String* oldText)
 {
     RefPtr<InspectorStyle> inspectorStyle = inspectorStyleForId(id);
     if (!inspectorStyle) {
@@ -875,7 +876,7 @@ bool InspectorStyleSheet::setPropertyText(ErrorString* errorString, const Inspec
         return false;
     }
 
-    return inspectorStyle->setPropertyText(errorString, propertyIndex, text, overwrite);
+    return inspectorStyle->setPropertyText(errorString, propertyIndex, text, overwrite, oldText);
 }
 
 bool InspectorStyleSheet::toggleProperty(ErrorString* errorString, const InspectorCSSId& id, unsigned propertyIndex, bool disable)
@@ -896,7 +897,7 @@ bool InspectorStyleSheet::toggleProperty(ErrorString* errorString, const Inspect
     return success;
 }
 
-bool InspectorStyleSheet::text(String* result) const
+bool InspectorStyleSheet::getText(String* result) const
 {
     if (!ensureText())
         return false;
@@ -1241,7 +1242,7 @@ void InspectorStyleSheetForInlineStyle::didModifyElementAttribute()
     m_ruleSourceData.clear();
 }
 
-bool InspectorStyleSheetForInlineStyle::text(String* result) const
+bool InspectorStyleSheetForInlineStyle::getText(String* result) const
 {
     if (!m_isStyleTextValid) {
         m_styleText = elementStyleText();
index f3195e8..d627b1d 100644 (file)
@@ -131,7 +131,7 @@ public:
     PassRefPtr<InspectorObject> buildObjectForStyle() const;
     PassRefPtr<InspectorArray> buildArrayForComputedStyle() const;
     bool hasDisabledProperties() const { return !m_disabledProperties.isEmpty(); }
-    bool setPropertyText(ErrorString*, unsigned index, const String& text, bool overwrite);
+    bool setPropertyText(ErrorString*, unsigned index, const String& text, bool overwrite, String* oldText);
     bool toggleProperty(ErrorString*, unsigned index, bool disable);
 
 private:
@@ -174,10 +174,10 @@ public:
     PassRefPtr<InspectorObject> buildObjectForStyleSheetInfo();
     PassRefPtr<InspectorObject> buildObjectForRule(CSSStyleRule*);
     PassRefPtr<InspectorObject> buildObjectForStyle(CSSStyleDeclaration*);
-    bool setPropertyText(ErrorString*, const InspectorCSSId&, unsigned propertyIndex, const String& text, bool overwrite);
+    bool setPropertyText(ErrorString*, const InspectorCSSId&, unsigned propertyIndex, const String& text, bool overwrite, String* oldPropertyText);
     bool toggleProperty(ErrorString*, const InspectorCSSId&, unsigned propertyIndex, bool disable);
 
-    virtual bool text(String* result) const;
+    virtual bool getText(String* result) const;
     virtual CSSStyleDeclaration* styleForId(const InspectorCSSId&) const;
 
 protected:
@@ -228,7 +228,7 @@ public:
     static PassRefPtr<InspectorStyleSheetForInlineStyle> create(const String& id, PassRefPtr<Element> element, const String& origin);
 
     void didModifyElementAttribute();
-    virtual bool text(String* result) const;
+    virtual bool getText(String* result) const;
     virtual CSSStyleDeclaration* styleForId(const InspectorCSSId& id) const { ASSERT_UNUSED(id, !id.ordinal()); return inlineStyle(); }
 
 protected:
index 1eb7d03..7b4872e 100644 (file)
@@ -643,6 +643,8 @@ WebInspector.CSSProperty.prototype = {
 
         // An index past all the properties adds a new property to the style.
         CSSAgent.setPropertyText(this.ownerStyle.id, this.index, propertyText, this.index < this.ownerStyle.pastLastSourcePropertyIndex(), callback.bind(this));
+        if (majorChange)
+            DOMAgent.markUndoableState();
     },
 
     setValue: function(newValue, majorChange, userCallback)
index 5256bd1..caae7e8 100644 (file)
@@ -167,6 +167,16 @@ WebInspector.ElementsPanel.prototype = {
 
         this.updateBreadcrumb(false);
 
+        this._updateSidebars();
+
+        if (selectedNode) {
+            ConsoleAgent.addInspectedNode(selectedNode.id);
+            this._lastValidSelectedNode = selectedNode;
+        }
+    },
+
+    _updateSidebars: function()
+    {
         for (var pane in this.sidebarPanes)
            this.sidebarPanes[pane].needsUpdate = true;
 
@@ -174,11 +184,6 @@ WebInspector.ElementsPanel.prototype = {
         this.updateMetrics();
         this.updateProperties();
         this.updateEventListeners();
-
-        if (selectedNode) {
-            ConsoleAgent.addInspectedNode(selectedNode.id);
-            this._lastValidSelectedNode = selectedNode;
-        }
     },
 
     _reset: function()
@@ -907,7 +912,7 @@ WebInspector.ElementsPanel.prototype = {
     {
         // Cmd/Control + Shift + C should be a shortcut to clicking the Node Search Button.
         // This shortcut matches Firebug.
-        if (event.keyIdentifier === "U+0043") {     // C key
+        if (event.keyIdentifier === "U+0043") { // C key
             if (WebInspector.isMac())
                 var isNodeSearchKey = event.metaKey && !event.ctrlKey && !event.altKey && event.shiftKey;
             else
@@ -918,7 +923,10 @@ WebInspector.ElementsPanel.prototype = {
                 event.handled = true;
                 return;
             }
+            return;
         }
+        if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && event.keyIdentifier === "U+005A")  // Z key
+            DOMAgent.undo(this._updateSidebars.bind(this));
     },
 
     handleCopyEvent: function(event)
index 6bec610..f43aa58 100644 (file)
@@ -1068,7 +1068,7 @@ WebInspector.StylePropertiesSection.prototype = {
             var inherited = this.isPropertyInherited(property.name);
             var overloaded = this.isPropertyOverloaded(property.name, isShorthand);
 
-            var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, isShorthand, inherited, overloaded);
+            var item = new WebInspector.StylePropertyTreeElement(this, this._parentPane, this.styleRule, style, property, isShorthand, inherited, overloaded);
             this.propertiesTreeOutline.appendChild(item);
             handledProperties[property.name] = property;
         }
@@ -1092,7 +1092,7 @@ WebInspector.StylePropertiesSection.prototype = {
     {
         var style = this.styleRule.style;
         var property = style.newBlankProperty();
-        var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, false, false, false);
+        var item = new WebInspector.StylePropertyTreeElement(this, this._parentPane, this.styleRule, style, property, false, false, false);
         this.propertiesTreeOutline.appendChild(item);
         item.listItemElement.textContent = "";
         item._newProperty = true;
@@ -1298,7 +1298,7 @@ WebInspector.ComputedStylePropertiesSection.prototype = {
         for (var i = 0; i < uniqueProperties.length; ++i) {
             var property = uniqueProperties[i];
             var inherited = this._isPropertyInherited(property.name);
-            var item = new WebInspector.StylePropertyTreeElement(null, this.styleRule, style, property, false, inherited, false);
+            var item = new WebInspector.StylePropertyTreeElement(this, null, this.styleRule, style, property, false, inherited, false);
             this.propertiesTreeOutline.appendChild(item);
             this._propertyTreeElements[property.name] = item;
         }
@@ -1424,8 +1424,9 @@ WebInspector.BlankStylePropertiesSection.prototype.__proto__ = WebInspector.Styl
  * @extends {TreeElement}
  * @param {?WebInspector.StylesSidebarPane} parentPane
  */
-WebInspector.StylePropertyTreeElement = function(parentPane, styleRule, style, property, shorthand, inherited, overloaded)
+WebInspector.StylePropertyTreeElement = function(section, parentPane, styleRule, style, property, shorthand, inherited, overloaded)
 {
+    this.section = section;
     this._parentPane = parentPane;
     this._styleRule = styleRule;
     this.style = style;
@@ -1819,7 +1820,7 @@ WebInspector.StylePropertyTreeElement.prototype = {
             }
 
             var liveProperty = this.style.getLiveProperty(name);
-            var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this._styleRule, this.style, liveProperty, false, inherited, overloaded);
+            var item = new WebInspector.StylePropertyTreeElement(this, this._parentPane, this._styleRule, this.style, liveProperty, false, inherited, overloaded);
             this.appendChild(item);
         }
     },