diff options
author | eroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-23 17:47:49 +0000 |
---|---|---|
committer | eroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-23 17:47:49 +0000 |
commit | fd9c0d9df665355c14d3c0df6f3c5b0b2059c129 (patch) | |
tree | 1d2c2cb1cd339587089ae0f913864ef163cf2778 /chrome/browser | |
parent | 3773020c131d144f0e0e1d849227b34d7c4cf6e1 (diff) | |
download | chromium_src-fd9c0d9df665355c14d3c0df6f3c5b0b2059c129.zip chromium_src-fd9c0d9df665355c14d3c0df6f3c5b0b2059c129.tar.gz chromium_src-fd9c0d9df665355c14d3c0df6f3c5b0b2059c129.tar.bz2 |
Add an initial implementation of net-internals inspector in javascript.
BUG=37421
Review URL: http://codereview.chromium.org/1088007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@42357 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/browser_resources.grd | 1 | ||||
-rw-r--r-- | chrome/browser/dom_ui/net_internals_ui.cc | 223 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/detailsview.js | 93 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/index.html | 87 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/layoutmanager.js | 151 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/loggrouper.js | 95 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/logviewpainter.js | 117 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/main.css | 134 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/main.js | 90 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/requestsview.js | 165 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/sourceentry.js | 182 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/timelineviewpainter.js | 20 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/util.js | 88 |
13 files changed, 1366 insertions, 80 deletions
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd index ce10283..2562a2c 100644 --- a/chrome/browser/browser_resources.grd +++ b/chrome/browser/browser_resources.grd @@ -65,7 +65,6 @@ without changes to the corresponding grd file. fbt1 --> <include name="IDR_NOTIFICATION_ICON_HTML" file="resources\notification_icon.html" type="BINDATA" /> <include name="IDR_NOTIFICATION_2LINE_HTML" file="resources\notification_2line.html" type="BINDATA" /> <include name="IDR_NOTIFICATION_1LINE_HTML" file="resources\notification_1line.html" type="BINDATA" /> - <include name="IDR_NET_INTERNALS_HTML" file="resources\net_internals\index.html" type="BINDATA" /> </includes> </release> </grit> diff --git a/chrome/browser/dom_ui/net_internals_ui.cc b/chrome/browser/dom_ui/net_internals_ui.cc index 5d7567c..c04b30a 100644 --- a/chrome/browser/dom_ui/net_internals_ui.cc +++ b/chrome/browser/dom_ui/net_internals_ui.cc @@ -5,17 +5,25 @@ #include "chrome/browser/dom_ui/net_internals_ui.h" #include "app/resource_bundle.h" +#include "base/file_util.h" +#include "base/path_service.h" #include "base/singleton.h" #include "base/string_piece.h" +#include "base/string_util.h" #include "base/values.h" +#include "chrome/browser/browser_process.h" #include "chrome/browser/dom_ui/chrome_url_data_manager.h" #include "chrome/browser/chrome_thread.h" +#include "chrome/browser/io_thread.h" +#include "chrome/browser/net/chrome_net_log.h" +#include "chrome/common/chrome_paths.h" #include "chrome/common/url_constants.h" -#include "grit/browser_resources.h" - namespace { +// TODO(eroman): Bootstrap the net-internals page using the passively logged +// data. + class NetInternalsHTMLSource : public ChromeURLDataManager::DataSource { public: NetInternalsHTMLSource(); @@ -72,7 +80,8 @@ class NetInternalsMessageHandler class NetInternalsMessageHandler::IOThreadImpl : public base::RefCountedThreadSafe< NetInternalsMessageHandler::IOThreadImpl, - ChromeThread::DeleteOnUIThread> { + ChromeThread::DeleteOnUIThread>, + public ChromeNetLog::Observer { public: // Type for methods that can be used as MessageHandler callbacks. typedef void (IOThreadImpl::*MessageHandler)(const Value*); @@ -80,8 +89,11 @@ class NetInternalsMessageHandler::IOThreadImpl // Creates a proxy for |handler| that will live on the IO thread. // |handler| is a weak pointer, since it is possible for the DOMMessageHandler // to be deleted on the UI thread while we were executing on the IO thread. - explicit IOThreadImpl( - const base::WeakPtr<NetInternalsMessageHandler>& handler); + // |io_thread| is the global IOThread (it is passed in as an argument since + // we need to grab it from the UI thread). + IOThreadImpl( + const base::WeakPtr<NetInternalsMessageHandler>& handler, + IOThread* io_thread); ~IOThreadImpl(); @@ -91,9 +103,6 @@ class NetInternalsMessageHandler::IOThreadImpl // on the IO thread. DOMUI::MessageCallback* CreateCallback(MessageHandler method); - // Called once the DOMUI has attached to the renderer, on the IO thread. - void Attach(); - // Called once the DOMUI has been deleted (i.e. renderer went away), on the // IO thread. void Detach(); @@ -102,8 +111,12 @@ class NetInternalsMessageHandler::IOThreadImpl // Javascript message handlers: //-------------------------------- - // TODO(eroman): This is temporary! - void OnTestMessage(const Value* value); + // This message is called after the webpage's onloaded handler has fired. + // it indicates that the renderer is ready to start receiving captured data. + void OnRendererReady(const Value* value); + + // ChromeNetLog::Observer implementation: + virtual void OnAddEntry(const net::NetLog::Entry& entry); private: class CallbackHelper; @@ -120,6 +133,12 @@ class NetInternalsMessageHandler::IOThreadImpl // Pointer to the UI-thread message handler. Only access this from // the UI thread. base::WeakPtr<NetInternalsMessageHandler> handler_; + + // The global IOThread, which contains the global NetLog to observer. + IOThread* io_thread_; + + // True if we have attached an observer to the NetLog already. + bool is_observing_log_; friend class base::RefCountedThreadSafe<IOThreadImpl>; }; @@ -169,19 +188,30 @@ NetInternalsHTMLSource::NetInternalsHTMLSource() void NetInternalsHTMLSource::StartDataRequest(const std::string& path, bool is_off_the_record, int request_id) { - // Serve up the HTML contained in the resource bundle. - base::StringPiece html( - ResourceBundle::GetSharedInstance().GetRawDataResource( - IDR_NET_INTERNALS_HTML)); + // The provided |path| identifies a file in resources/net_internals/. + std::string data_string; + FilePath file_path; + PathService::Get(chrome::DIR_NET_INTERNALS, &file_path); + std::string filename = path.empty() ? "index.html" : path; + file_path = file_path.AppendASCII(filename); + + if (!file_util::ReadFileToString(file_path, &data_string)) { + LOG(WARNING) << "Could not read resource: " << file_path.value(); + data_string = StringPrintf( + "Failed to read file RESOURCES/net_internals/%s", + filename.c_str()); + } - scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes); - html_bytes->data.resize(html.size()); - std::copy(html.begin(), html.end(), html_bytes->data.begin()); + scoped_refptr<RefCountedBytes> bytes(new RefCountedBytes); + bytes->data.resize(data_string.size()); + std::copy(data_string.begin(), data_string.end(), bytes->data.begin()); - SendResponse(request_id, html_bytes); + SendResponse(request_id, bytes); } std::string NetInternalsHTMLSource::GetMimeType(const std::string&) const { + // TODO(eroman): This is incorrect -- some of the subresources may be + // css/javascript. return "text/html"; } @@ -203,23 +233,16 @@ NetInternalsMessageHandler::~NetInternalsMessageHandler() { DOMMessageHandler* NetInternalsMessageHandler::Attach(DOMUI* dom_ui) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); - proxy_ = new IOThreadImpl(this->AsWeakPtr()); - + proxy_ = new IOThreadImpl(this->AsWeakPtr(), g_browser_process->io_thread()); DOMMessageHandler* result = DOMMessageHandler::Attach(dom_ui); - - // Notify the handler on the IO thread that a renderer is attached. - ChromeThread::PostTask(ChromeThread::IO, FROM_HERE, - NewRunnableMethod(proxy_.get(), &IOThreadImpl::Attach)); - return result; } void NetInternalsMessageHandler::RegisterMessages() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); - // TODO(eroman): Register message handlers here. - dom_ui_->RegisterMessageCallback("testMessage", - proxy_->CreateCallback(&IOThreadImpl::OnTestMessage)); + dom_ui_->RegisterMessageCallback("notifyReady", + proxy_->CreateCallback(&IOThreadImpl::OnRendererReady)); } void NetInternalsMessageHandler::CallJavascriptFunction( @@ -236,8 +259,11 @@ void NetInternalsMessageHandler::CallJavascriptFunction( //////////////////////////////////////////////////////////////////////////////// NetInternalsMessageHandler::IOThreadImpl::IOThreadImpl( - const base::WeakPtr<NetInternalsMessageHandler>& handler) - : handler_(handler) { + const base::WeakPtr<NetInternalsMessageHandler>& handler, + IOThread* io_thread) + : handler_(handler), + io_thread_(io_thread), + is_observing_log_(false) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); } @@ -252,38 +278,131 @@ NetInternalsMessageHandler::IOThreadImpl::CreateCallback( return new CallbackHelper(this, method); } -void NetInternalsMessageHandler::IOThreadImpl::Attach() { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - // TODO(eroman): Register with network stack to observe events. -} - void NetInternalsMessageHandler::IOThreadImpl::Detach() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - // TODO(eroman): Unregister with network stack to observe events. + // Unregister with network stack to observe events. + if (is_observing_log_) + io_thread_->globals()->net_log->RemoveObserver(this); } -void NetInternalsMessageHandler::IOThreadImpl::OnTestMessage( +void NetInternalsMessageHandler::IOThreadImpl::OnRendererReady( const Value* value) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + DCHECK(!is_observing_log_) << "notifyReady called twice"; + + // Register with network stack to observe events. + is_observing_log_ = true; + io_thread_->globals()->net_log->AddObserver(this); + + // Tell the javascript about the relationship between event type enums and + // their symbolic name. + { + std::vector<net::NetLog::EventType> event_types = + net::NetLog::GetAllEventTypes(); + + DictionaryValue* dict = new DictionaryValue(); - // TODO(eroman): This is just a temporary method, to see something in - // action. We expect to have been called with an array - // containing 1 string, and print it to the screen. - std::string str; - if (value && value->GetType() == Value::TYPE_LIST) { - const ListValue* list_value = static_cast<const ListValue*>(value); - Value* list_member; - if (list_value->Get(0, &list_member) && - list_member->GetType() == Value::TYPE_STRING) { - const StringValue* string_value = - static_cast<const StringValue*>(list_member); - string_value->GetAsString(&str); + for (size_t i = 0; i < event_types.size(); ++i) { + const char* name = net::NetLog::EventTypeToString(event_types[i]); + dict->SetInteger(ASCIIToWide(name), + static_cast<int>(event_types[i])); } + + CallJavascriptFunction(L"setLogEventTypeConstants", dict); + } + + // Tell the javascript about the relationship between event phase enums and + // their symbolic name. + { + DictionaryValue* dict = new DictionaryValue(); + + dict->SetInteger(L"PHASE_BEGIN", net::NetLog::PHASE_BEGIN); + dict->SetInteger(L"PHASE_END", net::NetLog::PHASE_END); + dict->SetInteger(L"PHASE_NONE", net::NetLog::PHASE_NONE); + + CallJavascriptFunction(L"setLogEventPhaseConstants", dict); + } + + // Tell the javascript about the relationship between source type enums and + // their symbolic name. + // TODO(eroman): Don't duplicate the values, it will never stay up to date! + { + DictionaryValue* dict = new DictionaryValue(); + + dict->SetInteger(L"NONE", net::NetLog::SOURCE_NONE); + dict->SetInteger(L"URL_REQUEST", net::NetLog::SOURCE_URL_REQUEST); + dict->SetInteger(L"SOCKET_STREAM", net::NetLog::SOURCE_SOCKET_STREAM); + dict->SetInteger(L"INIT_PROXY_RESOLVER", + net::NetLog::SOURCE_INIT_PROXY_RESOLVER); + dict->SetInteger(L"CONNECT_JOB", net::NetLog::SOURCE_CONNECT_JOB); + + CallJavascriptFunction(L"setLogSourceTypeConstants", dict); + } + + // Tell the javascript about the relationship between entry type enums and + // their symbolic name. + { + DictionaryValue* dict = new DictionaryValue(); + + dict->SetInteger(L"TYPE_EVENT", net::NetLog::Entry::TYPE_EVENT); + dict->SetInteger(L"TYPE_STRING", net::NetLog::Entry::TYPE_STRING); + dict->SetInteger(L"TYPE_ERROR_CODE", net::NetLog::Entry::TYPE_ERROR_CODE); + + CallJavascriptFunction(L"setLogEntryTypeConstants", dict); + } +} + +void NetInternalsMessageHandler::IOThreadImpl::OnAddEntry( + const net::NetLog::Entry& entry) { + DCHECK(is_observing_log_); + + // JSONify the NetLog::Entry. + // TODO(eroman): Need a better format for this. + DictionaryValue* entry_dict = new DictionaryValue(); + + // Set the entry type. + { + net::NetLog::Entry::Type entry_type = entry.type; + if (entry_type == net::NetLog::Entry::TYPE_STRING_LITERAL) + entry_type = net::NetLog::Entry::TYPE_STRING; + entry_dict->SetInteger(L"type", static_cast<int>(entry_type)); + } + + // Set the entry time. + entry_dict->SetInteger( + L"time", + static_cast<int>((entry.time - base::TimeTicks()).InMilliseconds())); + + // Set the entry source. + DictionaryValue* source_dict = new DictionaryValue(); + source_dict->SetInteger(L"id", entry.source.id); + source_dict->SetInteger(L"type", static_cast<int>(entry.source.type)); + entry_dict->Set(L"source", source_dict); + + // Set the event info (if it is an event entry). + if (entry.type == net::NetLog::Entry::TYPE_EVENT) { + DictionaryValue* event_dict = new DictionaryValue(); + event_dict->SetInteger(L"type", static_cast<int>(entry.event.type)); + event_dict->SetInteger(L"phase", static_cast<int>(entry.event.phase)); + entry_dict->Set(L"event", event_dict); + } + + // Add the string information (events my have a string too, due to current + // hacks). + if (entry.type == net::NetLog::Entry::TYPE_STRING || !entry.string.empty()) { + entry_dict->SetString(L"string", entry.string); + } + + // Treat string literals the same as strings. + if (entry.type == net::NetLog::Entry::TYPE_STRING_LITERAL) { + entry_dict->SetString(L"string", entry.literal); + } + + if (entry.type == net::NetLog::Entry::TYPE_ERROR_CODE) { + entry_dict->SetInteger(L"error_code", entry.error_code); } - CallJavascriptFunction( - L"log", - Value::CreateStringValue("Browser received testMessage: " + str)); + CallJavascriptFunction(L"onLogEntryAdded", entry_dict); } void NetInternalsMessageHandler::IOThreadImpl::DispatchToMessageHandler( diff --git a/chrome/browser/resources/net_internals/detailsview.js b/chrome/browser/resources/net_internals/detailsview.js new file mode 100644 index 0000000..271c19f --- /dev/null +++ b/chrome/browser/resources/net_internals/detailsview.js @@ -0,0 +1,93 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * The DetailsView handles the tabbed view that displays either the "log" or + * "timeline" view. This class keeps track of what the current view is, and + * invalidates the specific view each time the selected data has changed. + * + * @constructor + */ +function DetailsView(logTabHandleId, + timelineTabHandleId, + detailsTabContentId) { + // The DOM nodes that which contain the tab title. + this.tabHandles_ = {}; + + this.tabHandles_['timeline'] = document.getElementById(timelineTabHandleId); + this.tabHandles_['log'] = document.getElementById(logTabHandleId); + + // The DOM node that contains the currently active tab sheet. + this.contentArea_ = document.getElementById(detailsTabContentId); + + // Attach listeners to the "tab handles" so when you click them, it switches + // active view. + + var self = this; + + this.tabHandles_['timeline'].onclick = function() { + self.switchToView_('timeline'); + }; + + this.tabHandles_['log'].onclick = function() { + self.switchToView_('log'); + }; + + this.currentData_ = []; + + // Default to the log view. + this.switchToView_('log'); +}; + +// The delay between updates to repaint. +DetailsView.REPAINT_TIMEOUT_MS = 50; + +/** + * Switches to the tab with name |viewName|. (Either 'log' or 'timeline'. + */ +DetailsView.prototype.switchToView_ = function(viewName) { + if (this.currentView_) { + // Remove the selected styling on currently selected tab. + changeClassName(this.tabHandles_[this.currentView_], 'selected', false); + } + + this.currentView_ = viewName; + changeClassName(this.tabHandles_[this.currentView_], 'selected', true); + this.repaint_(); +}; + +/** + * Updates the data this view is using. + */ +DetailsView.prototype.setData = function(currentData) { + // Make a copy of the array (in case the caller mutates it), and sort it + // by the source ID. + this.currentData_ = DetailsView.createSortedCopy_(currentData); + + // Invalidate the view. + if (!this.outstandingRepaint_) { + this.outstandingRepaint_ = true; + window.setTimeout(this.repaint_.bind(this), + DetailsView.REPAINT_TIMEOUT_MS); + } +}; + +DetailsView.prototype.repaint_ = function() { + this.outstandingRepaint_ = false; + this.contentArea_.innerHTML = ''; + + if (this.currentView_ == 'log') { + PaintLogView(this.currentData_, this.contentArea_); + } else { + PaintTimelineView(this.currentData_, this.contentArea_); + } +}; + +DetailsView.createSortedCopy_ = function(origArray) { + var sortedArray = origArray.slice(0); + sortedArray.sort(function(a, b) { + return a.getSourceId() - b.getSourceId(); + }); + return sortedArray; +}; diff --git a/chrome/browser/resources/net_internals/index.html b/chrome/browser/resources/net_internals/index.html index 7aff02c..fab3304 100644 --- a/chrome/browser/resources/net_internals/index.html +++ b/chrome/browser/resources/net_internals/index.html @@ -1,32 +1,65 @@ <html> +<!-- +Copyright (c) 2010 The Chromium Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> <head> - <title>Under construction...</title> - <script> - -// TODO(eroman): This is all temporary... - -function sendTestMessageToBrowser() { - log("Sent message to browser"); - chrome.send('testMessage', [String((new Date()).toLocaleTimeString())]); -} - -function log(msg) { - var l = document.getElementById('log'); - l.appendChild(document.createTextNode(msg + "\n")); -} - - </script> + <link tyle="text/css" rel="stylesheet" href="main.css" /> + <script src="main.js"></script> + <script src="util.js"></script> + <script src="requestsview.js"></script> + <script src="detailsview.js"></script> + <script src="sourceentry.js"></script> + <script src="layoutmanager.js"></script> + <script src="timelineviewpainter.js"></script> + <script src="logviewpainter.js"></script> + <script src="loggrouper.js"></script> </head> - - - <body> - <p>This is a work in progress. See http://crbug.com/37421 for details.</p> - - <input onclick="sendTestMessageToBrowser()" - value="SendTestMessageToBrowser" - type=button /> - - <pre id=log></pre> - + <body onload="onLoaded()"> + <!-- Filter Box: This the top bar which contains the search box. --> + <div id=filterBox> + <table width=100% height=100%> + <tr> + <td width=1%>Filter:</td> + <td width=98%><input type="search" incremental id=filterInput /></td> + <td width=1% id=filterCount>(1 of 34)</td> + </tr> + </table> + </div> + <!-- Requests Box: This the panel on the left which lists the requests --> + <div id=requestsBox> + <table id=requestsListTable cellspacing=0 cellpadding=0> + <thead> + <tr> + <td><input type=checkbox id=selectAll /></td> + <td>ID</td> + <td>Source</td> + <td>URL</td> + </tr> + </thead> + <!-- Requests table body: This is where request rows go into --> + <tbody id=requestsListTableBody></tbody> + </table> + </div> + <!-- Action Box: This is a button bar along the bottom --> + <div id=actionBox> + <input type=button value="Stop capturing" onclick="alert('TODO')" /> + <input type=button value="Delete selected" id=deleteSelected /> + </div> + <!-- Splitter Box: This is a handle to resize the vertical divider --> + <div id=splitterBox></div> + <!-- Details box: This is the panel on the right which shows information --> + <div id=detailsBox> + <table class=tabSwitcher cellspacing=0> + <tr> + <th id=detailsLogTabHandle>Log</th> + <td class=tabSwitcherSpacer> </td> + <th id=detailsTimelineTabHandle>Timeline</th> + </tr> + </table> + <div class=tabSwitcherLine></div> + <div id=detailsTabArea></div> + </div> </body> </html> diff --git a/chrome/browser/resources/net_internals/layoutmanager.js b/chrome/browser/resources/net_internals/layoutmanager.js new file mode 100644 index 0000000..0491a63 --- /dev/null +++ b/chrome/browser/resources/net_internals/layoutmanager.js @@ -0,0 +1,151 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * The class LayoutManager implements a vertically split layout that takes up + * the whole window, and provides a draggable bar to resize the left and right + * panes. Its elements are layed out as follows: + * + * + * <<-- sizer -->> + * + * +----------------------++----------------+ + * | topbar || | + * +----------------------+| | + * | || | + * | || | + * | || | + * | || | + * | middlebox || rightbox | + * | || | + * | || | + * | || | + * | || | + * | || | + * +----------------------++ | + * | bottombar || | + * +----------------------++----------------+ + * + * The "topbar" and "bottombar" have a fixed height which is determined + * by their initial content height. The rest of the boxes fill to occupy the + * remaining space. + * + * The consumer provides DIVs for each of these regions, and the LayoutManager + * positions them correctly in the window. + * + * @constructor + */ +function LayoutManager(topbarId, + middleboxId, + bottombarId, + sizerId, + rightboxId) { + // Lookup the elements. + this.topbar_ = document.getElementById(topbarId); + this.middlebox_ = document.getElementById(middleboxId); + this.bottombar_ = document.getElementById(bottombarId); + this.sizer_ = document.getElementById(sizerId); + this.rightbox_ = document.getElementById(rightboxId); + + // Make all the elements absolutely positioned. + this.topbar_.style.position = "absolute"; + this.middlebox_.style.position = "absolute"; + this.bottombar_.style.position = "absolute"; + this.sizer_.style.position = "absolute"; + this.rightbox_.style.position = "absolute"; + + // Set the initial split position at 50%. + setNodeWidth(this.rightbox_, window.innerWidth / 2); + + // Setup the "sizer" so it can be dragged left/right to reposition the + // vertical split. + this.sizer_.addEventListener("mousedown", this.onDragSizerStart_.bind(this), + true); + + // Recalculate the layout whenever the window size changes. + window.addEventListener("resize", this.recalculateLayout_.bind(this), true); + + // Do the initial layout . + this.recalculateLayout_(); +} + +// Minimum width to size panels to, in pixels. +LayoutManager.MIN_PANEL_WIDTH = 50; + +/** + * Repositions all of the elements to fit the window. + */ +LayoutManager.prototype.recalculateLayout_ = function() { + // Calculate the horizontal split points. + var totalWidth = window.innerWidth; + var rightboxWidth = this.rightbox_.offsetWidth; + var sizerWidth = this.sizer_.offsetWidth; + var leftPaneWidth = totalWidth - (rightboxWidth + sizerWidth); + + // Don't let the left pane get too small. + if (leftPaneWidth < LayoutManager.MIN_PANEL_WIDTH) { + leftPaneWidth = LayoutManager.MIN_PANEL_WIDTH; + rightboxWidth = totalWidth - (sizerWidth + leftPaneWidth); + } + + // Calculate the vertical split points. + var totalHeight = window.innerHeight; + var topbarHeight = this.topbar_.offsetHeight; + var bottombarHeight = this.bottombar_.offsetHeight; + var middleboxHeight = totalHeight - (topbarHeight + bottombarHeight); + + // Position the boxes using calculated split points. + setNodePosition(this.topbar_, 0, 0, + leftPaneWidth, topbarHeight); + setNodePosition(this.middlebox_, 0, topbarHeight, + leftPaneWidth, + middleboxHeight); + setNodePosition(this.bottombar_, 0, (topbarHeight + middleboxHeight), + leftPaneWidth, bottombarHeight); + + setNodePosition(this.sizer_, leftPaneWidth, 0, + sizerWidth, totalHeight); + setNodePosition(this.rightbox_, leftPaneWidth + sizerWidth, 0, + rightboxWidth, totalHeight); +}; + +/** + * Called once we have clicked into the sizer. Starts capturing the mouse + * position to implement dragging. + */ +LayoutManager.prototype.onDragSizerStart_ = function(event) { + this.sizerMouseMoveListener_ = this.onDragSizer.bind(this); + this.sizerMouseUpListener_ = this.onDragSizerEnd.bind(this); + + window.addEventListener("mousemove", this.sizerMouseMoveListener_, true); + window.addEventListener("mouseup", this.sizerMouseUpListener_, true); + + event.preventDefault(); +}; + +/** + * Called when the mouse has moved after dragging started. + */ +LayoutManager.prototype.onDragSizer = function(event) { + var newWidth = window.innerWidth - event.pageX; + + // Avoid shrinking the right box too much. + newWidth = Math.max(newWidth, LayoutManager.MIN_PANEL_WIDTH); + + setNodeWidth(this.rightbox_, newWidth); + this.recalculateLayout_(); +}; + +/** + * Called once the mouse has been released, and the dragging is over. + */ +LayoutManager.prototype.onDragSizerEnd = function(event) { + window.removeEventListener("mousemove", this.sizerMouseMoveListener_, true); + window.removeEventListener("mouseup", this.sizerMouseUpListener_, true); + + this.sizerMouseMoveListener_ = null; + this.sizerMouseUpListener_ = null; + + event.preventDefault(); +}; diff --git a/chrome/browser/resources/net_internals/loggrouper.js b/chrome/browser/resources/net_internals/loggrouper.js new file mode 100644 index 0000000..a3cf7a5 --- /dev/null +++ b/chrome/browser/resources/net_internals/loggrouper.js @@ -0,0 +1,95 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * LogGroupEntry is a wrapper around log entries, which makes it easier to + * find the corresponding start/end of events. + * + * This is used internally by the log and timeline views to pretty print + * collections of log entries. + * + * @fileoverview + */ + +// TODO(eroman): document these methods! + +function LogGroupEntry(origEntry, index) { + this.orig = origEntry; + this.index = index; +} + +LogGroupEntry.prototype.isBegin = function() { + return this.orig.type == LogEntryType.TYPE_EVENT && + this.orig.event.phase == LogEventPhase.PHASE_BEGIN; +}; + +LogGroupEntry.prototype.isEnd = function() { + return this.orig.type == LogEntryType.TYPE_EVENT && + this.orig.event.phase == LogEventPhase.PHASE_END +}; + +LogGroupEntry.prototype.getDepth = function() { + var depth = 0; + var p = this.parentEntry; + while (p) { + depth += 1; + p = p.parentEntry; + } + return depth; +}; + +function findParentIndex(parentStack, eventType) { + for (var i = parentStack.length - 1; i >= 0; --i) { + if (parentStack[i].orig.event.type == eventType) + return i; + } + return -1; +} + +/** + * Returns a list of LogGroupEntrys. This basically wraps the original log + * entry, but makes it easier to find the start/end of the event. + */ +LogGroupEntry.createArrayFrom = function(origEntries) { + var groupedEntries = []; + + // Stack of enclosing PHASE_BEGIN elements. + var parentStack = []; + + for (var i = 0; i < origEntries.length; ++i) { + var origEntry = origEntries[i]; + + var groupEntry = new LogGroupEntry(origEntry, i); + groupedEntries.push(groupEntry); + + // If this is the end of an event, match it to the start. + if (groupEntry.isEnd()) { + // Walk up the parent stack to find the corresponding BEGIN for this END. + var parentIndex = + findParentIndex(parentStack, groupEntry.orig.event.type); + + if (parentIndex == -1) { + // Unmatched end. + } else { + groupEntry.begin = parentStack[parentIndex]; + + // Consider this as the terminator for all open BEGINs up until + // parentIndex. + for (var j = 0; j < parentStack.length - parentIndex; ++j) { + var p = parentStack.pop(); + p.end = groupEntry; + } + } + } + + // Inherit the current parent. + if (parentStack.length > 0) + groupEntry.parentEntry = parentStack[parentStack.length - 1]; + + if (groupEntry.isBegin()) + parentStack.push(groupEntry); + } + + return groupedEntries; +} diff --git a/chrome/browser/resources/net_internals/logviewpainter.js b/chrome/browser/resources/net_internals/logviewpainter.js new file mode 100644 index 0000000..8a068f7 --- /dev/null +++ b/chrome/browser/resources/net_internals/logviewpainter.js @@ -0,0 +1,117 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * TODO(eroman): This needs better presentation, and cleaner code. This + * implementation is more of a transitionary step as + * the old net-internals is replaced. + */ + +var PaintLogView; + +// Start of anonymous namespace. +(function() { + +PaintLogView = function(sourceEntries, node) { + for (var i = 0; i < sourceEntries.length; ++i) { + if (i != 0) + addNode(node, 'hr'); + addSourceEntry_(node, sourceEntries[i]); + } +} + +const INDENTATION_PX = 20; + +function addSourceEntry_(node, sourceEntry) { + var div = addNode(node, 'div'); + div.className = 'logSourceEntry'; + + var p = addNode(div, 'p'); + var nobr = addNode(p, 'nobr'); + + addTextNode(nobr, sourceEntry.getDescription()); + + var groupedEntries = LogGroupEntry.createArrayFrom( + sourceEntry.getLogEntries()); + + makeLoadLogTable_(div, groupedEntries); +} + +function makeLoadLogTable_(node, entries) { + var table = addNode(node, 'table'); + var tbody = addNode(node, 'tbody'); + + for (var i = 0; i < entries.length; ++i) { + var entry = entries[i]; + + // Avoid printing the END for a BEGIN that was immediately before. + if (entry.isEnd() && entry.begin && entry.begin.index == i - 1) { + continue; + } + + var tr = addNode(node, 'tr'); + + var timeLabelCell = addNode(tr, 'td'); + addTextNode(timeLabelCell, 't='); + + var timeCell = addNode(tr, 'td'); + timeCell.style.textAlign = 'right'; + timeCell.style.paddingRight = '5px'; + addTextNode(timeCell, entry.orig.time); + + var mainCell = addNode(tr, 'td'); + mainCell.style.paddingRight = '5px'; + var dtLabelCell = addNode(tr, 'td'); + var dtCell = addNode(tr, 'td'); + dtCell.style.textAlign = 'right'; + + mainCell.style.paddingLeft = (INDENTATION_PX * entry.getDepth()) + "px"; + + if (entry.orig.type == LogEntryType.TYPE_EVENT) { + addTextNode(mainCell, getTextForEvent(entry)); + + // Get the elapsed time. + if (entry.isBegin()) { + addTextNode(dtLabelCell, '[dt='); + + // Definite time. + if (entry.end) { + var dt = entry.end.orig.time - entry.orig.time; + addTextNode(dtCell, dt + ']'); + } else { + addTextNode(dtCell, '?]'); + } + } + } else { + mainCell.colSpan = '3'; + if (entry.orig.type == LogEntryType.TYPE_STRING) { + addTextNode(mainCell, entry.orig.string); + } else if (entry.orig.type == LogEntryType.TYPE_ERROR_CODE) { + // TODO(eroman): print symbolic name of error code. + addTextNode(mainCell, "Network error: " + entry.orig.error_code); + } else { + addTextNode(mainCell, "Unrecognized entry type: " + entry.orig.type); + } + } + } +} + +function getTextForEvent(entry) { + var text = ''; + + if (entry.isBegin()) { + text = '+' + text; + } else if (entry.isEnd()) { + text = '-' + text; + } else { + text = '.'; + } + + text += getKeyWithValue(LogEventType, entry.orig.event.type); + return text; +} + +// End of anonymous namespace. +})(); + diff --git a/chrome/browser/resources/net_internals/main.css b/chrome/browser/resources/net_internals/main.css new file mode 100644 index 0000000..e6d6437 --- /dev/null +++ b/chrome/browser/resources/net_internals/main.css @@ -0,0 +1,134 @@ +/* +Copyright (c) 2010 The Chromium Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +*/ + +* { + -webkit-box-sizing: border-box; +} +body { + font-family: sans-serif; +} + +#filterBox { + background: #efefef; + padding: 5px; + border-bottom: 1px solid gray; + overflow: hidden; +} + +#filterBox * { + white-space: nowrap; + font-family: sans-serif; + font-size: 12px; +} + +#filterBox input { + width: 100%; +} + +#actionBox { + background: #efefef; + white-space: nowrap; + border-top: 1px solid gray; + overflow: hidden; +} + +#requestsBox { + overflow-x: hidden; + overflow-y: auto +} + +#detailsBox { + overflow: auto; +} + +#splitterBox { + background: #bfbfbf; + border-left: 1px inset black; + border-right: 1px solid black; + position:absolute; + width: 8px; + cursor: col-resize; + user-select: none; +} + +#requestsListTable { + cursor: pointer; +} + +#requestsListTable thead td { + text-align: left; + font-weight: bold; + background: rgb(229, 236, 249); +} + +#requestsListTable td { + padding: 3px; + border-left: 1px solid #afafaf; + border-bottom: 1px solid #afafaf; + text-overflow: ellipsis; + font-size: 12px; + white-space: nowrap; +} + +#requestsListTableBody .mouseover { + background: rgb(244,244,255); +} + +#requestsListTableBody .selected, +#requestsListTableBody .mouseover .selected { + background: #C3D9FF; +} + +#requestsListTableBody .source_CONNECT_JOB { + color: blue; +} + +#requestsListTableBody .source_INIT_PROXY_RESOLVER { + color: orange; +} + +.tabSwitcher { + margin-top: 10px; + margin-left: 10px; +} + +.tabSwitcher th { + background: rgb(229,236,249); + cursor: pointer; + background-clip: border-box; + border-top-left-radius: 5px 5px; + border-top-right-radius: 5px 5px; + padding-left: 4px; + padding-top: 4px; + padding-right: 4px; + font-size: 12px; + margin-left: 30px; +} + +.tabSwitcher th.selected, .tabSwitcherLine { + background: rgb(195,217,255); +} + +.tabSwitcherLine { + height: 10px; +} + +#detailsTabArea { + margin-top: 10px; +} + +.logSourceEntry { + margin: 5px; +} + +.logSourceEntry * p { + font-weight: bold; + font-size: 12px; +} + +.logSourceEntry * td { + font-size: 10px; +} diff --git a/chrome/browser/resources/net_internals/main.js b/chrome/browser/resources/net_internals/main.js new file mode 100644 index 0000000..b23d71f --- /dev/null +++ b/chrome/browser/resources/net_internals/main.js @@ -0,0 +1,90 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * Dictionary of constants (initialized by browser). + */ +var LogEntryType = null; +var LogEventType = null; +var LogEventPhase = null; +var LogSourceType = null; + +/** + * Main entry point. called once the page has loaded. + */ +function onLoaded() { + // Layout the various DIVs in a vertically split fashion. + new LayoutManager("filterBox", + "requestsBox", + "actionBox", + "splitterBox", + "detailsBox"); + + // Create the view which displays information on the current selection. + var detailsView = new DetailsView("detailsLogTabHandle", + "detailsTimelineTabHandle", + "detailsTabArea"); + + // Create the view which displays requests lists, and lets you select, filter + // and delete them. + new RequestsView('requestsListTableBody', + 'filterInput', + 'filterCount', + 'deleteSelected', + 'selectAll', + detailsView); + + // Tell the browser that we are ready to start receiving log events. + notifyApplicationReady(); +} + +//------------------------------------------------------------------------------ +// Messages sent to the browser +//------------------------------------------------------------------------------ + +function notifyApplicationReady() { + chrome.send('notifyReady'); +} + +//------------------------------------------------------------------------------ +// Messages received from the browser +//------------------------------------------------------------------------------ + +function onLogEntryAdded(logEntry) { + LogDataProvider.broadcast(logEntry); +} + +function setLogEventTypeConstants(constantsMap) { + LogEventType = constantsMap; +} + +function setLogEventPhaseConstants(constantsMap) { + LogEventPhase = constantsMap; +} + +function setLogSourceTypeConstants(constantsMap) { + LogSourceType = constantsMap; +} + +function setLogEntryTypeConstants(constantsMap) { + LogEntryType = constantsMap; +} + +//------------------------------------------------------------------------------ +// LogDataProvider +//------------------------------------------------------------------------------ + +var LogDataProvider = {} + +LogDataProvider.observers_ = []; + +LogDataProvider.broadcast = function(logEntry) { + for (var i = 0; i < this.observers_.length; ++i) { + this.observers_[i].onLogEntryAdded(logEntry); + } +}; + +LogDataProvider.addObserver = function(observer) { + this.observers_.push(observer); +}; diff --git a/chrome/browser/resources/net_internals/requestsview.js b/chrome/browser/resources/net_internals/requestsview.js new file mode 100644 index 0000000..40707a4 --- /dev/null +++ b/chrome/browser/resources/net_internals/requestsview.js @@ -0,0 +1,165 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * RequestsView is the class which glues together the different components to + * form the primary UI: + * + * - The search filter + * - The requests table + * - The details panel + * - The action bar + * + * @constructor + */ +function RequestsView(tableBodyId, filterInputId, filterCountId, + deleteSelectedId, selectAllId, detailsView) { + this.sourceIdToEntryMap_ = {}; + this.currentSelectedSources_ = []; + + LogDataProvider.addObserver(this); + + this.tableBody_ = document.getElementById(tableBodyId); + this.detailsView_ = detailsView; + + this.filterInput_ = document.getElementById(filterInputId); + this.filterCount_ = document.getElementById(filterCountId); + + this.filterInput_.addEventListener("search", + this.onFilterTextChanged_.bind(this), true); + + document.getElementById(deleteSelectedId).onclick = + this.deleteSelected_.bind(this); + + document.getElementById(selectAllId).addEventListener( + 'click', this.selectAll_.bind(this), true); + + this.currentFilter_ = ''; + this.numPrefilter_ = 0; + this.numPostfilter_ = 0; + + this.invalidateFilterCounter_(); + this.invalidateDetailsView_(); +} + +// How soon after updating the filter list the counter should be updated. +RequestsView.REPAINT_FILTER_COUNTER_TIMEOUT_MS = 0; + +RequestsView.prototype.onFilterTextChanged_ = function() { + this.setFilter_(this.filterInput_.value); +}; + +RequestsView.prototype.setFilter_ = function(filterText) { + this.currentFilter_ = filterText; + + // Iterate through all of the rows and see if they match the filter. + for (var id in this.sourceIdToEntryMap_) { + var entry = this.sourceIdToEntryMap_[id]; + entry.setIsMatchedByFilter(entry.matchesFilter(this.currentFilter_)); + } +}; + +RequestsView.prototype.onLogEntryAdded = function(logEntry) { + // Lookup the source. + var sourceEntry = this.sourceIdToEntryMap_[logEntry.source.id]; + + if (!sourceEntry) { + sourceEntry = new SourceEntry(this); + this.sourceIdToEntryMap_[logEntry.source.id] = sourceEntry; + this.incrementPrefilterCount(1); + } + + sourceEntry.update(logEntry); + + if (sourceEntry.isSelected()) + this.invalidateDetailsView_(); +}; + +RequestsView.prototype.incrementPrefilterCount = function(offset) { + this.numPrefilter_ += offset; + this.invalidateFilterCounter_(); +}; + +RequestsView.prototype.incrementPostfilterCount = function(offset) { + this.numPostfilter_ += offset; + this.invalidateFilterCounter_(); +}; + +RequestsView.prototype.onSelectionChanged = function() { + this.invalidateDetailsView_(); +}; + +RequestsView.prototype.clearSelection = function() { + var prevSelection = this.currentSelectedSources_; + this.currentSelectedSources_ = []; + + // Unselect everything that is currently selected. + for (var i = 0; i < prevSelection.length; ++i) { + prevSelection[i].setSelected(false); + } + + this.onSelectionChanged(); +}; + +RequestsView.prototype.deleteSelected_ = function() { + var prevSelection = this.currentSelectedSources_; + this.currentSelectedSources_ = []; + + for (var i = 0; i < prevSelection.length; ++i) { + var entry = prevSelection[i]; + entry.remove(); + delete this.sourceIdToEntryMap_[entry.getSourceId()]; + this.incrementPrefilterCount(-1); + } +}; + +RequestsView.prototype.selectAll_ = function(event) { + for (var id in this.sourceIdToEntryMap_) { + var entry = this.sourceIdToEntryMap_[id]; + if (entry.isMatchedByFilter()) { + entry.setSelected(true); + } + } + event.preventDefault(); +}; + +RequestsView.prototype.modifySelectionArray = function( + sourceEntry, addToSelection) { + // Find the index for |sourceEntry| in the current selection list. + var index = -1; + for (var i = 0; i < this.currentSelectedSources_.length; ++i) { + if (this.currentSelectedSources_[i] == sourceEntry) { + index = i; + break; + } + } + + if (index != -1 && !addToSelection) { + // Remove from the selection. + this.currentSelectedSources_.splice(index, 1); + } + + if (index == -1 && addToSelection) { + this.currentSelectedSources_.push(sourceEntry); + } +} + +RequestsView.prototype.invalidateDetailsView_ = function() { + this.detailsView_.setData(this.currentSelectedSources_); +}; + +RequestsView.prototype.invalidateFilterCounter_ = function() { + if (!this.outstandingRepaintFilterCounter_) { + this.outstandingRepaintFilterCounter_ = true; + window.setTimeout(this.repaintFilterCounter_.bind(this), + RequestsView.REPAINT_FILTER_COUNTER_TIMEOUT_MS); + } +}; + +RequestsView.prototype.repaintFilterCounter_ = function() { + this.outstandingRepaintFilterCounter_ = false; + this.filterCount_.innerHTML = ''; + addTextNode(this.filterCount_, + this.numPostfilter_ + " of " + this.numPrefilter_); +}; diff --git a/chrome/browser/resources/net_internals/sourceentry.js b/chrome/browser/resources/net_internals/sourceentry.js new file mode 100644 index 0000000..72d3a44 --- /dev/null +++ b/chrome/browser/resources/net_internals/sourceentry.js @@ -0,0 +1,182 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * Each row in the filtered items list is backed by a SourceEntry. This + * instance contains all of the data pertaining to that row, and notifies + * its parent view (the RequestsView) whenever its data changes. + * + * @constructor + */ +function SourceEntry(parentView) { + this.entries_ = []; + this.parentView_ = parentView; + this.isSelected_ = false; + this.isMatchedByFilter_ = false; +} + +SourceEntry.prototype.isSelected = function() { + return this.isSelected_; +}; + +SourceEntry.prototype.setSelectedStyles = function(isSelected) { + changeClassName(this.row_, 'selected', isSelected); + this.getSelectionCheckbox().checked = isSelected; +}; + +SourceEntry.prototype.setMouseoverStyle = function(isMouseOver) { + changeClassName(this.row_, 'mouseover', isMouseOver); +}; + +SourceEntry.prototype.setIsMatchedByFilter = function(isMatchedByFilter) { + if (this.isMatchedByFilter() == isMatchedByFilter) + return; // No change. + + this.isMatchedByFilter_ = isMatchedByFilter; + + this.setFilterStyles(isMatchedByFilter); + + if (isMatchedByFilter) { + this.parentView_.incrementPostfilterCount(1); + } else { + this.parentView_.incrementPostfilterCount(-1); + // If we are filtering an entry away, make sure it is no longer + // part of the selection. + this.setSelected(false); + } +}; + +SourceEntry.prototype.isMatchedByFilter = function() { + return this.isMatchedByFilter_; +}; + +SourceEntry.prototype.setFilterStyles = function(isMatchedByFilter) { + // Hide rows which have been filtered away. + if (isMatchedByFilter) { + this.row_.style.display = ''; + } else { + this.row_.style.display = 'none'; + } +}; + +SourceEntry.prototype.update = function(logEntry) { + this.entries_.push(logEntry); + + if (this.entries_.length == 1) { + this.createRow_(); + + // Only apply the filter during the first update. + // TODO(eroman): once filters use other data, apply it on each update. + var matchesFilter = this.matchesFilter(this.parentView_.currentFilter_); + this.setIsMatchedByFilter(matchesFilter); + } +}; + +SourceEntry.prototype.onCheckboxToggled_ = function() { + this.setSelected(this.getSelectionCheckbox().checked); +}; + +SourceEntry.prototype.matchesFilter = function(filterText) { + // TODO(eroman): Support more advanced filter syntax. + if (filterText == '') + return true; + + var filterText = filterText.toLowerCase(); + + return this.getDescription().toLowerCase().indexOf(filterText) != -1 || + this.getSourceTypeString().toLowerCase().indexOf(filterText) != -1; +}; + +SourceEntry.prototype.setSelected = function(isSelected) { + if (isSelected == this.isSelected()) + return; + + this.isSelected_ = isSelected; + + this.setSelectedStyles(isSelected); + this.parentView_.modifySelectionArray(this, isSelected); + this.parentView_.onSelectionChanged(); +}; + +SourceEntry.prototype.onClicked_ = function() { + this.parentView_.clearSelection(); + this.setSelected(true); +}; + +SourceEntry.prototype.onMouseover_ = function() { + this.setMouseoverStyle(true); +}; + +SourceEntry.prototype.onMouseout_ = function() { + this.setMouseoverStyle(false); +}; + +SourceEntry.prototype.createRow_ = function() { + // Create a row. + var tr = addNode(this.parentView_.tableBody_, 'tr'); + tr.style.display = 'none'; + this.row_ = tr; + + var selectionCol = addNode(tr, 'td'); + var checkbox = addNode(selectionCol, 'input'); + checkbox.type = 'checkbox'; + + var idCell = addNode(tr, 'td'); + var typeCell = addNode(tr, 'td'); + var descriptionCell = addNode(tr, 'td'); + + // Connect listeners. + checkbox.onchange = this.onCheckboxToggled_.bind(this); + + var onclick = this.onClicked_.bind(this); + idCell.onclick = onclick; + typeCell.onclick = onclick; + descriptionCell.onclick = onclick; + + tr.onmouseover = this.onMouseover_.bind(this); + tr.onmouseout = this.onMouseout_.bind(this); + + // Set the cell values to match this source's data. + addTextNode(idCell, this.getSourceId()); + var sourceTypeString = this.getSourceTypeString(); + addTextNode(typeCell, sourceTypeString); + addTextNode(descriptionCell, this.getDescription()); + + // Add a CSS classname specific to this source type (so CSS can specify + // different stylings for different types). + changeClassName(this.row_, "source_" + sourceTypeString, true); +}; + +SourceEntry.prototype.getDescription = function() { + var e = this.entries_[0]; + if (e.type == LogEntryType.TYPE_EVENT && + e.event.phase == LogEventPhase.PHASE_BEGIN && + e.string != undefined) { + return e.string; // The URL / hostname / whatever. + } + return ''; +}; + +SourceEntry.prototype.getLogEntries = function() { + return this.entries_; +}; + +SourceEntry.prototype.getSourceTypeString = function() { + return getKeyWithValue(LogSourceType, this.entries_[0].source.type); +}; + +SourceEntry.prototype.getSelectionCheckbox = function() { + return this.row_.childNodes[0].firstChild; +}; + +SourceEntry.prototype.getSourceId = function() { + return this.entries_[0].source.id; +}; + +SourceEntry.prototype.remove = function() { + this.setSelected(false); + this.setIsMatchedByFilter(false); + this.row_.parentNode.removeChild(this.row_); +}; + diff --git a/chrome/browser/resources/net_internals/timelineviewpainter.js b/chrome/browser/resources/net_internals/timelineviewpainter.js new file mode 100644 index 0000000..2c7fb046 --- /dev/null +++ b/chrome/browser/resources/net_internals/timelineviewpainter.js @@ -0,0 +1,20 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var PaintTimelineView; + +(function() { + +PaintTimelineView = function(sourceEntries, node) { + addTextNode(node, 'TODO(eroman): Draw some sort of waterfall.'); + + addNode(node, 'br'); + addNode(node, 'br'); + + addTextNode(node, 'Selected nodes (' + sourceEntries.length + '):'); + addNode(node, 'br'); + +} + +})(); diff --git a/chrome/browser/resources/net_internals/util.js b/chrome/browser/resources/net_internals/util.js new file mode 100644 index 0000000..780a030 --- /dev/null +++ b/chrome/browser/resources/net_internals/util.js @@ -0,0 +1,88 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * Helper that binds the |this| object to a method to create a callback. + */ +Function.prototype.bind = function(thisObj) { + var func = this; + var args = Array.prototype.slice.call(arguments, 1); + return function() { + return func.apply(thisObj, + args.concat(Array.prototype.slice.call(arguments, 0))) + }; +}; + +/** + * Sets the width (in pixels) on a DOM node. + */ +function setNodeWidth(node, widthPx) { + node.style.width = widthPx.toFixed(0) + "px"; +} + +/** + * Sets the height (in pixels) on a DOM node. + */ +function setNodeHeight(node, heightPx) { + node.style.height = heightPx.toFixed(0) + "px"; +} + +/** + * Sets the position and size of a DOM node (in pixels). + */ +function setNodePosition(node, leftPx, topPx, widthPx, heightPx) { + node.style.left = leftPx.toFixed(0) + "px"; + node.style.top = topPx.toFixed(0) + "px"; + setNodeWidth(node, widthPx); + setNodeHeight(node, heightPx); +} + +/** + * Adds a node to |parentNode|, of type |tagName|. + */ +function addNode(parentNode, tagName) { + var elem = parentNode.ownerDocument.createElement(tagName); + parentNode.appendChild(elem); + return elem; +} + +/** + * Adds text to node |parentNode|. + */ +function addTextNode(parentNode, text) { + var textNode = parentNode.ownerDocument.createTextNode(text); + parentNode.appendChild(textNode); + return textNode; +} + +/** + * Adds or removes a CSS class to |node|. + */ +function changeClassName(node, classNameToAddOrRemove, isAdd) { + // Multiple classes can be separated by spaces. + var currentNames = node.className.split(" "); + + if (isAdd) { + if (!(classNameToAddOrRemove in currentNames)) { + currentNames.push(classNameToAddOrRemove); + } + } else { + for (var i = 0; i < currentNames.length; ++i) { + if (currentNames[i] == classNameToAddOrRemove) { + currentNames.splice(i, 1); + break; + } + } + } + + node.className = currentNames.join(" "); +} + +function getKeyWithValue(map, value) { + for (key in map) { + if (map[key] == value) + return key; + } + return '?'; +} |