Title: [118374] trunk/Source/WebCore
Revision
118374
Author
[email protected]
Date
2012-05-24 07:54:13 -0700 (Thu, 24 May 2012)

Log Message

Web Inspector: Support hierarchical context menus
https://bugs.webkit.org/show_bug.cgi?id=86847

Reviewed by Pavel Feldman.

This patch makes use of the WebMenuItemInfo SubMenu type to expose the capability of building submenu items
in the Web Inspector's context menu. ContextMenuItems are also passed/stored by reference/value rather than pointer
in order to be consistent with the PlatformMenuDescription typedef.

* bindings/js/JSInspectorFrontendHostCustom.cpp:
(WebCore::populateContextMenuItems): Enable submenu item population.
(WebCore):
(WebCore::JSInspectorFrontendHost::showContextMenu): Extract the menu population part into populateContextMenuItems().
* bindings/v8/custom/V8InspectorFrontendHostCustom.cpp:
(WebCore::populateContextMenuItems): Enable submenu item population.
(WebCore):
(WebCore::V8InspectorFrontendHost::showContextMenuCallback): Extract the menu population part into populateContextMenuItems().
* inspector/InspectorFrontendHost.cpp:
(WebCore::FrontendMenuProvider::create): Use reference instead of pointer for ContextMenuItems.
(WebCore::FrontendMenuProvider::FrontendMenuProvider): Use reference instead of pointer for ContextMenuItems.
(WebCore::FrontendMenuProvider::populateContextMenu): Use reference instead of pointer for ContextMenuItems.
(WebCore::FrontendMenuProvider::contextMenuCleared):
(FrontendMenuProvider):
(WebCore::InspectorFrontendHost::showContextMenu): Use reference instead of pointer for ContextMenuItems.
* inspector/InspectorFrontendHost.h:
(InspectorFrontendHost):
* inspector/front-end/ContextMenu.js: Support the tree-like structure of context menus.
(WebInspector.ContextMenuItem):
(WebInspector.ContextMenuItem.prototype.id):
(WebInspector.ContextMenuItem.prototype.type):
(WebInspector.ContextMenuItem.prototype._buildDescriptor):
(WebInspector.ContextSubMenuItem):
(WebInspector.ContextSubMenuItem.prototype.appendItem):
(WebInspector.ContextSubMenuItem.prototype.appendSubMenuItem):
(WebInspector.ContextSubMenuItem.prototype.appendCheckboxItem):
(WebInspector.ContextSubMenuItem.prototype.appendSeparator):
(WebInspector.ContextSubMenuItem.prototype._buildDescriptor):
(WebInspector.ContextMenu):
(WebInspector.ContextMenu.prototype.nextId):
(WebInspector.ContextMenu.prototype.show):
(WebInspector.ContextMenu.prototype._setHandler):
(WebInspector.ContextMenu.prototype._buildDescriptor):
* inspector/front-end/SoftContextMenu.js:
(.WebInspector.SoftContextMenu): Support sub-menus.
(.WebInspector.SoftContextMenu.prototype.show):
(.WebInspector.SoftContextMenu.prototype._parentGlassPaneElement):
(.WebInspector.SoftContextMenu.prototype._createMenuItem):
(.WebInspector.SoftContextMenu.prototype._createSubMenu):
(.WebInspector.SoftContextMenu.prototype._createSeparator):
(.WebInspector.SoftContextMenu.prototype._menuItemMouseUp):
(.WebInspector.SoftContextMenu.prototype._focus):
(.WebInspector.SoftContextMenu.prototype._triggerAction):
(.WebInspector.SoftContextMenu.prototype._showSubMenu):
(.WebInspector.SoftContextMenu.prototype._buildMouseEventForSubMenu):
(.WebInspector.SoftContextMenu.prototype._hideSubMenu):
(.WebInspector.SoftContextMenu.prototype._menuItemMouseOut):
(.WebInspector.SoftContextMenu.prototype._highlightMenuItem):
(.WebInspector.SoftContextMenu.prototype._menuKeyDown):
(.WebInspector.SoftContextMenu.prototype._glassPaneMouseUp):
(.WebInspector.SoftContextMenu.prototype._discardMenu):
(.WebInspector.SoftContextMenu.prototype._discardSubMenus):
* inspector/front-end/inspector.css: Support for sub-menus, separator improvement.
(.soft-context-menu-separator):
(.soft-context-menu-separator > .separator-line):
(.soft-context-menu-item-submenu-arrow):
* platform/chromium/ContextMenuChromium.cpp:
(WebCore::contextMenuItemVector): Implemented.
(WebCore):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (118373 => 118374)


