|  | /* | 
|  | * Copyright (C) 2011 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: | 
|  | * 1. Redistributions of source code must retain the above copyright | 
|  | *    notice, this list of conditions and the following disclaimer. | 
|  | * 2. 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. | 
|  | * | 
|  | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * @unrestricted | 
|  | */ | 
|  | UI.SoftContextMenu = class { | 
|  | /** | 
|  | * @param {!Array.<!InspectorFrontendHostAPI.ContextMenuDescriptor>} items | 
|  | * @param {function(string)} itemSelectedCallback | 
|  | * @param {!UI.SoftContextMenu=} parentMenu | 
|  | */ | 
|  | constructor(items, itemSelectedCallback, parentMenu) { | 
|  | this._items = items; | 
|  | this._itemSelectedCallback = itemSelectedCallback; | 
|  | this._parentMenu = parentMenu; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Document} document | 
|  | * @param {!AnchorBox} anchorBox | 
|  | */ | 
|  | show(document, anchorBox) { | 
|  | if (!this._items.length) | 
|  | return; | 
|  |  | 
|  | this._document = document; | 
|  |  | 
|  | this._glassPane = new UI.GlassPane(); | 
|  | this._glassPane.setPointerEventsBehavior( | 
|  | this._parentMenu ? UI.GlassPane.PointerEventsBehavior.PierceGlassPane : | 
|  | UI.GlassPane.PointerEventsBehavior.BlockedByGlassPane); | 
|  | this._glassPane.registerRequiredCSS('ui/softContextMenu.css'); | 
|  | this._glassPane.setContentAnchorBox(anchorBox); | 
|  | this._glassPane.setSizeBehavior(UI.GlassPane.SizeBehavior.MeasureContent); | 
|  | this._glassPane.setMarginBehavior(UI.GlassPane.MarginBehavior.NoMargin); | 
|  | this._glassPane.setAnchorBehavior( | 
|  | this._parentMenu ? UI.GlassPane.AnchorBehavior.PreferRight : UI.GlassPane.AnchorBehavior.PreferBottom); | 
|  |  | 
|  | this._contextMenuElement = this._glassPane.contentElement.createChild('div', 'soft-context-menu'); | 
|  | this._contextMenuElement.tabIndex = 0; | 
|  | this._contextMenuElement.addEventListener('mouseup', e => e.consume(), false); | 
|  | this._contextMenuElement.addEventListener('keydown', this._menuKeyDown.bind(this), false); | 
|  |  | 
|  | for (let i = 0; i < this._items.length; ++i) | 
|  | this._contextMenuElement.appendChild(this._createMenuItem(this._items[i])); | 
|  |  | 
|  | this._glassPane.show(document); | 
|  | this._focusRestorer = new UI.ElementFocusRestorer(this._contextMenuElement); | 
|  |  | 
|  | if (!this._parentMenu) { | 
|  | this._onBodyMouseDown = event => { | 
|  | this.discard(); | 
|  | event.consume(true); | 
|  | }; | 
|  | this._document.body.addEventListener('mousedown', this._onBodyMouseDown, false); | 
|  | } | 
|  | } | 
|  |  | 
|  | discard() { | 
|  | if (this._subMenu) | 
|  | this._subMenu.discard(); | 
|  | if (this._focusRestorer) | 
|  | this._focusRestorer.restore(); | 
|  | if (this._glassPane) { | 
|  | this._glassPane.hide(); | 
|  | delete this._glassPane; | 
|  | if (this._onBodyMouseDown) { | 
|  | this._document.body.removeEventListener('mousedown', this._onBodyMouseDown, false); | 
|  | delete this._onBodyMouseDown; | 
|  | } | 
|  | } | 
|  | if (this._parentMenu) | 
|  | delete this._parentMenu._subMenu; | 
|  | } | 
|  |  | 
|  | _createMenuItem(item) { | 
|  | if (item.type === 'separator') | 
|  | return this._createSeparator(); | 
|  |  | 
|  | if (item.type === 'subMenu') | 
|  | return this._createSubMenu(item); | 
|  |  | 
|  | const menuItemElement = createElementWithClass('div', 'soft-context-menu-item'); | 
|  | const checkMarkElement = UI.Icon.create('smallicon-checkmark', 'checkmark'); | 
|  | menuItemElement.appendChild(checkMarkElement); | 
|  | if (!item.checked) | 
|  | checkMarkElement.style.opacity = '0'; | 
|  |  | 
|  | if (item.element) { | 
|  | const wrapper = menuItemElement.createChild('div', 'soft-context-menu-custom-item'); | 
|  | wrapper.appendChild(item.element); | 
|  | menuItemElement._isCustom = true; | 
|  | return menuItemElement; | 
|  | } | 
|  |  | 
|  | if (!item.enabled) | 
|  | menuItemElement.classList.add('soft-context-menu-disabled'); | 
|  | menuItemElement.createTextChild(item.label); | 
|  | menuItemElement.createChild('span', 'soft-context-menu-shortcut').textContent = item.shortcut; | 
|  |  | 
|  | 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('mouseleave', this._menuItemMouseLeave.bind(this), false); | 
|  |  | 
|  | menuItemElement._actionId = item.id; | 
|  | return menuItemElement; | 
|  | } | 
|  |  | 
|  | _createSubMenu(item) { | 
|  | const menuItemElement = createElementWithClass('div', 'soft-context-menu-item'); | 
|  | menuItemElement._subItems = item.subItems; | 
|  |  | 
|  | // Occupy the same space on the left in all items. | 
|  | const checkMarkElement = UI.Icon.create('smallicon-checkmark', 'soft-context-menu-item-checkmark'); | 
|  | checkMarkElement.classList.add('checkmark'); | 
|  | menuItemElement.appendChild(checkMarkElement); | 
|  | checkMarkElement.style.opacity = '0'; | 
|  |  | 
|  | menuItemElement.createTextChild(item.label); | 
|  |  | 
|  | const subMenuArrowElement = menuItemElement.createChild('span', 'soft-context-menu-item-submenu-arrow'); | 
|  | subMenuArrowElement.textContent = '\u25B6';  // BLACK RIGHT-POINTING TRIANGLE | 
|  |  | 
|  | 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('mouseleave', this._menuItemMouseLeave.bind(this), false); | 
|  |  | 
|  | return menuItemElement; | 
|  | } | 
|  |  | 
|  | _createSeparator() { | 
|  | const separatorElement = createElementWithClass('div', 'soft-context-menu-separator'); | 
|  | separatorElement._isSeparator = true; | 
|  | separatorElement.createChild('div', 'separator-line'); | 
|  | return separatorElement; | 
|  | } | 
|  |  | 
|  | _menuItemMouseDown(event) { | 
|  | // Do not let separator's mouse down hit menu's handler - we need to receive mouse up! | 
|  | event.consume(true); | 
|  | } | 
|  |  | 
|  | _menuItemMouseUp(event) { | 
|  | this._triggerAction(event.target, event); | 
|  | event.consume(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!UI.SoftContextMenu} | 
|  | */ | 
|  | _root() { | 
|  | let root = this; | 
|  | while (root._parentMenu) | 
|  | root = root._parentMenu; | 
|  | return root; | 
|  | } | 
|  |  | 
|  | _triggerAction(menuItemElement, event) { | 
|  | if (!menuItemElement._subItems) { | 
|  | this._root().discard(); | 
|  | event.consume(true); | 
|  | if (typeof menuItemElement._actionId !== 'undefined') { | 
|  | this._itemSelectedCallback(menuItemElement._actionId); | 
|  | delete menuItemElement._actionId; | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | this._showSubMenu(menuItemElement); | 
|  | event.consume(); | 
|  | } | 
|  |  | 
|  | _showSubMenu(menuItemElement) { | 
|  | if (menuItemElement._subMenuTimer) { | 
|  | clearTimeout(menuItemElement._subMenuTimer); | 
|  | delete menuItemElement._subMenuTimer; | 
|  | } | 
|  | if (this._subMenu) | 
|  | return; | 
|  |  | 
|  | this._subMenu = new UI.SoftContextMenu(menuItemElement._subItems, this._itemSelectedCallback, this); | 
|  | const anchorBox = menuItemElement.boxInWindow(); | 
|  | // Adjust for padding. | 
|  | anchorBox.y -= 5; | 
|  | anchorBox.x += 3; | 
|  | anchorBox.width -= 6; | 
|  | anchorBox.height += 10; | 
|  | this._subMenu.show(this._document, anchorBox); | 
|  | } | 
|  |  | 
|  | _menuItemMouseOver(event) { | 
|  | this._highlightMenuItem(event.target, true); | 
|  | } | 
|  |  | 
|  | _menuItemMouseLeave(event) { | 
|  | if (!this._subMenu || !event.relatedTarget) { | 
|  | this._highlightMenuItem(null, true); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const relatedTarget = event.relatedTarget; | 
|  | if (relatedTarget === this._contextMenuElement) | 
|  | this._highlightMenuItem(null, true); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {?Element} menuItemElement | 
|  | * @param {boolean} scheduleSubMenu | 
|  | */ | 
|  | _highlightMenuItem(menuItemElement, scheduleSubMenu) { | 
|  | if (this._highlightedMenuItemElement === menuItemElement) | 
|  | return; | 
|  |  | 
|  | if (this._subMenu) | 
|  | this._subMenu.discard(); | 
|  | if (this._highlightedMenuItemElement) { | 
|  | this._highlightedMenuItemElement.classList.remove('force-white-icons'); | 
|  | this._highlightedMenuItemElement.classList.remove('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) { | 
|  | this._highlightedMenuItemElement.classList.add('force-white-icons'); | 
|  | this._highlightedMenuItemElement.classList.add('soft-context-menu-item-mouse-over'); | 
|  | this._contextMenuElement.focus(); | 
|  | if (scheduleSubMenu && this._highlightedMenuItemElement._subItems && | 
|  | !this._highlightedMenuItemElement._subMenuTimer) { | 
|  | this._highlightedMenuItemElement._subMenuTimer = | 
|  | setTimeout(this._showSubMenu.bind(this, this._highlightedMenuItemElement), 150); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | _highlightPrevious() { | 
|  | let menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.previousSibling : | 
|  | this._contextMenuElement.lastChild; | 
|  | while (menuItemElement && (menuItemElement._isSeparator || menuItemElement._isCustom)) | 
|  | menuItemElement = menuItemElement.previousSibling; | 
|  | if (menuItemElement) | 
|  | this._highlightMenuItem(menuItemElement, false); | 
|  | } | 
|  |  | 
|  | _highlightNext() { | 
|  | let menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.nextSibling : | 
|  | this._contextMenuElement.firstChild; | 
|  | while (menuItemElement && (menuItemElement._isSeparator || menuItemElement._isCustom)) | 
|  | menuItemElement = menuItemElement.nextSibling; | 
|  | if (menuItemElement) | 
|  | this._highlightMenuItem(menuItemElement, false); | 
|  | } | 
|  |  | 
|  | _menuKeyDown(event) { | 
|  | switch (event.key) { | 
|  | case 'ArrowUp': | 
|  | this._highlightPrevious(); | 
|  | break; | 
|  | case 'ArrowDown': | 
|  | this._highlightNext(); | 
|  | break; | 
|  | case 'ArrowLeft': | 
|  | if (this._parentMenu) { | 
|  | this._highlightMenuItem(null, false); | 
|  | this.discard(); | 
|  | } | 
|  | break; | 
|  | case 'ArrowRight': | 
|  | if (!this._highlightedMenuItemElement) | 
|  | break; | 
|  | if (this._highlightedMenuItemElement._subItems) { | 
|  | this._showSubMenu(this._highlightedMenuItemElement); | 
|  | this._subMenu._highlightNext(); | 
|  | } | 
|  | break; | 
|  | case 'Escape': | 
|  | this.discard(); | 
|  | break; | 
|  | case 'Enter': | 
|  | if (!isEnterKey(event)) | 
|  | break; | 
|  | // Fall through | 
|  | case ' ':  // Space | 
|  | if (this._highlightedMenuItemElement) | 
|  | this._triggerAction(this._highlightedMenuItemElement, event); | 
|  | if (this._highlightedMenuItemElement._subItems) | 
|  | this._subMenu._highlightNext(); | 
|  | break; | 
|  | } | 
|  | event.consume(true); | 
|  | } | 
|  | }; |