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