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 */ 44 devtools$$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 61 devtools.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 */ 74 devtools.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 */ 86 devtools.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 */ 95 devtools.ToolsAgent.prototype.getDebuggerAgent = function() 96 { 97 return this.debuggerAgent_; 98 }; 99 100 101 /** 102 * @return {devtools.ProfilerAgent} Profiler agent instance. 103 */ 104 devtools.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 */ 115 devtools.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 */ 134 devtools.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 */ 147 devtools.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 */ 157 WebInspector.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 */ 169 function 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 */ 186 devtools.tools = null; 187 188 189 var 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. 194 var oldLoaded = WebInspector.loaded; 195 WebInspector.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 */ 256 WebInspector.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 */ 280 WebInspector.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 */ 293 WebInspector.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 */ 331 WebInspector.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 () { 396 var orig = InjectedScriptAccess.prototype.getProperties; 397 InjectedScriptAccess.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 { 411 InjectedScriptAccess.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 421 WebInspector.resourceTrackingWasEnabled = function() 422 { 423 InspectorBackend._resourceTrackingEnabled = true; 424 this.panels.resources.resourceTrackingWasEnabled(); 425 }; 426 427 WebInspector.resourceTrackingWasDisabled = function() 428 { 429 InspectorBackend._resourceTrackingEnabled = false; 430 this.panels.resources.resourceTrackingWasDisabled(); 431 }; 432 433 (function() 434 { 435 var orig = WebInspector.ConsoleMessage.prototype.setMessageBody; 436 WebInspector.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 { 449 var orig = InjectedScriptAccess.prototype.getCompletions; 450 InjectedScriptAccess.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 { 462 WebInspector.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 474 WebInspector.runAfterPendingDispatchesQueue = []; 475 476 WebInspector.TestController.prototype.runAfterPendingDispatches = function(callback) 477 { 478 WebInspector.runAfterPendingDispatchesQueue.push(callback); 479 }; 480 481 WebInspector.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 { 491 var originalAddToFrame = InspectorFrontendHost.addResourceSourceToFrame; 492 InspectorFrontendHost.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 501 WebInspector.pausedScript = function(callFrames) 502 { 503 this.panels.scripts.debuggerPaused(callFrames); 504 }; 505