QuickConsole.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  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. WebInspector.QuickConsole = function(element)
  26. {
  27. WebInspector.Object.call(this);
  28. this._toggleOrFocusKeyboardShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Escape, this._toggleOrFocus.bind(this));
  29. var mainFrameExecutionContext = new WebInspector.ExecutionContext(WebInspector.QuickConsole.MainFrameContextExecutionIdentifier, WebInspector.UIString("Main Frame"), true, null);
  30. this._mainFrameExecutionContextPathComponent = this._createExecutionContextPathComponent(mainFrameExecutionContext.name, mainFrameExecutionContext.identifier);
  31. this._selectedExecutionContextPathComponent = this._mainFrameExecutionContextPathComponent;
  32. this._otherExecutionContextPathComponents = [];
  33. this._frameIdentifierToExecutionContextPathComponentMap = {};
  34. this._element = element || document.createElement("div");
  35. this._element.classList.add(WebInspector.QuickConsole.StyleClassName);
  36. this.prompt = new WebInspector.ConsolePrompt(null, "text/javascript");
  37. this.prompt.element.classList.add(WebInspector.QuickConsole.TextPromptStyleClassName);
  38. this._element.appendChild(this.prompt.element);
  39. this.prompt.shown();
  40. this._navigationBar = new WebInspector.QuickConsoleNavigationBar;
  41. this._element.appendChild(this._navigationBar.element);
  42. this._executionContextSelectorItem = new WebInspector.HierarchicalPathNavigationItem;
  43. this._executionContextSelectorItem.showSelectorArrows = true;
  44. this._navigationBar.addNavigationItem(this._executionContextSelectorItem);
  45. this._executionContextSelectorDivider = new WebInspector.DividerNavigationItem;
  46. this._navigationBar.addNavigationItem(this._executionContextSelectorDivider);
  47. this._rebuildExecutionContextPathComponents();
  48. // COMPATIBILITY (iOS 6): Execution contexts did not exist, evaluation worked with frame ids.
  49. if (WebInspector.ExecutionContext.supported()) {
  50. WebInspector.Frame.addEventListener(WebInspector.Frame.Event.PageExecutionContextChanged, this._framePageExecutionContextsChanged, this);
  51. WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ExecutionContextsCleared, this._frameExecutionContextsCleared, this);
  52. } else {
  53. WebInspector.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.FrameWasAdded, this._frameAdded, this);
  54. WebInspector.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.FrameWasRemoved, this._frameRemoved, this);
  55. WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._frameMainResourceChanged, this);
  56. }
  57. WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._debuggerActiveCallFrameDidChange, this);
  58. };
  59. WebInspector.QuickConsole.StyleClassName = "quick-console";
  60. WebInspector.QuickConsole.ShowingLogClassName = "showing-log";
  61. WebInspector.QuickConsole.NavigationBarContainerStyleClassName = "navigation-bar-container";
  62. WebInspector.QuickConsole.NavigationBarSpacerStyleClassName = "navigation-bar-spacer";
  63. WebInspector.QuickConsole.TextPromptStyleClassName = "text-prompt";
  64. WebInspector.QuickConsole.ToolbarSingleLineHeight = 21;
  65. WebInspector.QuickConsole.ToolbarPromptPadding = 4;
  66. WebInspector.QuickConsole.ToolbarTopBorder = 1;
  67. WebInspector.QuickConsole.MainFrameContextExecutionIdentifier = undefined;
  68. WebInspector.QuickConsole.Event = {
  69. DidResize: "quick-console-did-resize"
  70. };
  71. WebInspector.QuickConsole.prototype = {
  72. constructor: WebInspector.QuickConsole,
  73. // Public
  74. get element()
  75. {
  76. return this._element;
  77. },
  78. get navigationBar()
  79. {
  80. return this._navigationBar;
  81. },
  82. get executionContextIdentifier()
  83. {
  84. return this._selectedExecutionContextPathComponent._executionContextIdentifier;
  85. },
  86. updateLayout: function()
  87. {
  88. // A hard maximum size of 33% of the window.
  89. const maximumAllowedHeight = Math.round(window.innerHeight * 0.33);
  90. this.prompt.element.style.maxHeight = maximumAllowedHeight + "px";
  91. },
  92. consoleLogVisibilityChanged: function(visible)
  93. {
  94. if (visible)
  95. this.element.classList.add(WebInspector.QuickConsole.ShowingLogClassName);
  96. else
  97. this.element.classList.remove(WebInspector.QuickConsole.ShowingLogClassName);
  98. this.dispatchEventToListeners(WebInspector.QuickConsole.Event.DidResize);
  99. },
  100. // Private
  101. _executionContextPathComponentsToDisplay: function()
  102. {
  103. // If we are in the debugger the console will use the active call frame, don't show the selector.
  104. if (WebInspector.debuggerManager.activeCallFrame)
  105. return [];
  106. // If there is only the Main Frame, don't show the selector.
  107. if (!this._otherExecutionContextPathComponents.length)
  108. return [];
  109. return [this._selectedExecutionContextPathComponent];
  110. },
  111. _rebuildExecutionContextPathComponents: function()
  112. {
  113. var components = this._executionContextPathComponentsToDisplay();
  114. var isEmpty = !components.length;
  115. this._executionContextSelectorItem.components = components;
  116. this._executionContextSelectorItem.hidden = isEmpty;
  117. this._executionContextSelectorDivider.hidden = isEmpty;
  118. },
  119. _framePageExecutionContextsChanged: function(event)
  120. {
  121. var frame = event.target;
  122. var shouldAutomaticallySelect = this._restoreSelectedExecutionContextForFrame === frame;
  123. var newExecutionContextPathComponent = this._insertExecutionContextPathComponentForFrame(frame, shouldAutomaticallySelect);
  124. if (shouldAutomaticallySelect) {
  125. delete this._restoreSelectedExecutionContextForFrame;
  126. this._selectedExecutionContextPathComponent = newExecutionContextPathComponent;
  127. this._rebuildExecutionContextPathComponents();
  128. }
  129. },
  130. _frameExecutionContextsCleared: function(event)
  131. {
  132. var frame = event.target;
  133. // If this frame is navigating and it is selected in the UI we want to reselect its new item after navigation.
  134. if (event.data.committingProvisionalLoad && !this._restoreSelectedExecutionContextForFrame) {
  135. var executionContextPathComponent = this._frameIdentifierToExecutionContextPathComponentMap[frame.id];
  136. if (this._selectedExecutionContextPathComponent === executionContextPathComponent) {
  137. this._restoreSelectedExecutionContextForFrame = frame;
  138. // As a fail safe, if the frame never gets an execution context, clear the restore value.
  139. setTimeout(function() { delete this._restoreSelectedExecutionContextForFrame; }.bind(this), 10);
  140. }
  141. }
  142. this._removeExecutionContextPathComponentForFrame(frame);
  143. },
  144. _frameAdded: function(event)
  145. {
  146. var frame = event.data.frame;
  147. this._insertExecutionContextPathComponentForFrame(frame);
  148. },
  149. _frameRemoved: function(event)
  150. {
  151. var frame = event.data.frame;
  152. this._removeExecutionContextPathComponentForFrame(frame);
  153. },
  154. _frameMainResourceChanged: function(event)
  155. {
  156. var frame = event.target;
  157. this._updateExecutionContextPathComponentForFrame(frame);
  158. },
  159. _createExecutionContextPathComponent: function(name, identifier)
  160. {
  161. var pathComponent = new WebInspector.HierarchicalPathComponent(name, "execution-context", identifier, true, true);
  162. pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this);
  163. pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this);
  164. pathComponent.truncatedDisplayNameLength = 50;
  165. pathComponent._executionContextIdentifier = identifier;
  166. return pathComponent;
  167. },
  168. _createExecutionContextPathComponentFromFrame: function(frame)
  169. {
  170. var name = frame.name ? frame.name + " \u2014 " + frame.mainResource.displayName : frame.mainResource.displayName;
  171. var identifier = WebInspector.ExecutionContext.supported() ? frame.pageExecutionContext.id : frame.id;
  172. var pathComponent = this._createExecutionContextPathComponent(name, identifier);
  173. pathComponent._frame = frame;
  174. return pathComponent;
  175. },
  176. _compareExecutionContextPathComponents: function(a, b)
  177. {
  178. // "Main Frame" always on top.
  179. if (!a._frame)
  180. return -1;
  181. if (!b._frame)
  182. return 1;
  183. // Frames with a name above frames without a name.
  184. if (a._frame.name && !b._frame.name)
  185. return -1;
  186. if (!a._frame.name && b._frame.name)
  187. return 1;
  188. return a.displayName.localeCompare(b.displayName);
  189. },
  190. _insertExecutionContextPathComponentForFrame: function(frame, skipRebuild)
  191. {
  192. if (frame.isMainFrame())
  193. return;
  194. console.assert(!this._frameIdentifierToExecutionContextPathComponentMap[frame.id]);
  195. if (this._frameIdentifierToExecutionContextPathComponentMap[frame.id])
  196. return;
  197. var executionContextPathComponent = this._createExecutionContextPathComponentFromFrame(frame);
  198. var index = insertionIndexForObjectInListSortedByFunction(executionContextPathComponent, this._otherExecutionContextPathComponents, this._compareExecutionContextPathComponents);
  199. var prev = index > 0 ? this._otherExecutionContextPathComponents[index - 1] : this._mainFrameExecutionContextPathComponent;
  200. var next = this._otherExecutionContextPathComponents[index] || null;
  201. if (prev) {
  202. prev.nextSibling = executionContextPathComponent;
  203. executionContextPathComponent.previousSibling = prev;
  204. }
  205. if (next) {
  206. next.previousSibling = executionContextPathComponent;
  207. executionContextPathComponent.nextSibling = next;
  208. }
  209. this._otherExecutionContextPathComponents.splice(index, 0, executionContextPathComponent);
  210. this._frameIdentifierToExecutionContextPathComponentMap[frame.id] = executionContextPathComponent;
  211. if (!skipRebuild)
  212. this._rebuildExecutionContextPathComponents();
  213. return executionContextPathComponent;
  214. },
  215. _removeExecutionContextPathComponentForFrame: function(frame, skipRebuild)
  216. {
  217. if (frame.isMainFrame())
  218. return;
  219. var executionContextPathComponent = this._frameIdentifierToExecutionContextPathComponentMap[frame.id];
  220. console.assert(executionContextPathComponent);
  221. if (!executionContextPathComponent)
  222. return;
  223. executionContextPathComponent.removeEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this);
  224. executionContextPathComponent.removeEventListener(WebInspector.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this);
  225. var prev = executionContextPathComponent.previousSibling;
  226. var next = executionContextPathComponent.nextSibling;
  227. if (prev)
  228. prev.nextSibling = next;
  229. if (next)
  230. next.previousSibling = prev;
  231. if (this._selectedExecutionContextPathComponent === executionContextPathComponent)
  232. this._selectedExecutionContextPathComponent = this._mainFrameExecutionContextPathComponent;
  233. this._otherExecutionContextPathComponents.remove(executionContextPathComponent, true);
  234. delete this._frameIdentifierToExecutionContextPathComponentMap[frame.id];
  235. if (!skipRebuild)
  236. this._rebuildExecutionContextPathComponents();
  237. },
  238. _updateExecutionContextPathComponentForFrame: function(frame)
  239. {
  240. if (frame.isMainFrame())
  241. return;
  242. var executionContextPathComponent = this._frameIdentifierToExecutionContextPathComponentMap[frame.id];
  243. if (!executionContextPathComponent)
  244. return;
  245. var wasSelected = this._selectedExecutionContextPathComponent === executionContextPathComponent;
  246. this._removeExecutionContextPathComponentForFrame(frame, true);
  247. var newExecutionContextPathComponent = this._insertExecutionContextPathComponentForFrame(frame, true);
  248. if (wasSelected)
  249. this._selectedExecutionContextPathComponent = newExecutionContextPathComponent;
  250. this._rebuildExecutionContextPathComponents();
  251. },
  252. _pathComponentSelected: function(event)
  253. {
  254. if (event.data.pathComponent === this._selectedExecutionContextPathComponent)
  255. return;
  256. this._selectedExecutionContextPathComponent = event.data.pathComponent;
  257. this._rebuildExecutionContextPathComponents();
  258. },
  259. _pathComponentClicked: function(event)
  260. {
  261. this.prompt.focus();
  262. },
  263. _debuggerActiveCallFrameDidChange: function(event)
  264. {
  265. this._rebuildExecutionContextPathComponents();
  266. },
  267. _toggleOrFocus: function(event)
  268. {
  269. if (this.prompt.focused)
  270. WebInspector.toggleSplitConsole();
  271. else if (!WebInspector.isEditingAnyField() && !WebInspector.isEventTargetAnEditableField(event))
  272. this.prompt.focus();
  273. }
  274. };
  275. WebInspector.QuickConsole.prototype.__proto__ = WebInspector.Object.prototype;