--- trunk/Source/WebCore/ChangeLog	2012-05-24 14:54:04 UTC (rev 118373)
+++ trunk/Source/WebCore/ChangeLog	2012-05-24 14:54:13 UTC (rev 118374)
@@ -1,3 +1,74 @@
+2012-05-24  Alexander Pavlov  <[email protected]>
+
+        Web Inspector: Support hierarchical context menus
+        https://bugs.webkit.org/show_bug.cgi?id=86847
+
+        Reviewed by Pavel Feldman.
+
+        This patch makes use of the WebMenuItemInfo SubMenu type to expose the capability of building submenu items
+        in the Web Inspector's context menu. ContextMenuItems are also passed/stored by reference/value rather than pointer
+        in order to be consistent with the PlatformMenuDescription typedef.
+
+        * bindings/js/JSInspectorFrontendHostCustom.cpp:
+        (WebCore::populateContextMenuItems): Enable submenu item population.
+        (WebCore):
+        (WebCore::JSInspectorFrontendHost::showContextMenu): Extract the menu population part into populateContextMenuItems().
+        * bindings/v8/custom/V8InspectorFrontendHostCustom.cpp:
+        (WebCore::populateContextMenuItems): Enable submenu item population.
+        (WebCore):
+        (WebCore::V8InspectorFrontendHost::showContextMenuCallback): Extract the menu population part into populateContextMenuItems().
+        * inspector/InspectorFrontendHost.cpp:
+        (WebCore::FrontendMenuProvider::create): Use reference instead of pointer for ContextMenuItems.
+        (WebCore::FrontendMenuProvider::FrontendMenuProvider): Use reference instead of pointer for ContextMenuItems.
+        (WebCore::FrontendMenuProvider::populateContextMenu): Use reference instead of pointer for ContextMenuItems.
+        (WebCore::FrontendMenuProvider::contextMenuCleared):
+        (FrontendMenuProvider):
+        (WebCore::InspectorFrontendHost::showContextMenu): Use reference instead of pointer for ContextMenuItems.
+        * inspector/InspectorFrontendHost.h:
+        (InspectorFrontendHost):
+        * inspector/front-end/ContextMenu.js: Support the tree-like structure of context menus.
+        (WebInspector.ContextMenuItem):
+        (WebInspector.ContextMenuItem.prototype.id):
+        (WebInspector.ContextMenuItem.prototype.type):
+        (WebInspector.ContextMenuItem.prototype._buildDescriptor):
+        (WebInspector.ContextSubMenuItem):
+        (WebInspector.ContextSubMenuItem.prototype.appendItem):
+        (WebInspector.ContextSubMenuItem.prototype.appendSubMenuItem):
+        (WebInspector.ContextSubMenuItem.prototype.appendCheckboxItem):
+        (WebInspector.ContextSubMenuItem.prototype.appendSeparator):
+        (WebInspector.ContextSubMenuItem.prototype._buildDescriptor):
+        (WebInspector.ContextMenu):
+        (WebInspector.ContextMenu.prototype.nextId):
+        (WebInspector.ContextMenu.prototype.show):
+        (WebInspector.ContextMenu.prototype._setHandler):
+        (WebInspector.ContextMenu.prototype._buildDescriptor):
+        * inspector/front-end/SoftContextMenu.js:
+        (.WebInspector.SoftContextMenu): Support sub-menus.
+        (.WebInspector.SoftContextMenu.prototype.show):
+        (.WebInspector.SoftContextMenu.prototype._parentGlassPaneElement):
+        (.WebInspector.SoftContextMenu.prototype._createMenuItem):
+        (.WebInspector.SoftContextMenu.prototype._createSubMenu):
+        (.WebInspector.SoftContextMenu.prototype._createSeparator):
+        (.WebInspector.SoftContextMenu.prototype._menuItemMouseUp):
+        (.WebInspector.SoftContextMenu.prototype._focus):
+        (.WebInspector.SoftContextMenu.prototype._triggerAction):
+        (.WebInspector.SoftContextMenu.prototype._showSubMenu):
+        (.WebInspector.SoftContextMenu.prototype._buildMouseEventForSubMenu):
+        (.WebInspector.SoftContextMenu.prototype._hideSubMenu):
+        (.WebInspector.SoftContextMenu.prototype._menuItemMouseOut):
+        (.WebInspector.SoftContextMenu.prototype._highlightMenuItem):
+        (.WebInspector.SoftContextMenu.prototype._menuKeyDown):
+        (.WebInspector.SoftContextMenu.prototype._glassPaneMouseUp):
+        (.WebInspector.SoftContextMenu.prototype._discardMenu):
+        (.WebInspector.SoftContextMenu.prototype._discardSubMenus):
+        * inspector/front-end/inspector.css: Support for sub-menus, separator improvement.
+        (.soft-context-menu-separator):
+        (.soft-context-menu-separator > .separator-line):
+        (.soft-context-menu-item-submenu-arrow):
+        * platform/chromium/ContextMenuChromium.cpp:
+        (WebCore::contextMenuItemVector): Implemented.
+        (WebCore):
+
 2012-05-24  Vivek Galatage  <[email protected]>
 
         Web Inspector: Breakpoints Pane should not show context menu with no breakpoints

Modified: trunk/Source/WebCore/bindings/js/JSInspectorFrontendHostCustom.cpp (118373 => 118374)


--- trunk/Source/WebCore/bindings/js/JSInspectorFrontendHostCustom.cpp	2012-05-24 14:54:04 UTC (rev 118373)
+++ trunk/Source/WebCore/bindings/js/JSInspectorFrontendHostCustom.cpp	2012-05-24 14:54:13 UTC (rev 118374)
@@ -85,16 +85,9 @@
     return jsString(execState, port);
 }
 
