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
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
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.
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
)
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 = {
72 return !this._disabled
;
76 * @param {boolean} enabled
78 setEnabled: function(enabled
)
80 this._disabled
= !enabled
;
84 * @return {!InspectorFrontendHostAPI.ContextMenuDescriptor}
86 _buildDescriptor: function()
90 var result
= { type
: "item", id
: this._id
, label
: this._label
, enabled
: !this._disabled
};
91 if (this._customElement
)
92 result
.element
= this._customElement
;
94 result
.shortcut
= this._shortcut
;
97 return { type
: "separator" };
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
;
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>} */
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
);
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
);
155 * @param {string} actionId
156 * @param {string=} label
157 * @return {!WebInspector.ContextMenuItem}
159 appendAction: function(actionId
, 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
);
166 result
.setShortcut(shortcut
);
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
);
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
);
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
);
220 return !this._items
.length
;
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());
235 __proto__
: WebInspector
.ContextMenuItem
.prototype
240 * @extends {WebInspector.ContextSubMenuItem}
241 * @param {!Event} event
242 * @param {boolean=} useSoftMenu
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
= [];
254 this._useSoftMenu
= !!useSoftMenu
;
255 this._x
= x
=== undefined ? event
.x
: x
;
256 this._y
= y
=== undefined ? event
.y
: y
;
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()));
291 WebInspector
.ContextMenu
.prototype = {
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)
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
= [];
332 this._event
.consume(true);
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
);
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);
358 * @param {function(?)} handler
360 _setHandler: function(id
, handler
)
363 this._handlers
[id
] = handler
;
367 * @return {!Array.<!InspectorFrontendHostAPI.ContextMenuDescriptor>}
369 _buildDescriptors: function()
372 for (var i
= 0; i
< this._items
.length
; ++i
)
373 result
.push(this._items
[i
]._buildDescriptor());
378 * @param {!WebInspector.Event} event
380 _onItemSelected: function(event
)
382 this._itemSelected(/** @type {string} */ (event
.data
));
388 _itemSelected: function(id
)
390 if (this._handlers
[id
])
391 this._handlers
[id
].call(this);
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
+ "/"))
425 var itemGroup
= itemLocation
.includes("/") ? itemLocation
.substr(location
.length
+ 1) : "misc";
426 var group
= groups
.get(itemGroup
);
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
);
439 for (var extension
of group
)
440 this.appendAction(extension
.descriptor()["actionId"]);
441 this.appendSeparator();
445 __proto__
: WebInspector
.ContextSubMenuItem
.prototype
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
) { }