Fixed Bug 19158: Inspector should support console.group/console.groupEnd
authortimothy@apple.com <timothy@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 29 Jul 2008 18:04:29 +0000 (18:04 +0000)
committertimothy@apple.com <timothy@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 29 Jul 2008 18:04:29 +0000 (18:04 +0000)
        <https://bugs.webkit.org/show_bug.cgi?id=19158>

        Reviewed by Tim Hatcher and Adam Roben.

        * bindings/js/JSConsoleCustom.cpp:
        (WebCore::JSConsole::group): Added.
        * page/Console.cpp:
        (WebCore::Console::group): Added.
        (WebCore::Console::groupEnd): Added.
        * page/Console.h:
        (WebCore::):
        * page/Console.idl: Added group/groupEnd.
        * page/InspectorController.cpp:
        (WebCore::ConsoleMessage::ConsoleMessage):
        (WebCore::InspectorController::InspectorController): Added m_groupLevel.
        (WebCore::InspectorController::addMessageToConsole): Added groupLevel argument.
        (WebCore::InspectorController::startGroup): Increments m_groupLevel by one and calls js function if needed.
        (WebCore::InspectorController::endGroup): Decrements m_groupLevel by one and calls js function if needed.
        (WebCore::InspectorController::addScriptConsoleMessage): Added groupLevel argument.
        (WebCore::InspectorController::didCommitLoad): Resets m_groupLevel.
        * page/InspectorController.h:
        * page/inspector/Console.js:
        (WebInspector.Console): Added groupLevel and currentGroup topGroup.
        (WebInspector.Console.addMessage): Calls addMessage method in the currentGroup.
        (WebInspector.Console.startGroup): Added.
        (WebInspector.Console.endGroup): Added.
        (WebInspector.Console.clearMessages): Resets groupLevel and currentGroup.
        (WebInspector.ConsoleMessage): Added groupLevel property.
        (WebInspector.ConsoleMessage.MessageLevel.GroupTitle): Added.
        (WebInspector.ConsoleGroup): Added.
        (WebInspector.ConsoleGroup.addMessage): Adds console message to group.
        (WebInspector.ConsoleGroup._titleClicked): Adds "collapsed" style class.
        * page/inspector/inspector.css:
        * page/inspector/inspector.js:
        (WebInspector.startGroupInConsole): Calls console.startGroup
        (WebInspector.endGroupInConsole): Calls console.endGroup

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

WebCore/ChangeLog
WebCore/bindings/js/JSConsoleCustom.cpp
WebCore/page/Console.cpp
WebCore/page/Console.h
WebCore/page/Console.idl
WebCore/page/InspectorController.cpp
WebCore/page/InspectorController.h
WebCore/page/inspector/Console.js
WebCore/page/inspector/inspector.css
WebCore/page/inspector/inspector.js

index 5abcfab..0862a5a 100644 (file)
@@ -1,3 +1,44 @@
+2008-07-29  Keishi Hattori  <casey.hattori@gmail.com>
+
+        Fixed Bug 19158: Inspector should support console.group/console.groupEnd
+
+        <https://bugs.webkit.org/show_bug.cgi?id=19158>
+
+        Reviewed by Tim Hatcher and Adam Roben.
+
+        * bindings/js/JSConsoleCustom.cpp:
+        (WebCore::JSConsole::group): Added.
+        * page/Console.cpp:
+        (WebCore::Console::group): Added.
+        (WebCore::Console::groupEnd): Added.
+        * page/Console.h:
+        (WebCore::):
+        * page/Console.idl: Added group/groupEnd.
+        * page/InspectorController.cpp:
+        (WebCore::ConsoleMessage::ConsoleMessage):
+        (WebCore::InspectorController::InspectorController): Added m_groupLevel.
+        (WebCore::InspectorController::addMessageToConsole): Added groupLevel argument.
+        (WebCore::InspectorController::startGroup): Increments m_groupLevel by one and calls js function if needed.
+        (WebCore::InspectorController::endGroup): Decrements m_groupLevel by one and calls js function if needed.
+        (WebCore::InspectorController::addScriptConsoleMessage): Added groupLevel argument.
+        (WebCore::InspectorController::didCommitLoad): Resets m_groupLevel.
+        * page/InspectorController.h:
+        * page/inspector/Console.js:
+        (WebInspector.Console): Added groupLevel and currentGroup topGroup.
+        (WebInspector.Console.addMessage): Calls addMessage method in the currentGroup.
+        (WebInspector.Console.startGroup): Added.
+        (WebInspector.Console.endGroup): Added.
+        (WebInspector.Console.clearMessages): Resets groupLevel and currentGroup.
+        (WebInspector.ConsoleMessage): Added groupLevel property.
+        (WebInspector.ConsoleMessage.MessageLevel.GroupTitle): Added.
+        (WebInspector.ConsoleGroup): Added.
+        (WebInspector.ConsoleGroup.addMessage): Adds console message to group.
+        (WebInspector.ConsoleGroup._titleClicked): Adds "collapsed" style class.
+        * page/inspector/inspector.css:
+        * page/inspector/inspector.js:
+        (WebInspector.startGroupInConsole): Calls console.startGroup
+        (WebInspector.endGroupInConsole): Calls console.endGroup
+
 2008-07-29  Adam Roben  <aroben@apple.com>
 
         Add names for WebCore's threads