-JSValue JSInspectorFrontendHost::showContextMenu(ExecState* exec)
+#if ENABLE(CONTEXT_MENUS)
+static void populateContextMenuItems(ExecState* exec, JSArray* array, ContextMenu& menu)
 {
-    if (exec->argumentCount() < 2)
-        return jsUndefined();
-#if ENABLE(CONTEXT_MENUS)
-    Event* event = toEvent(exec->argument(0));
-
-    JSArray* array = asArray(exec->argument(1));
-    Vector<ContextMenuItem*> items;
-
     for (size_t i = 0; i < array->length(); ++i) {
         JSObject* item = asObject(array->getIndex(i));
         JSValue label = item->get(exec, Identifier(exec, "label"));
@@ -102,25 +95,54 @@
         JSValue id = item->get(exec, Identifier(exec, "id"));
         JSValue enabled = item->get(exec, Identifier(exec, "enabled"));
         JSValue checked = item->get(exec, Identifier(exec, "checked"));
+        JSValue subItems = item->get(exec, Identifier(exec, "subItems"));
         if (!type.isString())
             continue;
 
         String typeString = ustringToString(type.toString(exec)->value(exec));
         if (typeString == "separator") {
-            items.append(new ContextMenuItem(SeparatorType,
-                                             ContextMenuItemCustomTagNoAction,
-                                             String()));
+            ContextMenuItem item(SeparatorType,
+                                 ContextMenuItemCustomTagNoAction,
+                                 String());
+            menu.appendItem(item);
+        } else if (typeString == "subMenu" && subItems.inherits(&JSArray::s_info)) {
+            ContextMenu subMenu;
+            JSArray* subItemsArray = asArray(subItems);
+            populateContextMenuItems(exec, subItemsArray, subMenu);
+            ContextMenuItem item(SubmenuType,
+                                 ContextMenuItemCustomTagNoAction,
+                                 ustringToString(label.toString(exec)->value(exec)),
+                                 &subMenu);
+            menu.appendItem(item);
         } else {
             ContextMenuAction typedId = static_cast<ContextMenuAction>(ContextMenuItemBaseCustomTag + id.toInt32(exec));
-            ContextMenuItem* menuItem = new ContextMenuItem((typeString == "checkbox" ? CheckableActionType : ActionType), typedId, ustringToString(label.toString(exec)->value(exec)));
+            ContextMenuItem menuItem((typeString == "checkbox" ? CheckableActionType : ActionType), typedId, ustringToString(label.toString(exec)->value(exec)));
             if (!enabled.isUndefined())
-                menuItem->setEnabled(enabled.toBoolean());
+                menuItem.setEnabled(enabled.toBoolean());
             if (!checked.isUndefined())
-                menuItem->setChecked(checked.toBoolean());
-            items.append(menuItem);
+                menuItem.setChecked(checked.toBoolean());
+            menu.appendItem(menuItem);
         }
     }
+}
+#endif
 
+JSValue JSInspectorFrontendHost::showContextMenu(ExecState* exec)
+{
+#if ENABLE(CONTEXT_MENUS)
+    if (exec->argumentCount() < 2)
+        return jsUndefined();
+    Event* event = toEvent(exec->argument(0));
+
+    JSArray* array = asArray(exec->argument(1));
+    ContextMenu menu;
+    populateContextMenuItems(exec, array, menu);
+
+#if !USE(CROSS_PLATFORM_CONTEXT_MENUS)
+    Vector<ContextMenuItem> items = contextMenuItemVector(menu.platformDescription());
+#else
+    Vector<ContextMenuItem> items = menu.items();
+#endif
     impl()->showContextMenu(event, items);
 #endif
     return jsUndefined();

Modified: trunk/Source/WebCore/bindings/v8/custom/V8InspectorFrontendHostCustom.cpp (118373 => 118374)


--- trunk/Source/WebCore/bindings/v8/custom/V8InspectorFrontendHostCustom.cpp	2012-05-24 14:54:04 UTC (rev 118373)
+++ trunk/Source/WebCore/bindings/v8/custom/V8InspectorFrontendHostCustom.cpp	2012-05-24 14:54:13 UTC (rev 118374)
@@ -68,48 +68,68 @@
     return v8::Undefined();
 }
 
