2 * Copyright (C) 2011 Google Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 if (!InspectorFrontendHost.showContextMenu) {
31 WebInspector.SoftContextMenu = function(items)
36 WebInspector.SoftContextMenu.prototype = {
41 this._time = new Date().getTime();
43 // Absolutely position menu for iframes.
44 var absoluteX = event.pageX;
45 var absoluteY = event.pageY;
46 var targetElement = event.target;
47 while (targetElement && window !== targetElement.ownerDocument.defaultView) {
48 var frameElement = targetElement.ownerDocument.defaultView.frameElement;
49 absoluteY += frameElement.totalOffsetTop();
50 absoluteX += frameElement.totalOffsetLeft();
51 targetElement = frameElement;
54 // Install glass pane capturing events.
55 this._glassPaneElement = document.createElement("div");
56 this._glassPaneElement.className = "soft-context-menu-glass-pane";
57 this._glassPaneElement.tabIndex = 0;
58 this._glassPaneElement.addEventListener("mouseup", this._glassPaneMouseUp.bind(this), false);
60 // Create context menu.
61 this._contextMenuElement = document.createElement("div");
62 this._contextMenuElement.className = "soft-context-menu";
63 this._contextMenuElement.tabIndex = 0;
64 this._contextMenuElement.style.top = absoluteY + "px";
65 this._contextMenuElement.style.left = absoluteX + "px";
67 this._contextMenuElement.addEventListener("mousedown", this._discardMenu.bind(this), false);
68 this._contextMenuElement.addEventListener("keydown", this._menuKeyDown.bind(this), false);
69 this._contextMenuElement.addEventListener("blur", this._discardMenu.bind(this), false);
71 for (var i = 0; i < this._items.length; ++i)
72 this._contextMenuElement.appendChild(this._createMenuItem(this._items[i]));
74 this._glassPaneElement.appendChild(this._contextMenuElement);
75 document.body.appendChild(this._glassPaneElement);
76 this._contextMenuElement.focus();
78 // Re-position menu in case it does not fit.
79 if (document.body.offsetWidth < this._contextMenuElement.offsetLeft + this._contextMenuElement.offsetWidth)
80 this._contextMenuElement.style.left = (absoluteX - this._contextMenuElement.offsetWidth) + "px";
81 if (document.body.offsetHeight < this._contextMenuElement.offsetTop + this._contextMenuElement.offsetHeight)
82 this._contextMenuElement.style.top = (document.body.offsetHeight - this._contextMenuElement.offsetHeight) + "px";
84 event.stopPropagation();
85 event.preventDefault();
88 _createMenuItem: function(item)
90 if (item.type === "separator")
91 return this._createSeparator();
93 var menuItemElement = document.createElement("div");
94 menuItemElement.className = "soft-context-menu-item";
96 var checkMarkElement = document.createElement("span");
97 checkMarkElement.textContent = "\u2713 "; // Checkmark Unicode symbol
98 checkMarkElement.style.pointerEvents = "none";
100 checkMarkElement.style.opacity = "0";
102 menuItemElement.appendChild(checkMarkElement);
103 menuItemElement.appendChild(document.createTextNode(item.label));
105 menuItemElement.addEventListener("mousedown", this._menuItemMouseDown.bind(this), false);
106 menuItemElement.addEventListener("mouseup", this._menuItemMouseUp.bind(this), false);
108 // Manually manage hover highlight since :hover does not work in case of click-and-hold menu invocation.
109 menuItemElement.addEventListener("mouseover", this._menuItemMouseOver.bind(this), false);
110 menuItemElement.addEventListener("mouseout", this._menuItemMouseOut.bind(this), false);
112 menuItemElement._actionId = item.id;
113 return menuItemElement;
116 _createSeparator: function()
118 var separatorElement = document.createElement("div");
119 separatorElement.className = "soft-context-menu-separator";
120 separatorElement._isSeparator = true;
121 return separatorElement;
124 _menuItemMouseDown: function(event)
126 // Do not let separator's mouse down hit menu's handler - we need to receive mouse up!
127 event.stopPropagation();
128 event.preventDefault();
131 _menuItemMouseUp: function(event)
133 this._triggerAction(event.target, event);
136 _triggerAction: function(menuItemElement, event)
138 this._discardMenu(event);
139 if (typeof menuItemElement._actionId !== "undefined") {
140 WebInspector.contextMenuItemSelected(menuItemElement._actionId);
141 delete menuItemElement._actionId;
145 _menuItemMouseOver: function(event)
147 this._highlightMenuItem(event.target);
150 _menuItemMouseOut: function(event)
152 this._highlightMenuItem(null);
155 _highlightMenuItem: function(menuItemElement)
157 if (this._highlightedMenuItemElement)
158 this._highlightedMenuItemElement.removeStyleClass("soft-context-menu-item-mouse-over");
159 this._highlightedMenuItemElement = menuItemElement;
160 if (this._highlightedMenuItemElement)
161 this._highlightedMenuItemElement.addStyleClass("soft-context-menu-item-mouse-over");
164 _highlightPrevious: function()
166 var menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.previousSibling : this._contextMenuElement.lastChild;
167 while (menuItemElement && menuItemElement._isSeparator)
168 menuItemElement = menuItemElement.previousSibling;
170 this._highlightMenuItem(menuItemElement);
173 _highlightNext: function()
175 var menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.nextSibling : this._contextMenuElement.firstChild;
176 while (menuItemElement && menuItemElement._isSeparator)
177 menuItemElement = menuItemElement.nextSibling;
179 this._highlightMenuItem(menuItemElement);
182 _menuKeyDown: function(event)
184 switch (event.keyIdentifier) {
186 this._highlightPrevious(); break;
188 this._highlightNext(); break;
189 case "U+001B": // Escape
190 this._discardMenu(event); break;
192 if (!isEnterKey(event))
195 case "U+0020": // Space
196 if (this._highlightedMenuItemElement)
197 this._triggerAction(this._highlightedMenuItemElement, event);
200 event.stopPropagation();
201 event.preventDefault();
204 _glassPaneMouseUp: function(event)
206 // Return if this is simple 'click', since dispatched on glass pane, can't use 'click' event.
207 if (event.x === this._x && event.y === this._y && new Date().getTime() - this._time < 300)
209 this._discardMenu(event);
212 _discardMenu: function(event)
214 if (this._glassPaneElement) {
215 var glassPane = this._glassPaneElement;
216 delete this._glassPaneElement;
217 // This can re-enter discardMenu due to blur.
218 document.body.removeChild(glassPane);
220 event.stopPropagation();
221 event.preventDefault();
226 InspectorFrontendHost.showContextMenu = function(event, items)
228 new WebInspector.SoftContextMenu(items).show(event);