| /* |
| * Copyright (C) 2013, 2015 Apple Inc. All rights reserved. |
| * Copyright (C) 2009 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| WI.ContextMenuItem = class ContextMenuItem extends WI.Object |
| { |
| constructor(topLevelMenu, type, label, disabled, checked) |
| { |
| super(); |
| |
| this._type = type; |
| this._label = label; |
| this._disabled = disabled; |
| this._checked = checked; |
| this._contextMenu = topLevelMenu || this; |
| |
| if (type === "item" || type === "checkbox") |
| this._id = topLevelMenu.nextId(); |
| } |
| |
| // Public |
| |
| id() |
| { |
| return this._id; |
| } |
| |
| type() |
| { |
| return this._type; |
| } |
| |
| isEnabled() |
| { |
| return !this._disabled; |
| } |
| |
| setEnabled(enabled) |
| { |
| this._disabled = !enabled; |
| } |
| |
| // Private |
| |
| _buildDescriptor() |
| { |
| 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}; |
| } |
| } |
| }; |
| |
| WI.ContextSubMenuItem = class ContextSubMenuItem extends WI.ContextMenuItem |
| { |
| constructor(topLevelMenu, label, disabled) |
| { |
| super(topLevelMenu, "subMenu", label, disabled); |
| |
| this._items = []; |
| } |
| |
| // Public |
| |
| appendItem(label, handler, disabled) |
| { |
| let item = new WI.ContextMenuItem(this._contextMenu, "item", label, disabled); |
| this.pushItem(item); |
| this._contextMenu._setHandler(item.id(), handler); |
| return item; |
| } |
| |
| appendSubMenuItem(label, disabled) |
| { |
| let item = new WI.ContextSubMenuItem(this._contextMenu, label, disabled); |
| this.pushItem(item); |
| return item; |
| } |
| |
| appendCheckboxItem(label, handler, checked, disabled) |
| { |
| let item = new WI.ContextMenuItem(this._contextMenu, "checkbox", label, disabled, checked); |
| this.pushItem(item); |
| this._contextMenu._setHandler(item.id(), handler); |
| return item; |
| } |
| |
| appendHeader(label) |
| { |
| return this.appendItem(label, () => { |
| console.assert(false, "not reached"); |
| }, true); |
| } |
| |
| appendSeparator() |
| { |
| if (this._items.length) |
| this._pendingSeparator = true; |
| } |
| |
| pushItem(item) |
| { |
| if (this._pendingSeparator) { |
| this._items.push(new WI.ContextMenuItem(this._contextMenu, "separator")); |
| this._pendingSeparator = null; |
| } |
| this._items.push(item); |
| } |
| |
| isEmpty() |
| { |
| return !this._items.length; |
| } |
| |
| // Private |
| |
| _buildDescriptor() |
| { |
| if (this.isEmpty()) |
| return null; |
| |
| let subItems = this._items.map((item) => item._buildDescriptor()).filter((item) => !!item); |
| return {type: "subMenu", label: this._label, enabled: !this._disabled, subItems}; |
| } |
| }; |
| |
| WI.ContextMenu = class ContextMenu extends WI.ContextSubMenuItem |
| { |
| constructor(event) |
| { |
| super(null, ""); |
| |
| this._event = event; |
| this._handlers = {}; |
| this._id = 0; |
| |
| this._beforeShowCallbacks = []; |
| } |
| |
| // Static |
| |
| static createFromEvent(event, onlyExisting = false) |
| { |
| if (!event[WI.ContextMenu.ProposedMenuSymbol] && !onlyExisting) |
| event[WI.ContextMenu.ProposedMenuSymbol] = new WI.ContextMenu(event); |
| |
| return event[WI.ContextMenu.ProposedMenuSymbol] || null; |
| } |
| |
| static contextMenuItemSelected(id) |
| { |
| if (WI.ContextMenu._lastContextMenu) |
| WI.ContextMenu._lastContextMenu._itemSelected(id); |
| } |
| |
| static contextMenuCleared() |
| { |
| // FIXME: Unfortunately, contextMenuCleared is invoked between show and item selected |
| // so we can't delete last menu object from WI. Fix the contract. |
| } |
| |
| // Public |
| |
| nextId() |
| { |
| return this._id++; |
| } |
| |
| show() |
| { |
| console.assert(this._event instanceof MouseEvent); |
| |
| let menuObject = this._buildDescriptor(); |
| if (menuObject.length) { |
| WI.ContextMenu._lastContextMenu = this; |
| |
| if (this._event.type !== "contextmenu" && typeof InspectorFrontendHost.dispatchEventAsContextMenuEvent === "function") { |
| console.assert(event.type !== "mousedown" || this._beforeShowCallbacks.length > 0, "Calling show() in a mousedown handler should have a before show callback to enable quick selection."); |
| |
| this._menuObject = menuObject; |
| this._event.target.addEventListener("contextmenu", this, true); |
| InspectorFrontendHost.dispatchEventAsContextMenuEvent(this._event); |
| } else |
| InspectorFrontendHost.showContextMenu(this._event, menuObject); |
| } |
| |
| if (this._event) |
| this._event.stopImmediatePropagation(); |
| } |
| |
| addBeforeShowCallback(callback) |
| { |
| this._beforeShowCallbacks.push(callback); |
| } |
| |
| // Protected |
| |
| handleEvent(event) |
| { |
| console.assert(event.type === "contextmenu"); |
| |
| for (let callback of this._beforeShowCallbacks) |
| callback(this); |
| |
| this._event.target.removeEventListener("contextmenu", this, true); |
| InspectorFrontendHost.showContextMenu(event, this._menuObject); |
| this._menuObject = null; |
| |
| event.stopImmediatePropagation(); |
| } |
| |
| // Private |
| |
| _setHandler(id, handler) |
| { |
| if (handler) |
| this._handlers[id] = handler; |
| } |
| |
| _buildDescriptor() |
| { |
| return this._items.map((item) => item._buildDescriptor()).filter((item) => !!item); |
| } |
| |
| _itemSelected(id) |
| { |
| try { |
| if (this._handlers[id]) |
| this._handlers[id].call(this); |
| } catch (e) { |
| WI.reportInternalError(e); |
| } |
| } |
| }; |
| |
| WI.ContextMenu.ProposedMenuSymbol = Symbol("context-menu-proposed-menu"); |