1/* 2 * Copyright (C) 2011 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 32importScript("ExtensionAPI.js"); 33importScript("ExtensionRegistryStub.js"); 34importScript("ExtensionAuditCategory.js"); 35 36/** 37 * @constructor 38 * @implements {WebInspector.ExtensionServerAPI} 39 */ 40WebInspector.ExtensionServer = function() 41{ 42 this._clientObjects = {}; 43 this._handlers = {}; 44 this._subscribers = {}; 45 this._subscriptionStartHandlers = {}; 46 this._subscriptionStopHandlers = {}; 47 this._extraHeaders = {}; 48 this._requests = {}; 49 this._lastRequestId = 0; 50 this._registeredExtensions = {}; 51 this._status = new WebInspector.ExtensionStatus(); 52 53 var commands = WebInspector.extensionAPI.Commands; 54 55 this._registerHandler(commands.AddAuditCategory, this._onAddAuditCategory.bind(this)); 56 this._registerHandler(commands.AddAuditResult, this._onAddAuditResult.bind(this)); 57 this._registerHandler(commands.AddConsoleMessage, this._onAddConsoleMessage.bind(this)); 58 this._registerHandler(commands.AddRequestHeaders, this._onAddRequestHeaders.bind(this)); 59 this._registerHandler(commands.ApplyStyleSheet, this._onApplyStyleSheet.bind(this)); 60 this._registerHandler(commands.CreatePanel, this._onCreatePanel.bind(this)); 61 this._registerHandler(commands.CreateSidebarPane, this._onCreateSidebarPane.bind(this)); 62 this._registerHandler(commands.CreateStatusBarButton, this._onCreateStatusBarButton.bind(this)); 63 this._registerHandler(commands.EvaluateOnInspectedPage, this._onEvaluateOnInspectedPage.bind(this)); 64 this._registerHandler(commands.ForwardKeyboardEvent, this._onForwardKeyboardEvent.bind(this)); 65 this._registerHandler(commands.GetHAR, this._onGetHAR.bind(this)); 66 this._registerHandler(commands.GetConsoleMessages, this._onGetConsoleMessages.bind(this)); 67 this._registerHandler(commands.GetPageResources, this._onGetPageResources.bind(this)); 68 this._registerHandler(commands.GetRequestContent, this._onGetRequestContent.bind(this)); 69 this._registerHandler(commands.GetResourceContent, this._onGetResourceContent.bind(this)); 70 this._registerHandler(commands.Reload, this._onReload.bind(this)); 71 this._registerHandler(commands.SetOpenResourceHandler, this._onSetOpenResourceHandler.bind(this)); 72 this._registerHandler(commands.SetResourceContent, this._onSetResourceContent.bind(this)); 73 this._registerHandler(commands.SetSidebarHeight, this._onSetSidebarHeight.bind(this)); 74 this._registerHandler(commands.SetSidebarContent, this._onSetSidebarContent.bind(this)); 75 this._registerHandler(commands.SetSidebarPage, this._onSetSidebarPage.bind(this)); 76 this._registerHandler(commands.ShowPanel, this._onShowPanel.bind(this)); 77 this._registerHandler(commands.StopAuditCategoryRun, this._onStopAuditCategoryRun.bind(this)); 78 this._registerHandler(commands.Subscribe, this._onSubscribe.bind(this)); 79 this._registerHandler(commands.OpenResource, this._onOpenResource.bind(this)); 80 this._registerHandler(commands.Unsubscribe, this._onUnsubscribe.bind(this)); 81 this._registerHandler(commands.UpdateButton, this._onUpdateButton.bind(this)); 82 this._registerHandler(commands.UpdateAuditProgress, this._onUpdateAuditProgress.bind(this)); 83 window.addEventListener("message", this._onWindowMessage.bind(this), false); 84 85 this._initExtensions(); 86} 87 88WebInspector.ExtensionServer.prototype = { 89 /** 90 * @return {boolean} 91 */ 92 hasExtensions: function() 93 { 94 return !!Object.keys(this._registeredExtensions).length; 95 }, 96 97 notifySearchAction: function(panelId, action, searchString) 98 { 99 this._postNotification(WebInspector.extensionAPI.Events.PanelSearch + panelId, action, searchString); 100 }, 101 102 notifyViewShown: function(identifier, frameIndex) 103 { 104 this._postNotification(WebInspector.extensionAPI.Events.ViewShown + identifier, frameIndex); 105 }, 106 107 notifyViewHidden: function(identifier) 108 { 109 this._postNotification(WebInspector.extensionAPI.Events.ViewHidden + identifier); 110 }, 111 112 notifyButtonClicked: function(identifier) 113 { 114 this._postNotification(WebInspector.extensionAPI.Events.ButtonClicked + identifier); 115 }, 116 117 _inspectedURLChanged: function(event) 118 { 119 this._requests = {}; 120 var url = event.data; 121 this._postNotification(WebInspector.extensionAPI.Events.InspectedURLChanged, url); 122 }, 123 124 startAuditRun: function(category, auditRun) 125 { 126 this._clientObjects[auditRun.id] = auditRun; 127 this._postNotification("audit-started-" + category.id, auditRun.id); 128 }, 129 130 stopAuditRun: function(auditRun) 131 { 132 delete this._clientObjects[auditRun.id]; 133 }, 134 135 /** 136 * @param {string} type 137 * @return {boolean} 138 */ 139 hasSubscribers: function(type) 140 { 141 return !!this._subscribers[type]; 142 }, 143 144 /** 145 * @param {string} type 146 * @param {...*} vararg 147 */ 148 _postNotification: function(type, vararg) 149 { 150 var subscribers = this._subscribers[type]; 151 if (!subscribers) 152 return; 153 var message = { 154 command: "notify-" + type, 155 arguments: Array.prototype.slice.call(arguments, 1) 156 }; 157 for (var i = 0; i < subscribers.length; ++i) 158 subscribers[i].postMessage(message); 159 }, 160 161 _onSubscribe: function(message, port) 162 { 163 var subscribers = this._subscribers[message.type]; 164 if (subscribers) 165 subscribers.push(port); 166 else { 167 this._subscribers[message.type] = [ port ]; 168 if (this._subscriptionStartHandlers[message.type]) 169 this._subscriptionStartHandlers[message.type](); 170 } 171 }, 172 173 _onUnsubscribe: function(message, port) 174 { 175 var subscribers = this._subscribers[message.type]; 176 if (!subscribers) 177 return; 178 subscribers.remove(port); 179 if (!subscribers.length) { 180 delete this._subscribers[message.type]; 181 if (this._subscriptionStopHandlers[message.type]) 182 this._subscriptionStopHandlers[message.type](); 183 } 184 }, 185 186 _onAddRequestHeaders: function(message) 187 { 188 var id = message.extensionId; 189 if (typeof id !== "string") 190 return this._status.E_BADARGTYPE("extensionId", typeof id, "string"); 191 var extensionHeaders = this._extraHeaders[id]; 192 if (!extensionHeaders) { 193 extensionHeaders = {}; 194 this._extraHeaders[id] = extensionHeaders; 195 } 196 for (var name in message.headers) 197 extensionHeaders[name] = message.headers[name]; 198 var allHeaders = /** @type {!NetworkAgent.Headers} */ ({}); 199 for (var extension in this._extraHeaders) { 200 var headers = this._extraHeaders[extension]; 201 for (name in headers) { 202 if (typeof headers[name] === "string") 203 allHeaders[name] = headers[name]; 204 } 205 } 206 NetworkAgent.setExtraHTTPHeaders(allHeaders); 207 }, 208 209 _onApplyStyleSheet: function(message) 210 { 211 if (!WebInspector.experimentsSettings.applyCustomStylesheet.isEnabled()) 212 return; 213 var styleSheet = document.createElement("style"); 214 styleSheet.textContent = message.styleSheet; 215 document.head.appendChild(styleSheet); 216 }, 217 218 _onCreatePanel: function(message, port) 219 { 220 var id = message.id; 221 // The ids are generated on the client API side and must be unique, so the check below 222 // shouldn't be hit unless someone is bypassing the API. 223 if (id in this._clientObjects || WebInspector.inspectorView.hasPanel(id)) 224 return this._status.E_EXISTS(id); 225 226 var page = this._expandResourcePath(port._extensionOrigin, message.page); 227 var panelDescriptor = new WebInspector.ExtensionServerPanelDescriptor(id, message.title, new WebInspector.ExtensionPanel(id, page)); 228 this._clientObjects[id] = panelDescriptor.panel(); 229 WebInspector.inspectorView.addPanel(panelDescriptor); 230 return this._status.OK(); 231 }, 232 233 _onShowPanel: function(message) 234 { 235 // Note: WebInspector.inspectorView.showPanel already sanitizes input. 236 WebInspector.inspectorView.showPanel(message.id); 237 }, 238 239 _onCreateStatusBarButton: function(message, port) 240 { 241 var panel = this._clientObjects[message.panel]; 242 if (!panel || !(panel instanceof WebInspector.ExtensionPanel)) 243 return this._status.E_NOTFOUND(message.panel); 244 var button = new WebInspector.ExtensionButton(message.id, this._expandResourcePath(port._extensionOrigin, message.icon), message.tooltip, message.disabled); 245 this._clientObjects[message.id] = button; 246 panel.addStatusBarItem(button.element); 247 return this._status.OK(); 248 }, 249 250 _onUpdateButton: function(message, port) 251 { 252 var button = this._clientObjects[message.id]; 253 if (!button || !(button instanceof WebInspector.ExtensionButton)) 254 return this._status.E_NOTFOUND(message.id); 255 button.update(this._expandResourcePath(port._extensionOrigin, message.icon), message.tooltip, message.disabled); 256 return this._status.OK(); 257 }, 258 259 _onCreateSidebarPane: function(message) 260 { 261 var panel = WebInspector.inspectorView.panel(message.panel); 262 if (!panel) 263 return this._status.E_NOTFOUND(message.panel); 264 if (!panel.addExtensionSidebarPane) 265 return this._status.E_NOTSUPPORTED(); 266 var id = message.id; 267 var sidebar = new WebInspector.ExtensionSidebarPane(message.title, id); 268 this._clientObjects[id] = sidebar; 269 panel.addExtensionSidebarPane(id, sidebar); 270 271 return this._status.OK(); 272 }, 273 274 _onSetSidebarHeight: function(message) 275 { 276 var sidebar = this._clientObjects[message.id]; 277 if (!sidebar) 278 return this._status.E_NOTFOUND(message.id); 279 sidebar.setHeight(message.height); 280 return this._status.OK(); 281 }, 282 283 _onSetSidebarContent: function(message, port) 284 { 285 var sidebar = this._clientObjects[message.id]; 286 if (!sidebar) 287 return this._status.E_NOTFOUND(message.id); 288 289 /** 290 * @this {WebInspector.ExtensionServer} 291 */ 292 function callback(error) 293 { 294 var result = error ? this._status.E_FAILED(error) : this._status.OK(); 295 this._dispatchCallback(message.requestId, port, result); 296 } 297 if (message.evaluateOnPage) 298 return sidebar.setExpression(message.expression, message.rootTitle, message.evaluateOptions, port._extensionOrigin, callback.bind(this)); 299 sidebar.setObject(message.expression, message.rootTitle, callback.bind(this)); 300 }, 301 302 _onSetSidebarPage: function(message, port) 303 { 304 var sidebar = this._clientObjects[message.id]; 305 if (!sidebar) 306 return this._status.E_NOTFOUND(message.id); 307 sidebar.setPage(this._expandResourcePath(port._extensionOrigin, message.page)); 308 }, 309 310 _onOpenResource: function(message) 311 { 312 var uiSourceCode = WebInspector.workspace.uiSourceCodeForURL(message.url); 313 if (uiSourceCode) { 314 WebInspector.Revealer.reveal(uiSourceCode.uiLocation(message.lineNumber, 0)); 315 return this._status.OK(); 316 } 317 318 var resource = WebInspector.resourceForURL(message.url); 319 if (resource) { 320 WebInspector.Revealer.reveal(resource, message.lineNumber); 321 return this._status.OK(); 322 } 323 324 var request = WebInspector.networkLog.requestForURL(message.url); 325 if (request) { 326 WebInspector.Revealer.reveal(request); 327 return this._status.OK(); 328 } 329 330 return this._status.E_NOTFOUND(message.url); 331 }, 332 333 _onSetOpenResourceHandler: function(message, port) 334 { 335 var name = this._registeredExtensions[port._extensionOrigin].name || ("Extension " + port._extensionOrigin); 336 if (message.handlerPresent) 337 WebInspector.openAnchorLocationRegistry.registerHandler(name, this._handleOpenURL.bind(this, port)); 338 else 339 WebInspector.openAnchorLocationRegistry.unregisterHandler(name); 340 }, 341 342 _handleOpenURL: function(port, details) 343 { 344 var url = /** @type {string} */ (details.url); 345 var contentProvider = WebInspector.workspace.uiSourceCodeForOriginURL(url) || WebInspector.resourceForURL(url); 346 if (!contentProvider) 347 return false; 348 349 var lineNumber = details.lineNumber; 350 if (typeof lineNumber === "number") 351 lineNumber += 1; 352 port.postMessage({ 353 command: "open-resource", 354 resource: this._makeResource(contentProvider), 355 lineNumber: lineNumber 356 }); 357 return true; 358 }, 359 360 _onReload: function(message) 361 { 362 var options = /** @type {!ExtensionReloadOptions} */ (message.options || {}); 363 NetworkAgent.setUserAgentOverride(typeof options.userAgent === "string" ? options.userAgent : ""); 364 var injectedScript; 365 if (options.injectedScript) 366 injectedScript = "(function(){" + options.injectedScript + "})()"; 367 var preprocessingScript = options.preprocessingScript; 368 WebInspector.resourceTreeModel.reloadPage(!!options.ignoreCache, injectedScript, preprocessingScript); 369 return this._status.OK(); 370 }, 371 372 _onEvaluateOnInspectedPage: function(message, port) 373 { 374 /** 375 * @param {?Protocol.Error} error 376 * @param {?RuntimeAgent.RemoteObject} resultPayload 377 * @param {boolean=} wasThrown 378 * @this {WebInspector.ExtensionServer} 379 */ 380 function callback(error, resultPayload, wasThrown) 381 { 382 var result; 383 if (error || !resultPayload) 384 result = this._status.E_PROTOCOLERROR(error.toString()); 385 else if (wasThrown) 386 result = { isException: true, value: resultPayload.description }; 387 else 388 result = { value: resultPayload.value }; 389 390 this._dispatchCallback(message.requestId, port, result); 391 } 392 return this.evaluate(message.expression, true, true, message.evaluateOptions, port._extensionOrigin, callback.bind(this)); 393 }, 394 395 _onGetConsoleMessages: function() 396 { 397 return WebInspector.console.messages.map(this._makeConsoleMessage); 398 }, 399 400 _onAddConsoleMessage: function(message) 401 { 402 function convertSeverity(level) 403 { 404 switch (level) { 405 case WebInspector.extensionAPI.console.Severity.Log: 406 return WebInspector.ConsoleMessage.MessageLevel.Log; 407 case WebInspector.extensionAPI.console.Severity.Warning: 408 return WebInspector.ConsoleMessage.MessageLevel.Warning; 409 case WebInspector.extensionAPI.console.Severity.Error: 410 return WebInspector.ConsoleMessage.MessageLevel.Error; 411 case WebInspector.extensionAPI.console.Severity.Debug: 412 return WebInspector.ConsoleMessage.MessageLevel.Debug; 413 } 414 } 415 var level = convertSeverity(message.severity); 416 if (!level) 417 return this._status.E_BADARG("message.severity", message.severity); 418 419 var consoleMessage = new WebInspector.ConsoleMessage( 420 WebInspector.console.target(), 421 WebInspector.ConsoleMessage.MessageSource.JS, 422 level, 423 message.text, 424 WebInspector.ConsoleMessage.MessageType.Log, 425 message.url, 426 message.line); 427 WebInspector.console.addMessage(consoleMessage); 428 }, 429 430 _makeConsoleMessage: function(message) 431 { 432 function convertLevel(level) 433 { 434 if (!level) 435 return; 436 switch (level) { 437 case WebInspector.ConsoleMessage.MessageLevel.Log: 438 return WebInspector.extensionAPI.console.Severity.Log; 439 case WebInspector.ConsoleMessage.MessageLevel.Warning: 440 return WebInspector.extensionAPI.console.Severity.Warning; 441 case WebInspector.ConsoleMessage.MessageLevel.Error: 442 return WebInspector.extensionAPI.console.Severity.Error; 443 case WebInspector.ConsoleMessage.MessageLevel.Debug: 444 return WebInspector.extensionAPI.console.Severity.Debug; 445 default: 446 return WebInspector.extensionAPI.console.Severity.Log; 447 } 448 } 449 var result = { 450 severity: convertLevel(message.level), 451 text: message.messageText, 452 }; 453 if (message.url) 454 result.url = message.url; 455 if (message.line) 456 result.line = message.line; 457 return result; 458 }, 459 460 _onGetHAR: function() 461 { 462 // Wake up the "network" module for HAR operations. 463 WebInspector.inspectorView.panel("network"); 464 var requests = WebInspector.networkLog.requests; 465 var harLog = (new WebInspector.HARLog(requests)).build(); 466 for (var i = 0; i < harLog.entries.length; ++i) 467 harLog.entries[i]._requestId = this._requestId(requests[i]); 468 return harLog; 469 }, 470 471 /** 472 * @param {!WebInspector.ContentProvider} contentProvider 473 */ 474 _makeResource: function(contentProvider) 475 { 476 return { 477 url: contentProvider.contentURL(), 478 type: contentProvider.contentType().name() 479 }; 480 }, 481 482 /** 483 * @return {!Array.<!WebInspector.ContentProvider>} 484 */ 485 _onGetPageResources: function() 486 { 487 var resources = {}; 488 489 /** 490 * @this {WebInspector.ExtensionServer} 491 */ 492 function pushResourceData(contentProvider) 493 { 494 if (!resources[contentProvider.contentURL()]) 495 resources[contentProvider.contentURL()] = this._makeResource(contentProvider); 496 } 497 var uiSourceCodes = WebInspector.workspace.uiSourceCodesForProjectType(WebInspector.projectTypes.Network); 498 uiSourceCodes = uiSourceCodes.concat(WebInspector.workspace.uiSourceCodesForProjectType(WebInspector.projectTypes.ContentScripts)); 499 uiSourceCodes.forEach(pushResourceData.bind(this)); 500 WebInspector.resourceTreeModel.forAllResources(pushResourceData.bind(this)); 501 return Object.values(resources); 502 }, 503 504 /** 505 * @param {!WebInspector.ContentProvider} contentProvider 506 * @param {!Object} message 507 * @param {!MessagePort} port 508 */ 509 _getResourceContent: function(contentProvider, message, port) 510 { 511 /** 512 * @param {?string} content 513 * @this {WebInspector.ExtensionServer} 514 */ 515 function onContentAvailable(content) 516 { 517 var response = { 518 encoding: (content === null) || contentProvider.contentType().isTextType() ? "" : "base64", 519 content: content 520 }; 521 this._dispatchCallback(message.requestId, port, response); 522 } 523 524 contentProvider.requestContent(onContentAvailable.bind(this)); 525 }, 526 527 _onGetRequestContent: function(message, port) 528 { 529 var request = this._requestById(message.id); 530 if (!request) 531 return this._status.E_NOTFOUND(message.id); 532 this._getResourceContent(request, message, port); 533 }, 534 535 _onGetResourceContent: function(message, port) 536 { 537 var url = /** @type {string} */ (message.url); 538 var contentProvider = WebInspector.workspace.uiSourceCodeForOriginURL(url) || WebInspector.resourceForURL(url); 539 if (!contentProvider) 540 return this._status.E_NOTFOUND(url); 541 this._getResourceContent(contentProvider, message, port); 542 }, 543 544 _onSetResourceContent: function(message, port) 545 { 546 /** 547 * @param {?Protocol.Error} error 548 * @this {WebInspector.ExtensionServer} 549 */ 550 function callbackWrapper(error) 551 { 552 var response = error ? this._status.E_FAILED(error) : this._status.OK(); 553 this._dispatchCallback(message.requestId, port, response); 554 } 555 556 var url = /** @type {string} */ (message.url); 557 var uiSourceCode = WebInspector.workspace.uiSourceCodeForOriginURL(url); 558 if (!uiSourceCode) { 559 var resource = WebInspector.resourceTreeModel.resourceForURL(url); 560 if (!resource) 561 return this._status.E_NOTFOUND(url); 562 return this._status.E_NOTSUPPORTED("Resource is not editable") 563 } 564 uiSourceCode.setWorkingCopy(message.content); 565 if (message.commit) 566 uiSourceCode.commitWorkingCopy(callbackWrapper.bind(this)); 567 else 568 callbackWrapper.call(this, null); 569 }, 570 571 _requestId: function(request) 572 { 573 if (!request._extensionRequestId) { 574 request._extensionRequestId = ++this._lastRequestId; 575 this._requests[request._extensionRequestId] = request; 576 } 577 return request._extensionRequestId; 578 }, 579 580 _requestById: function(id) 581 { 582 return this._requests[id]; 583 }, 584 585 _onAddAuditCategory: function(message, port) 586 { 587 var category = new WebInspector.ExtensionAuditCategory(port._extensionOrigin, message.id, message.displayName, message.resultCount); 588 if (WebInspector.inspectorView.panel("audits").getCategory(category.id)) 589 return this._status.E_EXISTS(category.id); 590 this._clientObjects[message.id] = category; 591 // FIXME: register module manager extension instead of waking up audits module. 592 WebInspector.inspectorView.panel("audits").addCategory(category); 593 }, 594 595 _onAddAuditResult: function(message) 596 { 597 var auditResult = this._clientObjects[message.resultId]; 598 if (!auditResult) 599 return this._status.E_NOTFOUND(message.resultId); 600 try { 601 auditResult.addResult(message.displayName, message.description, message.severity, message.details); 602 } catch (e) { 603 return e; 604 } 605 return this._status.OK(); 606 }, 607 608 _onUpdateAuditProgress: function(message) 609 { 610 var auditResult = this._clientObjects[message.resultId]; 611 if (!auditResult) 612 return this._status.E_NOTFOUND(message.resultId); 613 auditResult.updateProgress(Math.min(Math.max(0, message.progress), 1)); 614 }, 615 616 _onStopAuditCategoryRun: function(message) 617 { 618 var auditRun = this._clientObjects[message.resultId]; 619 if (!auditRun) 620 return this._status.E_NOTFOUND(message.resultId); 621 auditRun.done(); 622 }, 623 624 _onForwardKeyboardEvent: function(message) 625 { 626 const Esc = "U+001B"; 627 message.entries.forEach(handleEventEntry); 628 629 function handleEventEntry(entry) 630 { 631 if (!entry.ctrlKey && !entry.altKey && !entry.metaKey && !/^F\d+$/.test(entry.keyIdentifier) && entry.keyIdentifier !== Esc) 632 return; 633 // Fool around closure compiler -- it has its own notion of both KeyboardEvent constructor 634 // and initKeyboardEvent methods and overriding these in externs.js does not have effect. 635 var event = new window.KeyboardEvent(entry.eventType, { 636 keyIdentifier: entry.keyIdentifier, 637 location: entry.location, 638 ctrlKey: entry.ctrlKey, 639 altKey: entry.altKey, 640 shiftKey: entry.shiftKey, 641 metaKey: entry.metaKey 642 }); 643 event.__keyCode = keyCodeForEntry(entry); 644 document.dispatchEvent(event); 645 } 646 647 function keyCodeForEntry(entry) 648 { 649 var keyCode = entry.keyCode; 650 if (!keyCode) { 651 // This is required only for synthetic events (e.g. dispatched in tests). 652 var match = entry.keyIdentifier.match(/^U\+([\dA-Fa-f]+)$/); 653 if (match) 654 keyCode = parseInt(match[1], 16); 655 } 656 return keyCode || 0; 657 } 658 }, 659 660 _dispatchCallback: function(requestId, port, result) 661 { 662 if (requestId) 663 port.postMessage({ command: "callback", requestId: requestId, result: result }); 664 }, 665 666 _initExtensions: function() 667 { 668 this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ConsoleMessageAdded, 669 WebInspector.console, WebInspector.ConsoleModel.Events.MessageAdded, this._notifyConsoleMessageAdded); 670 this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.NetworkRequestFinished, 671 WebInspector.networkManager, WebInspector.NetworkManager.EventTypes.RequestFinished, this._notifyRequestFinished); 672 this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ResourceAdded, 673 WebInspector.workspace, 674 WebInspector.Workspace.Events.UISourceCodeAdded, 675 this._notifyResourceAdded); 676 677 /** 678 * @this {WebInspector.ExtensionServer} 679 */ 680 function onElementsSubscriptionStarted() 681 { 682 WebInspector.notifications.addEventListener(WebInspector.NotificationService.Events.SelectedNodeChanged, this._notifyElementsSelectionChanged, this); 683 } 684 685 /** 686 * @this {WebInspector.ExtensionServer} 687 */ 688 function onElementsSubscriptionStopped() 689 { 690 WebInspector.notifications.removeEventListener(WebInspector.NotificationService.Events.SelectedNodeChanged, this._notifyElementsSelectionChanged, this); 691 } 692 693 this._registerSubscriptionHandler(WebInspector.extensionAPI.Events.PanelObjectSelected + "elements", 694 onElementsSubscriptionStarted.bind(this), onElementsSubscriptionStopped.bind(this)); 695 696 this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.PanelObjectSelected + "sources", 697 WebInspector.notifications, 698 WebInspector.SourceFrame.Events.SelectionChanged, 699 this._notifySourceFrameSelectionChanged); 700 this._registerResourceContentCommittedHandler(this._notifyUISourceCodeContentCommitted); 701 702 /** 703 * @this {WebInspector.ExtensionServer} 704 */ 705 function onTimelineSubscriptionStarted() 706 { 707 WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, 708 this._notifyTimelineEventRecorded, this); 709 WebInspector.timelineManager.start(); 710 } 711 712 /** 713 * @this {WebInspector.ExtensionServer} 714 */ 715 function onTimelineSubscriptionStopped() 716 { 717 WebInspector.timelineManager.stop(function() {}); 718 WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, 719 this._notifyTimelineEventRecorded, this); 720 } 721 722 this._registerSubscriptionHandler(WebInspector.extensionAPI.Events.TimelineEventRecorded, 723 onTimelineSubscriptionStarted.bind(this), onTimelineSubscriptionStopped.bind(this)); 724 725 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, 726 this._inspectedURLChanged, this); 727 728 InspectorExtensionRegistry.getExtensionsAsync(); 729 }, 730 731 /** 732 * @param {!WebInspector.TextRange} textRange 733 */ 734 _makeSourceSelection: function(textRange) 735 { 736 var sourcesPanel = WebInspector.inspectorView.panel("sources"); 737 var selection = { 738 startLine: textRange.startLine, 739 startColumn: textRange.startColumn, 740 endLine: textRange.endLine, 741 endColumn: textRange.endColumn, 742 url: sourcesPanel.sourcesView().currentUISourceCode().uri() 743 }; 744 745 return selection; 746 }, 747 748 _notifySourceFrameSelectionChanged: function(event) 749 { 750 this._postNotification(WebInspector.extensionAPI.Events.PanelObjectSelected + "sources", this._makeSourceSelection(event.data)); 751 }, 752 753 _notifyConsoleMessageAdded: function(event) 754 { 755 this._postNotification(WebInspector.extensionAPI.Events.ConsoleMessageAdded, this._makeConsoleMessage(event.data)); 756 }, 757 758 _notifyResourceAdded: function(event) 759 { 760 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); 761 this._postNotification(WebInspector.extensionAPI.Events.ResourceAdded, this._makeResource(uiSourceCode)); 762 }, 763 764 _notifyUISourceCodeContentCommitted: function(event) 765 { 766 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.uiSourceCode); 767 var content = /** @type {string} */ (event.data.content); 768 this._postNotification(WebInspector.extensionAPI.Events.ResourceContentCommitted, this._makeResource(uiSourceCode), content); 769 }, 770 771 _notifyRequestFinished: function(event) 772 { 773 var request = /** @type {!WebInspector.NetworkRequest} */ (event.data); 774 // Wake up the "network" module for HAR operations. 775 WebInspector.inspectorView.panel("network"); 776 this._postNotification(WebInspector.extensionAPI.Events.NetworkRequestFinished, this._requestId(request), (new WebInspector.HAREntry(request)).build()); 777 }, 778 779 _notifyElementsSelectionChanged: function() 780 { 781 this._postNotification(WebInspector.extensionAPI.Events.PanelObjectSelected + "elements"); 782 }, 783 784 _notifyTimelineEventRecorded: function(event) 785 { 786 this._postNotification(WebInspector.extensionAPI.Events.TimelineEventRecorded, event.data); 787 }, 788 789 /** 790 * @param {!Array.<!ExtensionDescriptor>} extensionInfos 791 */ 792 addExtensions: function(extensionInfos) 793 { 794 extensionInfos.forEach(this._addExtension, this); 795 }, 796 797 /** 798 * @param {!ExtensionDescriptor} extensionInfo 799 */ 800 _addExtension: function(extensionInfo) 801 { 802 const urlOriginRegExp = new RegExp("([^:]+:\/\/[^/]*)\/"); // Can't use regexp literal here, MinJS chokes on it. 803 var startPage = extensionInfo.startPage; 804 var name = extensionInfo.name; 805 806 try { 807 var originMatch = urlOriginRegExp.exec(startPage); 808 if (!originMatch) { 809 console.error("Skipping extension with invalid URL: " + startPage); 810 return false; 811 } 812 var extensionOrigin = originMatch[1]; 813 if (!this._registeredExtensions[extensionOrigin]) { 814 // See ExtensionAPI.js for details. 815 InspectorFrontendHost.setInjectedScriptForOrigin(extensionOrigin, buildExtensionAPIInjectedScript(extensionInfo)); 816 this._registeredExtensions[extensionOrigin] = { name: name }; 817 } 818 var iframe = document.createElement("iframe"); 819 iframe.src = startPage; 820 iframe.style.display = "none"; 821 document.body.appendChild(iframe); 822 } catch (e) { 823 console.error("Failed to initialize extension " + startPage + ":" + e); 824 return false; 825 } 826 return true; 827 }, 828 829 _registerExtension: function(origin, port) 830 { 831 if (!this._registeredExtensions.hasOwnProperty(origin)) { 832 if (origin !== window.location.origin) // Just ignore inspector frames. 833 console.error("Ignoring unauthorized client request from " + origin); 834 return; 835 } 836 port._extensionOrigin = origin; 837 port.addEventListener("message", this._onmessage.bind(this), false); 838 port.start(); 839 }, 840 841 _onWindowMessage: function(event) 842 { 843 if (event.data === "registerExtension") 844 this._registerExtension(event.origin, event.ports[0]); 845 }, 846 847 _onmessage: function(event) 848 { 849 var message = event.data; 850 var result; 851 852 if (message.command in this._handlers) 853 result = this._handlers[message.command](message, event.target); 854 else 855 result = this._status.E_NOTSUPPORTED(message.command); 856 857 if (result && message.requestId) 858 this._dispatchCallback(message.requestId, event.target, result); 859 }, 860 861 _registerHandler: function(command, callback) 862 { 863 console.assert(command); 864 this._handlers[command] = callback; 865 }, 866 867 _registerSubscriptionHandler: function(eventTopic, onSubscribeFirst, onUnsubscribeLast) 868 { 869 this._subscriptionStartHandlers[eventTopic] = onSubscribeFirst; 870 this._subscriptionStopHandlers[eventTopic] = onUnsubscribeLast; 871 }, 872 873 _registerAutosubscriptionHandler: function(eventTopic, eventTarget, frontendEventType, handler) 874 { 875 this._registerSubscriptionHandler(eventTopic, 876 eventTarget.addEventListener.bind(eventTarget, frontendEventType, handler, this), 877 eventTarget.removeEventListener.bind(eventTarget, frontendEventType, handler, this)); 878 }, 879 880 _registerResourceContentCommittedHandler: function(handler) 881 { 882 /** 883 * @this {WebInspector.ExtensionServer} 884 */ 885 function addFirstEventListener() 886 { 887 WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeContentCommitted, handler, this); 888 WebInspector.workspace.setHasResourceContentTrackingExtensions(true); 889 } 890 891 /** 892 * @this {WebInspector.ExtensionServer} 893 */ 894 function removeLastEventListener() 895 { 896 WebInspector.workspace.setHasResourceContentTrackingExtensions(false); 897 WebInspector.workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeContentCommitted, handler, this); 898 } 899 900 this._registerSubscriptionHandler(WebInspector.extensionAPI.Events.ResourceContentCommitted, 901 addFirstEventListener.bind(this), 902 removeLastEventListener.bind(this)); 903 }, 904 905 _expandResourcePath: function(extensionPath, resourcePath) 906 { 907 if (!resourcePath) 908 return; 909 return extensionPath + this._normalizePath(resourcePath); 910 }, 911 912 _normalizePath: function(path) 913 { 914 var source = path.split("/"); 915 var result = []; 916 917 for (var i = 0; i < source.length; ++i) { 918 if (source[i] === ".") 919 continue; 920 // Ignore empty path components resulting from //, as well as a leading and traling slashes. 921 if (source[i] === "") 922 continue; 923 if (source[i] === "..") 924 result.pop(); 925 else 926 result.push(source[i]); 927 } 928 return "/" + result.join("/"); 929 }, 930 931 /** 932 * @param {string} expression 933 * @param {boolean} exposeCommandLineAPI 934 * @param {boolean} returnByValue 935 * @param {?Object} options 936 * @param {string} securityOrigin 937 * @param {function(?string, !RuntimeAgent.RemoteObject, boolean=)} callback 938 * @return {!WebInspector.ExtensionStatus.Record|undefined} 939 */ 940 evaluate: function(expression, exposeCommandLineAPI, returnByValue, options, securityOrigin, callback) 941 { 942 var contextId; 943 944 /** 945 * @param {string} url 946 * @return {boolean} 947 */ 948 function resolveURLToFrame(url) 949 { 950 var found; 951 function hasMatchingURL(frame) 952 { 953 found = (frame.url === url) ? frame : null; 954 return found; 955 } 956 WebInspector.resourceTreeModel.frames().some(hasMatchingURL); 957 return found; 958 } 959 960 if (typeof options === "object") { 961 var frame = options.frameURL ? resolveURLToFrame(options.frameURL) : WebInspector.resourceTreeModel.mainFrame; 962 if (!frame) { 963 if (options.frameURL) 964 console.warn("evaluate: there is no frame with URL " + options.frameURL); 965 else 966 console.warn("evaluate: the main frame is not yet available"); 967 return this._status.E_NOTFOUND(options.frameURL || "<top>"); 968 } 969 970 var contextSecurityOrigin; 971 if (options.useContentScriptContext) 972 contextSecurityOrigin = securityOrigin; 973 else if (options.scriptExecutionContext) 974 contextSecurityOrigin = options.scriptExecutionContext; 975 976 var context; 977 var executionContexts = WebInspector.runtimeModel.executionContexts(); 978 if (contextSecurityOrigin) { 979 for (var i = 0; i < executionContexts.length; ++i) { 980 var executionContext = executionContexts[i]; 981 if (executionContext.frameId === frame.id && executionContext.name === contextSecurityOrigin && !executionContext.isMainWorldContext) 982 context = executionContext; 983 984 } 985 if (!context) { 986 console.warn("The JavaScript context " + contextSecurityOrigin + " was not found in the frame " + frame.url) 987 return this._status.E_NOTFOUND(contextSecurityOrigin) 988 } 989 } else { 990 for (var i = 0; i < executionContexts.length; ++i) { 991 var executionContext = executionContexts[i]; 992 if (executionContext.frameId === frame.id && executionContext.isMainWorldContext) 993 context = executionContext; 994 995 } 996 if (!context) 997 return this._status.E_FAILED(frame.url + " has no execution context"); 998 } 999 1000 contextId = context.id; 1001 } 1002 RuntimeAgent.evaluate(expression, "extension", exposeCommandLineAPI, true, contextId, returnByValue, false, callback); 1003 } 1004} 1005 1006/** 1007 * @constructor 1008 * @param {string} name 1009 * @param {string} title 1010 * @param {!WebInspector.Panel} panel 1011 * @implements {WebInspector.PanelDescriptor} 1012 */ 1013WebInspector.ExtensionServerPanelDescriptor = function(name, title, panel) 1014{ 1015 this._name = name; 1016 this._title = title; 1017 this._panel = panel; 1018} 1019 1020WebInspector.ExtensionServerPanelDescriptor.prototype = { 1021 /** 1022 * @return {string} 1023 */ 1024 name: function() 1025 { 1026 return this._name; 1027 }, 1028 1029 /** 1030 * @return {string} 1031 */ 1032 title: function() 1033 { 1034 return this._title; 1035 }, 1036 1037 /** 1038 * @return {!WebInspector.Panel} 1039 */ 1040 panel: function() 1041 { 1042 return this._panel; 1043 } 1044} 1045 1046/** 1047 * @constructor 1048 */ 1049WebInspector.ExtensionStatus = function() 1050{ 1051 /** 1052 * @param {string} code 1053 * @param {string} description 1054 * @return {!WebInspector.ExtensionStatus.Record} 1055 */ 1056 function makeStatus(code, description) 1057 { 1058 var details = Array.prototype.slice.call(arguments, 2); 1059 var status = { code: code, description: description, details: details }; 1060 if (code !== "OK") { 1061 status.isError = true; 1062 console.log("Extension server error: " + String.vsprintf(description, details)); 1063 } 1064 return status; 1065 } 1066 1067 this.OK = makeStatus.bind(null, "OK", "OK"); 1068 this.E_EXISTS = makeStatus.bind(null, "E_EXISTS", "Object already exists: %s"); 1069 this.E_BADARG = makeStatus.bind(null, "E_BADARG", "Invalid argument %s: %s"); 1070 this.E_BADARGTYPE = makeStatus.bind(null, "E_BADARGTYPE", "Invalid type for argument %s: got %s, expected %s"); 1071 this.E_NOTFOUND = makeStatus.bind(null, "E_NOTFOUND", "Object not found: %s"); 1072 this.E_NOTSUPPORTED = makeStatus.bind(null, "E_NOTSUPPORTED", "Object does not support requested operation: %s"); 1073 this.E_PROTOCOLERROR = makeStatus.bind(null, "E_PROTOCOLERROR", "Inspector protocol error: %s"); 1074 this.E_FAILED = makeStatus.bind(null, "E_FAILED", "Operation failed: %s"); 1075} 1076 1077/** 1078 * @typedef {{code: string, description: string, details: !Array.<*>}} 1079 */ 1080WebInspector.ExtensionStatus.Record; 1081 1082WebInspector.extensionAPI = {}; 1083defineCommonExtensionSymbols(WebInspector.extensionAPI); 1084 1085importScript("ExtensionPanel.js"); 1086importScript("ExtensionView.js"); 1087