-v8::Handle<v8::Value> V8InspectorFrontendHost::showContextMenuCallback(const v8::Arguments& args)
+static void populateContextMenuItems(v8::Local<v8::Array>& itemArray, ContextMenu& menu)
 {
-    if (args.Length() < 2)
-        return v8::Undefined();
-
-    v8::Local<v8::Object> eventWrapper = v8::Local<v8::Object>::Cast(args[0]);
-    if (!V8MouseEvent::info.equals(V8DOMWrapper::domWrapperType(eventWrapper)))
-        return v8::Undefined();
-
-    Event* event = V8Event::toNative(eventWrapper);
-    if (!args[1]->IsArray())
-        return v8::Undefined();
-
-    v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(args[1]);
-    Vector<ContextMenuItem*> items;
-
-    for (size_t i = 0; i < array->Length(); ++i) {
-        v8::Local<v8::Object> item = v8::Local<v8::Object>::Cast(array->Get(i));
+    for (size_t i = 0; i < itemArray->Length(); ++i) {
+        v8::Local<v8::Object> item = v8::Local<v8::Object>::Cast(itemArray->Get(i));
         v8::Local<v8::Value> type = item->Get(v8::String::New("type"));
         v8::Local<v8::Value> id = item->Get(v8::String::New("id"));
         v8::Local<v8::Value> label = item->Get(v8::String::New("label"));
         v8::Local<v8::Value> enabled = item->Get(v8::String::New("enabled"));
         v8::Local<v8::Value> checked = item->Get(v8::String::New("checked"));
+        v8::Local<v8::Value> subItems = item->Get(v8::String::New("subItems"));
         if (!type->IsString())
             continue;
         String typeString = toWebCoreStringWithNullCheck(type);
         if (typeString == "separator") {
-            items.append(new ContextMenuItem(SeparatorType,
-                                             ContextMenuItemCustomTagNoAction,
-                                             String()));
+            ContextMenuItem item(ContextMenuItem(SeparatorType,
+                                 ContextMenuItemCustomTagNoAction,
+                                 String()));
+            menu.appendItem(item);
+        } else if (typeString == "subMenu" && subItems->IsArray()) {
+            ContextMenu subMenu;
+            v8::Local<v8::Array> subItemsArray = v8::Local<v8::Array>::Cast(subItems);
+            populateContextMenuItems(subItemsArray, subMenu);
+            ContextMenuItem item(SubmenuType,
+                                 ContextMenuItemCustomTagNoAction,
+                                 toWebCoreStringWithNullCheck(label),
+                                 &subMenu);
+            menu.appendItem(item);
         } else {
             ContextMenuAction typedId = static_cast<ContextMenuAction>(ContextMenuItemBaseCustomTag + id->ToInt32()->Value());
-            ContextMenuItem* menuItem = new ContextMenuItem((typeString == "checkbox" ? CheckableActionType : ActionType), typedId, toWebCoreStringWithNullCheck(label));
+            ContextMenuItem menuItem((typeString == "checkbox" ? CheckableActionType : ActionType), typedId, toWebCoreStringWithNullCheck(label));
             if (checked->IsBoolean())
-                menuItem->setChecked(checked->ToBoolean()->Value());
+                menuItem.setChecked(checked->ToBoolean()->Value());
             if (enabled->IsBoolean())
-                menuItem->setEnabled(enabled->ToBoolean()->Value());
-            items.append(menuItem);
+                menuItem.setEnabled(enabled->ToBoolean()->Value());
+            menu.appendItem(menuItem);
         }
     }
+}
 
+v8::Handle<v8::Value> V8InspectorFrontendHost::showContextMenuCallback(const v8::Arguments& args)
+{
+    if (args.Length() < 2)
+        return v8::Undefined();
+
+    v8::Local<v8::Object> eventWrapper = v8::Local<v8::Object>::Cast(args[0]);
+    if (!V8MouseEvent::info.equals(V8DOMWrapper::domWrapperType(eventWrapper)))
+        return v8::Undefined();
+
+    Event* event = V8Event::toNative(eventWrapper);
+    if (!args[1]->IsArray())
+        return v8::Undefined();
+
+    v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(args[1]);
+    ContextMenu menu;
+    populateContextMenuItems(array, menu);
+
     InspectorFrontendHost* frontendHost = V8InspectorFrontendHost::toNative(args.Holder());
+#if !USE(CROSS_PLATFORM_CONTEXT_MENUS)
+    Vector<ContextMenuItem> items = contextMenuItemVector(menu.platformDescription());
+#else
+    Vector<ContextMenuItem> items = menu.items();
+#endif
     frontendHost->showContextMenu(event, items);
 
     return v8::Undefined();

Modified: trunk/Source/WebCore/inspector/InspectorFrontendHost.cpp (118373 => 118374)


--- trunk/Source/WebCore/inspector/InspectorFrontendHost.cpp	2012-05-24 14:54:04 UTC (rev 118373)
+++ trunk/Source/WebCore/inspector/InspectorFrontendHost.cpp	2012-05-24 14:54:13 UTC (rev 118374)
@@ -60,7 +60,7 @@
 #if ENABLE(CONTEXT_MENUS)
 class FrontendMenuProvider : public ContextMenuProvider {
 public:
-    static PassRefPtr<FrontendMenuProvider> create(InspectorFrontendHost* frontendHost, ScriptObject webInspector, const Vector<ContextMenuItem*>& items)
+    static PassRefPtr<FrontendMenuProvider> create(InspectorFrontendHost* frontendHost, ScriptObject webInspector, const Vector<ContextMenuItem>& items)
     {
         return adoptRef(new FrontendMenuProvider(frontendHost, webInspector, items));
     }
@@ -72,7 +72,7 @@
     }
     
 private:
-    FrontendMenuProvider(InspectorFrontendHost* frontendHost, ScriptObject webInspector,  const Vector<ContextMenuItem*>& items)
+    FrontendMenuProvider(InspectorFrontendHost* frontendHost, ScriptObject webInspector, const Vector<ContextMenuItem>& items)
         : m_frontendHost(frontendHost)
         , m_webInspector(webInspector)
         , m_items(items)
@@ -87,7 +87,7 @@
     virtual void populateContextMenu(ContextMenu* menu)
     {
         for (size_t i = 0; i < m_items.size(); ++i)
-            menu->appendItem(*m_items[i]);
+            menu->appendItem(m_items[i]);
     }
     
     virtual void contextMenuItemSelected(ContextMenuItem* item)
@@ -110,13 +110,12 @@
 
             m_frontendHost->m_menuProvider = 0;
         }
-        deleteAllValues(m_items);
         m_items.clear();
     }
 
     InspectorFrontendHost* m_frontendHost;
     ScriptObject m_webInspector;
