1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.QuickConsole = function(element) 27{ 28 WebInspector.Object.call(this); 29 30 this._toggleOrFocusKeyboardShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Escape, this._toggleOrFocus.bind(this)); 31 32 var mainFrameExecutionContext = new WebInspector.ExecutionContext(WebInspector.QuickConsole.MainFrameContextExecutionIdentifier, WebInspector.UIString("Main Frame"), true, null); 33 this._mainFrameExecutionContextPathComponent = this._createExecutionContextPathComponent(mainFrameExecutionContext.name, mainFrameExecutionContext.identifier); 34 this._selectedExecutionContextPathComponent = this._mainFrameExecutionContextPathComponent; 35 36 this._otherExecutionContextPathComponents = []; 37 this._frameIdentifierToExecutionContextPathComponentMap = {}; 38 39 this._element = element || document.createElement("div"); 40 this._element.classList.add(WebInspector.QuickConsole.StyleClassName); 41 42 this.prompt = new WebInspector.ConsolePrompt(null, "text/javascript"); 43 this.prompt.element.classList.add(WebInspector.QuickConsole.TextPromptStyleClassName); 44 this._element.appendChild(this.prompt.element); 45 46 // FIXME: CodeMirror 4 has a default "Esc" key handler that always prevents default. 47 // Our keyboard shortcut above will respect the default prevented and ignore the event 48 // and not toggle the console. Install our own Escape key handler that will trigger 49 // when the ConsolePrompt is empty, to restore toggling behavior. A better solution 50 // would be for CodeMirror's event handler to pass if it doesn't do anything. 51 this.prompt.escapeKeyHandlerWhenEmpty = function() { WebInspector.toggleSplitConsole(); }; 52 53 this.prompt.shown(); 54 55 this._navigationBar = new WebInspector.QuickConsoleNavigationBar; 56 this._element.appendChild(this._navigationBar.element); 57 58 this._executionContextSelectorItem = new WebInspector.HierarchicalPathNavigationItem; 59 this._executionContextSelectorItem.showSelectorArrows = true; 60 this._navigationBar.addNavigationItem(this._executionContextSelectorItem); 61 62 this._executionContextSelectorDivider = new WebInspector.DividerNavigationItem; 63 this._navigationBar.addNavigationItem(this._executionContextSelectorDivider); 64 65 this._rebuildExecutionContextPathComponents(); 66 67 // COMPATIBILITY (iOS 6): Execution contexts did not exist, evaluation worked with frame ids. 68 if (WebInspector.ExecutionContext.supported()) { 69 WebInspector.Frame.addEventListener(WebInspector.Frame.Event.PageExecutionContextChanged, this._framePageExecutionContextsChanged, this); 70 WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ExecutionContextsCleared, this._frameExecutionContextsCleared, this); 71 } else { 72 WebInspector.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.FrameWasAdded, this._frameAdded, this); 73 WebInspector.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.FrameWasRemoved, this._frameRemoved, this); 74 WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._frameMainResourceChanged, this); 75 } 76 77 WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._debuggerActiveCallFrameDidChange, this); 78}; 79 80WebInspector.QuickConsole.StyleClassName = "quick-console"; 81WebInspector.QuickConsole.ShowingLogClassName = "showing-log"; 82WebInspector.QuickConsole.NavigationBarContainerStyleClassName = "navigation-bar-container"; 83WebInspector.QuickConsole.NavigationBarSpacerStyleClassName = "navigation-bar-spacer"; 84WebInspector.QuickConsole.TextPromptStyleClassName = "text-prompt"; 85 86WebInspector.QuickConsole.ToolbarSingleLineHeight = 21; 87WebInspector.QuickConsole.ToolbarPromptPadding = 4; 88WebInspector.QuickConsole.ToolbarTopBorder = 1; 89 90WebInspector.QuickConsole.MainFrameContextExecutionIdentifier = undefined; 91 92WebInspector.QuickConsole.Event = { 93 DidResize: "quick-console-did-resize" 94}; 95 96WebInspector.QuickConsole.prototype = { 97 constructor: WebInspector.QuickConsole, 98 99 // Public 100 101 get element() 102 { 103 return this._element; 104 }, 105 106 get navigationBar() 107 { 108 return this._navigationBar; 109 }, 110 111 get executionContextIdentifier() 112 { 113 return this._selectedExecutionContextPathComponent._executionContextIdentifier; 114 }, 115 116 updateLayout: function() 117 { 118 // A hard maximum size of 33% of the window. 119 const maximumAllowedHeight = Math.round(window.innerHeight * 0.33); 120 this.prompt.element.style.maxHeight = maximumAllowedHeight + "px"; 121 }, 122 123 consoleLogVisibilityChanged: function(visible) 124 { 125 if (visible) 126 this.element.classList.add(WebInspector.QuickConsole.ShowingLogClassName); 127 else 128 this.element.classList.remove(WebInspector.QuickConsole.ShowingLogClassName); 129 130 this.dispatchEventToListeners(WebInspector.QuickConsole.Event.DidResize); 131 }, 132 133 // Private 134 135 _executionContextPathComponentsToDisplay: function() 136 { 137 // If we are in the debugger the console will use the active call frame, don't show the selector. 138 if (WebInspector.debuggerManager.activeCallFrame) 139 return []; 140 141 // If there is only the Main Frame, don't show the selector. 142 if (!this._otherExecutionContextPathComponents.length) 143 return []; 144 145 return [this._selectedExecutionContextPathComponent]; 146 }, 147 148 _rebuildExecutionContextPathComponents: function() 149 { 150 var components = this._executionContextPathComponentsToDisplay(); 151 var isEmpty = !components.length; 152 153 this._executionContextSelectorItem.components = components; 154 155 this._executionContextSelectorItem.hidden = isEmpty; 156 this._executionContextSelectorDivider.hidden = isEmpty; 157 }, 158 159 _framePageExecutionContextsChanged: function(event) 160 { 161 var frame = event.target; 162 163 var shouldAutomaticallySelect = this._restoreSelectedExecutionContextForFrame === frame; 164 165 var newExecutionContextPathComponent = this._insertExecutionContextPathComponentForFrame(frame, shouldAutomaticallySelect); 166 167 if (shouldAutomaticallySelect) { 168 delete this._restoreSelectedExecutionContextForFrame; 169 this._selectedExecutionContextPathComponent = newExecutionContextPathComponent; 170 this._rebuildExecutionContextPathComponents(); 171 } 172 }, 173 174 _frameExecutionContextsCleared: function(event) 175 { 176 var frame = event.target; 177 178 // If this frame is navigating and it is selected in the UI we want to reselect its new item after navigation. 179 if (event.data.committingProvisionalLoad && !this._restoreSelectedExecutionContextForFrame) { 180 var executionContextPathComponent = this._frameIdentifierToExecutionContextPathComponentMap[frame.id]; 181 if (this._selectedExecutionContextPathComponent === executionContextPathComponent) { 182 this._restoreSelectedExecutionContextForFrame = frame; 183 // As a fail safe, if the frame never gets an execution context, clear the restore value. 184 setTimeout(function() { delete this._restoreSelectedExecutionContextForFrame; }.bind(this), 10); 185 } 186 } 187 188 this._removeExecutionContextPathComponentForFrame(frame); 189 }, 190 191 _frameAdded: function(event) 192 { 193 var frame = event.data.frame; 194 this._insertExecutionContextPathComponentForFrame(frame); 195 }, 196 197 _frameRemoved: function(event) 198 { 199 var frame = event.data.frame; 200 this._removeExecutionContextPathComponentForFrame(frame); 201 }, 202 203 _frameMainResourceChanged: function(event) 204 { 205 var frame = event.target; 206 this._updateExecutionContextPathComponentForFrame(frame); 207 }, 208 209 _createExecutionContextPathComponent: function(name, identifier) 210 { 211 var pathComponent = new WebInspector.HierarchicalPathComponent(name, "execution-context", identifier, true, true); 212 pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this); 213 pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this); 214 pathComponent.truncatedDisplayNameLength = 50; 215 pathComponent._executionContextIdentifier = identifier; 216 return pathComponent; 217 }, 218 219 _createExecutionContextPathComponentFromFrame: function(frame) 220 { 221 var name = frame.name ? frame.name + " \u2014 " + frame.mainResource.displayName : frame.mainResource.displayName; 222 var identifier = WebInspector.ExecutionContext.supported() ? frame.pageExecutionContext.id : frame.id; 223 224 var pathComponent = this._createExecutionContextPathComponent(name, identifier); 225 pathComponent._frame = frame; 226 227 return pathComponent; 228 }, 229 230 _compareExecutionContextPathComponents: function(a, b) 231 { 232 // "Main Frame" always on top. 233 if (!a._frame) 234 return -1; 235 if (!b._frame) 236 return 1; 237 238 // Frames with a name above frames without a name. 239 if (a._frame.name && !b._frame.name) 240 return -1; 241 if (!a._frame.name && b._frame.name) 242 return 1; 243 244 return a.displayName.localeCompare(b.displayName); 245 }, 246 247 _insertExecutionContextPathComponentForFrame: function(frame, skipRebuild) 248 { 249 if (frame.isMainFrame()) 250 return; 251 252 console.assert(!this._frameIdentifierToExecutionContextPathComponentMap[frame.id]); 253 if (this._frameIdentifierToExecutionContextPathComponentMap[frame.id]) 254 return; 255 256 var executionContextPathComponent = this._createExecutionContextPathComponentFromFrame(frame); 257 258 var index = insertionIndexForObjectInListSortedByFunction(executionContextPathComponent, this._otherExecutionContextPathComponents, this._compareExecutionContextPathComponents); 259 260 var prev = index > 0 ? this._otherExecutionContextPathComponents[index - 1] : this._mainFrameExecutionContextPathComponent; 261 var next = this._otherExecutionContextPathComponents[index] || null; 262 if (prev) { 263 prev.nextSibling = executionContextPathComponent; 264 executionContextPathComponent.previousSibling = prev; 265 } 266 if (next) { 267 next.previousSibling = executionContextPathComponent; 268 executionContextPathComponent.nextSibling = next; 269 } 270 271 this._otherExecutionContextPathComponents.splice(index, 0, executionContextPathComponent); 272 this._frameIdentifierToExecutionContextPathComponentMap[frame.id] = executionContextPathComponent; 273 274 if (!skipRebuild) 275 this._rebuildExecutionContextPathComponents(); 276 277 return executionContextPathComponent; 278 }, 279 280 _removeExecutionContextPathComponentForFrame: function(frame, skipRebuild) 281 { 282 if (frame.isMainFrame()) 283 return; 284 285 var executionContextPathComponent = this._frameIdentifierToExecutionContextPathComponentMap[frame.id]; 286 console.assert(executionContextPathComponent); 287 if (!executionContextPathComponent) 288 return; 289 290 executionContextPathComponent.removeEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this); 291 executionContextPathComponent.removeEventListener(WebInspector.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this); 292 293 var prev = executionContextPathComponent.previousSibling; 294 var next = executionContextPathComponent.nextSibling; 295 if (prev) 296 prev.nextSibling = next; 297 if (next) 298 next.previousSibling = prev; 299 300 if (this._selectedExecutionContextPathComponent === executionContextPathComponent) 301 this._selectedExecutionContextPathComponent = this._mainFrameExecutionContextPathComponent; 302 303 this._otherExecutionContextPathComponents.remove(executionContextPathComponent, true); 304 delete this._frameIdentifierToExecutionContextPathComponentMap[frame.id]; 305 306 if (!skipRebuild) 307 this._rebuildExecutionContextPathComponents(); 308 }, 309 310 _updateExecutionContextPathComponentForFrame: function(frame) 311 { 312 if (frame.isMainFrame()) 313 return; 314 315 var executionContextPathComponent = this._frameIdentifierToExecutionContextPathComponentMap[frame.id]; 316 if (!executionContextPathComponent) 317 return; 318 319 var wasSelected = this._selectedExecutionContextPathComponent === executionContextPathComponent; 320 321 this._removeExecutionContextPathComponentForFrame(frame, true); 322 var newExecutionContextPathComponent = this._insertExecutionContextPathComponentForFrame(frame, true); 323 324 if (wasSelected) 325 this._selectedExecutionContextPathComponent = newExecutionContextPathComponent; 326 327 this._rebuildExecutionContextPathComponents(); 328 }, 329 330 _pathComponentSelected: function(event) 331 { 332 if (event.data.pathComponent === this._selectedExecutionContextPathComponent) 333 return; 334 335 this._selectedExecutionContextPathComponent = event.data.pathComponent; 336 337 this._rebuildExecutionContextPathComponents(); 338 }, 339 340 _pathComponentClicked: function(event) 341 { 342 this.prompt.focus(); 343 }, 344 345 _debuggerActiveCallFrameDidChange: function(event) 346 { 347 this._rebuildExecutionContextPathComponents(); 348 }, 349 350 _toggleOrFocus: function(event) 351 { 352 if (this.prompt.focused) 353 WebInspector.toggleSplitConsole(); 354 else if (!WebInspector.isEditingAnyField() && !WebInspector.isEventTargetAnEditableField(event)) 355 this.prompt.focus(); 356 } 357}; 358 359WebInspector.QuickConsole.prototype.__proto__ = WebInspector.Object.prototype; 360