index 8e4efc9..66410d0 100644 (file)
@@ -83,4 +83,10 @@ JSValue* JSConsole::profileEnd(ExecState* exec, const ArgList& arguments)
     return jsUndefined();
 }
 
+JSValue* JSConsole::group(ExecState* exec, const ArgList& arguments)
+{
+    impl()->group(exec, arguments);
+    return jsUndefined();
+}
+
 } // namespace WebCore
index adc96e4..fd40e61 100644 (file)
@@ -303,6 +303,35 @@ void Console::timeEnd(const UString& title)
     page->inspectorController()->addMessageToConsole(JSMessageSource, LogMessageLevel, message, 0, String());
 }
 
+void Console::group(ExecState* exec, const ArgList& arguments)
+{
+    if (!m_frame)
+        return;
+
+    Page* page = m_frame->page();
+    if (!page)
+        return;
+
+    page->inspectorController()->startGroup();
+
+    if (arguments.isEmpty())
+        page->inspectorController()->addMessageToConsole(JSMessageSource, GroupTitleMessageLevel, String(), 0, String());
+    else
+        page->inspectorController()->addMessageToConsole(JSMessageSource, GroupTitleMessageLevel, exec, arguments, 0, String());
+}
+
+void Console::groupEnd()
+{
+    if (!m_frame)
+        return;
+
+    Page* page = m_frame->page();
+    if (!page)
+        return;
+
+    page->inspectorController()->endGroup();
+}
+
 void Console::finishedProfiling(PassRefPtr<Profile> prpProfile)
 {
     if (Page* page = m_frame->page())
index 047ac5a..c508297 100644 (file)
@@ -58,7 +58,8 @@ namespace WebCore {
         TipMessageLevel,
         LogMessageLevel,
         WarningMessageLevel,
-        ErrorMessageLevel
+        ErrorMessageLevel,
+        GroupTitleMessageLevel
     };
 
     class Console : public RefCounted<Console>, public KJS::ProfilerClient {
@@ -79,6 +80,8 @@ namespace WebCore {
         void profileEnd(KJS::ExecState*, const KJS::ArgList& arguments);
         void time(const KJS::UString& title);
         void timeEnd(const KJS::UString& title);
+        void group(KJS::ExecState*, const KJS::ArgList& arguments);
+        void groupEnd();
 
         void finishedProfiling(PassRefPtr<KJS::Profile>);
 
index f8a20e1..8dce97a 100644 (file)
@@ -40,6 +40,8 @@ module window {
         [Custom] void profileEnd();
         void time(in [ConvertUndefinedOrNullToNullString] DOMString title);
         void timeEnd(in [ConvertUndefinedOrNullToNullString] DOMString title);
+        [Custom] void group();
+        void groupEnd();
     };
 
 }
index 67118fe..da9fc29 100644 (file)
@@ -152,21 +152,23 @@ JSValueRef InspectorController::callFunction(JSContextRef context, JSObjectRef t
 // ConsoleMessage Struct
 
 struct ConsoleMessage {
-    ConsoleMessage(MessageSource s, MessageLevel l, const String& m, unsigned li, const String& u)
+    ConsoleMessage(MessageSource s, MessageLevel l, const String& m, unsigned li, const String& u, unsigned g)
         : source(s)
         , level(l)
         , message(m)
         , line(li)
         , url(u)
+        , groupLevel(g)
     {
     }
 
-    ConsoleMessage(MessageSource s, MessageLevel l, ExecState* exec, const ArgList& args, unsigned li, const String& u)
+    ConsoleMessage(MessageSource s, MessageLevel l, ExecState* exec, const ArgList& args, unsigned li, const String& u, unsigned g)
         : source(s)
         , level(l)
         , wrappedArguments(args.size())
         , line(li)
         , url(u)
+        , groupLevel(g)
     {
         JSLock lock(false);
         for (unsigned i = 0; i < args.size(); ++i)
@@ -179,6 +181,7 @@ struct ConsoleMessage {
     Vector<ProtectedPtr<JSValue> > wrappedArguments;
     unsigned line;
     String url;
+    unsigned groupLevel;
 };
 
 // XMLHttpRequestResource Class
@@ -1031,6 +1034,7 @@ InspectorController::InspectorController(Page* page, InspectorClient* client)
     , m_recordingUserInitiatedProfile(false)
     , m_showAfterVisible(ElementsPanel)
     , m_nextIdentifier(-2)
+    , m_groupLevel(0)
 {
     ASSERT_ARG(page, page);
     ASSERT_ARG(client, client);
@@ -1195,7 +1199,7 @@ void InspectorController::addMessageToConsole(MessageSource source, MessageLevel
     if (!enabled())
         return;
 
-    addConsoleMessage(new ConsoleMessage(source, level, exec, arguments, lineNumber, sourceURL));
+    addConsoleMessage(new ConsoleMessage(source, level, exec, arguments, lineNumber, sourceURL, m_groupLevel));
 }
 
 void InspectorController::addMessageToConsole(MessageSource source, MessageLevel level, const String& message, unsigned lineNumber, const String& sourceID)
@@ -1203,7 +1207,7 @@ void InspectorController::addMessageToConsole(MessageSource source, MessageLevel
     if (!enabled())
         return;
 
-    addConsoleMessage(new ConsoleMessage(source, level, message, lineNumber, sourceID));
+    addConsoleMessage(new ConsoleMessage(source, level, message, lineNumber, sourceID, m_groupLevel));
 }
 
 void InspectorController::addConsoleMessage(ConsoleMessage* consoleMessage)
@@ -1217,6 +1221,29 @@ void InspectorController::addConsoleMessage(ConsoleMessage* consoleMessage)
         addScriptConsoleMessage(consoleMessage);
 }
 
+void InspectorController::startGroup()
+{    
+    JSValueRef exception = 0;
+
+    ++m_groupLevel;
+
+    if (windowVisible())
+        callFunction(m_scriptContext, m_scriptObject, "startGroupInConsole", 0, NULL, exception);
+}
+
+void InspectorController::endGroup()
+{
+    JSValueRef exception = 0;
+
+    if (m_groupLevel == 0)
+        return;
+
+    --m_groupLevel;
+
+    if (windowVisible())
+        callFunction(m_scriptContext, m_scriptObject, "endGroupInConsole", 0, NULL, exception);
+}
+
 void InspectorController::addProfile(PassRefPtr<Profile> prpProfile)
 {
     if (!enabled())
@@ -1884,6 +1911,7 @@ void InspectorController::addScriptConsoleMessage(const ConsoleMessage* message)
     JSValueRef levelValue = JSValueMakeNumber(m_scriptContext, message->level);
     JSValueRef lineValue = JSValueMakeNumber(m_scriptContext, message->line);
     JSValueRef urlValue = JSValueMakeString(m_scriptContext, jsStringRef(message->url).get());
+    JSValueRef groupLevelValue = JSValueMakeNumber(m_scriptContext, message->groupLevel);
 
     static const unsigned maximumMessageArguments = 256;
     JSValueRef arguments[maximumMessageArguments];
@@ -1892,6 +1920,7 @@ void InspectorController::addScriptConsoleMessage(const ConsoleMessage* message)
     arguments[argumentCount++] = levelValue;
     arguments[argumentCount++] = lineValue;
     arguments[argumentCount++] = urlValue;
+    arguments[argumentCount++] = groupLevelValue;
 
     if (!message->wrappedArguments.isEmpty()) {
         unsigned remainingSpaceInArguments = maximumMessageArguments - argumentCount;
@@ -1971,6 +2000,7 @@ void InspectorController::didCommitLoad(DocumentLoader* loader)
 
         deleteAllValues(m_consoleMessages);
         m_consoleMessages.clear();
+        m_groupLevel = 0;
 
         m_profiles.clear();
 
index d8be159..c1962e2 100644 (file)
@@ -173,6 +173,9 @@ public:
     void startTiming(const KJS::UString& title);
     bool stopTiming(const KJS::UString& title, double& elapsed);
 
+    void startGroup();
+    void endGroup();
+
 private:
     void focusNode();
 
@@ -237,6 +240,7 @@ private:
     SpecialPanels m_showAfterVisible;
     long long m_nextIdentifier;
     RefPtr<Node> m_highlightedNode;
+    unsigned m_groupLevel;
 };
 
 } // namespace WebCore
index 6d9931f..79aef1f 100644 (file)
@@ -48,6 +48,11 @@ WebInspector.Console = function()
     this.clearButton.title = WebInspector.UIString("Clear console log.");
     this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false);
 
+    this.topGroup = new WebInspector.ConsoleGroup(null, 0);
+    this.messagesElement.insertBefore(this.topGroup.element, this.promptElement);
+    this.groupLevel = 0;
+    this.currentGroup = this.topGroup;
+
     document.getElementById("main-status-bar").addEventListener("mousedown", this._startStatusBarDragging.bind(this), true);
 }
 
@@ -143,8 +148,12 @@ WebInspector.Console.prototype = {
 
         this.messages.push(msg);
 
-        var element = msg.toMessageElement();
-        this.messagesElement.insertBefore(element, this.promptElement);
+        while (msg.groupLevel > this.groupLevel)
+            this.startGroup();
+        while (msg.groupLevel < this.groupLevel)
+            this.endGroup();
+
+        this.currentGroup.addMessage(msg);
         this.promptElement.scrollIntoView(false);
     },
 
@@ -154,8 +163,9 @@ WebInspector.Console.prototype = {
 
         this.messages = [];
 
-        while (this.messagesElement.firstChild != this.promptElement)
-            this.messagesElement.removeChild(this.messagesElement.firstChild);
+        this.groupLevel = 0;
+        this.currentGroup = this.topGroup;
+        this.topGroup.messagesElement.removeChildren();
     },
 
     completions: function(wordRange, bestMatchOnly)
@@ -209,6 +219,23 @@ WebInspector.Console.prototype = {
         return results;
     },
 
+    startGroup: function() {
+        this.groupLevel++;
+        
+        var group = new WebInspector.ConsoleGroup(this.currentGroup, this.groupLevel);
+        this.currentGroup.messagesElement.appendChild(group.element);
+        this.currentGroup = group;
+    },
+
+    endGroup: function() {
+        if (this.groupLevel < 1)
+            return;
+
+        this.groupLevel--;
+
+        this.currentGroup = this.currentGroup.parentGroup;
+    },
+
     _toggleButtonClicked: function()
     {
         this.visible = !this.visible;
@@ -443,19 +470,20 @@ WebInspector.Console.prototype = {
 
 WebInspector.Console.prototype.__proto__ = WebInspector.View.prototype;
 
-WebInspector.ConsoleMessage = function(source, level, line, url)
+WebInspector.ConsoleMessage = function(source, level, line, url, groupLevel)
 {
     this.source = source;
     this.level = level;
     this.line = line;
     this.url = url;
+    this.groupLevel = groupLevel;
 
     // This _format call passes in true for the plainText argument. The result's textContent is
     // used for inline message bubbles in SourceFrames, or other plain-text representations.
-    this.message = this._format(Array.prototype.slice.call(arguments, 4), true).textContent;
+    this.message = this._format(Array.prototype.slice.call(arguments, 5), true).textContent;
 
     // The formatedMessage property is used for the rich and interactive console.
-    this.formattedMessage = this._format(Array.prototype.slice.call(arguments, 4));
+    this.formattedMessage = this._format(Array.prototype.slice.call(arguments, 5));
 }
 
 WebInspector.ConsoleMessage.prototype = {
@@ -544,6 +572,9 @@ WebInspector.ConsoleMessage.prototype = {
                 break;
             case WebInspector.ConsoleMessage.MessageLevel.Error:
                 element.addStyleClass("console-error-level");
+                break;
+            case WebInspector.ConsoleMessage.MessageLevel.GroupTitle:
+                element.addStyleClass("console-group-title-level");
         }
 
         var messageTextElement = document.createElement("span");
@@ -608,6 +639,9 @@ WebInspector.ConsoleMessage.prototype = {
             case WebInspector.ConsoleMessage.MessageLevel.Error:
                 levelString = "Error";
                 break;
+            case WebInspector.ConsoleMessage.MessageLevel.GroupTitle:
+                levelString = "GroupTitle";
+                break;
         }
 
         return sourceString + " " + levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line;
@@ -627,7 +661,8 @@ WebInspector.ConsoleMessage.MessageLevel = {
     Tip: 0,
     Log: 1,
     Warning: 2,
-    Error: 3
+    Error: 3,
+    GroupTitle: 4
 }
 
 WebInspector.ConsoleCommand = function(command, result, formattedResultElement, level)
@@ -672,3 +707,49 @@ WebInspector.ConsoleCommand.prototype = {
         return element;
     }
 }
+
+WebInspector.ConsoleGroup = function(parentGroup, level)
+{
+    this.parentGroup = parentGroup;
+    this.level = level;
+
+    var element = document.createElement("div");
+    element.className = "console-group";
+    element.group = this;
+    this.element = element;
+
+    var messagesElement = document.createElement("div");
+    messagesElement.className = "console-group-messages";
+    element.appendChild(messagesElement);
+    this.messagesElement = messagesElement;
+}
+
+WebInspector.ConsoleGroup.prototype = {
+    addMessage: function(msg)
+    {
+        var element = msg.toMessageElement();
+        
+        if (msg.level === WebInspector.ConsoleMessage.MessageLevel.GroupTitle) {
+            this.messagesElement.parentNode.insertBefore(element, this.messagesElement);
+            element.addEventListener("click", this._titleClicked.bind(this), true);
+        } else
+            this.messagesElement.appendChild(element);
+    },
+    
+    _titleClicked: function(event)
+    {
+        var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title-level");
+        if (groupTitleElement) {
+            var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group");
+            if (groupElement)
+                if (groupElement.hasStyleClass("collapsed"))
+                    groupElement.removeStyleClass("collapsed");
+                else
+                    groupElement.addStyleClass("collapsed");
+            groupTitleElement.scrollIntoViewIfNeeded(true);
+        }
+
+        event.stopPropagation();
+        event.preventDefault();
+    }
+}
index 7990e41..5d59a94 100644 (file)
@@ -466,7 +466,7 @@ body.console-visible #console {
     min-height: 16px; 
 }
 
-.console-message::before, .console-user-command::before, #console-prompt::before {
+.console-message::before, .console-user-command::before, #console-prompt::before, .console-group-title-level::before {
     position: absolute;
     display: block;
     content: "";
@@ -482,6 +482,29 @@ body.console-visible #console {
     white-space: pre-wrap;
 }
 
+.console-group .console-group > .console-group-messages {
+    margin-left: 16px;
+}
+
+.console-group-title-level {
+    font-weight: bold;
+}
+
+.console-group-title-level::before {
+    background-image: url(Images/disclosureTriangleSmallDown.png);
+    top: 0.6em;
+    width: 11px;
+    height: 12px;
+}
+
+.console-group.collapsed .console-group-title-level::before {
+    background-image: url(Images/disclosureTriangleSmallRight.png);
+}
+
+.console-group.collapsed > .console-group-messages {
+    display: none;
+}
+
 .console-error-level .console-message-text {
     color: red;
 }
index 1dd06d5..a392a1e 100644 (file)
@@ -802,6 +802,16 @@ WebInspector.addMessageToConsole = function(msg)
     this.console.addMessage(msg);
 }
 
+WebInspector.startGroupInConsole = function()
+{
+    this.console.startGroup();
+}
+
+WebInspector.endGroupInConsole = function()
+{
+    this.console.endGroup();
+}
+
 WebInspector.addProfile = function(profile)
 {
     this.panels.profiles.addProfile(profile);