1/* 2 * Copyright (C) 2010 Google 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 are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 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. 29 */ 30 31/** 32 * FIXME: change field naming style to use trailing underscore. 33 * @fileoverview Tools is a main class that wires all components of the 34 * DevTools frontend together. It is also responsible for overriding existing 35 * WebInspector functionality while it is getting upstreamed into WebCore. 36 */ 37 38/** 39 * Dispatches raw message from the host. 40 * @param {string} remoteName 41 * @prama {string} methodName 42 * @param {string} param1, param2, param3 Arguments to dispatch. 43 */ 44devtools$$dispatch = function(remoteName, methodName, param1, param2, param3) 45{ 46 remoteName = "Remote" + remoteName.substring(0, remoteName.length - 8); 47 var agent = window[remoteName]; 48 if (!agent) { 49 debugPrint("No remote agent '" + remoteName + "' found."); 50 return; 51 } 52 var method = agent[methodName]; 53 if (!method) { 54 debugPrint("No method '" + remoteName + "." + methodName + "' found."); 55 return; 56 } 57 method.call(this, param1, param2, param3); 58}; 59 60 61devtools.ToolsAgent = function() 62{ 63 RemoteToolsAgent.didDispatchOn = WebInspector.Callback.processCallback; 64 RemoteToolsAgent.frameNavigate = this.frameNavigate_.bind(this); 65 RemoteToolsAgent.dispatchOnClient = this.dispatchOnClient_.bind(this); 66 this.debuggerAgent_ = new devtools.DebuggerAgent(); 67 this.profilerAgent_ = new devtools.ProfilerAgent(); 68}; 69 70 71/** 72 * Resets tools agent to its initial state. 73 */ 74devtools.ToolsAgent.prototype.reset = function() 75{ 76 this.debuggerAgent_.reset(); 77}; 78 79 80/** 81 * @param {string} script Script exression to be evaluated in the context of the 82 * inspected page. 83 * @param {function(Object|string, boolean):undefined} opt_callback Function to 84 * call with the result. 85 */ 86devtools.ToolsAgent.prototype.evaluateJavaScript = function(script, opt_callback) 87{ 88 InspectorBackend.evaluate(script, opt_callback || function() {}); 89}; 90 91 92/** 93 * @return {devtools.DebuggerAgent} Debugger agent instance. 94 */ 95devtools.ToolsAgent.prototype.getDebuggerAgent = function() 96{ 97 return this.debuggerAgent_; 98}; 99 100 101/** 102 * @return {devtools.ProfilerAgent} Profiler agent instance. 103 */ 104devtools.ToolsAgent.prototype.getProfilerAgent = function() 105{ 106 return this.profilerAgent_; 107}; 108 109 110/** 111 * @param {string} url Url frame navigated to. 112 * @see tools_agent.h 113 * @private 114 */ 115devtools.ToolsAgent.prototype.frameNavigate_ = function(url) 116{ 117 this.reset(); 118 // Do not reset Profiles panel. 119 var profiles = null; 120 if ("profiles" in WebInspector.panels) { 121 profiles = WebInspector.panels["profiles"]; 122 delete WebInspector.panels["profiles"]; 123 } 124 WebInspector.reset(); 125 if (profiles !== null) 126 WebInspector.panels["profiles"] = profiles; 127}; 128 129 130/** 131 * @param {string} message Serialized call to be dispatched on WebInspector. 132 * @private 133 */ 134devtools.ToolsAgent.prototype.dispatchOnClient_ = function(message) 135{ 136 var args = JSON.parse(message); 137 var methodName = args[0]; 138 var parameters = args.slice(1); 139 WebInspector[methodName].apply(WebInspector, parameters); 140}; 141 142 143/** 144 * Evaluates js expression. 145 * @param {string} expr 146 */ 147devtools.ToolsAgent.prototype.evaluate = function(expr) 148{ 149 RemoteToolsAgent.evaluate(expr); 150}; 151 152 153/** 154 * Enables / disables resources panel in the ui. 155 * @param {boolean} enabled New panel status. 156 */ 157WebInspector.setResourcesPanelEnabled = function(enabled) 158{ 159 InspectorBackend._resourceTrackingEnabled = enabled; 160 WebInspector.panels.resources.reset(); 161}; 162 163 164/** 165 * Prints string to the inspector console or shows alert if the console doesn't 166 * exist. 167 * @param {string} text 168 */ 169function debugPrint(text) { 170 var console = WebInspector.console; 171 if (console) { 172 console.addMessage(new WebInspector.ConsoleMessage( 173 WebInspector.ConsoleMessage.MessageSource.JS, 174 WebInspector.ConsoleMessage.MessageType.Log, 175 WebInspector.ConsoleMessage.MessageLevel.Log, 176 1, "chrome://devtools/<internal>", undefined, -1, text)); 177 } else 178 alert(text); 179} 180 181 182/** 183 * Global instance of the tools agent. 184 * @type {devtools.ToolsAgent} 185 */ 186devtools.tools = null; 187 188 189var context = {}; // Used by WebCore's inspector routines. 190 191/////////////////////////////////////////////////////////////////////////////// 192// Here and below are overrides to existing WebInspector methods only. 193// TODO(pfeldman): Patch WebCore and upstream changes. 194var oldLoaded = WebInspector.loaded; 195WebInspector.loaded = function() 196{ 197 devtools.tools = new devtools.ToolsAgent(); 198 devtools.tools.reset(); 199 200 Preferences.ignoreWhitespace = false; 201 Preferences.samplingCPUProfiler = true; 202 Preferences.heapProfilerPresent = true; 203 oldLoaded.call(this); 204 205 InspectorFrontendHost.loaded(); 206}; 207 208 209(function() 210{ 211 212 /** 213 * Handles an F3 keydown event to focus the Inspector search box. 214 * @param {KeyboardEvent} event Event to optionally handle 215 * @return {boolean} whether the event has been handled 216 */ 217 function handleF3Keydown(event) { 218 if (event.keyIdentifier === "F3" && !event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey) { 219 var searchField = document.getElementById("search"); 220 searchField.focus(); 221 searchField.select(); 222 event.preventDefault(); 223 return true; 224 } 225 return false; 226 } 227 228 229 var oldKeyDown = WebInspector.documentKeyDown; 230 /** 231 * This override allows to intercept keydown events we want to handle in a 232 * custom way. Some nested documents (iframes) delegate keydown handling to 233 * WebInspector.documentKeyDown (e.g. SourceFrame). 234 * @param {KeyboardEvent} event 235 * @override 236 */ 237 WebInspector.documentKeyDown = function(event) { 238 var isHandled = handleF3Keydown(event); 239 if (!isHandled) { 240 // Mute refresh action. 241 if (event.keyIdentifier === "F5") 242 event.preventDefault(); 243 else if (event.keyIdentifier === "U+0052" /* "R" */ && (event.ctrlKey || event.metaKey)) 244 event.preventDefault(); 245 else 246 oldKeyDown.call(this, event); 247 } 248 }; 249})(); 250 251 252/** 253 * This override is necessary for adding script source asynchronously. 254 * @override 255 */ 256WebInspector.ScriptView.prototype.setupSourceFrameIfNeeded = function() 257{ 258 if (!this._frameNeedsSetup) 259 return; 260 261 this.attach(); 262 263 if (this.script.source) 264 this.didResolveScriptSource_(); 265 else { 266 var self = this; 267 devtools.tools.getDebuggerAgent().resolveScriptSource( 268 this.script.sourceID, 269 function(source) { 270 self.script.source = source || WebInspector.UIString("<source is not available>"); 271 self.didResolveScriptSource_(); 272 }); 273 } 274}; 275 276 277/** 278 * Performs source frame setup when script source is aready resolved. 279 */ 280WebInspector.ScriptView.prototype.didResolveScriptSource_ = function() 281{ 282 this.sourceFrame.setContent("text/javascript", this.script.source); 283 this._sourceFrameSetup = true; 284 delete this._frameNeedsSetup; 285}; 286 287 288/** 289 * @param {string} type Type of the the property value("object" or "function"). 290 * @param {string} className Class name of the property value. 291 * @constructor 292 */ 293WebInspector.UnresolvedPropertyValue = function(type, className) 294{ 295 this.type = type; 296 this.className = className; 297}; 298 299 300(function() 301{ 302 var oldShow = WebInspector.ScriptsPanel.prototype.show; 303 WebInspector.ScriptsPanel.prototype.show = function() 304 { 305 devtools.tools.getDebuggerAgent().initUI(); 306 this.enableToggleButton.visible = false; 307 oldShow.call(this); 308 }; 309})(); 310 311 312(function InterceptProfilesPanelEvents() 313{ 314 var oldShow = WebInspector.ProfilesPanel.prototype.show; 315 WebInspector.ProfilesPanel.prototype.show = function() 316 { 317 devtools.tools.getProfilerAgent().initializeProfiling(); 318 this.enableToggleButton.visible = false; 319 oldShow.call(this); 320 // Show is called on every show event of a panel, so 321 // we only need to intercept it once. 322 WebInspector.ProfilesPanel.prototype.show = oldShow; 323 }; 324})(); 325 326 327/* 328 * @override 329 * TODO(mnaganov): Restore l10n when it will be agreed that it is needed. 330 */ 331WebInspector.UIString = function(string) 332{ 333 return String.vsprintf(string, Array.prototype.slice.call(arguments, 1)); 334}; 335 336 337// There is no clear way of setting frame title yet. So sniffing main resource 338// load. 339(function OverrideUpdateResource() { 340 var originalUpdateResource = WebInspector.updateResource; 341 WebInspector.updateResource = function(identifier, payload) 342 { 343 originalUpdateResource.call(this, identifier, payload); 344 var resource = this.resources[identifier]; 345 if (resource && resource.mainResource && resource.finished) 346 document.title = WebInspector.UIString("Developer Tools - %s", resource.url); 347 }; 348})(); 349 350 351// Highlight extension content scripts in the scripts list. 352(function () { 353 var original = WebInspector.ScriptsPanel.prototype._addScriptToFilesMenu; 354 WebInspector.ScriptsPanel.prototype._addScriptToFilesMenu = function(script) 355 { 356 var result = original.apply(this, arguments); 357 var debuggerAgent = devtools.tools.getDebuggerAgent(); 358 var type = debuggerAgent.getScriptContextType(script.sourceID); 359 var option = script.filesSelectOption; 360 if (type === "injected" && option) 361 option.addStyleClass("injected"); 362 return result; 363 }; 364})(); 365 366 367/** Pending WebKit upstream by apavlov). Fixes iframe vs drag problem. */ 368(function() 369{ 370 var originalDragStart = WebInspector.elementDragStart; 371 WebInspector.elementDragStart = function(element) 372 { 373 if (element) { 374 var glassPane = document.createElement("div"); 375 glassPane.style.cssText = "position:absolute;width:100%;height:100%;opacity:0;z-index:1"; 376 glassPane.id = "glass-pane-for-drag"; 377 element.parentElement.appendChild(glassPane); 378 } 379 380 originalDragStart.apply(this, arguments); 381 }; 382 383 var originalDragEnd = WebInspector.elementDragEnd; 384 WebInspector.elementDragEnd = function() 385 { 386 originalDragEnd.apply(this, arguments); 387 388 var glassPane = document.getElementById("glass-pane-for-drag"); 389 if (glassPane) 390 glassPane.parentElement.removeChild(glassPane); 391 }; 392})(); 393 394 395(function () { 396var orig = InjectedScriptAccess.prototype.getProperties; 397InjectedScriptAccess.prototype.getProperties = function(objectProxy, ignoreHasOwnProperty, abbreviate, callback) 398{ 399 if (objectProxy.isScope) 400 devtools.tools.getDebuggerAgent().resolveScope(objectProxy.objectId, callback); 401 else if (objectProxy.isV8Ref) 402 devtools.tools.getDebuggerAgent().resolveChildren(objectProxy.objectId, callback, false); 403 else 404 orig.apply(this, arguments); 405}; 406})(); 407 408 409(function() 410{ 411InjectedScriptAccess.prototype.evaluateInCallFrame = function(callFrameId, code, objectGroup, callback) 412{ 413 //TODO(pfeldman): remove once 49084 is rolled. 414 if (!callback) 415 callback = objectGroup; 416 devtools.tools.getDebuggerAgent().evaluateInCallFrame(callFrameId, code, callback); 417}; 418})(); 419 420 421WebInspector.resourceTrackingWasEnabled = function() 422{ 423 InspectorBackend._resourceTrackingEnabled = true; 424 this.panels.resources.resourceTrackingWasEnabled(); 425}; 426 427WebInspector.resourceTrackingWasDisabled = function() 428{ 429 InspectorBackend._resourceTrackingEnabled = false; 430 this.panels.resources.resourceTrackingWasDisabled(); 431}; 432 433(function() 434{ 435var orig = WebInspector.ConsoleMessage.prototype.setMessageBody; 436WebInspector.ConsoleMessage.prototype.setMessageBody = function(args) 437{ 438 for (var i = 0; i < args.length; ++i) { 439 if (typeof args[i] === "string") 440 args[i] = WebInspector.ObjectProxy.wrapPrimitiveValue(args[i]); 441 } 442 orig.call(this, args); 443}; 444})(); 445 446 447(function() 448{ 449var orig = InjectedScriptAccess.prototype.getCompletions; 450InjectedScriptAccess.prototype.getCompletions = function(expressionString, includeInspectorCommandLineAPI, callFrameId, reportCompletions) 451{ 452 if (typeof callFrameId === "number") 453 devtools.tools.getDebuggerAgent().resolveCompletionsOnFrame(expressionString, callFrameId, reportCompletions); 454 else 455 return orig.apply(this, arguments); 456}; 457})(); 458 459 460(function() 461{ 462WebInspector.ElementsPanel.prototype._nodeSearchButtonClicked = function( event) 463{ 464 InspectorBackend.toggleNodeSearch(); 465 this.nodeSearchButton.toggled = !this.nodeSearchButton.toggled; 466}; 467})(); 468 469 470// We need to have a place for postponed tasks 471// which should be executed when all the messages between agent and frontend 472// are processed. 473 474WebInspector.runAfterPendingDispatchesQueue = []; 475 476WebInspector.TestController.prototype.runAfterPendingDispatches = function(callback) 477{ 478 WebInspector.runAfterPendingDispatchesQueue.push(callback); 479}; 480 481WebInspector.queuesAreEmpty = function() 482{ 483 var copy = this.runAfterPendingDispatchesQueue.slice(); 484 this.runAfterPendingDispatchesQueue = []; 485 for (var i = 0; i < copy.length; ++i) 486 copy[i].call(this); 487}; 488 489(function() 490{ 491var originalAddToFrame = InspectorFrontendHost.addResourceSourceToFrame; 492InspectorFrontendHost.addResourceSourceToFrame = function(identifier, element) 493{ 494 var resource = WebInspector.resources[identifier]; 495 if (!resource) 496 return; 497 originalAddToFrame.call(this, identifier, resource.mimeType, element); 498}; 499})(); 500 501WebInspector.pausedScript = function(callFrames) 502{ 503 this.panels.scripts.debuggerPaused(callFrames); 504}; 505