| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2009 Google Inc. All rights reserved. | 2 * Copyright (C) 2009 Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
| 6 * met: | 6 * met: |
| 7 * | 7 * |
| 8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
| 11 * copyright notice, this list of conditions and the following disclaimer | 11 * copyright notice, this list of conditions and the following disclaimer |
| 12 * in the documentation and/or other materials provided with the | 12 * in the documentation and/or other materials provided with the |
| 13 * distribution. | 13 * distribution. |
| 14 * * Neither the name of Google Inc. nor the names of its | 14 * * Neither the name of Google Inc. nor the names of its |
| 15 * contributors may be used to endorse or promote products derived from | 15 * contributors may be used to endorse or promote products derived from |
| 16 * this software without specific prior written permission. | 16 * this software without specific prior written permission. |
| 17 * | 17 * |
| 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 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. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 */ | 29 */ |
| 30 | |
| 31 /** | 30 /** |
| 32 * @constructor | 31 * @unrestricted |
| 33 * @param {?WebInspector.ContextMenu} topLevelMenu | 32 */ |
| 34 * @param {string} type | 33 WebInspector.ContextMenuItem = class { |
| 35 * @param {string=} label | 34 /** |
| 36 * @param {boolean=} disabled | 35 * @param {?WebInspector.ContextMenu} topLevelMenu |
| 37 * @param {boolean=} checked | 36 * @param {string} type |
| 38 */ | 37 * @param {string=} label |
| 39 WebInspector.ContextMenuItem = function(topLevelMenu, type, label, disabled, che
cked) | 38 * @param {boolean=} disabled |
| 40 { | 39 * @param {boolean=} checked |
| 40 */ |
| 41 constructor(topLevelMenu, type, label, disabled, checked) { |
| 41 this._type = type; | 42 this._type = type; |
| 42 this._label = label; | 43 this._label = label; |
| 43 this._disabled = disabled; | 44 this._disabled = disabled; |
| 44 this._checked = checked; | 45 this._checked = checked; |
| 45 this._contextMenu = topLevelMenu; | 46 this._contextMenu = topLevelMenu; |
| 46 if (type === "item" || type === "checkbox") | 47 if (type === 'item' || type === 'checkbox') |
| 47 this._id = topLevelMenu ? topLevelMenu._nextId() : 0; | 48 this._id = topLevelMenu ? topLevelMenu._nextId() : 0; |
| 49 } |
| 50 |
| 51 /** |
| 52 * @return {number} |
| 53 */ |
| 54 id() { |
| 55 return this._id; |
| 56 } |
| 57 |
| 58 /** |
| 59 * @return {string} |
| 60 */ |
| 61 type() { |
| 62 return this._type; |
| 63 } |
| 64 |
| 65 /** |
| 66 * @return {boolean} |
| 67 */ |
| 68 isEnabled() { |
| 69 return !this._disabled; |
| 70 } |
| 71 |
| 72 /** |
| 73 * @param {boolean} enabled |
| 74 */ |
| 75 setEnabled(enabled) { |
| 76 this._disabled = !enabled; |
| 77 } |
| 78 |
| 79 /** |
| 80 * @return {!InspectorFrontendHostAPI.ContextMenuDescriptor} |
| 81 */ |
| 82 _buildDescriptor() { |
| 83 switch (this._type) { |
| 84 case 'item': |
| 85 var result = {type: 'item', id: this._id, label: this._label, enabled: !
this._disabled}; |
| 86 if (this._customElement) |
| 87 result.element = this._customElement; |
| 88 if (this._shortcut) |
| 89 result.shortcut = this._shortcut; |
| 90 return result; |
| 91 case 'separator': |
| 92 return {type: 'separator'}; |
| 93 case 'checkbox': |
| 94 return {type: 'checkbox', id: this._id, label: this._label, checked: !!t
his._checked, enabled: !this._disabled}; |
| 95 } |
| 96 throw new Error('Invalid item type:' + this._type); |
| 97 } |
| 98 |
| 99 /** |
| 100 * @param {string} shortcut |
| 101 */ |
| 102 setShortcut(shortcut) { |
| 103 this._shortcut = shortcut; |
| 104 } |
| 48 }; | 105 }; |
| 49 | 106 |
| 50 WebInspector.ContextMenuItem.prototype = { | |
| 51 /** | |
| 52 * @return {number} | |
| 53 */ | |
| 54 id: function() | |
| 55 { | |
| 56 return this._id; | |
| 57 }, | |
| 58 | |
| 59 /** | |
| 60 * @return {string} | |
| 61 */ | |
| 62 type: function() | |
| 63 { | |
| 64 return this._type; | |
| 65 }, | |
| 66 | |
| 67 /** | |
| 68 * @return {boolean} | |
| 69 */ | |
| 70 isEnabled: function() | |
| 71 { | |
| 72 return !this._disabled; | |
| 73 }, | |
| 74 | |
| 75 /** | |
| 76 * @param {boolean} enabled | |
| 77 */ | |
| 78 setEnabled: function(enabled) | |
| 79 { | |
| 80 this._disabled = !enabled; | |
| 81 }, | |
| 82 | |
| 83 /** | |
| 84 * @return {!InspectorFrontendHostAPI.ContextMenuDescriptor} | |
| 85 */ | |
| 86 _buildDescriptor: function() | |
| 87 { | |
| 88 switch (this._type) { | |
| 89 case "item": | |
| 90 var result = { type: "item", id: this._id, label: this._label, enabl
ed: !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 }; | |
| 100 } | |
| 101 throw new Error("Invalid item type:" + this._type); | |
| 102 }, | |
| 103 | |
| 104 /** | |
| 105 * @param {string} shortcut | |
| 106 */ | |
| 107 setShortcut: function(shortcut) | |
| 108 { | |
| 109 this._shortcut = shortcut; | |
| 110 } | |
| 111 }; | |
| 112 | |
| 113 /** | 107 /** |
| 114 * @constructor | 108 * @unrestricted |
| 115 * @extends {WebInspector.ContextMenuItem} | 109 */ |
| 116 * @param {?WebInspector.ContextMenu} topLevelMenu | 110 WebInspector.ContextSubMenuItem = class extends WebInspector.ContextMenuItem { |
| 117 * @param {string=} label | 111 /** |
| 118 * @param {boolean=} disabled | 112 * @param {?WebInspector.ContextMenu} topLevelMenu |
| 119 */ | 113 * @param {string=} label |
| 120 WebInspector.ContextSubMenuItem = function(topLevelMenu, label, disabled) | 114 * @param {boolean=} disabled |
| 121 { | 115 */ |
| 122 WebInspector.ContextMenuItem.call(this, topLevelMenu, "subMenu", label, disa
bled); | 116 constructor(topLevelMenu, label, disabled) { |
| 117 super(topLevelMenu, 'subMenu', label, disabled); |
| 123 /** @type {!Array.<!WebInspector.ContextMenuItem>} */ | 118 /** @type {!Array.<!WebInspector.ContextMenuItem>} */ |
| 124 this._items = []; | 119 this._items = []; |
| 120 } |
| 121 |
| 122 /** |
| 123 * @param {string} label |
| 124 * @param {function(?)} handler |
| 125 * @param {boolean=} disabled |
| 126 * @return {!WebInspector.ContextMenuItem} |
| 127 */ |
| 128 appendItem(label, handler, disabled) { |
| 129 var item = new WebInspector.ContextMenuItem(this._contextMenu, 'item', label
, disabled); |
| 130 this._pushItem(item); |
| 131 this._contextMenu._setHandler(item.id(), handler); |
| 132 return item; |
| 133 } |
| 134 |
| 135 /** |
| 136 * @param {!Element} element |
| 137 * @return {!WebInspector.ContextMenuItem} |
| 138 */ |
| 139 appendCustomItem(element) { |
| 140 var item = new WebInspector.ContextMenuItem(this._contextMenu, 'item', '<cus
tom>'); |
| 141 item._customElement = element; |
| 142 this._pushItem(item); |
| 143 return item; |
| 144 } |
| 145 |
| 146 /** |
| 147 * @param {string} actionId |
| 148 * @param {string=} label |
| 149 * @return {!WebInspector.ContextMenuItem} |
| 150 */ |
| 151 appendAction(actionId, label) { |
| 152 var action = WebInspector.actionRegistry.action(actionId); |
| 153 if (!label) |
| 154 label = action.title(); |
| 155 var result = this.appendItem(label, action.execute.bind(action)); |
| 156 var shortcut = WebInspector.shortcutRegistry.shortcutTitleForAction(actionId
); |
| 157 if (shortcut) |
| 158 result.setShortcut(shortcut); |
| 159 return result; |
| 160 } |
| 161 |
| 162 /** |
| 163 * @param {string} label |
| 164 * @param {boolean=} disabled |
| 165 * @param {string=} subMenuId |
| 166 * @return {!WebInspector.ContextSubMenuItem} |
| 167 */ |
| 168 appendSubMenuItem(label, disabled, subMenuId) { |
| 169 var item = new WebInspector.ContextSubMenuItem(this._contextMenu, label, dis
abled); |
| 170 if (subMenuId) |
| 171 this._contextMenu._namedSubMenus.set(subMenuId, item); |
| 172 this._pushItem(item); |
| 173 return item; |
| 174 } |
| 175 |
| 176 /** |
| 177 * @param {string} label |
| 178 * @param {function()} handler |
| 179 * @param {boolean=} checked |
| 180 * @param {boolean=} disabled |
| 181 * @return {!WebInspector.ContextMenuItem} |
| 182 */ |
| 183 appendCheckboxItem(label, handler, checked, disabled) { |
| 184 var item = new WebInspector.ContextMenuItem(this._contextMenu, 'checkbox', l
abel, disabled, checked); |
| 185 this._pushItem(item); |
| 186 this._contextMenu._setHandler(item.id(), handler); |
| 187 return item; |
| 188 } |
| 189 |
| 190 appendSeparator() { |
| 191 if (this._items.length) |
| 192 this._pendingSeparator = true; |
| 193 } |
| 194 |
| 195 /** |
| 196 * @param {!WebInspector.ContextMenuItem} item |
| 197 */ |
| 198 _pushItem(item) { |
| 199 if (this._pendingSeparator) { |
| 200 this._items.push(new WebInspector.ContextMenuItem(this._contextMenu, 'sepa
rator')); |
| 201 delete this._pendingSeparator; |
| 202 } |
| 203 this._items.push(item); |
| 204 } |
| 205 |
| 206 /** |
| 207 * @return {boolean} |
| 208 */ |
| 209 isEmpty() { |
| 210 return !this._items.length; |
| 211 } |
| 212 |
| 213 /** |
| 214 * @override |
| 215 * @return {!InspectorFrontendHostAPI.ContextMenuDescriptor} |
| 216 */ |
| 217 _buildDescriptor() { |
| 218 var result = {type: 'subMenu', label: this._label, enabled: !this._disabled,
subItems: []}; |
| 219 for (var i = 0; i < this._items.length; ++i) |
| 220 result.subItems.push(this._items[i]._buildDescriptor()); |
| 221 return result; |
| 222 } |
| 223 |
| 224 /** |
| 225 * @param {string} location |
| 226 */ |
| 227 appendItemsAtLocation(location) { |
| 228 /** |
| 229 * @param {!WebInspector.ContextSubMenuItem} menu |
| 230 * @param {!Runtime.Extension} extension |
| 231 */ |
| 232 function appendExtension(menu, extension) { |
| 233 var subMenuId = extension.descriptor()['subMenuId']; |
| 234 if (subMenuId) { |
| 235 var subMenuItem = menu.appendSubMenuItem(extension.title(), false, subMe
nuId); |
| 236 subMenuItem.appendItemsAtLocation(subMenuId); |
| 237 } else { |
| 238 menu.appendAction(extension.descriptor()['actionId']); |
| 239 } |
| 240 } |
| 241 |
| 242 // Hard-coded named groups for elements to maintain generic order. |
| 243 var groupWeights = ['new', 'open', 'clipboard', 'navigate', 'footer']; |
| 244 |
| 245 /** @type {!Map.<string, !Array.<!Runtime.Extension>>} */ |
| 246 var groups = new Map(); |
| 247 var extensions = self.runtime.extensions('context-menu-item'); |
| 248 for (var extension of extensions) { |
| 249 var itemLocation = extension.descriptor()['location'] || ''; |
| 250 if (!itemLocation.startsWith(location + '/')) |
| 251 continue; |
| 252 |
| 253 var itemGroup = itemLocation.substr(location.length + 1); |
| 254 if (!itemGroup || itemGroup.includes('/')) |
| 255 continue; |
| 256 var group = groups.get(itemGroup); |
| 257 if (!group) { |
| 258 group = []; |
| 259 groups.set(itemGroup, group); |
| 260 if (groupWeights.indexOf(itemGroup) === -1) |
| 261 groupWeights.splice(4, 0, itemGroup); |
| 262 } |
| 263 group.push(extension); |
| 264 } |
| 265 for (var groupName of groupWeights) { |
| 266 var group = groups.get(groupName); |
| 267 if (!group) |
| 268 continue; |
| 269 group.forEach(appendExtension.bind(null, this)); |
| 270 this.appendSeparator(); |
| 271 } |
| 272 } |
| 125 }; | 273 }; |
| 126 | 274 |
| 127 WebInspector.ContextSubMenuItem.prototype = { | |
| 128 /** | |
| 129 * @param {string} label | |
| 130 * @param {function(?)} handler | |
| 131 * @param {boolean=} disabled | |
| 132 * @return {!WebInspector.ContextMenuItem} | |
| 133 */ | |
| 134 appendItem: function(label, handler, disabled) | |
| 135 { | |
| 136 var item = new WebInspector.ContextMenuItem(this._contextMenu, "item", l
abel, disabled); | |
| 137 this._pushItem(item); | |
| 138 this._contextMenu._setHandler(item.id(), handler); | |
| 139 return item; | |
| 140 }, | |
| 141 | |
| 142 /** | |
| 143 * @param {!Element} element | |
| 144 * @return {!WebInspector.ContextMenuItem} | |
| 145 */ | |
| 146 appendCustomItem: function(element) | |
| 147 { | |
| 148 var item = new WebInspector.ContextMenuItem(this._contextMenu, "item", "
<custom>"); | |
| 149 item._customElement = element; | |
| 150 this._pushItem(item); | |
| 151 return item; | |
| 152 }, | |
| 153 | |
| 154 /** | |
| 155 * @param {string} actionId | |
| 156 * @param {string=} label | |
| 157 * @return {!WebInspector.ContextMenuItem} | |
| 158 */ | |
| 159 appendAction: function(actionId, label) | |
| 160 { | |
| 161 var action = WebInspector.actionRegistry.action(actionId); | |
| 162 if (!label) | |
| 163 label = action.title(); | |
| 164 var result = this.appendItem(label, action.execute.bind(action)); | |
| 165 var shortcut = WebInspector.shortcutRegistry.shortcutTitleForAction(acti
onId); | |
| 166 if (shortcut) | |
| 167 result.setShortcut(shortcut); | |
| 168 return result; | |
| 169 }, | |
| 170 | |
| 171 /** | |
| 172 * @param {string} label | |
| 173 * @param {boolean=} disabled | |
| 174 * @param {string=} subMenuId | |
| 175 * @return {!WebInspector.ContextSubMenuItem} | |
| 176 */ | |
| 177 appendSubMenuItem: function(label, disabled, subMenuId) | |
| 178 { | |
| 179 var item = new WebInspector.ContextSubMenuItem(this._contextMenu, label,
disabled); | |
| 180 if (subMenuId) | |
| 181 this._contextMenu._namedSubMenus.set(subMenuId, item); | |
| 182 this._pushItem(item); | |
| 183 return item; | |
| 184 }, | |
| 185 | |
| 186 /** | |
| 187 * @param {string} label | |
| 188 * @param {function()} handler | |
| 189 * @param {boolean=} checked | |
| 190 * @param {boolean=} disabled | |
| 191 * @return {!WebInspector.ContextMenuItem} | |
| 192 */ | |
| 193 appendCheckboxItem: function(label, handler, checked, disabled) | |
| 194 { | |
| 195 var item = new WebInspector.ContextMenuItem(this._contextMenu, "checkbox
", label, disabled, checked); | |
| 196 this._pushItem(item); | |
| 197 this._contextMenu._setHandler(item.id(), handler); | |
| 198 return item; | |
| 199 }, | |
| 200 | |
| 201 appendSeparator: function() | |
| 202 { | |
| 203 if (this._items.length) | |
| 204 this._pendingSeparator = true; | |
| 205 }, | |
| 206 | |
| 207 /** | |
| 208 * @param {!WebInspector.ContextMenuItem} item | |
| 209 */ | |
| 210 _pushItem: function(item) | |
| 211 { | |
| 212 if (this._pendingSeparator) { | |
| 213 this._items.push(new WebInspector.ContextMenuItem(this._contextMenu,
"separator")); | |
| 214 delete this._pendingSeparator; | |
| 215 } | |
| 216 this._items.push(item); | |
| 217 }, | |
| 218 | |
| 219 /** | |
| 220 * @return {boolean} | |
| 221 */ | |
| 222 isEmpty: function() | |
| 223 { | |
| 224 return !this._items.length; | |
| 225 }, | |
| 226 | |
| 227 /** | |
| 228 * @override | |
| 229 * @return {!InspectorFrontendHostAPI.ContextMenuDescriptor} | |
| 230 */ | |
| 231 _buildDescriptor: function() | |
| 232 { | |
| 233 var result = { type: "subMenu", label: this._label, enabled: !this._disa
bled, subItems: [] }; | |
| 234 for (var i = 0; i < this._items.length; ++i) | |
| 235 result.subItems.push(this._items[i]._buildDescriptor()); | |
| 236 return result; | |
| 237 }, | |
| 238 | |
| 239 /** | |
| 240 * @param {string} location | |
| 241 */ | |
| 242 appendItemsAtLocation: function(location) | |
| 243 { | |
| 244 /** | |
| 245 * @param {!WebInspector.ContextSubMenuItem} menu | |
| 246 * @param {!Runtime.Extension} extension | |
| 247 */ | |
| 248 function appendExtension(menu, extension) | |
| 249 { | |
| 250 var subMenuId = extension.descriptor()["subMenuId"]; | |
| 251 if (subMenuId) { | |
| 252 var subMenuItem = menu.appendSubMenuItem(extension.title(), fals
e, subMenuId); | |
| 253 subMenuItem.appendItemsAtLocation(subMenuId); | |
| 254 } else { | |
| 255 menu.appendAction(extension.descriptor()["actionId"]); | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 // Hard-coded named groups for elements to maintain generic order. | |
| 260 var groupWeights = ["new", "open", "clipboard", "navigate", "footer"]; | |
| 261 | |
| 262 /** @type {!Map.<string, !Array.<!Runtime.Extension>>} */ | |
| 263 var groups = new Map(); | |
| 264 var extensions = self.runtime.extensions("context-menu-item"); | |
| 265 for (var extension of extensions) { | |
| 266 var itemLocation = extension.descriptor()["location"] || ""; | |
| 267 if (!itemLocation.startsWith(location + "/")) | |
| 268 continue; | |
| 269 | |
| 270 var itemGroup = itemLocation.substr(location.length + 1); | |
| 271 if (!itemGroup || itemGroup.includes("/")) | |
| 272 continue; | |
| 273 var group = groups.get(itemGroup); | |
| 274 if (!group) { | |
| 275 group = []; | |
| 276 groups.set(itemGroup, group); | |
| 277 if (groupWeights.indexOf(itemGroup) === -1) | |
| 278 groupWeights.splice(4, 0, itemGroup); | |
| 279 } | |
| 280 group.push(extension); | |
| 281 } | |
| 282 for (var groupName of groupWeights) { | |
| 283 var group = groups.get(groupName); | |
| 284 if (!group) | |
| 285 continue; | |
| 286 group.forEach(appendExtension.bind(null, this)); | |
| 287 this.appendSeparator(); | |
| 288 } | |
| 289 }, | |
| 290 | |
| 291 __proto__: WebInspector.ContextMenuItem.prototype | |
| 292 }; | |
| 293 | |
| 294 /** | 275 /** |
| 295 * @constructor | 276 * @unrestricted |
| 296 * @extends {WebInspector.ContextSubMenuItem} | 277 */ |
| 297 * @param {!Event} event | 278 WebInspector.ContextMenu = class extends WebInspector.ContextSubMenuItem { |
| 298 * @param {boolean=} useSoftMenu | 279 /** |
| 299 * @param {number=} x | 280 * @param {!Event} event |
| 300 * @param {number=} y | 281 * @param {boolean=} useSoftMenu |
| 301 */ | 282 * @param {number=} x |
| 302 WebInspector.ContextMenu = function(event, useSoftMenu, x, y) | 283 * @param {number=} y |
| 303 { | 284 */ |
| 304 WebInspector.ContextSubMenuItem.call(this, null, ""); | 285 constructor(event, useSoftMenu, x, y) { |
| 286 super(null, ''); |
| 305 this._contextMenu = this; | 287 this._contextMenu = this; |
| 306 /** @type {!Array.<!Promise.<!Array.<!WebInspector.ContextMenu.Provider>>>}
*/ | 288 /** @type {!Array.<!Promise.<!Array.<!WebInspector.ContextMenu.Provider>>>}
*/ |
| 307 this._pendingPromises = []; | 289 this._pendingPromises = []; |
| 308 /** @type {!Array<!Object>} */ | 290 /** @type {!Array<!Object>} */ |
| 309 this._pendingTargets = []; | 291 this._pendingTargets = []; |
| 310 this._event = event; | 292 this._event = event; |
| 311 this._useSoftMenu = !!useSoftMenu; | 293 this._useSoftMenu = !!useSoftMenu; |
| 312 this._x = x === undefined ? event.x : x; | 294 this._x = x === undefined ? event.x : x; |
| 313 this._y = y === undefined ? event.y : y; | 295 this._y = y === undefined ? event.y : y; |
| 314 this._handlers = {}; | 296 this._handlers = {}; |
| 315 this._id = 0; | 297 this._id = 0; |
| 316 /** @type {!Map<string, !WebInspector.ContextSubMenuItem>} */ | 298 /** @type {!Map<string, !WebInspector.ContextSubMenuItem>} */ |
| 317 this._namedSubMenus = new Map(); | 299 this._namedSubMenus = new Map(); |
| 318 }; | 300 } |
| 319 | 301 |
| 320 WebInspector.ContextMenu.initialize = function() | 302 static initialize() { |
| 321 { | |
| 322 InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Event
s.SetUseSoftMenu, setUseSoftMenu); | 303 InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Event
s.SetUseSoftMenu, setUseSoftMenu); |
| 323 /** | 304 /** |
| 324 * @param {!WebInspector.Event} event | 305 * @param {!WebInspector.Event} event |
| 325 */ | 306 */ |
| 326 function setUseSoftMenu(event) | 307 function setUseSoftMenu(event) { |
| 327 { | 308 WebInspector.ContextMenu._useSoftMenu = /** @type {boolean} */ (event.data
); |
| 328 WebInspector.ContextMenu._useSoftMenu = /** @type {boolean} */ (event.da
ta); | |
| 329 } | 309 } |
| 330 }; | 310 } |
| 331 | 311 |
| 332 /** | 312 /** |
| 333 * @param {!Document} doc | 313 * @param {!Document} doc |
| 334 */ | 314 */ |
| 335 WebInspector.ContextMenu.installHandler = function(doc) | 315 static installHandler(doc) { |
| 336 { | 316 doc.body.addEventListener('contextmenu', handler, false); |
| 337 doc.body.addEventListener("contextmenu", handler, false); | |
| 338 | 317 |
| 339 /** | 318 /** |
| 340 * @param {!Event} event | 319 * @param {!Event} event |
| 341 */ | 320 */ |
| 342 function handler(event) | 321 function handler(event) { |
| 343 { | 322 var contextMenu = new WebInspector.ContextMenu(event); |
| 344 var contextMenu = new WebInspector.ContextMenu(event); | 323 contextMenu.appendApplicableItems(/** @type {!Object} */ (event.deepElemen
tFromPoint())); |
| 345 contextMenu.appendApplicableItems(/** @type {!Object} */ (event.deepElem
entFromPoint())); | 324 contextMenu.show(); |
| 346 contextMenu.show(); | |
| 347 } | 325 } |
| 326 } |
| 327 |
| 328 /** |
| 329 * @return {number} |
| 330 */ |
| 331 _nextId() { |
| 332 return this._id++; |
| 333 } |
| 334 |
| 335 /** |
| 336 * @param {function()} callback |
| 337 */ |
| 338 beforeShow(callback) { |
| 339 this._beforeShow = callback; |
| 340 } |
| 341 |
| 342 show() { |
| 343 Promise.all(this._pendingPromises).then(populate.bind(this)).then(this._inne
rShow.bind(this)); |
| 344 WebInspector.ContextMenu._pendingMenu = this; |
| 345 |
| 346 /** |
| 347 * @param {!Array.<!Array.<!WebInspector.ContextMenu.Provider>>} appendCallR
esults |
| 348 * @this {WebInspector.ContextMenu} |
| 349 */ |
| 350 function populate(appendCallResults) { |
| 351 if (WebInspector.ContextMenu._pendingMenu !== this) |
| 352 return; |
| 353 delete WebInspector.ContextMenu._pendingMenu; |
| 354 |
| 355 for (var i = 0; i < appendCallResults.length; ++i) { |
| 356 var providers = appendCallResults[i]; |
| 357 var target = this._pendingTargets[i]; |
| 358 |
| 359 for (var j = 0; j < providers.length; ++j) { |
| 360 var provider = /** @type {!WebInspector.ContextMenu.Provider} */ (prov
iders[j]); |
| 361 this.appendSeparator(); |
| 362 provider.appendApplicableItems(this._event, this, target); |
| 363 this.appendSeparator(); |
| 364 } |
| 365 } |
| 366 |
| 367 this._pendingPromises = []; |
| 368 this._pendingTargets = []; |
| 369 } |
| 370 |
| 371 this._event.consume(true); |
| 372 } |
| 373 |
| 374 discard() { |
| 375 if (this._softMenu) |
| 376 this._softMenu.discard(); |
| 377 } |
| 378 |
| 379 _innerShow() { |
| 380 if (typeof this._beforeShow === 'function') { |
| 381 this._beforeShow(); |
| 382 delete this._beforeShow; |
| 383 } |
| 384 |
| 385 var menuObject = this._buildDescriptors(); |
| 386 |
| 387 WebInspector._contextMenu = this; |
| 388 if (this._useSoftMenu || WebInspector.ContextMenu._useSoftMenu || InspectorF
rontendHost.isHostedMode()) { |
| 389 this._softMenu = new WebInspector.SoftContextMenu(menuObject, this._itemSe
lected.bind(this)); |
| 390 this._softMenu.show(this._event.target.ownerDocument, this._x, this._y); |
| 391 } else { |
| 392 InspectorFrontendHost.showContextMenuAtPoint(this._x, this._y, menuObject,
this._event.target.ownerDocument); |
| 393 |
| 394 /** |
| 395 * @this {WebInspector.ContextMenu} |
| 396 */ |
| 397 function listenToEvents() { |
| 398 InspectorFrontendHost.events.addEventListener( |
| 399 InspectorFrontendHostAPI.Events.ContextMenuCleared, this._menuCleare
d, this); |
| 400 InspectorFrontendHost.events.addEventListener( |
| 401 InspectorFrontendHostAPI.Events.ContextMenuItemSelected, this._onIte
mSelected, this); |
| 402 } |
| 403 |
| 404 // showContextMenuAtPoint call above synchronously issues a clear event fo
r previous context menu (if any), |
| 405 // so we skip it before subscribing to the clear event. |
| 406 setImmediate(listenToEvents.bind(this)); |
| 407 } |
| 408 } |
| 409 |
| 410 /** |
| 411 * @param {number} id |
| 412 * @param {function(?)} handler |
| 413 */ |
| 414 _setHandler(id, handler) { |
| 415 if (handler) |
| 416 this._handlers[id] = handler; |
| 417 } |
| 418 |
| 419 /** |
| 420 * @return {!Array.<!InspectorFrontendHostAPI.ContextMenuDescriptor>} |
| 421 */ |
| 422 _buildDescriptors() { |
| 423 var result = []; |
| 424 for (var i = 0; i < this._items.length; ++i) |
| 425 result.push(this._items[i]._buildDescriptor()); |
| 426 return result; |
| 427 } |
| 428 |
| 429 /** |
| 430 * @param {!WebInspector.Event} event |
| 431 */ |
| 432 _onItemSelected(event) { |
| 433 this._itemSelected(/** @type {string} */ (event.data)); |
| 434 } |
| 435 |
| 436 /** |
| 437 * @param {string} id |
| 438 */ |
| 439 _itemSelected(id) { |
| 440 if (this._handlers[id]) |
| 441 this._handlers[id].call(this); |
| 442 this._menuCleared(); |
| 443 } |
| 444 |
| 445 _menuCleared() { |
| 446 InspectorFrontendHost.events.removeEventListener( |
| 447 InspectorFrontendHostAPI.Events.ContextMenuCleared, this._menuCleared, t
his); |
| 448 InspectorFrontendHost.events.removeEventListener( |
| 449 InspectorFrontendHostAPI.Events.ContextMenuItemSelected, this._onItemSel
ected, this); |
| 450 } |
| 451 |
| 452 /** |
| 453 * @param {!Object} target |
| 454 */ |
| 455 appendApplicableItems(target) { |
| 456 this._pendingPromises.push(self.runtime.allInstances(WebInspector.ContextMen
u.Provider, target)); |
| 457 this._pendingTargets.push(target); |
| 458 } |
| 459 |
| 460 /** |
| 461 * @param {string} name |
| 462 * @return {?WebInspector.ContextSubMenuItem} |
| 463 */ |
| 464 namedSubMenu(name) { |
| 465 return this._namedSubMenus.get(name) || null; |
| 466 } |
| 348 }; | 467 }; |
| 349 | 468 |
| 350 WebInspector.ContextMenu.prototype = { | |
| 351 /** | |
| 352 * @return {number} | |
| 353 */ | |
| 354 _nextId: function() | |
| 355 { | |
| 356 return this._id++; | |
| 357 }, | |
| 358 | |
| 359 /** | |
| 360 * @param {function()} callback | |
| 361 */ | |
| 362 beforeShow: function(callback) | |
| 363 { | |
| 364 this._beforeShow = callback; | |
| 365 }, | |
| 366 | |
| 367 show: function() | |
| 368 { | |
| 369 Promise.all(this._pendingPromises).then(populate.bind(this)).then(this._
innerShow.bind(this)); | |
| 370 WebInspector.ContextMenu._pendingMenu = this; | |
| 371 | |
| 372 /** | |
| 373 * @param {!Array.<!Array.<!WebInspector.ContextMenu.Provider>>} appendC
allResults | |
| 374 * @this {WebInspector.ContextMenu} | |
| 375 */ | |
| 376 function populate(appendCallResults) | |
| 377 { | |
| 378 if (WebInspector.ContextMenu._pendingMenu !== this) | |
| 379 return; | |
| 380 delete WebInspector.ContextMenu._pendingMenu; | |
| 381 | |
| 382 for (var i = 0; i < appendCallResults.length; ++i) { | |
| 383 var providers = appendCallResults[i]; | |
| 384 var target = this._pendingTargets[i]; | |
| 385 | |
| 386 for (var j = 0; j < providers.length; ++j) { | |
| 387 var provider = /** @type {!WebInspector.ContextMenu.Provider
} */ (providers[j]); | |
| 388 this.appendSeparator(); | |
| 389 provider.appendApplicableItems(this._event, this, target); | |
| 390 this.appendSeparator(); | |
| 391 } | |
| 392 } | |
| 393 | |
| 394 this._pendingPromises = []; | |
| 395 this._pendingTargets = []; | |
| 396 } | |
| 397 | |
| 398 this._event.consume(true); | |
| 399 }, | |
| 400 | |
| 401 discard: function() | |
| 402 { | |
| 403 if (this._softMenu) | |
| 404 this._softMenu.discard(); | |
| 405 }, | |
| 406 | |
| 407 _innerShow: function() | |
| 408 { | |
| 409 if (typeof this._beforeShow === "function") { | |
| 410 this._beforeShow(); | |
| 411 delete this._beforeShow; | |
| 412 } | |
| 413 | |
| 414 var menuObject = this._buildDescriptors(); | |
| 415 | |
| 416 WebInspector._contextMenu = this; | |
| 417 if (this._useSoftMenu || WebInspector.ContextMenu._useSoftMenu || Inspec
torFrontendHost.isHostedMode()) { | |
| 418 this._softMenu = new WebInspector.SoftContextMenu(menuObject, this._
itemSelected.bind(this)); | |
| 419 this._softMenu.show(this._event.target.ownerDocument, this._x, this.
_y); | |
| 420 } else { | |
| 421 InspectorFrontendHost.showContextMenuAtPoint(this._x, this._y, menuO
bject, this._event.target.ownerDocument); | |
| 422 | |
| 423 /** | |
| 424 * @this {WebInspector.ContextMenu} | |
| 425 */ | |
| 426 function listenToEvents() | |
| 427 { | |
| 428 InspectorFrontendHost.events.addEventListener(InspectorFrontendH
ostAPI.Events.ContextMenuCleared, this._menuCleared, this); | |
| 429 InspectorFrontendHost.events.addEventListener(InspectorFrontendH
ostAPI.Events.ContextMenuItemSelected, this._onItemSelected, this); | |
| 430 } | |
| 431 | |
| 432 // showContextMenuAtPoint call above synchronously issues a clear ev
ent for previous context menu (if any), | |
| 433 // so we skip it before subscribing to the clear event. | |
| 434 setImmediate(listenToEvents.bind(this)); | |
| 435 } | |
| 436 }, | |
| 437 | |
| 438 /** | |
| 439 * @param {number} id | |
| 440 * @param {function(?)} handler | |
| 441 */ | |
| 442 _setHandler: function(id, handler) | |
| 443 { | |
| 444 if (handler) | |
| 445 this._handlers[id] = handler; | |
| 446 }, | |
| 447 | |
| 448 /** | |
| 449 * @return {!Array.<!InspectorFrontendHostAPI.ContextMenuDescriptor>} | |
| 450 */ | |
| 451 _buildDescriptors: function() | |
| 452 { | |
| 453 var result = []; | |
| 454 for (var i = 0; i < this._items.length; ++i) | |
| 455 result.push(this._items[i]._buildDescriptor()); | |
| 456 return result; | |
| 457 }, | |
| 458 | |
| 459 /** | |
| 460 * @param {!WebInspector.Event} event | |
| 461 */ | |
| 462 _onItemSelected: function(event) | |
| 463 { | |
| 464 this._itemSelected(/** @type {string} */ (event.data)); | |
| 465 }, | |
| 466 | |
| 467 /** | |
| 468 * @param {string} id | |
| 469 */ | |
| 470 _itemSelected: function(id) | |
| 471 { | |
| 472 if (this._handlers[id]) | |
| 473 this._handlers[id].call(this); | |
| 474 this._menuCleared(); | |
| 475 }, | |
| 476 | |
| 477 _menuCleared: function() | |
| 478 { | |
| 479 InspectorFrontendHost.events.removeEventListener(InspectorFrontendHostAP
I.Events.ContextMenuCleared, this._menuCleared, this); | |
| 480 InspectorFrontendHost.events.removeEventListener(InspectorFrontendHostAP
I.Events.ContextMenuItemSelected, this._onItemSelected, this); | |
| 481 }, | |
| 482 | |
| 483 /** | |
| 484 * @param {!Object} target | |
| 485 */ | |
| 486 appendApplicableItems: function(target) | |
| 487 { | |
| 488 this._pendingPromises.push(self.runtime.allInstances(WebInspector.Contex
tMenu.Provider, target)); | |
| 489 this._pendingTargets.push(target); | |
| 490 }, | |
| 491 | |
| 492 /** | |
| 493 * @param {string} name | |
| 494 * @return {?WebInspector.ContextSubMenuItem} | |
| 495 */ | |
| 496 namedSubMenu: function(name) | |
| 497 { | |
| 498 return this._namedSubMenus.get(name) || null; | |
| 499 }, | |
| 500 | |
| 501 __proto__: WebInspector.ContextSubMenuItem.prototype | |
| 502 }; | |
| 503 | 469 |
| 504 /** | 470 /** |
| 505 * @interface | 471 * @interface |
| 506 */ | 472 */ |
| 507 WebInspector.ContextMenu.Provider = function() { | 473 WebInspector.ContextMenu.Provider = function() {}; |
| 508 }; | |
| 509 | 474 |
| 510 WebInspector.ContextMenu.Provider.prototype = { | 475 WebInspector.ContextMenu.Provider.prototype = { |
| 511 /** | 476 /** |
| 512 * @param {!Event} event | 477 * @param {!Event} event |
| 513 * @param {!WebInspector.ContextMenu} contextMenu | 478 * @param {!WebInspector.ContextMenu} contextMenu |
| 514 * @param {!Object} target | 479 * @param {!Object} target |
| 515 */ | 480 */ |
| 516 appendApplicableItems: function(event, contextMenu, target) { } | 481 appendApplicableItems: function(event, contextMenu, target) {} |
| 517 }; | 482 }; |
| OLD | NEW |