Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / ui / ContextMenu.js
blob139189abf88473e7366b2663ded0bc7b00ca6b86
1 /*
2 * Copyright (C) 2009 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 are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 /**
32 * @constructor
33 * @param {!WebInspector.ContextMenu} topLevelMenu
34 * @param {string} type
35 * @param {string=} label
36 * @param {boolean=} disabled
37 * @param {boolean=} checked
39 WebInspector.ContextMenuItem = function(topLevelMenu, type, label, disabled, checked)
41 this._type = type;
42 this._label = label;
43 this._disabled = disabled;
44 this._checked = checked;
45 this._contextMenu = topLevelMenu;
46 if (type === "item" || type === "checkbox")
47 this._id = topLevelMenu._nextId();
50 WebInspector.ContextMenuItem.prototype = {
51 /**
52 * @return {number}
54 id: function()
56 return this._id;
59 /**
60 * @return {string}
62 type: function()
64 return this._type;
67 /**
68 * @return {boolean}
70 isEnabled: function()
72 return !this._disabled;
75 /**
76 * @param {boolean} enabled
78 setEnabled: function(enabled)
80 this._disabled = !enabled;
83 /**
84 * @return {!InspectorFrontendHostAPI.ContextMenuDescriptor}
86 _buildDescriptor: function()
88 switch (this._type) {
89 case "item":
90 var result = { type: "item", id: this._id, label: this._label, enabled: !this._disabled };
91 if (this._customElement)
92 result.element = this._customElement;
93 if (this._shortcut)
94 result.shortcut = this._shortcut;
95 return result;
96 case "separator":
97 return { type: "separator" };
98 case "checkbox":
99 return { type: "checkbox", id: this._id, label: this._label, checked: !!this._checked, enabled: !this._disabled };
101 throw new Error("Invalid item type:" + this._type);
105 * @param {string} shortcut
107 setShortcut: function(shortcut)
109 this._shortcut = shortcut;
114 * @constructor
115 * @extends {WebInspector.ContextMenuItem}
116 * @param {!WebInspector.ContextMenu} topLevelMenu
117 * @param {string=} label
118 * @param {boolean=} disabled
120 WebInspector.ContextSubMenuItem = function(topLevelMenu, label, disabled)
122 WebInspector.ContextMenuItem.call(this, topLevelMenu, "subMenu", label, disabled);
123 /** @type {!Array.<!WebInspector.ContextMenuItem>} */
124 this._items = [];
127 WebInspector.ContextSubMenuItem.prototype = {
129 * @param {string} label
130 * @param {function(?)} handler
131 * @param {boolean=} disabled
132 * @return {!WebInspector.ContextMenuItem}
134 appendItem: function(label, handler, disabled)
136 var item = new WebInspector.ContextMenuItem(this._contextMenu, "item", label, disabled);
137 this._pushItem(item);
138 this._contextMenu._setHandler(item.id(), handler);
139 return item;
143 * @param {!Element} element
144 * @return {!WebInspector.ContextMenuItem}
146 appendCustomItem: function(element)
148 var item = new WebInspector.ContextMenuItem(this._contextMenu, "item", "<custom>");
149 item._customElement = element;
150 this._pushItem(item);
151 return item;
155 * @param {string} actionId
156 * @param {string=} label
157 * @return {!WebInspector.ContextMenuItem}
159 appendAction: function(actionId, label)
161 if (!label)
162 label = WebInspector.actionRegistry.actionTitle(actionId);
163 var result = this.appendItem(label, WebInspector.actionRegistry.execute.bind(WebInspector.actionRegistry, actionId));
164 var shortcut = WebInspector.shortcutRegistry.shortcutTitleForAction(actionId);
165 if (shortcut)
166 result.setShortcut(shortcut);
167 return result;
171 * @param {string} label
172 * @param {boolean=} disabled
173 * @return {!WebInspector.ContextSubMenuItem}
175 appendSubMenuItem: function(label, disabled)
177 var item = new WebInspector.ContextSubMenuItem(this._contextMenu, label, disabled);
178 this._pushItem(item);
179 return item;
183 * @param {string} label
184 * @param {function()} handler
185 * @param {boolean=} checked
186 * @param {boolean=} disabled
187 * @return {!WebInspector.ContextMenuItem}
189 appendCheckboxItem: function(label, handler, checked, disabled)
191 var item = new WebInspector.ContextMenuItem(this._contextMenu, "checkbox", label, disabled, checked);
192 this._pushItem(item);
193 this._contextMenu._setHandler(item.id(), handler);
194 return item;
197 appendSeparator: function()
199 if (this._items.length)
200 this._pendingSeparator = true;
204 * @param {!WebInspector.ContextMenuItem} item
206 _pushItem: function(item)
208 if (this._pendingSeparator) {
209 this._items.push(new WebInspector.ContextMenuItem(this._contextMenu, "separator"));
210 delete this._pendingSeparator;
212 this._items.push(item);
216 * @return {boolean}
218 isEmpty: function()
220 return !this._items.length;
224 * @override
225 * @return {!InspectorFrontendHostAPI.ContextMenuDescriptor}
227 _buildDescriptor: function()
229 var result = { type: "subMenu", label: this._label, enabled: !this._disabled, subItems: [] };
230 for (var i = 0; i < this._items.length; ++i)
231 result.subItems.push(this._items[i]._buildDescriptor());
232 return result;
235 __proto__: WebInspector.ContextMenuItem.prototype
239 * @constructor
240 * @extends {WebInspector.ContextSubMenuItem}
241 * @param {!Event} event
242 * @param {boolean=} useSoftMenu
243 * @param {number=} x
244 * @param {number=} y
246 WebInspector.ContextMenu = function(event, useSoftMenu, x, y)
248 WebInspector.ContextSubMenuItem.call(this, this, "");
249 /** @type {!Array.<!Promise.<!Array.<!WebInspector.ContextMenu.Provider>>>} */
250 this._pendingPromises = [];
251 /** @type {!Array.<!Promise.<!Object>>} */
252 this._pendingTargets = [];
253 this._event = event;
254 this._useSoftMenu = !!useSoftMenu;
255 this._x = x === undefined ? event.x : x;
256 this._y = y === undefined ? event.y : y;
257 this._handlers = {};
258 this._id = 0;
261 WebInspector.ContextMenu.initialize = function()
263 InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.SetUseSoftMenu, setUseSoftMenu);
265 * @param {!WebInspector.Event} event
267 function setUseSoftMenu(event)
269 WebInspector.ContextMenu._useSoftMenu = /** @type {boolean} */ (event.data);
274 * @param {!Document} doc
276 WebInspector.ContextMenu.installHandler = function(doc)
278 doc.body.addEventListener("contextmenu", handler, false);
281 * @param {!Event} event
283 function handler(event)
285 var contextMenu = new WebInspector.ContextMenu(event);
286 contextMenu.appendApplicableItems(/** @type {!Object} */ (event.deepElementFromPoint()));
287 contextMenu.show();
291 WebInspector.ContextMenu.prototype = {
293 * @return {number}
295 _nextId: function()
297 return this._id++;
300 show: function()
302 Promise.all(this._pendingPromises).then(populateAndShow.bind(this));
303 WebInspector.ContextMenu._pendingMenu = this;
306 * @param {!Array.<!Array.<!WebInspector.ContextMenu.Provider>>} appendCallResults
307 * @this {WebInspector.ContextMenu}
309 function populateAndShow(appendCallResults)
311 if (WebInspector.ContextMenu._pendingMenu !== this)
312 return;
313 delete WebInspector.ContextMenu._pendingMenu;
315 for (var i = 0; i < appendCallResults.length; ++i) {
316 var providers = appendCallResults[i];
317 var target = this._pendingTargets[i];
319 for (var j = 0; j < providers.length; ++j) {
320 var provider = /** @type {!WebInspector.ContextMenu.Provider} */ (providers[j]);
321 this.appendSeparator();
322 provider.appendApplicableItems(this._event, this, target);
323 this.appendSeparator();
327 this._pendingPromises = [];
328 this._pendingTargets = [];
329 this._innerShow();
332 this._event.consume(true);
335 discard: function()
337 if (this._softMenu)
338 this._softMenu.discard();
341 _innerShow: function()
343 var menuObject = this._buildDescriptors();
345 WebInspector._contextMenu = this;
346 if (this._useSoftMenu || WebInspector.ContextMenu._useSoftMenu || InspectorFrontendHost.isHostedMode()) {
347 this._softMenu = new WebInspector.SoftContextMenu(menuObject, this._itemSelected.bind(this));
348 this._softMenu.show(this._event.target.ownerDocument, this._x, this._y);
349 } else {
350 InspectorFrontendHost.showContextMenuAtPoint(this._x, this._y, menuObject, this._event.target.ownerDocument);
351 InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.ContextMenuCleared, this._menuCleared, this);
352 InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.ContextMenuItemSelected, this._onItemSelected, this);
357 * @param {number} id
358 * @param {function(?)} handler
360 _setHandler: function(id, handler)
362 if (handler)
363 this._handlers[id] = handler;
367 * @return {!Array.<!InspectorFrontendHostAPI.ContextMenuDescriptor>}
369 _buildDescriptors: function()
371 var result = [];
372 for (var i = 0; i < this._items.length; ++i)
373 result.push(this._items[i]._buildDescriptor());
374 return result;
378 * @param {!WebInspector.Event} event
380 _onItemSelected: function(event)
382 this._itemSelected(/** @type {string} */ (event.data));
386 * @param {string} id
388 _itemSelected: function(id)
390 if (this._handlers[id])
391 this._handlers[id].call(this);
392 this._menuCleared();
395 _menuCleared: function()
397 InspectorFrontendHost.events.removeEventListener(InspectorFrontendHostAPI.Events.ContextMenuCleared, this._menuCleared, this);
398 InspectorFrontendHost.events.removeEventListener(InspectorFrontendHostAPI.Events.ContextMenuItemSelected, this._onItemSelected, this);
402 * @param {!Object} target
404 appendApplicableItems: function(target)
406 this._pendingPromises.push(self.runtime.instancesPromise(WebInspector.ContextMenu.Provider, target));
407 this._pendingTargets.push(target);
411 * @param {string} location
413 appendItemsAtLocation: function(location)
415 // Hard-coded named groups for elements to maintain generic order.
416 var groupWeights = ["new", "open", "clipboard", "navigate", "footer"];
418 var groups = new Map();
419 var extensions = self.runtime.extensions("context-menu-item");
420 for (var extension of extensions) {
421 var itemLocation = extension.descriptor()["location"] || "";
422 if (itemLocation !== location && !itemLocation.startsWith(location + "/"))
423 continue;
425 var itemGroup = itemLocation.includes("/") ? itemLocation.substr(location.length + 1) : "misc";
426 var group = groups.get(itemGroup);
427 if (!group) {
428 group = [];
429 groups.set(itemGroup, group);
430 if (groupWeights.indexOf(itemGroup) === -1)
431 groupWeights.splice(4, 0, itemGroup);
433 group.push(extension);
435 for (var groupName of groupWeights) {
436 var group = groups.get(groupName);
437 if (!group)
438 continue;
439 for (var extension of group)
440 this.appendAction(extension.descriptor()["actionId"]);
441 this.appendSeparator();
445 __proto__: WebInspector.ContextSubMenuItem.prototype
449 * @interface
451 WebInspector.ContextMenu.Provider = function() {
454 WebInspector.ContextMenu.Provider.prototype = {
456 * @param {!Event} event
457 * @param {!WebInspector.ContextMenu} contextMenu
458 * @param {!Object} target
460 appendApplicableItems: function(event, contextMenu, target) { }