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