-    Vector<ContextMenuItem*> m_items;
+    Vector<ContextMenuItem> m_items;
 };
 #endif
 
@@ -258,7 +257,7 @@
 }
 
 #if ENABLE(CONTEXT_MENUS)
-void InspectorFrontendHost::showContextMenu(Event* event, const Vector<ContextMenuItem*>& items)
+void InspectorFrontendHost::showContextMenu(Event* event, const Vector<ContextMenuItem>& items)
 {
     ASSERT(m_frontendPage);
     ScriptState* frontendScriptState = scriptStateFromPage(debuggerWorld(), m_frontendPage);

Modified: trunk/Source/WebCore/inspector/InspectorFrontendHost.h (118373 => 118374)


--- trunk/Source/WebCore/inspector/InspectorFrontendHost.h	2012-05-24 14:54:04 UTC (rev 118373)
+++ trunk/Source/WebCore/inspector/InspectorFrontendHost.h	2012-05-24 14:54:13 UTC (rev 118374)
@@ -80,7 +80,7 @@
     void append(const String& url, const String& content);
 
     // Called from [Custom] implementations.
-    void showContextMenu(Event*, const Vector<ContextMenuItem*>& items);
+    void showContextMenu(Event*, const Vector<ContextMenuItem>& items);
     void sendMessageToBackend(const String& message);
 
     String loadResourceSynchronously(const String& url);

Modified: trunk/Source/WebCore/inspector/front-end/ContextMenu.js (118373 => 118374)


--- trunk/Source/WebCore/inspector/front-end/ContextMenu.js	2012-05-24 14:54:04 UTC (rev 118373)
+++ trunk/Source/WebCore/inspector/front-end/ContextMenu.js	2012-05-24 14:54:13 UTC (rev 118374)
@@ -30,54 +30,100 @@
 
 /**
  * @constructor
+ * @param {WebInspector.ContextSubMenuItem} topLevelMenu
+ * @param {string} type
+ * @param {string=} label
+ * @param {boolean=} disabled
+ * @param {boolean=} checked
  */
-WebInspector.ContextMenu = function() {
-    this._items = [];
-    this._handlers = {};
+WebInspector.ContextMenuItem = function(topLevelMenu, type, label, disabled, checked)
+{
+    this._type = type;
+    this._label = label;
+    this._disabled = disabled;
+    this._checked = checked;
+    this._contextMenu = topLevelMenu;
+    if (type === "item" || type === "checkbox")
+        this._id = topLevelMenu.nextId();
 }
 
-WebInspector.ContextMenu.prototype = {
-    show: function(event)
+WebInspector.ContextMenuItem.prototype = {
+    id: function()
     {
-        // Remove trailing separator.
-        while (this._items.length > 0 && !("id" in this._items[this._items.length - 1]))
-            this._items.splice(this._items.length - 1, 1);
+        return this._id;
+    },
 
-        if (this._items.length) {
-            WebInspector._contextMenu = this;
-            InspectorFrontendHost.showContextMenu(event, this._items);
-        }
-        event.consume();
+    type: function()
+    {
+        return this._type;
     },
 
+    _buildDescriptor: function()
+    {
+        switch (this._type) {
+        case "item":
+            return { type: "item", id: this._id, label: this._label, enabled: !this._disabled };
+        case "separator":
+            return { type: "separator" };
+        case "checkbox":
+            return { type: "checkbox", id: this._id, label: this._label, checked: !!this._checked, enabled: !this._disabled };
+        }
+    }
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.ContextMenuItem}
+ * @param topLevelMenu
+ * @param {string=} label
+ * @param {boolean=} disabled
+ */
+WebInspector.ContextSubMenuItem = function(topLevelMenu, label, disabled)
+{
+    WebInspector.ContextMenuItem.call(this, topLevelMenu, "subMenu", label, disabled);
+    this._items = [];
+}
+
+WebInspector.ContextSubMenuItem.prototype = {
     /**
      * @param {boolean=} disabled
      */
     appendItem: function(label, handler, disabled)
     {
-        var id = this._items.length;
-        this._items.push({type: "item", id: id, label: label, enabled: !disabled});
-        this._handlers[id] = handler;
+        var item = new WebInspector.ContextMenuItem(this._contextMenu, "item", label, disabled);
+        this._items.push(item);
+        this._contextMenu._setHandler(item.id(), handler);
+        return item;
     },
 
+    appendSubMenuItem: function(label, disabled)
+    {
+        var item = new WebInspector.ContextSubMenuItem(this._contextMenu, label, disabled);
+        this._items.push(item);
+        return item;
+    },
+
     /**
      * @param {boolean=} disabled
      */
     appendCheckboxItem: function(label, handler, checked, disabled)
     {
-        var id = this._items.length;
-        this._items.push({type: "checkbox", id: id, label: label, checked: !!checked, enabled: !disabled});
-        this._handlers[id] = handler;
+        var item = new WebInspector.ContextMenuItem(this._contextMenu, "checkbox", label, disabled, checked);
+        this._items.push(item);
+        this._contextMenu._setHandler(item.id(), handler);
+        return item;
     },
 
     appendSeparator: function()
     {
         // No separator dupes allowed.
         if (this._items.length === 0)
-            return;
-        if (!("id" in this._items[this._items.length - 1]))
-            return;
-        this._items.push({type: "separator"});
+            return null;
+        if (this._items[this._items.length - 1].type() === "separator")
+            return null;
+        var item = new WebInspector.ContextMenuItem(this._contextMenu, "separator");
+        this._items.push(item);
+        return item;
     },
 
     /**
@@ -88,6 +134,58 @@
         return !this._items.length;
     },
 
+    _buildDescriptor: function()
+    {
+        var result = { type: "subMenu", label: this._label, enabled: !this._disabled, subItems: [] };
+        for (var i = 0; i < this._items.length; ++i)
+            result.subItems.push(this._items[i]._buildDescriptor());
+        return result;
+    }
+}
+
+WebInspector.ContextSubMenuItem.prototype.__proto__ = WebInspector.ContextMenuItem.prototype;
+
+/**
+ * @constructor
+ * @extends {WebInspector.ContextSubMenuItem}
+ */
+WebInspector.ContextMenu = function() {
+    WebInspector.ContextSubMenuItem.call(this, this, "");
+    this._handlers = {};
+    this._id = 0;
+}
+
+WebInspector.ContextMenu.prototype = {
+    nextId: function()
+    {
+        return this._id++;
+    },
+
+    show: function(event)
+    {
+        var menuObject = this._buildDescriptor();
+
+        if (menuObject.length) {
+            WebInspector._contextMenu = this;
+            InspectorFrontendHost.showContextMenu(event, menuObject);
+        }
+        event.consume();
+    },
+
+    _setHandler: function(id, handler)
+    {
+        if (handler)
+            this._handlers[id] = handler;
+    },
+
+    _buildDescriptor: function()
+    {
+        var result = [];
+        for (var i = 0; i < this._items.length; ++i)
+            result.push(this._items[i]._buildDescriptor());
+        return result;
+    },
+
     _itemSelected: function(id)
     {
         if (this._handlers[id])
@@ -108,6 +206,8 @@
     }
 }
 
+WebInspector.ContextMenu.prototype.__proto__ = WebInspector.ContextSubMenuItem.prototype;
+
 /**
  * @interface
  */

Modified: trunk/Source/WebCore/inspector/front-end/SoftContextMenu.js (118373 => 118374)


--- trunk/Source/WebCore/inspector/front-end/SoftContextMenu.js	2012-05-24 14:54:04 UTC (rev 118373)
+++ trunk/Source/WebCore/inspector/front-end/SoftContextMenu.js	2012-05-24 14:54:13 UTC (rev 118374)
@@ -27,10 +27,12 @@
 
 /**
  * @constructor
+ * @param {WebInspector.SoftContextMenu=} parentMenu
  */
-WebInspector.SoftContextMenu = function(items)
+WebInspector.SoftContextMenu = function(items, parentMenu)
 {
     this._items = items;
+    this._parentMenu = parentMenu;
 }
 
 WebInspector.SoftContextMenu.prototype = {
@@ -51,12 +53,6 @@
             targetElement = frameElement;
         }
 
-        // Install glass pane capturing events.
-        this._glassPaneElement = document.createElement("div");
-        this._glassPaneElement.className = "soft-context-menu-glass-pane";
-        this._glassPaneElement.tabIndex = 0;
-        this._glassPaneElement.addEventListener("mouseup", this._glassPaneMouseUp.bind(this), false);
-
         // Create context menu.
         this._contextMenuElement = document.createElement("div");
         this._contextMenuElement.className = "soft-context-menu";
@@ -64,16 +60,23 @@
         this._contextMenuElement.style.top = absoluteY + "px";
         this._contextMenuElement.style.left = absoluteX + "px";
 
-        this._contextMenuElement.addEventListener("mousedown", this._discardMenu.bind(this), false);
+        this._contextMenuElement.addEventListener("mouseup", consumeEvent, false);
         this._contextMenuElement.addEventListener("keydown", this._menuKeyDown.bind(this), false);
-        this._contextMenuElement.addEventListener("blur", this._discardMenu.bind(this), false);
 
         for (var i = 0; i < this._items.length; ++i)
             this._contextMenuElement.appendChild(this._createMenuItem(this._items[i]));
 
-        this._glassPaneElement.appendChild(this._contextMenuElement);
-        document.body.appendChild(this._glassPaneElement);
-        this._contextMenuElement.focus();
+        // Install glass pane capturing events.
+        if (!this._parentMenu) {
+            this._glassPaneElement = document.createElement("div");
+            this._glassPaneElement.className = "soft-context-menu-glass-pane";
+            this._glassPaneElement.tabIndex = 0;
+            this._glassPaneElement.addEventListener("mouseup", this._glassPaneMouseUp.bind(this), false);
+            this._glassPaneElement.appendChild(this._contextMenuElement);
+            document.body.appendChild(this._glassPaneElement);
+            this._focus();
+        } else
+            this._parentMenu._parentGlassPaneElement().appendChild(this._contextMenuElement);
 
         // Re-position menu in case it does not fit.
         if (document.body.offsetWidth <  this._contextMenuElement.offsetLeft + this._contextMenuElement.offsetWidth)
@@ -84,11 +87,23 @@
         event.consume(true);
     },
 
+    _parentGlassPaneElement: function()
+    {
+        if (this._glassPaneElement)
+            return this._glassPaneElement;
+        if (this._parentMenu)
+            return this._parentMenu._parentGlassPaneElement();
+        return null;
+    },
+
     _createMenuItem: function(item)
     {
         if (item.type === "separator")
             return this._createSeparator();
 
+        if (item.type === "subMenu")
+            return this._createSubMenu(item);
+
         var menuItemElement = document.createElement("div");
         menuItemElement.className = "soft-context-menu-item";
 
@@ -112,11 +127,43 @@
         return menuItemElement;
     },
 
+    _createSubMenu: function(item)
+    {
+        var menuItemElement = document.createElement("div");
+        menuItemElement.className = "soft-context-menu-item";
+        menuItemElement._subItems = item.subItems;
+
+        // Occupy the same space on the left in all items.
+        var checkMarkElement = document.createElement("span");
+        checkMarkElement.textContent = "\u2713 "; // Checkmark Unicode symbol
+        checkMarkElement.className = "soft-context-menu-item-checkmark";
+        checkMarkElement.style.opacity = "0";
+        menuItemElement.appendChild(checkMarkElement);
+
+        var subMenuArrowElement = document.createElement("span");
+        subMenuArrowElement.textContent = "\u25B6"; // BLACK RIGHT-POINTING TRIANGLE
+        subMenuArrowElement.className = "soft-context-menu-item-submenu-arrow";
+
+        menuItemElement.appendChild(document.createTextNode(item.label));
+        menuItemElement.appendChild(subMenuArrowElement);
+
+        menuItemElement.addEventListener("mousedown", this._menuItemMouseDown.bind(this), false);
+        menuItemElement.addEventListener("mouseup", this._menuItemMouseUp.bind(this), false);
+
+        // Manually manage hover highlight since :hover does not work in case of click-and-hold menu invocation.
+        menuItemElement.addEventListener("mouseover", this._menuItemMouseOver.bind(this), false);
+        menuItemElement.addEventListener("mouseout", this._menuItemMouseOut.bind(this), false);
+
+        return menuItemElement;
+    },
+
     _createSeparator: function()
     {
         var separatorElement = document.createElement("div");
         separatorElement.className = "soft-context-menu-separator";
         separatorElement._isSeparator = true;
+        separatorElement.addEventListener("mouseover", this._hideSubMenu.bind(this), false);
+        separatorElement.createChild("div", "separator-line");
         return separatorElement;
     },
 
@@ -129,17 +176,60 @@
     _menuItemMouseUp: function(event)
     {
         this._triggerAction(event.target, event);
+        event.consume();
     },
 
+    _focus: function()
+    {
+        this._contextMenuElement.focus();
+    },
+
     _triggerAction: function(menuItemElement, event)
     {
-        this._discardMenu(event);
-        if (typeof menuItemElement._actionId !== "undefined") {
-            WebInspector.contextMenuItemSelected(menuItemElement._actionId);
-            delete menuItemElement._actionId;
+        if (!menuItemElement._subItems) {
+            this._discardMenu(true, event);
+            if (typeof menuItemElement._actionId !== "undefined") {
+                WebInspector.contextMenuItemSelected(menuItemElement._actionId);
+                delete menuItemElement._actionId;
+            }
+            return;
         }
+
+        this._showSubMenu(menuItemElement, event);
+        event.consume();
     },
 
+    _showSubMenu: function(menuItemElement, event)
+    {
+        if (menuItemElement._subMenuTimer) {
+            clearTimeout(menuItemElement._subMenuTimer);
+            delete menuItemElement._subMenuTimer;
+        }
+        if (this._subMenu)
+            return;
+
+        this._subMenu = new WebInspector.SoftContextMenu(menuItemElement._subItems, this);
+        this._subMenu.show(this._buildMouseEventForSubMenu(menuItemElement));
+    },
+
+    _buildMouseEventForSubMenu: function(subMenuItemElement)
+    {
+        var subMenuOffset = { x: subMenuItemElement.offsetWidth - 3, y: subMenuItemElement.offsetTop - 1 };
+        var targetX = this._x + subMenuOffset.x;
+        var targetY = this._y + subMenuOffset.y;
+        var targetPageX = parseInt(this._contextMenuElement.style.left, 10) + subMenuOffset.x;
+        var targetPageY = parseInt(this._contextMenuElement.style.top, 10) + subMenuOffset.y;
+        return { x: targetX, y: targetY, pageX: targetPageX, pageY: targetPageY, consume: function() {} };
+    },
+
+    _hideSubMenu: function()
+    {
+        if (!this._subMenu)
+            return;
+        this._subMenu._discardSubMenus();
+        this._focus();
+    },
+
     _menuItemMouseOver: function(event)
     {
         this._highlightMenuItem(event.target);
@@ -147,16 +237,36 @@
 
     _menuItemMouseOut: function(event)
     {
-        this._highlightMenuItem(null);
+        if (!this._subMenu || !event.relatedTarget) {
+            this._highlightMenuItem(null);
+            return;
+        }
+
+        var relatedTarget = event.relatedTarget;
+        if (this._contextMenuElement.isSelfOrAncestor(relatedTarget) || relatedTarget.hasStyleClass("soft-context-menu-glass-pane"))
+            this._highlightMenuItem(null);
     },
 
     _highlightMenuItem: function(menuItemElement)
     {
-        if (this._highlightedMenuItemElement)
+        if (this._highlightedMenuItemElement ===  menuItemElement)
+            return;
+
+        this._hideSubMenu();
+        if (this._highlightedMenuItemElement) {
             this._highlightedMenuItemElement.removeStyleClass("soft-context-menu-item-mouse-over");
+            if (this._highlightedMenuItemElement._subItems && this._highlightedMenuItemElement._subMenuTimer) {
+                clearTimeout(this._highlightedMenuItemElement._subMenuTimer);
+                delete this._highlightedMenuItemElement._subMenuTimer;
+            }
+        }
         this._highlightedMenuItemElement = menuItemElement;
-        if (this._highlightedMenuItemElement)
+        if (this._highlightedMenuItemElement) {
             this._highlightedMenuItemElement.addStyleClass("soft-context-menu-item-mouse-over");
+            this._contextMenuElement.focus();
+            if (this._highlightedMenuItemElement._subItems && !this._highlightedMenuItemElement._subMenuTimer)
+                this._highlightedMenuItemElement._subMenuTimer = setTimeout(this._showSubMenu.bind(this, this._highlightedMenuItemElement, this._buildMouseEventForSubMenu(this._highlightedMenuItemElement)), 150);
+        }
     },
 
     _highlightPrevious: function()
@@ -184,8 +294,23 @@
             this._highlightPrevious(); break;
         case "Down":
             this._highlightNext(); break;
+        case "Left":
+            if (this._parentMenu) {
+                this._highlightMenuItem(null);
+                this._parentMenu._focus();
+            }
+            break;
+        case "Right":
+            if (!this._highlightedMenuItemElement)
+                break;
+            if (this._highlightedMenuItemElement._subItems) {
+                this._showSubMenu(this._highlightedMenuItemElement, this._buildMouseEventForSubMenu(this._highlightedMenuItemElement));
+                this._subMenu._focus();
+                this._subMenu._highlightNext();
+            }
+            break;
         case "U+001B": // Escape
-            this._discardMenu(event); break;
+            this._discardMenu(true, event); break;
         case "Enter":
             if (!isEnterKey(event))
                 break;
@@ -203,19 +328,49 @@
         // Return if this is simple 'click', since dispatched on glass pane, can't use 'click' event.
         if (event.x === this._x && event.y === this._y && new Date().getTime() - this._time < 300)
             return;
-        this._discardMenu(event);
+        this._discardMenu(true, event);
+        event.consume();
     },
 
-    _discardMenu: function(event)
+    /**
+     * @param {boolean} closeParentMenus
+     * @param {Event=} event
+     */
+    _discardMenu: function(closeParentMenus, event)
     {
+        if (this._subMenu && !closeParentMenus)
+            return;
         if (this._glassPaneElement) {
             var glassPane = this._glassPaneElement;
             delete this._glassPaneElement;
             // This can re-enter discardMenu due to blur.
             document.body.removeChild(glassPane);
+            if (this._parentMenu) {
+                delete this._parentMenu._subMenu;
+                if (closeParentMenus)
+                    this._parentMenu._discardMenu(closeParentMenus, event);
+            }
 
-            event.consume(true);
+            if (event)
+                event.consume(true);
+        } else if (this._parentMenu && this._contextMenuElement.parentElement) {
+            this._discardSubMenus();
+            if (closeParentMenus)
+                this._parentMenu._discardMenu(closeParentMenus, event);
+
+            if (event)
+                event.consume(true);
         }
+    },
+
+    _discardSubMenus: function()
+    {
+        if (this._subMenu)
+            this._subMenu._discardSubMenus();
+        if (this._contextMenuElement.parentElement)
+            this._contextMenuElement.parentElement.removeChild(this._contextMenuElement);
+        if (this._parentMenu)
+            delete this._parentMenu._subMenu;
     }
 }
 

Modified: trunk/Source/WebCore/inspector/front-end/inspector.css (118373 => 118374)


--- trunk/Source/WebCore/inspector/front-end/inspector.css	2012-05-24 14:54:04 UTC (rev 118373)
+++ trunk/Source/WebCore/inspector/front-end/inspector.css	2012-05-24 14:54:13 UTC (rev 118374)
@@ -2345,8 +2345,15 @@
 }
 
 .soft-context-menu-separator {
-    margin: 5px 1px 6px 1px;
+    height: 10px;
+    margin: 0 1px;
+}
+
+.soft-context-menu-separator > .separator-line {
+    margin: 0;
+    height: 5px;
     border-bottom: 1px solid rgb(227, 227, 227);
+    pointer-events: none;
 }
 
 .soft-context-menu-item-mouse-over {
@@ -2367,6 +2374,12 @@
     pointer-events: none;
 }
 
+.soft-context-menu-item-submenu-arrow {
+    color: black;
+    float: right;
+    pointer-events: none;
+}
+
 .soft-context-menu-item-mouse-over .soft-context-menu-item-checkmark {
     color: white;
 }

Modified: trunk/Source/WebCore/platform/chromium/ContextMenuChromium.cpp (118373 => 118374)


--- trunk/Source/WebCore/platform/chromium/ContextMenuChromium.cpp	2012-05-24 14:54:04 UTC (rev 118373)
+++ trunk/Source/WebCore/platform/chromium/ContextMenuChromium.cpp	2012-05-24 14:54:13 UTC (rev 118374)
@@ -99,4 +99,9 @@
     return 0;
 }
 
+Vector<ContextMenuItem> contextMenuItemVector(const PlatformMenuDescription menuDescription)
+{
+    return *menuDescription;
+}
+
 } // namespace WebCore
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to