Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * Dictionary of constants (initialized by browser). | 6 * Dictionary of constants (initialized by browser). |
| 7 */ | 7 */ |
| 8 var LogEventType = null; | 8 var LogEventType = null; |
| 9 var LogEventPhase = null; | 9 var LogEventPhase = null; |
| 10 var ClientInfo = null; | 10 var ClientInfo = null; |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 68 'hostResolverIPv6Disabled', | 68 'hostResolverIPv6Disabled', |
| 69 'hostResolverEnableIPv6', | 69 'hostResolverEnableIPv6', |
| 70 'hostResolverCacheCapacity', | 70 'hostResolverCacheCapacity', |
| 71 'hostResolverCacheTTLSuccess', | 71 'hostResolverCacheTTLSuccess', |
| 72 'hostResolverCacheTTLFailure'); | 72 'hostResolverCacheTTLFailure'); |
| 73 | 73 |
| 74 // Create a view which will display import/export options to control the | 74 // Create a view which will display import/export options to control the |
| 75 // captured data. | 75 // captured data. |
| 76 var dataView = new DataView('dataTabContent', 'exportedDataText', | 76 var dataView = new DataView('dataTabContent', 'exportedDataText', |
| 77 'exportToText', 'securityStrippingCheckbox', | 77 'exportToText', 'securityStrippingCheckbox', |
| 78 'byteLoggingCheckbox', | 78 'byteLoggingCheckbox', 'passivelyCapturedCount', |
| 79 'passivelyCapturedCount', | 79 'activelyCapturedCount', 'dataViewDeleteAll', |
| 80 'activelyCapturedCount', | 80 'dataViewDumpDataDiv', 'dataViewLoadDataDiv', |
| 81 'dataViewDeleteAll'); | 81 'dataViewLoadLogFile', |
| 82 'dataViewCapturingTextSpan', | |
| 83 'dataViewLoggingTextSpan'); | |
| 82 | 84 |
| 83 // Create a view which will display the results and controls for connection | 85 // Create a view which will display the results and controls for connection |
| 84 // tests. | 86 // tests. |
| 85 var testView = new TestView('testTabContent', 'testUrlInput', | 87 var testView = new TestView('testTabContent', 'testUrlInput', |
| 86 'connectionTestsForm', 'testSummary'); | 88 'connectionTestsForm', 'testSummary'); |
| 87 | 89 |
| 88 var httpCacheView = new HttpCacheView('httpCacheTabContent', | 90 var httpCacheView = new HttpCacheView('httpCacheTabContent', |
| 89 'httpCacheStats'); | 91 'httpCacheStats'); |
| 90 | 92 |
| 91 var socketsView = new SocketsView('socketsTabContent', | 93 var socketsView = new SocketsView('socketsTabContent', |
| 92 'socketPoolDiv', | 94 'socketPoolDiv', |
| 93 'socketPoolGroupsDiv'); | 95 'socketPoolGroupsDiv'); |
| 94 | 96 |
| 95 var spdyView = new SpdyView('spdyTabContent', | 97 var spdyView = new SpdyView('spdyTabContent', |
| 96 'spdySessionNoneSpan', | 98 'spdySessionNoneSpan', |
| 97 'spdySessionLinkSpan', | 99 'spdySessionLinkSpan', |
| 98 'spdySessionDiv'); | 100 'spdySessionDiv'); |
| 99 | 101 |
| 100 | 102 |
| 101 var serviceView; | 103 var serviceView; |
| 102 if (g_browser.isPlatformWindows()) { | 104 if (g_browser.isPlatformWindows()) { |
| 103 serviceView = new ServiceProvidersView('serviceProvidersTab', | 105 serviceView = new ServiceProvidersView('serviceProvidersTab', |
| 104 'serviceProvidersTabContent', | 106 'serviceProvidersTabContent', |
| 105 'serviceProvidersTbody', | 107 'serviceProvidersTbody', |
| 106 'namespaceProvidersTbody'); | 108 'namespaceProvidersTbody'); |
| 107 } | 109 } |
| 108 | 110 |
| 109 // Create a view which lets you tab between the different sub-views. | 111 // Create a view which lets you tab between the different sub-views. |
| 110 var categoryTabSwitcher = new TabSwitcherView('categoryTabHandles'); | 112 var categoryTabSwitcher = new TabSwitcherView('categoryTabHandles'); |
| 113 g_browser.setTabSwitcher(categoryTabSwitcher); | |
| 111 | 114 |
| 112 // Populate the main tabs. | 115 // Populate the main tabs. |
| 113 categoryTabSwitcher.addTab('eventsTab', eventsView, false); | 116 categoryTabSwitcher.addTab('eventsTab', eventsView, false); |
| 114 categoryTabSwitcher.addTab('proxyTab', proxyView, false); | 117 categoryTabSwitcher.addTab('proxyTab', proxyView, false); |
| 115 categoryTabSwitcher.addTab('dnsTab', dnsView, false); | 118 categoryTabSwitcher.addTab('dnsTab', dnsView, false); |
| 116 categoryTabSwitcher.addTab('socketsTab', socketsView, false); | 119 categoryTabSwitcher.addTab('socketsTab', socketsView, false); |
| 117 categoryTabSwitcher.addTab('spdyTab', spdyView, false); | 120 categoryTabSwitcher.addTab('spdyTab', spdyView, false); |
| 118 categoryTabSwitcher.addTab('httpCacheTab', httpCacheView, false); | 121 categoryTabSwitcher.addTab('httpCacheTab', httpCacheView, false); |
| 119 categoryTabSwitcher.addTab('dataTab', dataView, false); | 122 categoryTabSwitcher.addTab('dataTab', dataView, false); |
| 120 if (g_browser.isPlatformWindows()) | 123 if (g_browser.isPlatformWindows()) |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 137 | 140 |
| 138 // Make this category tab widget the primary view, that fills the whole page. | 141 // Make this category tab widget the primary view, that fills the whole page. |
| 139 var windowView = new WindowView(categoryTabSwitcher); | 142 var windowView = new WindowView(categoryTabSwitcher); |
| 140 | 143 |
| 141 // Trigger initial layout. | 144 // Trigger initial layout. |
| 142 windowView.resetGeometry(); | 145 windowView.resetGeometry(); |
| 143 | 146 |
| 144 // Select the initial view based on the current URL. | 147 // Select the initial view based on the current URL. |
| 145 window.onhashchange(); | 148 window.onhashchange(); |
| 146 | 149 |
| 150 // Inform observers a log file is not currently being displayed. | |
| 151 g_browser.setIsViewingLogFile(false); | |
| 152 | |
| 147 // Tell the browser that we are ready to start receiving log events. | 153 // Tell the browser that we are ready to start receiving log events. |
| 148 g_browser.sendReady(); | 154 g_browser.sendReady(); |
| 149 } | 155 } |
| 150 | 156 |
| 151 /** | 157 /** |
| 152 * This class provides a "bridge" for communicating between the javascript and | 158 * This class provides a "bridge" for communicating between the javascript and |
| 153 * the browser. | 159 * the browser. |
| 154 * | 160 * |
| 155 * @constructor | 161 * @constructor |
| 156 */ | 162 */ |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 184 this.sendGetServiceProviders.bind(this)); | 190 this.sendGetServiceProviders.bind(this)); |
| 185 } | 191 } |
| 186 | 192 |
| 187 // Cache of the data received. | 193 // Cache of the data received. |
| 188 this.numPassivelyCapturedEvents_ = 0; | 194 this.numPassivelyCapturedEvents_ = 0; |
| 189 this.capturedEvents_ = []; | 195 this.capturedEvents_ = []; |
| 190 | 196 |
| 191 // Next unique id to be assigned to a log entry without a source. | 197 // Next unique id to be assigned to a log entry without a source. |
| 192 // Needed to simplify deletion, identify associated GUI elements, etc. | 198 // Needed to simplify deletion, identify associated GUI elements, etc. |
| 193 this.nextSourcelessEventId_ = -1; | 199 this.nextSourcelessEventId_ = -1; |
| 200 | |
| 201 // True when viewing a log file rather than actively logged events. | |
| 202 // When viewing a log file, all tabs are hidden except the event view, | |
| 203 // and all received events are ignored. | |
| 204 this.isViewingLogFile_ = false; | |
| 194 } | 205 } |
| 195 | 206 |
| 196 /* | 207 /* |
| 197 * Takes the current hash in form of "#tab¶m1=value1¶m2=value2&...". | 208 * Takes the current hash in form of "#tab¶m1=value1¶m2=value2&...". |
| 198 * Puts the parameters in an object, and passes the resulting object to | 209 * Puts the parameters in an object, and passes the resulting object to |
| 199 * |categoryTabSwitcher|. Uses tab and |anchorMap| to find a tab ID, | 210 * |categoryTabSwitcher|. Uses tab and |anchorMap| to find a tab ID, |
| 200 * which it also passes to the tab switcher. | 211 * which it also passes to the tab switcher. |
| 201 * | 212 * |
| 202 * Parameters and values are decoded with decodeURIComponent(). | 213 * Parameters and values are decoded with decodeURIComponent(). |
| 203 */ | 214 */ |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 295 }; | 306 }; |
| 296 | 307 |
| 297 BrowserBridge.prototype.enableIPv6 = function() { | 308 BrowserBridge.prototype.enableIPv6 = function() { |
| 298 chrome.send('enableIPv6'); | 309 chrome.send('enableIPv6'); |
| 299 }; | 310 }; |
| 300 | 311 |
| 301 BrowserBridge.prototype.setLogLevel = function(logLevel) { | 312 BrowserBridge.prototype.setLogLevel = function(logLevel) { |
| 302 chrome.send('setLogLevel', ['' + logLevel]); | 313 chrome.send('setLogLevel', ['' + logLevel]); |
| 303 } | 314 } |
| 304 | 315 |
| 316 BrowserBridge.prototype.loadLogFile = function(logLevel) { | |
|
eroman
2011/01/12 05:57:34
the parameter here is unused.
mmenke
2011/01/13 03:45:29
Oops...Removed.
| |
| 317 chrome.send('loadLogFile'); | |
| 318 } | |
| 319 | |
| 305 //------------------------------------------------------------------------------ | 320 //------------------------------------------------------------------------------ |
| 306 // Messages received from the browser | 321 // Messages received from the browser |
| 307 //------------------------------------------------------------------------------ | 322 //------------------------------------------------------------------------------ |
| 308 | 323 |
| 309 BrowserBridge.prototype.receivedLogEntries = function(logEntries) { | 324 BrowserBridge.prototype.receivedLogEntries = function(logEntries) { |
| 310 for (var e = 0; e < logEntries.length; ++e) { | 325 // Does nothing if viewing a log file. |
| 311 var logEntry = logEntries[e]; | 326 if (this.isViewingLogFile_) |
| 312 | 327 return; |
| 313 // Assign unique ID, if needed. | 328 this.addLogEntries(logEntries); |
| 314 if (logEntry.source.id == 0) { | |
| 315 logEntry.source.id = this.nextSourcelessEventId_; | |
| 316 --this.nextSourcelessEventId_; | |
| 317 } | |
| 318 this.capturedEvents_.push(logEntry); | |
| 319 for (var i = 0; i < this.logObservers_.length; ++i) | |
| 320 this.logObservers_[i].onLogEntryAdded(logEntry); | |
| 321 } | |
| 322 }; | 329 }; |
| 323 | 330 |
| 324 BrowserBridge.prototype.receivedLogEventTypeConstants = function(constantsMap) { | 331 BrowserBridge.prototype.receivedLogEventTypeConstants = function(constantsMap) { |
| 325 LogEventType = constantsMap; | 332 LogEventType = constantsMap; |
| 326 }; | 333 }; |
| 327 | 334 |
| 328 BrowserBridge.prototype.receivedClientInfo = | 335 BrowserBridge.prototype.receivedClientInfo = |
| 329 function(info) { | 336 function(info) { |
| 330 ClientInfo = info; | 337 ClientInfo = info; |
| 331 }; | 338 }; |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 432 | 439 |
| 433 BrowserBridge.prototype.receivedCompletedConnectionTestSuite = function() { | 440 BrowserBridge.prototype.receivedCompletedConnectionTestSuite = function() { |
| 434 for (var i = 0; i < this.connectionTestsObservers_.length; ++i) | 441 for (var i = 0; i < this.connectionTestsObservers_.length; ++i) |
| 435 this.connectionTestsObservers_[i].onCompletedConnectionTestSuite(); | 442 this.connectionTestsObservers_[i].onCompletedConnectionTestSuite(); |
| 436 }; | 443 }; |
| 437 | 444 |
| 438 BrowserBridge.prototype.receivedHttpCacheInfo = function(info) { | 445 BrowserBridge.prototype.receivedHttpCacheInfo = function(info) { |
| 439 this.pollableDataHelpers_.httpCacheInfo.update(info); | 446 this.pollableDataHelpers_.httpCacheInfo.update(info); |
| 440 }; | 447 }; |
| 441 | 448 |
| 449 BrowserBridge.prototype.loadedLogFile = function(logFileContents) { | |
| 450 var match; | |
| 451 // Replace carriage returns with linebreaks and then split around linebreaks. | |
| 452 var lines = logFileContents.replace(/\r/g, '\n').split('\n'); | |
| 453 var entries = []; | |
| 454 | |
| 455 for (var i = 0; i < lines.length; ++i) { | |
| 456 // Parse all valid lines, skipping any others. | |
| 457 try { | |
| 458 var entry = JSON.parse(lines[i]); | |
| 459 if (entry && | |
| 460 typeof(entry) == 'object' && | |
| 461 entry.phase != undefined && | |
| 462 entry.source != undefined && | |
| 463 entry.time != undefined && | |
| 464 entry.type != undefined) { | |
| 465 entries.push(entry); | |
| 466 } | |
| 467 } catch (err) { | |
| 468 err = err; | |
|
eroman
2011/01/12 05:57:34
is this line intended to be a no-op?
Perhaps we s
mmenke
2011/01/13 03:45:29
Forgot to remove that line. Was using it as a bre
| |
| 469 } | |
| 470 } | |
| 471 | |
| 472 if (entries.length == 0) { | |
| 473 window.alert("Loading log file failed."); | |
| 474 return; | |
| 475 } | |
| 476 | |
| 477 this.deleteAllEvents(); | |
| 478 | |
| 479 this.setIsViewingLogFile(true); | |
| 480 | |
| 481 var validEntries = []; | |
| 482 for (var i = 0; i < entries.length; ++i) { | |
| 483 entries[i].wasPassivelyCaptured = true; | |
|
eroman
2011/01/12 05:57:34
any reason to initialize this to true rather than
mmenke
2011/01/13 03:45:29
I was just thinking the (P) emphasizes that we're
| |
| 484 entries[i].source.type = LogSourceType[entries[i].source.type] | |
| 485 entries[i].type = LogEventType[entries[i].type] | |
| 486 entries[i].phase = LogEventPhase[entries[i].phase]; | |
| 487 if (entries[i].source.type != undefined && | |
| 488 entries[i].type != undefined && | |
| 489 entries[i].phase != undefined) { | |
| 490 // TODO(mmenke): Do something reasonable when the event type isn't | |
| 491 // found, which could happen when event types are | |
| 492 // removed or added between versions. Could also happen | |
| 493 // with source types, but less likely. | |
| 494 validEntries.push(entries[i]); | |
| 495 } | |
| 496 } | |
| 497 | |
| 498 this.numPassivelyCapturedEvents_ = validEntries.length; | |
| 499 this.addLogEntries(validEntries); | |
| 500 | |
| 501 var numInvalidEntries = entries.length - validEntries.length; | |
| 502 if (numInvalidEntries > 0) { | |
| 503 window.alert(numInvalidEntries.toString() + | |
| 504 " entries could not be loaded, possibly due to version differences."); | |
| 505 } | |
| 506 } | |
| 507 | |
| 442 //------------------------------------------------------------------------------ | 508 //------------------------------------------------------------------------------ |
| 443 | 509 |
| 444 /** | 510 /** |
| 511 * Sets the |categoryTabSwitcher_| of BrowserBridge. Since views depend on | |
| 512 * g_browser being initialized, have to have a BrowserBridge prior to tab | |
| 513 * construction. | |
| 514 */ | |
| 515 BrowserBridge.prototype.setTabSwitcher = function(categoryTabSwitcher) { | |
| 516 this.categoryTabSwitcher_ = categoryTabSwitcher; | |
| 517 }; | |
| 518 | |
| 519 /** | |
| 445 * Adds a listener of log entries. |observer| will be called back when new log | 520 * Adds a listener of log entries. |observer| will be called back when new log |
| 446 * data arrives, through: | 521 * data arrives, through: |
| 447 * | 522 * |
| 448 * observer.onLogEntryAdded(logEntry) | 523 * observer.onLogEntryAdded(logEntry) |
| 449 */ | 524 */ |
| 450 BrowserBridge.prototype.addLogObserver = function(observer) { | 525 BrowserBridge.prototype.addLogObserver = function(observer) { |
| 451 this.logObservers_.push(observer); | 526 this.logObservers_.push(observer); |
| 452 }; | 527 }; |
| 453 | 528 |
| 454 /** | 529 /** |
| (...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 585 | 660 |
| 586 /** | 661 /** |
| 587 * Returns the number of events that were captured passively by the | 662 * Returns the number of events that were captured passively by the |
| 588 * browser prior to when the net-internals page was started. | 663 * browser prior to when the net-internals page was started. |
| 589 */ | 664 */ |
| 590 BrowserBridge.prototype.getNumPassivelyCapturedEvents = function() { | 665 BrowserBridge.prototype.getNumPassivelyCapturedEvents = function() { |
| 591 return this.numPassivelyCapturedEvents_; | 666 return this.numPassivelyCapturedEvents_; |
| 592 }; | 667 }; |
| 593 | 668 |
| 594 /** | 669 /** |
| 670 * Sends each entry to all log observers, and updates |capturedEvents_|. | |
| 671 * Also assigns unique ids to log entries without a source. | |
| 672 */ | |
| 673 BrowserBridge.prototype.addLogEntries = function(logEntries) { | |
| 674 for (var e = 0; e < logEntries.length; ++e) { | |
| 675 var logEntry = logEntries[e]; | |
| 676 | |
| 677 // Assign unique ID, if needed. | |
| 678 if (logEntry.source.id == 0) { | |
| 679 logEntry.source.id = this.nextSourcelessEventId_; | |
| 680 --this.nextSourcelessEventId_; | |
| 681 } | |
| 682 this.capturedEvents_.push(logEntry); | |
| 683 for (var i = 0; i < this.logObservers_.length; ++i) | |
| 684 this.logObservers_[i].onLogEntryAdded(logEntry); | |
| 685 } | |
| 686 }; | |
| 687 | |
| 688 /** | |
| 595 * Deletes captured events with source IDs in |sourceIds|. | 689 * Deletes captured events with source IDs in |sourceIds|. |
| 596 */ | 690 */ |
| 597 BrowserBridge.prototype.deleteEventsBySourceId = function(sourceIds) { | 691 BrowserBridge.prototype.deleteEventsBySourceId = function(sourceIds) { |
| 598 var sourceIdDict = {}; | 692 var sourceIdDict = {}; |
| 599 for (var i = 0; i < sourceIds.length; i++) | 693 for (var i = 0; i < sourceIds.length; i++) |
| 600 sourceIdDict[sourceIds[i]] = true; | 694 sourceIdDict[sourceIds[i]] = true; |
| 601 | 695 |
| 602 var newEventList = []; | 696 var newEventList = []; |
| 603 for (var i = 0; i < this.capturedEvents_.length; ++i) { | 697 for (var i = 0; i < this.capturedEvents_.length; ++i) { |
| 604 var id = this.capturedEvents_[i].source.id; | 698 var id = this.capturedEvents_[i].source.id; |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 619 * Deletes all captured events. | 713 * Deletes all captured events. |
| 620 */ | 714 */ |
| 621 BrowserBridge.prototype.deleteAllEvents = function() { | 715 BrowserBridge.prototype.deleteAllEvents = function() { |
| 622 this.capturedEvents_ = []; | 716 this.capturedEvents_ = []; |
| 623 this.numPassivelyCapturedEvents_ = 0; | 717 this.numPassivelyCapturedEvents_ = 0; |
| 624 for (var i = 0; i < this.logObservers_.length; ++i) | 718 for (var i = 0; i < this.logObservers_.length; ++i) |
| 625 this.logObservers_[i].onAllLogEntriesDeleted(); | 719 this.logObservers_[i].onAllLogEntriesDeleted(); |
| 626 }; | 720 }; |
| 627 | 721 |
| 628 /** | 722 /** |
| 629 * If |force| is true, calls all startUpdate functions. Otherwise, just | 723 * Informs log observers whether or not future events will be from a log file. |
| 630 * runs updates with active observers. | 724 * Hides all tabs except the events and data tabs when viewing a log file, shows |
| 725 * them all otherwise. | |
| 726 */ | |
| 727 BrowserBridge.prototype.setIsViewingLogFile = function(isViewingLogFile) { | |
| 728 this.isViewingLogFile_ = isViewingLogFile; | |
| 729 var tabIds = this.categoryTabSwitcher_.getAllTabIds(); | |
| 730 | |
| 731 for (var i = 0; i < this.logObservers_.length; ++i) | |
| 732 this.logObservers_[i].onSetIsViewingLogFile(isViewingLogFile); | |
| 733 | |
| 734 // Shows/hides tabs not used when viewing a log file. | |
| 735 for (var i = 0; i < tabIds.length; ++i) { | |
| 736 if (tabIds[i] == 'eventsTab' || tabIds[i] == 'dataTab') | |
| 737 continue; | |
| 738 this.categoryTabSwitcher_.showTabHandleNode(tabIds[i], !isViewingLogFile); | |
| 739 } | |
| 740 | |
| 741 if (isViewingLogFile) { | |
| 742 var activeTab = this.categoryTabSwitcher_.findActiveTab(); | |
| 743 if (activeTab.id != 'eventsTab') | |
| 744 this.categoryTabSwitcher_.switchToTab('dataTab', null); | |
| 745 } | |
| 746 }; | |
| 747 | |
| 748 /** | |
| 749 * If |force| is true, calls all startUpdate functions. Otherwise, just runs | |
| 750 * updates with active observers. | |
| 631 */ | 751 */ |
| 632 BrowserBridge.prototype.checkForUpdatedInfo = function(force) { | 752 BrowserBridge.prototype.checkForUpdatedInfo = function(force) { |
| 633 for (name in this.pollableDataHelpers_) { | 753 for (name in this.pollableDataHelpers_) { |
| 634 var helper = this.pollableDataHelpers_[name]; | 754 var helper = this.pollableDataHelpers_[name]; |
| 635 if (force || helper.hasActiveObserver()) | 755 if (force || helper.hasActiveObserver()) |
| 636 helper.startUpdate(); | 756 helper.startUpdate(); |
| 637 } | 757 } |
| 638 }; | 758 }; |
| 639 | 759 |
| 640 /** | 760 /** |
| (...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 755 return true; | 875 return true; |
| 756 }; | 876 }; |
| 757 | 877 |
| 758 UpdateAllObserver.prototype.onDataReceived_ = function(helper, name, data) { | 878 UpdateAllObserver.prototype.onDataReceived_ = function(helper, name, data) { |
| 759 helper.removeObserver(this); | 879 helper.removeObserver(this); |
| 760 --this.observingCount_; | 880 --this.observingCount_; |
| 761 this.updatedData_[name] = data; | 881 this.updatedData_[name] = data; |
| 762 if (this.observingCount_ == 0) | 882 if (this.observingCount_ == 0) |
| 763 this.callback_(this.updatedData_); | 883 this.callback_(this.updatedData_); |
| 764 }; | 884 }; |
| OLD | NEW |