summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorscottfr@chromium.org <scottfr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-20 23:01:23 +0000
committerscottfr@chromium.org <scottfr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-20 23:01:23 +0000
commitebaf5d0037fb655dce4cdeee00faa16800cfc956 (patch)
tree98f9d5cd42e11993fee658139ec8ccda0c5bb6fc
parent3b172116930bdad38aeb24c3e70d80d6b9df77a3 (diff)
downloadchromium_src-ebaf5d0037fb655dce4cdeee00faa16800cfc956.zip
chromium_src-ebaf5d0037fb655dce4cdeee00faa16800cfc956.tar.gz
chromium_src-ebaf5d0037fb655dce4cdeee00faa16800cfc956.tar.bz2
Display media cache events on chrome://media-internals.
BUG=none TEST=unittests, manually Review URL: http://codereview.chromium.org/7395031 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@93272 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/resources/media_internals.html4
-rw-r--r--chrome/browser/resources/media_internals/cache_entry.js91
-rw-r--r--chrome/browser/resources/media_internals/disjoint_range_set.js134
-rw-r--r--chrome/browser/resources/media_internals/disjoint_range_set_test.html82
-rw-r--r--chrome/browser/resources/media_internals/media_internals.js109
-rw-r--r--chrome/browser/ui/webui/media/media_internals_proxy.cc110
-rw-r--r--chrome/browser/ui/webui/media/media_internals_proxy.h32
7 files changed, 558 insertions, 4 deletions
diff --git a/chrome/browser/resources/media_internals.html b/chrome/browser/resources/media_internals.html
index 51f01b8..482c30b 100644
--- a/chrome/browser/resources/media_internals.html
+++ b/chrome/browser/resources/media_internals.html
@@ -9,12 +9,16 @@ found in the LICENSE file.
<link rel="stylesheet" href="webui.css" />
<link rel="stylesheet" href="media_internals/media_internals.css" />
<script src="shared/js/cr.js"></script>
+ <script src="media_internals/cache_entry.js"></script>
<script src="media_internals/item_store.js"></script>
<script src="media_internals/media_internals.js"></script>
+ <script src="media_internals/disjoint_range_set.js"></script>
<title>Media Internals</title>
</head>
<body>
<h2>Active audio streams:</h2>
<div id="audio-streams"></div>
+ <h2>Cached resources:</h2>
+ <div id="cache-entries"></div>
</body>
</html>
diff --git a/chrome/browser/resources/media_internals/cache_entry.js b/chrome/browser/resources/media_internals/cache_entry.js
new file mode 100644
index 0000000..1136a3f
--- /dev/null
+++ b/chrome/browser/resources/media_internals/cache_entry.js
@@ -0,0 +1,91 @@
+// Copyright (c) 2011 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.
+
+cr.define('media', function() {
+
+ /**
+ * This class represents a file cached by net.
+ */
+ function CacheEntry() {
+ this.read_ = new media.DisjointRangeSet;
+ this.written_ = new media.DisjointRangeSet;
+ this.available_ = new media.DisjointRangeSet;
+
+ // Set to true when we know the entry is sparse.
+ this.sparse = false;
+ this.key = null;
+ this.size = null;
+ };
+
+ CacheEntry.prototype = {
+ /**
+ * Mark a range of bytes as read from the cache.
+ * @param {int} start The first byte read.
+ * @param {int} length The number of bytes read.
+ */
+ readBytes: function(start, length) {
+ start = parseInt(start);
+ length = parseInt(length);
+ this.read_.add(start, start + length);
+ this.available_.add(start, start + length);
+ this.sparse = true;
+ },
+
+ /**
+ * Mark a range of bytes as written to the cache.
+ * @param {int} start The first byte written.
+ * @param {int} length The number of bytes written.
+ */
+ writeBytes: function(start, length) {
+ start = parseInt(start);
+ length = parseInt(length);
+ this.written_.add(start, start + length);
+ this.available_.add(start, start + length);
+ this.sparse = true;
+ },
+
+ /**
+ * Merge this CacheEntry with another, merging recorded ranges and flags.
+ * @param {CacheEntry} other The CacheEntry to merge into this one.
+ */
+ merge: function(other) {
+ this.read_.merge(other.read_);
+ this.written_.merge(other.written_);
+ this.available_.merge(other.available_);
+ this.sparse = this.sparse || other.sparse;
+ this.key = this.key || other.key;
+ this.size = this.size || other.size;
+ },
+
+ /**
+ * Render this CacheEntry as a <li>.
+ * @return {HTMLElement} A <li> representing this CacheEntry.
+ */
+ toListItem: function() {
+ var result = document.createElement('li');
+ result.id = this.key;
+ result.className = 'cache-entry';
+ result.innerHTML = this.key + '<br />Read: ';
+ result.innerHTML += this.read_.map(function(start, end) {
+ return start + '-' + end;
+ }).join(', ');
+
+ result.innerHTML += '. Written: ';
+ result.innerHTML += this.written_.map(function(start, end) {
+ return start + '-' + end;
+ }).join(', ');
+
+ // Include the total size if we know it.
+ if (this.size) {
+ result.innerHTML += '. Out of ';
+ result.innerHTML += this.size;
+ }
+ return result;
+ }
+ };
+
+ return {
+ CacheEntry: CacheEntry
+ };
+});
diff --git a/chrome/browser/resources/media_internals/disjoint_range_set.js b/chrome/browser/resources/media_internals/disjoint_range_set.js
new file mode 100644
index 0000000..cff8f69
--- /dev/null
+++ b/chrome/browser/resources/media_internals/disjoint_range_set.js
@@ -0,0 +1,134 @@
+// Copyright (c) 2011 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.
+
+cr.define('media', function() {
+
+ /**
+ * This class represents a collection of non-intersecting ranges. Ranges
+ * specified by (start, end) can be added and removed at will. It is used to
+ * record which sections of a media file have been cached, e.g. the first and
+ * last few kB plus several MB in the middle.
+ *
+ * Example usage:
+ * someRange.add(0, 100); // Contains 0-100.
+ * someRange.add(150, 200); // Contains 0-100, 150-200.
+ * someRange.remove(25, 75); // Contains 0-24, 76-100, 150-200.
+ * someRange.add(25, 149); // Contains 0-200.
+ */
+ function DisjointRangeSet() {
+ this.ranges_ = {};
+ };
+
+ DisjointRangeSet.prototype = {
+ /**
+ * Deletes all ranges intersecting with (start ... end) and returns the
+ * extents of the cleared area.
+ * @param {int} start The start of the range to remove.
+ * @param {int} end The end of the range to remove.
+ * @param {int} sloppiness 0 removes only strictly overlapping ranges, and
+ * 1 removes adjacent ones.
+ * @return {Object} The start and end of the newly cleared range.
+ */
+ clearRange: function(start, end, sloppiness) {
+ var ranges = this.ranges_;
+ var result = {start: start, end: end};
+
+ for (var rangeStart in this.ranges_) {
+ rangeEnd = this.ranges_[rangeStart];
+ // A range intersects another if its start lies within the other range
+ // or vice versa.
+ if ((rangeStart >= start && rangeStart <= (end + sloppiness)) ||
+ (start >= rangeStart && start <= (rangeEnd + sloppiness))) {
+ delete ranges[rangeStart];
+ result.start = Math.min(result.start, rangeStart);
+ result.end = Math.max(result.end, rangeEnd);
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Adds a range to this DisjointRangeSet.
+ * Joins adjacent and overlapping ranges together.
+ * @param {int} start The beginning of the range to add, inclusive.
+ * @param {int} end The end of the range to add, inclusive.
+ */
+ add: function(start, end) {
+ if (end < start)
+ return;
+
+ // Remove all touching ranges.
+ result = this.clearRange(start, end, 1);
+ // Add back a single contiguous range.
+ this.ranges_[Math.min(start, result.start)] = Math.max(end, result.end);
+ },
+
+ /**
+ * Combines a DisjointRangeSet with this one.
+ * @param {DisjointRangeSet} ranges A DisjointRangeSet to be squished into
+ * this one.
+ */
+ merge: function(other) {
+ var ranges = this;
+ other.forEach(function(start, end) { ranges.add(start, end); });
+ },
+
+ /**
+ * Removes a range from this DisjointRangeSet.
+ * Will split existing ranges if necessary.
+ * @param {int} start The beginning of the range to remove, inclusive.
+ * @param {int} end The end of the range to remove, inclusive.
+ */
+ remove: function(start, end) {
+ if (end < start)
+ return;
+
+ // Remove instersecting ranges.
+ result = this.clearRange(start, end, 0);
+
+ // Add back non-overlapping ranges.
+ if (result.start < start)
+ this.ranges_[result.start] = start - 1;
+ if (result.end > end)
+ this.ranges_[end + 1] = result.end;
+ },
+
+ /**
+ * Iterates over every contiguous range in this DisjointRangeSet, calling a
+ * function for each (start, end).
+ * @param {function(int, int)} iterator The function to call on each range.
+ */
+ forEach: function(iterator) {
+ for (var start in this.ranges_)
+ iterator(start, this.ranges_[start]);
+ },
+
+ /**
+ * Maps this DisjointRangeSet to an array by calling a given function on the
+ * start and end of each contiguous range, sorted by start.
+ * @param {function(int, int)} mapper Maps a range to an array element.
+ * @return {Array} An array of each mapper(range).
+ */
+ map: function(mapper) {
+ var starts = [];
+ for (var start in this.ranges_)
+ starts.push(parseInt(start));
+ starts.sort(function(a, b) {
+ return a - b;
+ });
+
+ var ranges = this.ranges_;
+ var results = starts.map(function(s) {
+ return mapper(s, ranges[s]);
+ });
+
+ return results;
+ }
+ };
+
+ return {
+ DisjointRangeSet: DisjointRangeSet
+ };
+});
diff --git a/chrome/browser/resources/media_internals/disjoint_range_set_test.html b/chrome/browser/resources/media_internals/disjoint_range_set_test.html
new file mode 100644
index 0000000..b7c61d9
--- /dev/null
+++ b/chrome/browser/resources/media_internals/disjoint_range_set_test.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+<!--
+Copyright (c) 2011 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></title>
+ <script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>
+ <script src="../shared/js/cr.js"></script>
+ <script src="disjoint_range_set.js"></script>
+ <script>
+ goog.require('goog.testing.jsunit');
+ </script>
+ </head>
+ <body>
+ <script>
+
+ var range;
+
+ function assertRangeEquals(ranges) {
+ assertArrayEquals(
+ ranges, range.map(function(start, end) { return [start, end]; }));
+ };
+
+ function setUp() {
+ range = new media.DisjointRangeSet;
+ };
+
+ function testAdd() {
+ range.add(1, 6);
+ assertRangeEquals([[1, 6]]);
+ range.add(-5, -3);
+ assertRangeEquals([[-5, -3], [1, 6]]);
+ };
+
+ function testAddAdjacent() {
+ range.add(3, 6);
+ assertRangeEquals([[3, 6]]);
+ range.add(1, 2);
+ assertRangeEquals([[1, 6]]);
+ range.add(7, 9);
+ assertRangeEquals([[1, 9]]);
+ };
+
+ function testAddNotQuiteAdjacent() {
+ range.add(3, 6);
+ assertRangeEquals([[3, 6]]);
+ range.add(0, 1);
+ assertRangeEquals([[0, 1], [3, 6]]);
+ range.add(8, 9);
+ assertRangeEquals([[0, 1], [3, 6], [8, 9]]);
+ };
+
+ function testAddOverlapping() {
+ range.add(1, 6);
+ assertRangeEquals([[1, 6]]);
+ range.add(5, 8);
+ assertRangeEquals([[1, 8]]);
+ range.add(0, 1);
+ assertRangeEquals([[0, 8]]);
+ };
+
+ function testRemove() {
+ range.add(1, 20);
+ assertRangeEquals([[1, 20]]);
+ range.remove(0, 3);
+ assertRangeEquals([[4, 20]]);
+ range.remove(18, 20);
+ assertRangeEquals([[4, 17]]);
+ range.remove(5, 16);
+ assertRangeEquals([[4, 4], [17, 17]]);
+ };
+
+ function testStartsEmpty() {
+ assertRangeEquals([]);
+ };
+
+ </script>
+ </body>
+</html>
diff --git a/chrome/browser/resources/media_internals/media_internals.js b/chrome/browser/resources/media_internals/media_internals.js
index c046889..a01a06d 100644
--- a/chrome/browser/resources/media_internals/media_internals.js
+++ b/chrome/browser/resources/media_internals/media_internals.js
@@ -6,14 +6,28 @@ cr.define('media', function() {
// Stores information on open audio streams, referenced by id.
var audioStreams = new media.ItemStore;
- // The <div> on the page in which to display audio stream data.
+
+ // Cached files indexed by key and source id.
+ var cacheEntriesByKey = {};
+ var cacheEntries = {};
+
+ // Map of event source -> url.
+ var requestURLs = {};
+
+ // Constants passed to us from Chrome.
+ var eventTypes = {};
+ var eventPhases = {};
+
+ // The <div>s on the page in which to display information.
var audioStreamDiv;
+ var cacheDiv;
/**
* Initialize variables and ask MediaInternals for all its data.
*/
initialize = function() {
audioStreamDiv = document.getElementById('audio-streams');
+ cacheDiv = document.getElementById('cache-entries');
// Get information about all currently active media.
chrome.send('getEverything');
};
@@ -50,6 +64,20 @@ cr.define('media', function() {
};
/**
+ * Write the set of sparse CacheEntries to the DOM.
+ */
+ printSparseCacheEntries = function() {
+ var out = document.createElement('ul');
+ for (var key in cacheEntriesByKey) {
+ if (cacheEntriesByKey[key].sparse)
+ out.appendChild(cacheEntriesByKey[key].toListItem());
+ }
+
+ cacheDiv.textContent = '';
+ cacheDiv.appendChild(out);
+ };
+
+ /**
* Receiving data for an audio stream.
* Add it to audioStreams and update the page.
* @param {Object} stream JSON representation of an audio stream.
@@ -85,11 +113,90 @@ cr.define('media', function() {
}
};
+ /**
+ * Receiving net events.
+ * Update cache information and update that section of the page.
+ * @param {Array} updates A list of net events that have occurred.
+ */
+ onNetUpdate = function(updates) {
+ updates.forEach(function(update) {
+ var id = update.source.id;
+ if (!cacheEntries[id])
+ cacheEntries[id] = new media.CacheEntry;
+
+ switch (eventPhases[update.phase] + '.' + eventTypes[update.type]) {
+ case 'PHASE_BEGIN.DISK_CACHE_ENTRY_IMPL':
+ var key = update.params.key;
+
+ // Merge this source with anything we already know about this key.
+ if (cacheEntriesByKey[key]) {
+ cacheEntriesByKey[key].merge(cacheEntries[id]);
+ cacheEntries[id] = cacheEntriesByKey[key];
+ } else {
+ cacheEntriesByKey[key] = cacheEntries[id];
+ }
+ cacheEntriesByKey[key].key = key;
+ break;
+
+ case 'PHASE_BEGIN.SPARSE_READ':
+ cacheEntries[id].readBytes(update.params.offset,
+ update.params.buff_len);
+ cacheEntries[id].sparse = true;
+ break;
+
+ case 'PHASE_BEGIN.SPARSE_WRITE':
+ cacheEntries[id].writeBytes(update.params.offset,
+ update.params.buff_len);
+ cacheEntries[id].sparse = true;
+ break;
+
+ case 'PHASE_BEGIN.URL_REQUEST_START_JOB':
+ requestURLs[update.source.id] = update.params.url;
+ break;
+
+ case 'PHASE_NONE.HTTP_TRANSACTION_READ_RESPONSE_HEADERS':
+ // Record the total size of the file if this was a range request.
+ var range = /content-range:\s*bytes\s*\d+-\d+\/(\d+)/i.exec(
+ update.params.headers);
+ var key = requestURLs[update.source.id];
+ delete requestURLs[update.source.id];
+ if (range && key) {
+ if (!cacheEntriesByKey[key]) {
+ cacheEntriesByKey[key] = new CacheEntry;
+ cacheEntriesByKey[key].key = key;
+ }
+ cacheEntriesByKey[key].size = range[1];
+ }
+ break;
+ }
+ });
+
+ printSparseCacheEntries();
+ };
+
+ /**
+ * Receiving values for constants. Store them for later use.
+ * @param {Object} constants A dictionary of constants.
+ * @param {Object} constants.eventTypes A dictionary of event name -> int.
+ * @param {Object} constants.eventPhases A dictionary of event phase -> int.
+ */
+ onReceiveConstants = function(constants) {
+ var events = constants.eventTypes;
+ for (var e in events)
+ eventTypes[events[e]] = e;
+
+ var phases = constants.eventPhases;
+ for (var p in phases)
+ eventPhases[phases[p]] = p;
+ };
+
return {
initialize: initialize,
addAudioStream: addAudioStream,
onReceiveEverything: onReceiveEverything,
onItemDeleted: onItemDeleted,
+ onNetUpdate: onNetUpdate,
+ onReceiveConstants: onReceiveConstants,
};
});
diff --git a/chrome/browser/ui/webui/media/media_internals_proxy.cc b/chrome/browser/ui/webui/media/media_internals_proxy.cc
index 94b2ce1..2f2e46a 100644
--- a/chrome/browser/ui/webui/media/media_internals_proxy.cc
+++ b/chrome/browser/ui/webui/media/media_internals_proxy.cc
@@ -9,7 +9,21 @@
#include "chrome/browser/media/media_internals.h"
#include "chrome/browser/ui/webui/media/media_internals_handler.h"
-MediaInternalsProxy::MediaInternalsProxy() {
+static const int kMediaInternalsProxyEventDelayMilliseconds = 100;
+
+static const net::NetLog::EventType kNetEventTypeFilter[] = {
+ net::NetLog::TYPE_DISK_CACHE_ENTRY_IMPL,
+ net::NetLog::TYPE_SPARSE_READ,
+ net::NetLog::TYPE_SPARSE_WRITE,
+ net::NetLog::TYPE_URL_REQUEST_START_JOB,
+ net::NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS,
+};
+
+static const char kSendConstantsFunction[] = "media.onReceiveConstants";
+static const char kSendNetworkEventsFunction[] = "media.onNetUpdate";
+
+MediaInternalsProxy::MediaInternalsProxy()
+ : ThreadSafeObserver(net::NetLog::LOG_ALL_BUT_BYTES) {
io_thread_ = g_browser_process->io_thread();
}
@@ -31,8 +45,13 @@ void MediaInternalsProxy::Detach() {
void MediaInternalsProxy::GetEverything() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Ask MediaInternals for all its data.
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
NewRunnableMethod(this, &MediaInternalsProxy::GetEverythingOnIOThread));
+
+ // Send the page names for constants.
+ CallJavaScriptFunctionOnUIThread(kSendConstantsFunction, GetConstants());
}
void MediaInternalsProxy::OnUpdate(const string16& update) {
@@ -42,22 +61,111 @@ void MediaInternalsProxy::OnUpdate(const string16& update) {
&MediaInternalsProxy::UpdateUIOnUIThread, update));
}
+void MediaInternalsProxy::OnAddEntry(net::NetLog::EventType type,
+ const base::TimeTicks& time,
+ const net::NetLog::Source& source,
+ net::NetLog::EventPhase phase,
+ net::NetLog::EventParameters* params) {
+ bool is_event_interesting = false;
+ for (size_t i = 0; i < arraysize(kNetEventTypeFilter); i++) {
+ if (type == kNetEventTypeFilter[i]) {
+ is_event_interesting = true;
+ break;
+ }
+ }
+
+ if (!is_event_interesting)
+ return;
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ NewRunnableMethod(
+ this, &MediaInternalsProxy::AddNetEventOnUIThread,
+ net::NetLog::EntryToDictionaryValue(type, time, source, phase,
+ params, false)));
+}
+
MediaInternalsProxy::~MediaInternalsProxy() {}
+Value* MediaInternalsProxy::GetConstants() {
+ DictionaryValue* event_types = new DictionaryValue();
+ std::vector<net::NetLog::EventType> types_vec =
+ net::NetLog::GetAllEventTypes();
+
+ for (size_t i = 0; i < types_vec.size(); ++i) {
+ const char* name = net::NetLog::EventTypeToString(types_vec[i]);
+ event_types->SetInteger(name, static_cast<int>(types_vec[i]));
+ }
+
+ DictionaryValue* event_phases = new DictionaryValue();
+ event_phases->SetInteger(
+ net::NetLog::EventPhaseToString(net::NetLog::PHASE_NONE),
+ net::NetLog::PHASE_NONE);
+ event_phases->SetInteger(
+ net::NetLog::EventPhaseToString(net::NetLog::PHASE_BEGIN),
+ net::NetLog::PHASE_BEGIN);
+ event_phases->SetInteger(
+ net::NetLog::EventPhaseToString(net::NetLog::PHASE_END),
+ net::NetLog::PHASE_END);
+
+ DictionaryValue* constants = new DictionaryValue();
+ constants->Set("eventTypes", event_types);
+ constants->Set("eventPhases", event_phases);
+
+ return constants;
+}
+
void MediaInternalsProxy::ObserveMediaInternalsOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
io_thread_->globals()->media.media_internals->AddObserver(this);
+ // TODO(scottfr): Get the passive log data as well.
+ io_thread_->net_log()->AddObserver(this);
}
void MediaInternalsProxy::StopObservingMediaInternalsOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
io_thread_->globals()->media.media_internals->RemoveObserver(this);
+ io_thread_->net_log()->RemoveObserver(this);
}
void MediaInternalsProxy::GetEverythingOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
io_thread_->globals()->media.media_internals->SendEverything();
}
void MediaInternalsProxy::UpdateUIOnUIThread(const string16& update) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Don't forward updates to a destructed UI.
if (handler_)
handler_->OnUpdate(update);
}
+
+void MediaInternalsProxy::AddNetEventOnUIThread(Value* entry) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Send the updates to the page in kMediaInternalsProxyEventDelayMilliseconds
+ // if an update is not already pending.
+ if (!pending_net_updates_.get()) {
+ pending_net_updates_.reset(new ListValue());
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ NewRunnableMethod(
+ this, &MediaInternalsProxy::SendNetEventsOnUIThread),
+ kMediaInternalsProxyEventDelayMilliseconds);
+ }
+ pending_net_updates_->Append(entry);
+}
+
+void MediaInternalsProxy::SendNetEventsOnUIThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ CallJavaScriptFunctionOnUIThread(kSendNetworkEventsFunction,
+ pending_net_updates_.release());
+}
+
+void MediaInternalsProxy::CallJavaScriptFunctionOnUIThread(
+ const std::string& function, Value* args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ scoped_ptr<Value> args_value(args);
+ std::vector<const Value*> args_vector;
+ args_vector.push_back(args_value.get());
+ string16 update = WebUI::GetJavascriptCall(function, args_vector);
+ UpdateUIOnUIThread(update);
+}
diff --git a/chrome/browser/ui/webui/media/media_internals_proxy.h b/chrome/browser/ui/webui/media/media_internals_proxy.h
index ccb0995..1915e94 100644
--- a/chrome/browser/ui/webui/media/media_internals_proxy.h
+++ b/chrome/browser/ui/webui/media/media_internals_proxy.h
@@ -6,20 +6,27 @@
#define CHROME_BROWSER_UI_WEBUI_MEDIA_MEDIA_INTERNALS_PROXY_H_
#pragma once
-#include "base/string16.h"
#include "base/memory/ref_counted.h"
+#include "base/string16.h"
#include "chrome/browser/media/media_internals_observer.h"
+#include "chrome/browser/net/chrome_net_log.h"
class IOThread;
class MediaInternalsMessageHandler;
+namespace base {
+class ListValue;
+class Value;
+}
+
// This class is a proxy between MediaInternals (on the IO thread) and
// MediaInternalsMessageHandler (on the UI thread).
// It is ref_counted to ensure that it completes all pending Tasks on both
// threads before destruction.
class MediaInternalsProxy
: public MediaInternalsObserver,
- public base::RefCountedThreadSafe<MediaInternalsProxy> {
+ public base::RefCountedThreadSafe<MediaInternalsProxy>,
+ public ChromeNetLog::ThreadSafeObserver {
public:
MediaInternalsProxy();
@@ -35,17 +42,38 @@ class MediaInternalsProxy
// MediaInternalsObserver implementation. Called on the IO thread.
virtual void OnUpdate(const string16& update);
+ // ChromeNetLog::ThreadSafeObserver implementation. Callable from any thread:
+ virtual void OnAddEntry(net::NetLog::EventType type,
+ const base::TimeTicks& time,
+ const net::NetLog::Source& source,
+ net::NetLog::EventPhase phase,
+ net::NetLog::EventParameters* params);
+
private:
friend class base::RefCountedThreadSafe<MediaInternalsProxy>;
virtual ~MediaInternalsProxy();
+ // Build a dictionary mapping constant names to values.
+ base::Value* GetConstants();
+
void ObserveMediaInternalsOnIOThread();
void StopObservingMediaInternalsOnIOThread();
void GetEverythingOnIOThread();
void UpdateUIOnUIThread(const string16& update);
+ // Put |entry| on a list of events to be sent to the page.
+ void AddNetEventOnUIThread(base::Value* entry);
+
+ // Send all pending events to the page.
+ void SendNetEventsOnUIThread();
+
+ // Call a JavaScript function on the page. Takes ownership of |args|.
+ void CallJavaScriptFunctionOnUIThread(const std::string& function,
+ base::Value* args);
+
MediaInternalsMessageHandler* handler_;
IOThread* io_thread_;
+ scoped_ptr<base::ListValue> pending_net_updates_;
DISALLOW_COPY_AND_ASSIGN(MediaInternalsProxy);
};