Merge mozilla-central to tracemonkey.
authorRobert Sayre <sayrer@gmail.com>
Fri, 02 Apr 2010 10:10:27 -0400
changeset 40441 c0a4b27c724f74162a1577e04ec13598c016012e
parent 40440 a00d7a9303c3b0714bbacbbae3b608fc896b8f0c (current diff)
parent 40143 ee907f10a61829f22de1cb4e82fc88e0fa1d36d7 (diff)
child 40442 7b370d70eb6861f34d56ab523e45fa9858bd6d2a
push id12610
push userrsayre@mozilla.com
push dateMon, 05 Apr 2010 17:26:41 +0000
treeherdermozilla-central@1942c0b4e101 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone1.9.3a4pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
Merge mozilla-central to tracemonkey.
configure.in
content/canvas/src/CustomQS_WebGL.h
dom/src/geolocation/MaemoLocationProvider.cpp
dom/src/geolocation/MaemoLocationProvider.h
js/ctypes/CTypes.cpp
layout/generic/crashtests/414740.html
layout/reftests/bugs/reftest.list
toolkit/components/places/tests/autocomplete/head_000.js
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -1174,16 +1174,19 @@ nsDocAccessible::AttributeChangedImpl(ns
 // nsDocAccessible protected member
 void
 nsDocAccessible::ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute)
 {
   nsCOMPtr<nsIDOMNode> targetNode(do_QueryInterface(aContent));
   if (!targetNode)
     return;
 
+  // Note: For universal/global ARIA states and properties we don't care if
+  // there is an ARIA role present or not.
+
   if (aAttribute == nsAccessibilityAtoms::aria_required) {
     nsRefPtr<nsAccEvent> event =
       new nsAccStateChangeEvent(targetNode,
                                 nsIAccessibleStates::STATE_REQUIRED,
                                 PR_FALSE);
     FireDelayedAccessibleEvent(event);
     return;
   }
@@ -1204,16 +1207,34 @@ nsDocAccessible::ARIAAttributeChanged(ns
     if (SameCOMIdentity(nsCoreUtils::GetRoleContent(currentFocus), targetNode)) {
       nsRefPtr<nsRootAccessible> rootAcc = GetRootAccessible();
       if (rootAcc)
         rootAcc->FireAccessibleFocusEvent(nsnull, currentFocus, nsnull, PR_TRUE);
     }
     return;
   }
 
+  // For aria drag and drop changes we fire a generic attribute change event;
+  // at least until native API comes up with a more meaningful event.
+  if (aAttribute == nsAccessibilityAtoms::aria_grabbed ||
+      aAttribute == nsAccessibilityAtoms::aria_dropeffect) {
+    FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
+                               targetNode);
+  }
+
+  // We treat aria-expanded as a global ARIA state for historical reasons
+  if (aAttribute == nsAccessibilityAtoms::aria_expanded) {
+    nsRefPtr<nsAccEvent> event =
+      new nsAccStateChangeEvent(targetNode,
+                                nsIAccessibleStates::STATE_EXPANDED,
+                                PR_FALSE);
+    FireDelayedAccessibleEvent(event);
+    return;
+  }
+
   if (!aContent->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::role)) {
     // We don't care about these other ARIA attribute changes unless there is
     // an ARIA role set for the element
     // XXX: we should check the role map to see if the changed property is
     // relevant for that particular role.
     return;
   }
 
@@ -1244,25 +1265,16 @@ nsDocAccessible::ARIAAttributeChanged(ns
                                       PR_FALSE, isMixed);
           FireDelayedAccessibleEvent(event);
         }
       }
     }
     return;
   }
 
-  if (aAttribute == nsAccessibilityAtoms::aria_expanded) {
-    nsRefPtr<nsAccEvent> event =
-      new nsAccStateChangeEvent(targetNode,
-                                nsIAccessibleStates::STATE_EXPANDED,
-                                PR_FALSE);
-    FireDelayedAccessibleEvent(event);
-    return;
-  }
-
   if (aAttribute == nsAccessibilityAtoms::aria_readonly) {
     nsRefPtr<nsAccEvent> event =
       new nsAccStateChangeEvent(targetNode,
                                 nsIAccessibleStates::STATE_READONLY,
                                 PR_FALSE);
     FireDelayedAccessibleEvent(event);
     return;
   }
@@ -1285,24 +1297,16 @@ nsDocAccessible::ARIAAttributeChanged(ns
       aContent->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::role)) {
     // This affects whether the accessible supports nsIAccessibleSelectable.
     // COM says we cannot change what interfaces are supported on-the-fly,
     // so invalidate this object. A new one will be created on demand.
     InvalidateCacheSubtree(aContent,
                            nsIAccessibilityService::NODE_SIGNIFICANT_CHANGE);
     return;
   }
-
-  // For aria drag and drop changes we fire a generic attribute change event;
-  // at least until native API comes up with a more meaningful event.
-  if (aAttribute == nsAccessibilityAtoms::aria_grabbed ||
-      aAttribute == nsAccessibilityAtoms::aria_dropeffect) {
-    FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
-                               targetNode);
-  }
 }
 
 void nsDocAccessible::ContentAppended(nsIDocument *aDocument,
                                       nsIContent* aContainer,
                                       PRInt32 aNewIndexInContainer)
 {
   if ((!mIsContentLoaded || !mDocument) && mAccessNodeCache.Count() <= 1) {
     // See comments in nsDocAccessible::InvalidateCacheSubtree
--- a/accessible/tests/mochitest/events/Makefile.in
+++ b/accessible/tests/mochitest/events/Makefile.in
@@ -43,16 +43,17 @@ VPATH		= @srcdir@
 relativesrcdir  = accessible/events
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES =\
 		focus.html \
 		scroll.html \
+		test_aria_statechange.html \
 		test_attrs.html \
 		test_caretmove.html \
 	$(warning	test_coalescence.html temporarily disabled) \
 		test_doc.html \
 		test_dragndrop.html \
 		test_flush.html \
 		test_focus.html \
 		test_focus.xul \
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_statechange.html
@@ -0,0 +1,85 @@
+<html>
+
+<head>
+  <title>ARIA state change event testing</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/a11y/accessible/common.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/a11y/accessible/states.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/a11y/accessible/events.js"></script>
+
+  <script type="application/javascript">
+
+
+    /**
+     * Do tests.
+     */
+    var gQueue = null;
+
+    function expandNode(aNodeOrID, bExpand)
+    {
+      this.DOMNode = getNode(aNodeOrID);
+
+      this.invoke = function expand_invoke() {
+        // Note: this should fire an EVENT_STATE_CHANGE
+        this.DOMNode.setAttribute("aria-expanded", bExpand);
+      };
+
+      this.check = function expand_check() {
+        testStates(aNodeOrID,
+                   bExpand ? STATE_EXPANDED : STATE_COLLAPSED,
+                   EXT_STATE_EXPANDABLE);
+      };
+
+      this.getID = function changeValue_getID() {
+        return prettyName(aNodeOrID) + " aria-expanded changed";
+      };
+    }
+
+    function doTests()
+    {
+      gQueue = new eventQueue(nsIAccessibleEvent.EVENT_STATE_CHANGE);
+
+      gQueue.push(new expandNode("section", true));
+      gQueue.push(new expandNode("section", false));
+      gQueue.push(new expandNode("div", true));
+      gQueue.push(new expandNode("div", false));
+
+      gQueue.invoke(); // Will call SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTests);
+  </script>
+</head>
+
+<body>
+
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=551684"
+     title="No statechange event for aria-expanded on native HTML elements, is fired on ARIA widgets">
+    Mozilla Bug 551684
+  </a>
+
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+  <div id="eventdump"></div>
+
+  <!-- aria-expanded -->
+  <div id="section" role="section" aria-expanded="false">expandable section</div>
+  <div id="div" aria-expanded="false">expandable native div</div>
+
+</body>
+</html>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -839,19 +839,16 @@ pref("browser.offline-apps.notify", true
 pref("browser.zoom.full", true);
 
 // Whether or not to save and restore zoom levels on a per-site basis.
 pref("browser.zoom.siteSpecific", true);
 
 // Whether or not to update background tabs to the current zoom level.
 pref("browser.zoom.updateBackgroundTabs", true);
 
-// replace newlines with spaces when pasting into <input type="text"> fields
-pref("editor.singleLine.pasteNewlines", 2);
-
 // The breakpad report server to link to in about:crashes
 pref("breakpad.reportURL", "http://crash-stats.mozilla.com/report/index/");
 
 // base URL for web-based support pages
 pref("app.support.baseURL", "http://support.mozilla.com/1/%APP%/%VERSION%/%OS%/%LOCALE%/");
 
 // Name of alternate about: page for certificate errors (when undefined, defaults to about:neterror)
 pref("security.alternate_certificate_error_page", "certerror");
--- a/browser/base/content/browser-tabPreviews.js
+++ b/browser/base/content/browser-tabPreviews.js
@@ -39,36 +39,46 @@
  */
 
 /**
  * Tab previews utility, produces thumbnails
  */
 var tabPreviews = {
   aspectRatio: 0.5625, // 16:9
   init: function tabPreviews_init() {
+    if (this._selectedTab)
+      return;
+    this._selectedTab = gBrowser.selectedTab;
+
     this.width = Math.ceil(screen.availWidth / 5.75);
     this.height = Math.round(this.width * this.aspectRatio);
 
+    window.addEventListener("unload", this, false);
     gBrowser.tabContainer.addEventListener("TabSelect", this, false);
     gBrowser.tabContainer.addEventListener("SSTabRestored", this, false);
   },
   uninit: function tabPreviews_uninit() {
+    window.removeEventListener("unload", this, false);
     gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
     gBrowser.tabContainer.removeEventListener("SSTabRestored", this, false);
     this._selectedTab = null;
   },
   get: function tabPreviews_get(aTab) {
+    this.init();
+
     if (aTab.__thumbnail_lastURI &&
         aTab.__thumbnail_lastURI != aTab.linkedBrowser.currentURI.spec) {
       aTab.__thumbnail = null;
       aTab.__thumbnail_lastURI = null;
     }
     return aTab.__thumbnail || this.capture(aTab, !aTab.hasAttribute("busy"));
   },
   capture: function tabPreviews_capture(aTab, aStore) {
+    this.init();
+
     var thumbnail = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
     thumbnail.mozOpaque = true;
     thumbnail.height = this.height;
     thumbnail.width = this.width;
 
     var ctx = thumbnail.getContext("2d");
     var win = aTab.linkedBrowser.contentWindow;
     var snippetWidth = win.innerWidth * .6;
@@ -100,16 +110,19 @@ var tabPreviews = {
               self.capture(aTab, true);
           }, 2000, this, this._selectedTab);
         }
         this._selectedTab = event.target;
         break;
       case "SSTabRestored":
         this.capture(event.target, true);
         break;
+      case "unload":
+        this.uninit();
+        break;
     }
   }
 };
 
 var tabPreviewPanelHelper = {
   opening: function (host) {
     host.panel.hidden = false;
 
@@ -220,16 +233,18 @@ var ctrlTab = {
       }
     }
 
     return this._tabList = list;
   },
 
   init: function ctrlTab_init() {
     if (!this._recentlyUsedTabs) {
+      tabPreviews.init();
+
       this._recentlyUsedTabs = [gBrowser.selectedTab];
       this._init(true);
     }
   },
 
   uninit: function ctrlTab_uninit() {
     this._recentlyUsedTabs = null;
     this._init(false);
@@ -572,16 +587,18 @@ var allTabs = {
   get previews () this.container.getElementsByClassName("allTabs-preview"),
   get isOpen () this.panel.state == "open" || this.panel.state == "showing",
 
   init: function allTabs_init() {
     if (this._initiated)
       return;
     this._initiated = true;
 
+    tabPreviews.init();
+
     Array.forEach(gBrowser.tabs, function (tab) {
       this._addPreview(tab);
     }, this);
 
     gBrowser.tabContainer.addEventListener("TabOpen", this, false);
     gBrowser.tabContainer.addEventListener("TabAttrModified", this, false);
     gBrowser.tabContainer.addEventListener("TabMove", this, false);
     gBrowser.tabContainer.addEventListener("TabClose", this, false);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1302,17 +1302,16 @@ function delayedStartup(isLoadingBlank, 
   gBookmarkAllTabsHandler.init();
 
   // Attach a listener to watch for "command" events bubbling up from error
   // pages.  This lets us fix bugs like 401575 which require error page UI to
   // do privileged things, without letting error pages have any privilege
   // themselves.
   gBrowser.addEventListener("command", BrowserOnCommand, false);
 
-  tabPreviews.init();
   ctrlTab.readPref();
   gPrefService.addObserver(ctrlTab.prefName, ctrlTab, false);
   gPrefService.addObserver(allTabs.prefName, allTabs, false);
 
   // Initialize the microsummary service by retrieving it, prompting its factory
   // to create its singleton, whose constructor initializes the service.
   // Started 4 seconds after delayedStartup (before the livemarks service below).
   setTimeout(function() {
@@ -1368,17 +1367,16 @@ function delayedStartup(isLoadingBlank, 
 
 function BrowserShutdown()
 {
   if (Win7Features)
     Win7Features.onCloseWindow();
 
   gPrefService.removeObserver(ctrlTab.prefName, ctrlTab);
   gPrefService.removeObserver(allTabs.prefName, allTabs);
-  tabPreviews.uninit();
   ctrlTab.uninit();
   allTabs.uninit();
 
   CombinedStopReload.uninit();
 
   gGestureSupport.init(false);
 
   FullScreen.cleanup();
@@ -7336,16 +7334,20 @@ let gPrivateBrowsingUI = {
       let searchBox = BrowserSearch.searchBar.textbox;
       searchBox.reset();
       if (this._searchBarValue) {
         searchBox.value = this._searchBarValue;
         this._searchBarValue = null;
       }
     }
 
+    if (gURLBar) {
+      gURLBar.editor.transactionManager.clear();
+    }
+
     document.getElementById("menu_import").removeAttribute("disabled");
 
     // Re-enable the Clear Recent History... menu item on exit of PB mode
     // temporary fix until bug 463607 is fixed
     document.getElementById("Tools:Sanitize").removeAttribute("disabled");
 
     if (gFindBar) {
       let findbox = gFindBar.getElement("findbar-textbox");
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -95,20 +95,16 @@ function nsContextMenu(aXulMenu, aBrowse
   } catch (e) { }
 
   // Initialize new menu.
   this.initMenu(aXulMenu, aBrowser);
 }
 
 // Prototype for nsContextMenu "class."
 nsContextMenu.prototype = {
-  // onDestroy is a no-op at this point.
-  onDestroy: function () {
-  },
-
   // Initialize context menu.
   initMenu: function CM_initMenu(aPopup, aBrowser) {
     this.menu = aPopup;
     this.browser = aBrowser;
 
     this.isFrameImage = document.getElementById("isFrameImage");
 
     // Get contextual info.
--- a/browser/base/content/test/browser_bug484315.js
+++ b/browser/base/content/test/browser_bug484315.js
@@ -1,13 +1,11 @@
 function test() {
   var contentWin = window.open("about:blank", "", "width=100,height=100");
-  var enumerator = Cc["@mozilla.org/appshell/window-mediator;1"]
-                     .getService(Ci.nsIWindowMediator)
-                     .getEnumerator("navigator:browser");
+  var enumerator = Services.wm.getEnumerator("navigator:browser");
 
   while (enumerator.hasMoreElements()) {
     let win = enumerator.getNext();
     if (win.content == contentWin) {
       gPrefService.setBoolPref("browser.tabs.closeWindowWithLastTab", false);
       win.gBrowser.removeCurrentTab();
       ok(win.closed, "popup is closed");
 
--- a/browser/base/content/test/browser_getshortcutoruri.js
+++ b/browser/base/content/test/browser_getshortcutoruri.js
@@ -95,39 +95,35 @@ function test() {
   }
 
   cleanupKeywords();
 }
 
 var gBMFolder = null;
 var gAddedEngines = [];
 function setupKeywords() {
-  var searchService = Cc["@mozilla.org/browser/search-service;1"].
-                      getService(Ci.nsIBrowserSearchService);
   gBMFolder = Application.bookmarks.menu.addFolder("keyword-test");
   for each (var item in testData) {
     var data = item[0];
     if (data instanceof bmKeywordData) {
       var bm = gBMFolder.addBookmark(data.keyword, data.uri);
       bm.keyword = data.keyword;
       if (data.postData)
         bm.annotations.set("bookmarkProperties/POSTData", data.postData, Ci.nsIAnnotationService.EXPIRE_SESSION);
     }
 
     if (data instanceof searchKeywordData) {
-      searchService.addEngineWithDetails(data.keyword, "", data.keyword, "", data.method, data.uri.spec);
-      var addedEngine = searchService.getEngineByName(data.keyword);
+      Services.search.addEngineWithDetails(data.keyword, "", data.keyword, "", data.method, data.uri.spec);
+      var addedEngine = Services.search.getEngineByName(data.keyword);
       if (data.postData) {
         var [paramName, paramValue] = data.postData.split("=");
         addedEngine.addParam(paramName, paramValue, null);
       }
 
       gAddedEngines.push(addedEngine);
     }
   }
 }
 
 function cleanupKeywords() {
-  var searchService = Cc["@mozilla.org/browser/search-service;1"].
-                      getService(Ci.nsIBrowserSearchService);
   gBMFolder.remove();
-  gAddedEngines.map(searchService.removeEngine);
+  gAddedEngines.map(Services.search.removeEngine);
 }
--- a/browser/base/content/test/browser_pageInfo.js
+++ b/browser/base/content/test/browser_pageInfo.js
@@ -1,19 +1,17 @@
-var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
-
 function test() {
   waitForExplicitFinish();
 
   var pageInfo, atTest = 0;
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function () {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     pageInfo = BrowserPageInfo();
-    obs.addObserver(observer, "page-info-dialog-loaded", false);
+    Services.obs.addObserver(observer, "page-info-dialog-loaded", false);
   }, true);
   content.location =
     "https://example.com/browser/browser/base/content/test/feed_tab.html";
 
   function observer(win, topic, data) {
     if (topic != "page-info-dialog-loaded")
       return;
 
@@ -24,17 +22,17 @@ function test() {
         break;
       case 1:
         atTest++;
         pageInfo = win;
         testLockClick();
         break;
       case 2:
         atTest++;
-        obs.removeObserver(observer, "page-info-dialog-loaded");
+        Services.obs.removeObserver(observer, "page-info-dialog-loaded");
         testLockDoubleClick();
         break;
     }
   }
 
   function $(aId) { return pageInfo.document.getElementById(aId) };
 
   function handlePageInfo() {
@@ -71,19 +69,17 @@ function test() {
       var lockIcon = document.getElementById("security-button");
       EventUtils.synthesizeMouse(lockIcon, 0, 0, {clickCount: 1});
       EventUtils.synthesizeMouse(lockIcon, 0, 0, {clickCount: 2});
     }, false);
     pageInfo.close();
   }
 
   function testLockDoubleClick() {
-    var pageInfoDialogs = Cc["@mozilla.org/appshell/window-mediator;1"]
-                            .getService(Ci.nsIWindowMediator)
-                            .getEnumerator("Browser:page-info");
+    var pageInfoDialogs = Services.wm.getEnumerator("Browser:page-info");
     var i = 0;
     while (pageInfoDialogs.hasMoreElements()) {
       i++;
       pageInfo = pageInfoDialogs.getNext();
       pageInfo.close();
     }
     is(i, 1, "When the lock is clicked twice there should be only one page info dialog");
     gBrowser.removeCurrentTab();
--- a/browser/base/content/test/browser_pluginnotification.js
+++ b/browser/base/content/test/browser_pluginnotification.js
@@ -1,16 +1,15 @@
 const gTestRoot = "chrome://mochikit/content/browser/browser/base/content/test/";
 
 var gTestBrowser = null;
 var gNextTest = null;
 
 function get_test_plugin() {
-  var ph = Components.classes["@mozilla.org/plugin/host;1"]
-                     .getService(Components.interfaces.nsIPluginHost);
+  var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
   var tags = ph.getPluginTags();
   
   // Find the test plugin
   for (var i = 0; i < tags.length; i++) {
     if (tags[i].name == "Test Plug-in")
       return tags[i];
   }
 }
@@ -18,19 +17,17 @@ function get_test_plugin() {
 // This listens for the next opened window and checks it is of the right url.
 // opencallback is called when the new window is fully loaded
 // closecallback is called when the window is closed
 function WindowOpenListener(url, opencallback, closecallback) {
   this.url = url;
   this.opencallback = opencallback;
   this.closecallback = closecallback;
 
-  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-                     .getService(Components.interfaces.nsIWindowMediator);
-  wm.addListener(this);
+  Services.wm.addListener(this);
 }
 
 WindowOpenListener.prototype = {
   url: null,
   opencallback: null,
   closecallback: null,
   window: null,
   domwindow: null,
@@ -56,19 +53,17 @@ WindowOpenListener.prototype = {
                            .getInterface(Components.interfaces.nsIDOMWindowInternal);
     this.domwindow.addEventListener("load", this, false);
   },
 
   onCloseWindow: function(window) {
     if (this.window != window)
       return;
 
-    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-                       .getService(Components.interfaces.nsIWindowMediator);
-    wm.removeListener(this);
+    Services.wm.removeListener(this);
     this.opencallback = null;
     this.window = null;
     this.domwindow = null;
 
     // Let the window close complete
     executeSoon(this.closecallback);
     this.closecallback = null;
   }
--- a/browser/base/content/test/browser_sanitize-download-history.js
+++ b/browser/base/content/test/browser_sanitize-download-history.js
@@ -66,23 +66,21 @@ function test()
 
   function test_checkedAfterAddingDownload(aWin)
   {
     let doc = aWin.document;
     let downloads = doc.getElementById("downloads-checkbox");
     let history = doc.getElementById("history-checkbox");
 
     // Add download to DB
-    let ios = Cc["@mozilla.org/network/io-service;1"].
-              getService(Ci.nsIIOService);
     let file = Cc["@mozilla.org/file/directory_service;1"].
                getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
     file.append("satitize-dm-test.file");
     file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
-    let testPath = ios.newFileURI(file).spec;
+    let testPath = Services.io.newFileURI(file).spec;
     let data = {
       name: "381603.patch",
       source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520",
       target: testPath,
       startTime: 1180493839859230,
       endTime: 1180493839859239,
       state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
       currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0
@@ -137,42 +135,40 @@ function test()
   let dm = Cc["@mozilla.org/download-manager;1"].
            getService(Ci.nsIDownloadManager);
   let db = dm.DBConnection;
 
   // Empty any old downloads
   db.executeSimpleSQL("DELETE FROM moz_downloads");
 
   // Close the UI if necessary
-  let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-           getService(Ci.nsIWindowWatcher);
-  let win = ww.getWindowByName("Sanatize", null);
+  let win = Services.ww.getWindowByName("Sanatize", null);
   if (win && (win instanceof Ci.nsIDOMWindowInternal))
     win.close();
 
   // Start the test when the sanitize window loads
-  ww.registerNotification(function (aSubject, aTopic, aData) {
-    ww.unregisterNotification(arguments.callee);
+  Services.ww.registerNotification(function (aSubject, aTopic, aData) {
+    Services.ww.unregisterNotification(arguments.callee);
     aSubject.QueryInterface(Ci.nsIDOMEventTarget)
             .addEventListener("DOMContentLoaded", doTest, false);
   });
 
   // Let the methods that run onload finish before we test
   let doTest = function() setTimeout(function() {
-    let win = ww.getWindowByName("Sanitize", null)
+    let win = Services.ww.getWindowByName("Sanitize", null)
                 .QueryInterface(Ci.nsIDOMWindowInternal);
 
     for (let i = 0; i < tests.length; i++)
       tests[i](win);
 
     win.close();
     finish();
   }, 0);
  
   // Show the UI
-  ww.openWindow(window,
-                "chrome://browser/content/sanitize.xul",
-                "Sanitize",
-                "chrome,titlebar,centerscreen",
-                null);
+  Services.ww.openWindow(window,
+                         "chrome://browser/content/sanitize.xul",
+                         "Sanitize",
+                         "chrome,titlebar,centerscreen",
+                         null);
 
   waitForExplicitFinish();
 }
--- a/browser/base/content/test/browser_sanitizeDialog.js
+++ b/browser/base/content/test/browser_sanitizeDialog.js
@@ -46,25 +46,23 @@
  * test checks the UI of the dialog and makes sure it's correctly connected to
  * the sanitize timespan code.
  *
  * Some of this code, especially the history creation parts, was taken from
  * browser/base/content/test/browser_sanitize-timespans.js.
  */
 
 Cc["@mozilla.org/moz/jssubscript-loader;1"].
-  getService(Components.interfaces.mozIJSSubScriptLoader).
+  getService(Ci.mozIJSSubScriptLoader).
   loadSubScript("chrome://mochikit/content/MochiKit/packed.js");
 
 Cc["@mozilla.org/moz/jssubscript-loader;1"].
-  getService(Components.interfaces.mozIJSSubScriptLoader).
+  getService(Ci.mozIJSSubScriptLoader).
   loadSubScript("chrome://browser/content/sanitize.js");
 
-const winWatch = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-                 getService(Ci.nsIWindowWatcher);
 const dm = Cc["@mozilla.org/download-manager;1"].
            getService(Ci.nsIDownloadManager);
 const bhist = Cc["@mozilla.org/browser/global-history;2"].
               getService(Ci.nsIBrowserHistory);
 const formhist = Cc["@mozilla.org/satchel/form-history;1"].
                  getService(Ci.nsIFormHistory2);
 
 // Add tests here.  Each is a function that's called by doNextTest().
@@ -535,17 +533,17 @@ WindowHelper.prototype = {
    */
   open: function () {
     let wh = this;
 
     function windowObserver(aSubject, aTopic, aData) {
       if (aTopic != "domwindowopened")
         return;
 
-      winWatch.unregisterNotification(windowObserver);
+      Services.ww.unregisterNotification(windowObserver);
 
       var loaded = false;
       let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
 
       win.addEventListener("load", function onload(event) {
         win.removeEventListener("load", onload, false);
 
         if (win.name !== "SanitizeDialog")
@@ -592,22 +590,22 @@ WindowHelper.prototype = {
           catch (exc) {
             win.close();
             ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
             finish();
           }
         });
       }, false);
     }
-    winWatch.registerNotification(windowObserver);
-    winWatch.openWindow(null,
-                        "chrome://browser/content/sanitize.xul",
-                        "SanitizeDialog",
-                        "chrome,titlebar,dialog,centerscreen,modal",
-                        null);
+    Services.ww.registerNotification(windowObserver);
+    Services.ww.openWindow(null,
+                           "chrome://browser/content/sanitize.xul",
+                           "SanitizeDialog",
+                           "chrome,titlebar,dialog,centerscreen,modal",
+                           null);
   },
 
   /**
    * Selects a duration in the duration dropdown.
    *
    * @param aDurVal
    *        One of the Sanitizer.TIMESPAN_* values
    */
--- a/browser/base/content/test/browser_sanitizeDialog_treeView.js
+++ b/browser/base/content/test/browser_sanitizeDialog_treeView.js
@@ -45,25 +45,23 @@
  * test checks the UI of the dialog and makes sure it's correctly connected to
  * the sanitize timespan code.
  *
  * Some of this code, especially the history creation parts, was taken from
  * browser/base/content/test/browser_sanitize-timespans.js.
  */
 
 Cc["@mozilla.org/moz/jssubscript-loader;1"].
-  getService(Components.interfaces.mozIJSSubScriptLoader).
+  getService(Ci.mozIJSSubScriptLoader).
   loadSubScript("chrome://mochikit/content/MochiKit/packed.js");
 
 Cc["@mozilla.org/moz/jssubscript-loader;1"].
-  getService(Components.interfaces.mozIJSSubScriptLoader).
+  getService(Ci.mozIJSSubScriptLoader).
   loadSubScript("chrome://browser/content/sanitize.js");
 
-const winWatch = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-                 getService(Ci.nsIWindowWatcher);
 const dm = Cc["@mozilla.org/download-manager;1"].
            getService(Ci.nsIDownloadManager);
 const bhist = Cc["@mozilla.org/browser/global-history;2"].
               getService(Ci.nsIBrowserHistory);
 const formhist = Cc["@mozilla.org/satchel/form-history;1"].
                  getService(Ci.nsIFormHistory2);
 
 // Add tests here.  Each is a function that's called by doNextTest().
@@ -617,17 +615,17 @@ function ensureHistoryClearedState(aURIs
  * @param aOnloadCallback
  *        A function that will be called once the dialog has loaded
  */
 function openWindow(aOnloadCallback) {
   function windowObserver(aSubject, aTopic, aData) {
     if (aTopic != "domwindowopened")
       return;
 
-    winWatch.unregisterNotification(windowObserver);
+    Services.ww.unregisterNotification(windowObserver);
     let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
     win.addEventListener("load", function onload(event) {
       win.removeEventListener("load", onload, false);
       executeSoon(function () {
         // Some exceptions that reach here don't reach the test harness, but
         // ok()/is() do...
         try {
           aOnloadCallback(win);
@@ -636,22 +634,22 @@ function openWindow(aOnloadCallback) {
         catch (exc) {
           win.close();
           ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
           finish();
         }
       });
     }, false);
   }
-  winWatch.registerNotification(windowObserver);
-  winWatch.openWindow(null,
-                      "chrome://browser/content/sanitize.xul",
-                      "Sanitize",
-                      "chrome,titlebar,dialog,centerscreen,modal",
-                      null);
+  Services.ww.registerNotification(windowObserver);
+  Services.ww.openWindow(null,
+                         "chrome://browser/content/sanitize.xul",
+                         "Sanitize",
+                         "chrome,titlebar,dialog,centerscreen,modal",
+                         null);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 function test() {
   blankSlate();
   waitForExplicitFinish();
   // Kick off all the tests in the gAllTests array.
--- a/browser/components/places/tests/unit/head_bookmarks.js
+++ b/browser/components/places/tests/unit/head_bookmarks.js
@@ -32,321 +32,70 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-version(170);
-
-const NS_APP_USER_PROFILE_50_DIR = "ProfD";
-const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
-const NS_APP_BOOKMARKS_50_FILE = "BMarks";
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
 
-var Ci = Components.interfaces;
-var Cc = Components.classes;
-var Cr = Components.results;
+Cu.import("resource://gre/modules/Services.jsm");
 
-function LOG(aMsg) {
-  aMsg = ("*** PLACES TESTS: " + aMsg);
-  Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
-                                      logStringMessage(aMsg);
-  print(aMsg);
+// Import common head.
+let (commonFile = do_get_file("../../test_places/head_common.js", false)) {
+  let uri = Services.io.newFileURI(commonFile);
+  Services.scriptloader.loadSubScript(uri.spec, this);
 }
 
-var gProfD = do_get_profile();
-var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
-             getService(Ci.nsIProperties);
+// Put any other stuff relative to this test folder below.
+
 
-var dirProvider = {
-  getFile: function(prop, persistent) {
-    persistent.value = true;
-    if (prop == NS_APP_BOOKMARKS_50_FILE) {
-      var bmarks = gProfD.clone();
-      bmarks.append("bookmarks.html");
-      return bmarks;
-    }
-    return null;
-  },
-  QueryInterface: function(iid) {
-    if (iid.equals(Ci.nsIDirectoryServiceProvider) ||
-        iid.equals(Ci.nsISupports)) {
-      return this;
-    }
-    throw Cr.NS_ERROR_NO_INTERFACE;
-  }
-};
-dirSvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(dirProvider);
-
-var XULAppInfo = {
+// Needed by some test that relies on having an app  registered.
+let (XULAppInfo = {
   vendor: "Mozilla",
   name: "PlacesTest",
   ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}",
   version: "1",
   appBuildID: "2007010101",
   platformVersion: "",
   platformBuildID: "2007010101",
   inSafeMode: false,
   logConsoleErrors: true,
   OS: "XPCShell",
   XPCOMABI: "noarch-spidermonkey",
 
-  QueryInterface: function QueryInterface(iid) {
-    if (iid.equals(Ci.nsIXULAppInfo) ||
-        iid.equals(Ci.nsIXULRuntime) ||
-        iid.equals(Ci.nsISupports))
-      return this;
-    throw Cr.NS_ERROR_NO_INTERFACE;
-  }
-};
-
-var XULAppInfoFactory = {
-  createInstance: function (outer, iid) {
-    if (outer != null)
-      throw Cr.NS_ERROR_NO_AGGREGATION;
-    return XULAppInfo.QueryInterface(iid);
-  }
-};
-
-var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
-registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"),
-                          "XULAppInfo", "@mozilla.org/xre/app-info;1",
-                          XULAppInfoFactory);
-
-var iosvc = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
-
-function uri(spec) {
-  return iosvc.newURI(spec, null, null);
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIXULAppInfo,
+    Ci.nsIXULRuntime,
+  ])
+}) {
+  let XULAppInfoFactory = {
+    createInstance: function (outer, iid) {
+      if (outer != null)
+        throw Cr.NS_ERROR_NO_AGGREGATION;
+      return XULAppInfo.QueryInterface(iid);
+    }
+  };
+  let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+  registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"),
+                            "XULAppInfo", "@mozilla.org/xre/app-info;1",
+                            XULAppInfoFactory);
 }
 
-/*
- * Removes all bookmarks and checks for correct cleanup
- */
-function remove_all_bookmarks() {
-  var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
-           getService(Ci.nsINavBookmarksService);
-  // Clear all bookmarks.
-  bs.removeFolderChildren(bs.bookmarksMenuFolder);
-  bs.removeFolderChildren(bs.toolbarFolder);
-  bs.removeFolderChildren(bs.unfiledBookmarksFolder);
 
-  // Check for correct cleanup.
-  dump_table("moz_bookmarks");
-  dump_table("moz_places");
-  check_no_bookmarks()
+const FILENAME_BOOKMARKS_HTML = "bookmarks.html";
+let (backup_date = new Date().toLocaleFormat("%Y-%m-%d")) {
+  const FILENAME_BOOKMARKS_JSON = "bookmarks-" + backup_date + ".json";
 }
 
-/*
- * Checks that we don't have any bookmark
- */
-function check_no_bookmarks() {
-  var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
-           getService(Ci.nsINavHistoryService);
-  var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
-           getService(Ci.nsINavBookmarksService);
-  var query = hs.getNewQuery();
-  query.setFolders([bs.toolbarFolder, bs.bookmarksMenuFolder, bs.unfiledBookmarksFolder], 3);
-  var options = hs.getNewQueryOptions();
-  options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
-  var result = hs.executeQuery(query, options);
-  var root = result.root;
-  root.containerOpen = true;
-  var cc = root.childCount;
-  // Dump contents if found.
-  for (var i = 0; i < cc ; i++) {
-    var node = root.getChild(i);
-    print("Found unexpected child at " + i + ": " + node.title);
-  }
-  do_check_eq(cc, 0);
-  root.containerOpen = false;
-}
-
-let gTestDir = do_get_cwd();
-const FILENAME_BOOKMARKS_HTML = "bookmarks.html";
-let backup_date = new Date().toLocaleFormat("%Y-%m-%d");
-const FILENAME_BOOKMARKS_JSON = "bookmarks-" + backup_date + ".json";
-
 // Smart bookmarks constants.
 const SMART_BOOKMARKS_VERSION = 2;
 const SMART_BOOKMARKS_ON_TOOLBAR = 1;
 const SMART_BOOKMARKS_ON_MENU = 3; // Takes in count the additional separator.
 
 // Default bookmarks constants.
 const DEFAULT_BOOKMARKS_ON_TOOLBAR = 2;
 const DEFAULT_BOOKMARKS_ON_MENU = 3;
-
-/**
- * Creates a bookmarks.html file in the profile folder from a given source file.
- *
- * @param aFilename
- *        Name of the file to copy to the profile folder.  This file must
- *        exist in the directory that contains the test files.
- *
- * @return nsIFile object for the file.
- */
-function create_bookmarks_html(aFilename) {
-  if (!aFilename)
-    do_throw("you must pass a filename to create_bookmarks_html function");
-  remove_bookmarks_html();
-  let bookmarksHTMLFile = gTestDir.clone();
-  bookmarksHTMLFile.append(aFilename);
-  do_check_true(bookmarksHTMLFile.exists());
-  bookmarksHTMLFile.copyTo(gProfD, FILENAME_BOOKMARKS_HTML);
-  let profileBookmarksHTMLFile = gProfD.clone();
-  profileBookmarksHTMLFile.append(FILENAME_BOOKMARKS_HTML);
-  do_check_true(profileBookmarksHTMLFile.exists());
-  return profileBookmarksHTMLFile;
-}
-
-/**
- * Remove bookmarks.html file from the profile folder.
- */
-function remove_bookmarks_html() {
-  let profileBookmarksHTMLFile = gProfD.clone();
-  profileBookmarksHTMLFile.append(FILENAME_BOOKMARKS_HTML);
-  if (profileBookmarksHTMLFile.exists()) {
-    profileBookmarksHTMLFile.remove(false);
-    do_check_false(profileBookmarksHTMLFile.exists());
-  }
-}
-
-/**
- * Check bookmarks.html file exists in the profile folder.
- *
- * @return nsIFile object for the file.
- */
-function check_bookmarks_html() {
-  let profileBookmarksHTMLFile = gProfD.clone();
-  profileBookmarksHTMLFile.append(FILENAME_BOOKMARKS_HTML);
-  do_check_true(profileBookmarksHTMLFile.exists());
-  return profileBookmarksHTMLFile;
-}
-
-/**
- * Creates a JSON backup in the profile folder folder from a given source file.
- *
- * @param aFilename
- *        Name of the file to copy to the profile folder.  This file must
- *        exist in the directory that contains the test files.
- *
- * @return nsIFile object for the file.
- */
-function create_JSON_backup(aFilename) {
-  if (!aFilename)
-    do_throw("you must pass a filename to create_JSON_backup function");
-  remove_all_JSON_backups();
-  let bookmarksBackupDir = gProfD.clone();
-  bookmarksBackupDir.append("bookmarkbackups");
-  if (!bookmarksBackupDir.exists()) {
-    bookmarksBackupDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0777);
-    do_check_true(bookmarksBackupDir.exists());
-  }
-  let bookmarksJSONFile = gTestDir.clone();
-  bookmarksJSONFile.append(aFilename);
-  do_check_true(bookmarksJSONFile.exists());
-  bookmarksJSONFile.copyTo(bookmarksBackupDir, FILENAME_BOOKMARKS_JSON);
-  let profileBookmarksJSONFile = bookmarksBackupDir.clone();
-  profileBookmarksJSONFile.append(FILENAME_BOOKMARKS_JSON);
-  do_check_true(profileBookmarksJSONFile.exists());
-  return profileBookmarksJSONFile;
-}
-
-/**
- * Remove bookmarksbackup dir and all backups from the profile folder.
- */
-function remove_all_JSON_backups() {
-  let bookmarksBackupDir = gProfD.clone();
-  bookmarksBackupDir.append("bookmarkbackups");
-  if (bookmarksBackupDir.exists()) {
-    bookmarksBackupDir.remove(true);
-    do_check_false(bookmarksBackupDir.exists());
-  }
-}
-
-/**
- * Check a JSON backup file for today exists in the profile folder.
- *
- * @return nsIFile object for the file.
- */
-function check_JSON_backup() {
-  let profileBookmarksJSONFile = gProfD.clone();
-  profileBookmarksJSONFile.append("bookmarkbackups");
-  profileBookmarksJSONFile.append(FILENAME_BOOKMARKS_JSON);
-  do_check_true(profileBookmarksJSONFile.exists());
-  return profileBookmarksJSONFile;
-}
-
-/**
- * Dumps the rows of a table out to the console.
- *
- * @param aName
- *        The name of the table or view to output.
- */
-function dump_table(aName)
-{
-  let db = Cc["@mozilla.org/browser/nav-history-service;1"].
-           getService(Ci.nsPIPlacesDatabase).
-           DBConnection;
-  let stmt = db.createStatement("SELECT * FROM " + aName);
-
-  dump("\n*** Printing data from " + aName + ":\n");
-  let count = 0;
-  while (stmt.executeStep()) {
-    let columns = stmt.numEntries;
-
-    if (count == 0) {
-      // print the column names
-      for (let i = 0; i < columns; i++)
-        dump(stmt.getColumnName(i) + "\t");
-      dump("\n");
-    }
-
-    // print the row
-    for (let i = 0; i < columns; i++) {
-      switch (stmt.getTypeOfIndex(i)) {
-        case Ci.mozIStorageValueArray.VALUE_TYPE_NULL:
-          dump("NULL\t");
-          break;
-        case Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER:
-          dump(stmt.getInt64(i) + "\t");
-          break;
-        case Ci.mozIStorageValueArray.VALUE_TYPE_FLOAT:
-          dump(stmt.getDouble(i) + "\t");
-          break;
-        case Ci.mozIStorageValueArray.VALUE_TYPE_TEXT:
-          dump(stmt.getString(i) + "\t");
-          break;
-      }
-    }
-    dump("\n");
-
-    count++;
-  }
-  dump("*** There were a total of " + count + " rows of data.\n\n");
-
-  stmt.reset();
-  stmt.finalize();
-  stmt = null;
-}
-
-/**
- * Flushes any events in the event loop of the main thread.
- */
-function flush_main_thread_events()
-{
-  let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
-  while (tm.mainThread.hasPendingEvents())
-    tm.mainThread.processNextEvent(false);
-}
-
-// These tests are known to randomly fail due to bug 507790 when database
-// flushes are active, so we turn off syncing for them.
-let randomFailingSyncTests = [
-  "test_browserGlue_smartBookmarks.js",
-];
-let currentTestFilename = do_get_file(_TEST_FILE[0], true).leafName;
-if (randomFailingSyncTests.indexOf(currentTestFilename) != -1) {
-  print("Test " + currentTestFilename + " is known random due to bug 507790, disabling PlacesDBFlush component.");
-  let sync = Cc["@mozilla.org/places/sync;1"].getService(Ci.nsIObserver);
-  sync.observe(null, "places-debug-stop-sync", null);
-}
--- a/browser/components/places/tests/unit/test_384370.js
+++ b/browser/components/places/tests/unit/test_384370.js
@@ -68,17 +68,17 @@ function run_test() {
   // avoid creating the places smart folder during tests
   Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch).
   setIntPref("browser.places.smartBookmarksVersion", -1);
 
   // file pointer to legacy bookmarks file
   //var bookmarksFileOld = do_get_file("bookmarks.large.html");
   var bookmarksFileOld = do_get_file("bookmarks.preplaces.html");
   // file pointer to a new places-exported json file
-  var jsonFile = dirSvc.get("ProfD", Ci.nsILocalFile);
+  var jsonFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
   jsonFile.append("bookmarks.exported.json");
 
   // create bookmarks.exported.json
   if (jsonFile.exists())
     jsonFile.remove(false);
   jsonFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0600);
   if (!jsonFile.exists())
     do_throw("couldn't create file: bookmarks.exported.json");
--- a/browser/components/places/tests/unit/test_457441-import-export-corrupt-bookmarks-html.js
+++ b/browser/components/places/tests/unit/test_457441-import-export-corrupt-bookmarks-html.js
@@ -85,17 +85,17 @@ function run_test() {
                                         uri("http://test.mozilla.org"),
                                         bs.DEFAULT_INDEX, "We love belugas");
   var stmt = dbConn.createStatement("UPDATE moz_bookmarks SET fk = NULL WHERE id = :itemId");
   stmt.params.itemId = corruptItemId;
   stmt.execute();
   stmt.finalize();
 
   // Export bookmarks
-  var bookmarksFile = dirSvc.get("ProfD", Ci.nsILocalFile);
+  var bookmarksFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
   bookmarksFile.append("bookmarks.exported.html");
   if (bookmarksFile.exists())
     bookmarksFile.remove(false);
   bookmarksFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0600);
   if (!bookmarksFile.exists())
     do_throw("couldn't create file: bookmarks.exported.html");
   try {
     ies.exportHTMLToFile(bookmarksFile);
--- a/browser/components/places/tests/unit/test_bookmarksRestoreNotification.js
+++ b/browser/components/places/tests/unit/test_bookmarksRestoreNotification.js
@@ -110,17 +110,17 @@ var tests = [
 
   {
     desc:       "JSON restore: nonexisting file should fail",
     currTopic:  NSIOBSERVER_TOPIC_BEGIN,
     finalTopic: NSIOBSERVER_TOPIC_FAILED,
     data:       NSIOBSERVER_DATA_JSON,
     folderId:   null,
     run:        function () {
-      this.file = dirSvc.get("ProfD", Ci.nsILocalFile);
+      this.file = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
       this.file.append("this file doesn't exist because nobody created it");
       try {
         PlacesUtils.restoreBookmarksFromJSONFile(this.file);
         do_throw("  Restore should have failed");
       }
       catch (e) {}
     }
   },
@@ -164,17 +164,17 @@ var tests = [
 
   {
     desc:       "HTML restore: nonexisting file should fail",
     currTopic:  NSIOBSERVER_TOPIC_BEGIN,
     finalTopic: NSIOBSERVER_TOPIC_FAILED,
     data:       NSIOBSERVER_DATA_HTML,
     folderId:   null,
     run:        function () {
-      this.file = dirSvc.get("ProfD", Ci.nsILocalFile);
+      this.file = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
       this.file.append("this file doesn't exist because nobody created it");
       try {
         importer.importHTMLFromFile(this.file, false);
         do_throw("  Restore should have failed");
       }
       catch (e) {}
     }
   },
@@ -218,17 +218,17 @@ var tests = [
 
   {
     desc:       "HTML initial restore: nonexisting file should fail",
     currTopic:  NSIOBSERVER_TOPIC_BEGIN,
     finalTopic: NSIOBSERVER_TOPIC_FAILED,
     data:       NSIOBSERVER_DATA_HTML_INIT,
     folderId:   null,
     run:        function () {
-      this.file = dirSvc.get("ProfD", Ci.nsILocalFile);
+      this.file = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
       this.file.append("this file doesn't exist because nobody created it");
       try {
         importer.importHTMLFromFile(this.file, true);
         do_throw("  Restore should have failed");
       }
       catch (e) {}
     }
   },
@@ -279,17 +279,17 @@ var tests = [
   },
 
   {
     desc:       "HTML restore into folder: nonexisting file should fail",
     currTopic:  NSIOBSERVER_TOPIC_BEGIN,
     finalTopic: NSIOBSERVER_TOPIC_FAILED,
     data:       NSIOBSERVER_DATA_HTML,
     run:        function () {
-      this.file = dirSvc.get("ProfD", Ci.nsILocalFile);
+      this.file = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
       this.file.append("this file doesn't exist because nobody created it");
       this.folderId = bmsvc.createFolder(bmsvc.unfiledBookmarksFolder,
                                          "test folder",
                                          bmsvc.DEFAULT_INDEX);
       print("  Sanity check: createFolder() should have succeeded");
       do_check_true(this.folderId > 0);
       try {
         importer.importHTMLFromFileToFolder(this.file, this.folderId, false);
@@ -402,17 +402,17 @@ function checkBookmarksExist() {
 /**
  * Creates an nsILocalFile in the profile directory.
  *
  * @param  aBasename
  *         e.g., "foo.txt" in the path /some/long/path/foo.txt
  * @return The nsILocalFile
  */
 function createFile(aBasename) {
-  var file = dirSvc.get("ProfD", Ci.nsILocalFile);
+  var file = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
   file.append(aBasename);
   if (file.exists())
     file.remove(false);
   file.create(file.NORMAL_FILE_TYPE, 0666);
   if (!file.exists())
     do_throw("Couldn't create file: " + aBasename);
   return file;
 }
--- a/browser/components/places/tests/unit/test_bookmarks_html.js
+++ b/browser/components/places/tests/unit/test_bookmarks_html.js
@@ -99,17 +99,17 @@ function run_test() {
 
   // avoid creating the places smart folder during tests
   Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch).
   setIntPref("browser.places.smartBookmarksVersion", -1);
 
   // file pointer to legacy bookmarks file
   var bookmarksFileOld = do_get_file("bookmarks.preplaces.html");
   // file pointer to a new places-exported bookmarks file
-  var bookmarksFileNew = dirSvc.get("ProfD", Ci.nsILocalFile);
+  var bookmarksFileNew = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
   bookmarksFileNew.append("bookmarks.exported.html");
 
   // create bookmarks.exported.html
   if (bookmarksFileNew.exists())
     bookmarksFileNew.remove(false);
   bookmarksFileNew.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0600);
   if (!bookmarksFileNew.exists())
     do_throw("couldn't create file: bookmarks.exported.html");
--- a/browser/components/places/tests/unit/test_browserGlue_distribution.js
+++ b/browser/components/places/tests/unit/test_browserGlue_distribution.js
@@ -42,24 +42,23 @@
  * bookmark on init, we should not try to import.
  */
 
 const PREF_SMART_BOOKMARKS_VERSION = "browser.places.smartBookmarksVersion";
 const PREF_BMPROCESSED = "distribution.516444.bookmarksProcessed";
 const PREF_DISTRIBUTION_ID = "distribution.id";
 
 const TOPIC_FINAL_UI_STARTUP = "final-ui-startup";
-const TOPIC_PLACES_INIT_COMPLETE = "places-init-complete";
 const TOPIC_CUSTOMIZATION_COMPLETE = "distribution-customization-complete";
 
 function run_test() {
   do_test_pending();
 
   // Copy distribution.ini file to our app dir.
-  let distroDir = dirSvc.get("XCurProcD", Ci.nsIFile);
+  let distroDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
   distroDir.append("distribution");
   let iniFile = distroDir.clone();
   iniFile.append("distribution.ini");
   if (iniFile.exists()) {
     iniFile.remove(false);
     print("distribution.ini already exists, did some test forget to cleanup?");
   }
 
@@ -92,23 +91,23 @@ function run_test() {
     observe: function(aSubject, aTopic, aData) {
       os.removeObserver(this, TOPIC_PLACES_INIT_COMPLETE);
 
       // Simulate browser startup.
       bg.QueryInterface(Ci.nsIObserver).observe(null,
                                                 TOPIC_FINAL_UI_STARTUP,
                                                 null);
       // Test will continue on customization complete notification.
-      let observer = {
+      let cObserver = {
         observe: function(aSubject, aTopic, aData) {
           os.removeObserver(this, TOPIC_CUSTOMIZATION_COMPLETE);
-          continue_test();
+          do_execute_soon(continue_test);
         }
       }
-      os.addObserver(observer, TOPIC_CUSTOMIZATION_COMPLETE, false);
+      os.addObserver(cObserver, TOPIC_CUSTOMIZATION_COMPLETE, false);
     }
   }
   os.addObserver(observer, TOPIC_PLACES_INIT_COMPLETE, false);
 }
 
 function continue_test() {
   let bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
            getService(Ci.nsINavBookmarksService);
@@ -140,14 +139,14 @@ function continue_test() {
   do_check_eq(ps.getCharPref(PREF_DISTRIBUTION_ID), "516444");
 
   do_test_finished();
 }
 
 do_register_cleanup(function() {
   // Remove the distribution file, even if the test failed, otherwise all
   // next tests will import it.
-  let iniFile = dirSvc.get("XCurProcD", Ci.nsIFile);
+  let iniFile = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
   iniFile.append("distribution");
   iniFile.append("distribution.ini");
   iniFile.remove(false);
   do_check_false(iniFile.exists());
 });
--- a/browser/components/places/tests/unit/test_browserGlue_prefs.js
+++ b/browser/components/places/tests/unit/test_browserGlue_prefs.js
@@ -58,18 +58,17 @@ let ps = Cc["@mozilla.org/preferences-se
          getService(Ci.nsIPrefBranch);
 let os = Cc["@mozilla.org/observer-service;1"].
          getService(Ci.nsIObserverService);
 
 const PREF_IMPORT_BOOKMARKS_HTML = "browser.places.importBookmarksHTML";
 const PREF_RESTORE_DEFAULT_BOOKMARKS = "browser.bookmarks.restore_default_bookmarks";
 const PREF_SMART_BOOKMARKS_VERSION = "browser.places.smartBookmarksVersion";
 const PREF_AUTO_EXPORT_HTML = "browser.bookmarks.autoExportHTML";
-const TOPIC_PLACES_INIT_COMPLETE = "places-init-complete";
-const TOPIC_PLACES_DATABASE_LOCKED = "places-database-locked";
+
 let tests = [];
 //------------------------------------------------------------------------------
 
 tests.push({
   description: "Import from bookmarks.html if importBookmarksHTML is true.",
   exec: function() {
     // Sanity check: we should not have any bookmark on the toolbar.
     do_check_eq(bs.getIdForItemAt(bs.toolbarFolder, 0), -1);
--- a/browser/components/preferences/tests/browser_bug410900.js
+++ b/browser/components/preferences/tests/browser_bug410900.js
@@ -11,27 +11,24 @@ function test() {
               getService(Ci.nsIExternalProtocolService);
   var info = extps.getProtocolHandlerInfo("apppanetest");
   info.possibleApplicationHandlers.appendElement(handler, false);
 
   var hserv = Cc["@mozilla.org/uriloader/handler-service;1"].
               getService(Ci.nsIHandlerService);
   hserv.store(info);
 
-  var obs = Cc["@mozilla.org/observer-service;1"].
-            getService(Ci.nsIObserverService);
-
   function observer(win, topic, data) {
     if (topic != "app-handler-pane-loaded")
       return;
 
-    obs.removeObserver(observer, "app-handler-pane-loaded");
+    Services.obs.removeObserver(observer, "app-handler-pane-loaded");
     runTest(win);
   }
-  obs.addObserver(observer, "app-handler-pane-loaded", false);
+  Services.obs.addObserver(observer, "app-handler-pane-loaded", false);
 
   openDialog("chrome://browser/content/preferences/preferences.xul", "Preferences",
              "chrome,titlebar,toolbar,centerscreen,dialog=no", "paneApplications");
 }
 
 function runTest(win) {
   var sel = win.document.documentElement.getAttribute("lastSelected");
   ok(sel == "paneApplications", "Specified pane was opened");
--- a/browser/components/preferences/tests/privacypane_tests.js
+++ b/browser/components/preferences/tests/privacypane_tests.js
@@ -31,38 +31,36 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  * 
  * ***** END LICENSE BLOCK ***** */
 
 function runTestOnPrivacyPrefPane(testFunc) {
-  let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-           getService(Ci.nsIWindowWatcher);
   let observer = {
     observe: function(aSubject, aTopic, aData) {
       if (aTopic == "domwindowopened") {
-        ww.unregisterNotification(this);
+        Services.ww.unregisterNotification(this);
 
         let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
         win.addEventListener("load", function() {
           win.removeEventListener("load", arguments.callee, false);
           testFunc(dialog.document.defaultView);
 
-          ww.registerNotification(observer);
+          Services.ww.registerNotification(observer);
           dialog.close();
         }, false);
       } else if (aTopic == "domwindowclosed") {
-        ww.unregisterNotification(this);
+        Services.ww.unregisterNotification(this);
         testRunner.runNext();
       }
     }
   };
-  ww.registerNotification(observer);
+  Services.ww.registerNotification(observer);
 
   let dialog = openDialog("chrome://browser/content/preferences/preferences.xul", "Preferences",
                           "chrome,titlebar,toolbar,centerscreen,dialog=no", "panePrivacy");
 }
 
 function controlChanged(element) {
   element.doCommand();
 }
@@ -523,30 +521,28 @@ function reset_preferences(win) {
   let prefs = win.document.getElementsByTagName("preference");
   for (let i = 0; i < prefs.length; ++i)
     if (prefs[i].hasUserValue)
       prefs[i].reset();
 }
 
 let testRunner;
 function run_test_subset(subset) {
-  let psvc = Cc["@mozilla.org/preferences-service;1"].
-             getService(Ci.nsIPrefBranch);
-  let instantApplyOrig = psvc.getBoolPref("browser.preferences.instantApply");
-  psvc.setBoolPref("browser.preferences.instantApply", true);
+  let instantApplyOrig = Services.prefs.getBoolPref("browser.preferences.instantApply");
+  Services.prefs.setBoolPref("browser.preferences.instantApply", true);
 
   waitForExplicitFinish();
 
   testRunner = {
     tests: subset,
     counter: 0,
     runNext: function() {
       if (this.counter == this.tests.length) {
         // cleanup
-        psvc.setBoolPref("browser.preferences.instantApply", instantApplyOrig);
+        Services.prefs.setBoolPref("browser.preferences.instantApply", instantApplyOrig);
         finish();
       } else {
         let self = this;
         setTimeout(function() {
           runTestOnPrivacyPrefPane(self.tests[self.counter++]);
         }, 0);
       }
     }
--- a/browser/components/privatebrowsing/test/browser/Makefile.in
+++ b/browser/components/privatebrowsing/test/browser/Makefile.in
@@ -67,16 +67,17 @@ include $(topsrcdir)/config/rules.mk
 		browser_privatebrowsing_protocolhandler.js \
 		browser_privatebrowsing_protocolhandler_page.html \
 		browser_privatebrowsing_searchbar.js \
 		browser_privatebrowsing_sslsite_transition.js \
 		browser_privatebrowsing_theming.js \
 		browser_privatebrowsing_transition.js \
 		browser_privatebrowsing_ui.js \
 		browser_privatebrowsing_urlbarfocus.js \
+		browser_privatebrowsing_urlbarundo.js \
 		browser_privatebrowsing_viewsource.js \
 		browser_privatebrowsing_windowtitle.js \
 		browser_privatebrowsing_windowtitle_page.html \
 		browser_privatebrowsing_zoom.js \
 		browser_privatebrowsing_zoomrestore.js \
 		ctxmenu.html \
 		ctxmenu-image.png \
 		popup.html \
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_beforeunload.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_beforeunload.js
@@ -52,19 +52,17 @@ function test() {
     let dialogWin = aSubject.QueryInterface(Ci.nsIDOMWindow);
     confirmCalls++;
     if (acceptDialog-- > 0)
       dialogWin.document.documentElement.getButton("accept").click();
     else if (rejectDialog-- > 0)
       dialogWin.document.documentElement.getButton("cancel").click();
   }
 
-  Cc["@mozilla.org/observer-service;1"]
-    .getService(Ci.nsIObserverService)
-    .addObserver(promptObserver, "common-dialog-loaded", false);
+  Services.obs.addObserver(promptObserver, "common-dialog-loaded", false);
 
   waitForExplicitFinish();
   let browser1 = gBrowser.getBrowserForTab(gBrowser.addTab());
   browser1.addEventListener("load", function() {
     browser1.removeEventListener("load", arguments.callee, true);
 
     let browser2 = gBrowser.getBrowserForTab(gBrowser.addTab());
     browser2.addEventListener("load", function() {
@@ -145,19 +143,17 @@ function test() {
                  "The last tab should be the same one we opened");
               is(acceptDialog, 0, "Two confirm dialogs should have been accepted");
               is(acceptDialog, 0, "Two prompts should have been raised");
 
               acceptDialog = 2;
               gBrowser.removeTab(gBrowser.tabContainer.lastChild);
               gBrowser.removeTab(gBrowser.tabContainer.lastChild);
 
-              Cc["@mozilla.org/observer-service;1"]
-                .getService(Ci.nsIObserverService)
-                .removeObserver(promptObserver, "common-dialog-loaded", false);
+              Services.obs.removeObserver(promptObserver, "common-dialog-loaded", false);
               finish();
             }
             for (let i = 0; i < gBrowser.browsers.length; ++i)
               gBrowser.browsers[i].addEventListener("load", waitForLoad, true);
           }, true);
           gBrowser.selectedBrowser.loadURI(TEST_PAGE_2);
         }, true);
         gBrowser.selectedBrowser.loadURI(TEST_PAGE_1);
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_certexceptionsui.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_certexceptionsui.js
@@ -58,20 +58,18 @@ function test() {
   function step1() {
     let params = {
       exceptionAdded : false,
       location: INVALID_CERT_LOCATION,
       handlePrivateBrowsing : true,
       prefetchCert: true,
     };
     function testCheckbox() {
-      let obsSvc = Cc["@mozilla.org/observer-service;1"].
-                   getService(Ci.nsIObserverService);
-      obsSvc.addObserver(function (aSubject, aTopic, aData) {
-        obsSvc.removeObserver(arguments.callee, "cert-exception-ui-ready", false);
+      Services.obs.addObserver(function (aSubject, aTopic, aData) {
+        Services.obs.removeObserver(arguments.callee, "cert-exception-ui-ready", false);
         ok(win.gCert, "The certificate information should be available now");
 
         let checkbox = win.document.getElementById("permanent");
         ok(checkbox.hasAttribute("disabled"),
           "the permanent checkbox should be disabled when handling the private browsing mode");
         ok(!checkbox.hasAttribute("checked"),
           "the permanent checkbox should not be checked when handling the private browsing mode");
         win.close();
@@ -85,20 +83,18 @@ function test() {
   // Test the certificate excetions dialog as it is invoked from the Preferences dialog
   function step2() {
     let params = {
       exceptionAdded : false,
       location: INVALID_CERT_LOCATION,
       prefetchCert: true,
     };
     function testCheckbox() {
-      let obsSvc = Cc["@mozilla.org/observer-service;1"].
-                   getService(Ci.nsIObserverService);
-      obsSvc.addObserver(function (aSubject, aTopic, aData) {
-        obsSvc.removeObserver(arguments.callee, "cert-exception-ui-ready", false);
+      Services.obs.addObserver(function (aSubject, aTopic, aData) {
+        Services.obs.removeObserver(arguments.callee, "cert-exception-ui-ready", false);
         ok(win.gCert, "The certificate information should be available now");
 
         let checkbox = win.document.getElementById("permanent");
         ok(!checkbox.hasAttribute("disabled"),
           "the permanent checkbox should not be disabled when not handling the private browsing mode");
         ok(checkbox.hasAttribute("checked"),
           "the permanent checkbox should be checked when not handling the private browsing mode");
         win.close();
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_commandline_toggle.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_commandline_toggle.js
@@ -37,18 +37,16 @@
 
 // This test makes sure that private browsing toggles correctly via the -private
 // command line argument.
 
 function test() {
   // initialization
   let pb = Cc["@mozilla.org/privatebrowsing;1"].
            getService(Ci.nsIPrivateBrowsingService);
-  let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-           getService(Ci.nsIWindowWatcher);
 
   waitForExplicitFinish();
 
   function simulatePrivateCommandLineArgument() {
     function testprivatecl() {
     }
 
     testprivatecl.prototype = {
@@ -115,17 +113,17 @@ function test() {
       let handler = Cc[contractID].getService(Ci.nsICommandLineHandler);
       handler.handle(testcl);
     }
   }
 
   function observer(aSubject, aTopic, aData) {
     isnot(aTopic, "domwindowopened", "The -private-toggle argument should be silent");
   }
-  ww.registerNotification(observer);
+  Services.ww.registerNotification(observer);
 
   let tab = gBrowser.selectedTab;
   let browser = gBrowser.getBrowserForTab(tab);
   browser.addEventListener("load", function () {
     browser.removeEventListener("load", arguments.callee, true);
     ok(!pb.privateBrowsingEnabled, "The private browsing mode should not be started");
     is(browser.contentWindow.location, "about:", "The correct page has been loaded");
 
@@ -144,15 +142,15 @@ function test() {
       browser.addEventListener("load", function() {
         browser.removeEventListener("load", arguments.callee, true);
         ok(!pb.privateBrowsingEnabled, "The private browsing mode should be stopped");
         is(browser.contentWindow.location, "about:",
            "about: should now be loaded");
 
         let newTab = gBrowser.addTab();
         gBrowser.removeTab(tab);
-        ww.unregisterNotification(observer);
+        Services.ww.unregisterNotification(observer);
         finish();
       }, true);
     }, true);
   }, true);
   browser.loadURI("about:");
 }
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cookieacceptdialog.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cookieacceptdialog.js
@@ -37,29 +37,27 @@
 
 // This test makes sure that private browsing mode disables the "remember"
 // option in the cookie accept dialog.
 
 function test() {
   // initialization
   let pb = Cc["@mozilla.org/privatebrowsing;1"].
            getService(Ci.nsIPrivateBrowsingService);
-  let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-           getService(Ci.nsIWindowWatcher);
   let cp = Cc["@mozilla.org/embedcomp/cookieprompt-service;1"].
            getService(Ci.nsICookiePromptService);
 
   waitForExplicitFinish();
 
   function checkRememberOption(expectedDisabled, callback) {
     function observer(aSubject, aTopic, aData) {
       if (aTopic != "domwindowopened")
         return;
 
-      ww.unregisterNotification(observer);
+      Services.ww.unregisterNotification(observer);
       let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
       win.addEventListener("load", function onLoad(event) {
         win.removeEventListener("load", onLoad, false);
 
         executeSoon(function () {
           let doc = win.document;
           let remember = doc.getElementById("persistDomainAcceptance");
           ok(remember, "The remember checkbox should exist");
@@ -71,17 +69,17 @@ function test() {
             ok(!remember.hasAttribute("disabled"),
                "The checkbox should not be disabled");
 
           win.close();
           callback();
         });
       }, false);
     }
-    ww.registerNotification(observer);
+    Services.ww.registerNotification(observer);
 
     let remember = {};
     const time = (new Date("Jan 1, 2030")).getTime() / 1000;
     let cookie = {
       name: "foo",
       value: "bar",
       isDomain: true,
       host: "mozilla.org",
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadmonitor.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadmonitor.js
@@ -41,18 +41,16 @@
 function test() {
   // initialization
   let pb = Cc["@mozilla.org/privatebrowsing;1"].
            getService(Ci.nsIPrivateBrowsingService);
   let dm = Cc["@mozilla.org/download-manager;1"].
            getService(Ci.nsIDownloadManager);
   if (!gDownloadMgr)
     gDownloadMgr = dm;
-  let iosvc = Cc["@mozilla.org/network/io-service;1"].
-              getService(Ci.nsIIOService);
   let panel = document.getElementById("download-monitor");
   waitForExplicitFinish();
 
   // Add a new download
   addDownload(dm, {
     resultFileName: "pbtest-1",
     downloadName: "PB Test 1"
   });
@@ -154,15 +152,13 @@ function addDownload(dm, aParams)
   aParams.runBeforeStart.call(undefined, dl);
 
   persist.progressListener = dl.QueryInterface(Ci.nsIWebProgressListener);
   persist.saveURI(dl.source, null, null, null, null, dl.targetFile);
 
   return dl;
 }
 
-function createURI(aObj)
-{
-  let ios = Cc["@mozilla.org/network/io-service;1"].
-            getService(Ci.nsIIOService);
+function createURI(aObj) {
+  let ios = Services.io;
   return (aObj instanceof Ci.nsIFile) ? ios.newFileURI(aObj) :
                                         ios.newURI(aObj, null, null);
 }
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_forgetthissite.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_forgetthissite.js
@@ -37,34 +37,32 @@
 
 // This test makes sure that the Forget This Site command is hidden in private
 // browsing mode.
 
 function test() {
   // initialization
   let pb = Cc["@mozilla.org/privatebrowsing;1"].
            getService(Ci.nsIPrivateBrowsingService);
-  let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-           getService(Ci.nsIWindowWatcher);
   waitForExplicitFinish();
 
   // Add a history entry.
   const TEST_URI = "http://www.mozilla.org/privatebrowsing";
   ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
   let history = PlacesUtils.history;
   let visitId = history.addVisit(PlacesUtils._uri(TEST_URI), Date.now() * 1000,
                                  null, PlacesUtils.history.TRANSITION_TYPED, false, 0);
   ok(visitId > 0, TEST_URI + " successfully marked visited");
 
   function testForgetThisSiteVisibility(expected, funcNext) {
     function observer(aSubject, aTopic, aData) {
       if (aTopic != "domwindowopened")
         return;
 
-      ww.unregisterNotification(observer);
+      Services.ww.unregisterNotification(observer);
       let organizer = aSubject.QueryInterface(Ci.nsIDOMWindow);
       organizer.addEventListener("load", function onLoad(event) {
         organizer.removeEventListener("load", onLoad, false);
         executeSoon(function () {
           // Select History in the left pane.
           let PO = organizer.PlacesOrganizer;
           PO.selectLeftPaneQuery('History');
           let histContainer = PO._places.selectedNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
@@ -98,22 +96,22 @@ function test() {
           event.initMouseEvent("contextmenu", true, true, organizer, 0,
                                0, 0, 0, 0, false, false, false, false,
                                0, null);
           tree.dispatchEvent(event);
         });
       }, false);
     }
 
-    ww.registerNotification(observer);
-    ww.openWindow(null,
-                  "chrome://browser/content/places/places.xul",
-                  "",
-                  "chrome,toolbar=yes,dialog=no,resizable",
-                  null);
+    Services.ww.registerNotification(observer);
+    Services.ww.openWindow(null,
+                           "chrome://browser/content/places/places.xul",
+                           "",
+                           "chrome,toolbar=yes,dialog=no,resizable",
+                           null);
   }
 
   testForgetThisSiteVisibility(true, function() {
     // Enter private browsing mode
     pb.privateBrowsingEnabled = true;
     testForgetThisSiteVisibility(false, function() {
       // Leave private browsing mode
       pb.privateBrowsingEnabled = false;
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_transition.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_transition.js
@@ -37,41 +37,39 @@
  
 // Tests the order of events and notifications when entering/exiting private
 // browsing mode. This ensures that all private data is removed on exit, e.g.
 // a cookie set in on unload handler, see bug 476463.
 let cookieManager = Cc["@mozilla.org/cookiemanager;1"].
                     getService(Ci.nsICookieManager2);
 let pb = Cc["@mozilla.org/privatebrowsing;1"].
          getService(Ci.nsIPrivateBrowsingService);
-let _obs = Cc["@mozilla.org/observer-service;1"].
-           getService(Ci.nsIObserverService);
 let observerNotified = 0, firstUnloadFired = 0, secondUnloadFired = 0;
 
 function pbObserver(aSubject, aTopic, aData) {
   if (aTopic != "private-browsing")
     return;
   switch (aData) {
     case "enter":
       observerNotified++;
       is(observerNotified, 1, "This should be the first notification");
       is(firstUnloadFired, 1, "The first unload event should have been processed by now");
       break;
     case "exit":
-      _obs.removeObserver(pbObserver, "private-browsing");
+      Services.obs.removeObserver(pbObserver, "private-browsing");
       observerNotified++;
       is(observerNotified, 2, "This should be the second notification");
       is(secondUnloadFired, 1, "The second unload event should have been processed by now");
       break;
   }
 }
 
 function test() {
   waitForExplicitFinish();
-  _obs.addObserver(pbObserver, "private-browsing", false);
+  Services.obs.addObserver(pbObserver, "private-browsing", false);
   is(gBrowser.tabs.length, 1, "There should only be one tab");
   let testTab = gBrowser.addTab();
   gBrowser.selectedTab = testTab;
   testTab.linkedBrowser.addEventListener("unload", function () {
     testTab.linkedBrowser.removeEventListener("unload", arguments.callee, true);
     firstUnloadFired++;
     is(observerNotified, 0, "The notification shouldn't have been sent yet");
   }, true);
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
@@ -43,19 +43,17 @@ function test() {
   gPrefService.setBoolPref("browser.privatebrowsing.keep_current_session", true);
   let pb = Cc["@mozilla.org/privatebrowsing;1"].
            getService(Ci.nsIPrivateBrowsingService);
   let observerData;
   function observer(aSubject, aTopic, aData) {
     if (aTopic == "private-browsing")
       observerData = aData;
   }
-  let os = Cc["@mozilla.org/observer-service;1"].
-           getService(Ci.nsIObserverService);
-  os.addObserver(observer, "private-browsing", false);
+  Services.obs.addObserver(observer, "private-browsing", false);
   let pbMenuItem = document.getElementById("privateBrowsingItem");
   // add a new blank tab to ensure the title can be meaningfully compared later
   gBrowser.selectedTab = gBrowser.addTab();
   let originalTitle = document.title;
 
   // test the gPrivateBrowsingUI object
   ok(gPrivateBrowsingUI, "The gPrivateBrowsingUI object exists");
   is(pb.privateBrowsingEnabled, false, "The private browsing mode should not be started initially");
@@ -87,11 +85,11 @@ function test() {
   func.call(cmd);
   // check to see if the Private Browsing mode was deactivated successfully
   is(observerData, "exit", "Private Browsing mode was deactivated using the command object");
   // check to see that the window title has been restored correctly
   is(document.title, originalTitle, "Private browsing mode has correctly restored the title");
 
   // cleanup
   gBrowser.removeCurrentTab();
-  os.removeObserver(observer, "private-browsing");
+  Services.obs.removeObserver(observer, "private-browsing");
   gPrefService.clearUserPref("browser.privatebrowsing.keep_current_session");
 }
new file mode 100644
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarundo.js
@@ -0,0 +1,64 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Private Browsing Tests.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ehsan Akhgari <ehsan.akhgari@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// This test makes sure that the undo history of the URL bar is cleared when
+// leaving the private browsing mode.
+
+function test() {
+  // initialization
+  Services.prefs.setBoolPref("browser.privatebrowsing.keep_current_session", true);
+  let pb = Cc["@mozilla.org/privatebrowsing;1"].
+           getService(Ci.nsIPrivateBrowsingService);
+
+  // enter private browsing mode
+  pb.privateBrowsingEnabled = true;
+
+  // fill in the URL bar with something
+  gURLBar.value = "some test value";
+
+  ok(gURLBar.editor.transactionManager.numberOfUndoItems > 0,
+     "The undo history for the URL bar should not be empty");
+
+  // leave private browsing mode
+  pb.privateBrowsingEnabled = false;
+
+  is(gURLBar.editor.transactionManager.numberOfUndoItems, 0,
+     "The undo history of the URL bar should be cleared after leaving the private browsing mode");
+
+  // cleanup
+  Services.prefs.clearUserPref("browser.privatebrowsing.keep_current_session");
+}
+
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_viewsource.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_viewsource.js
@@ -45,58 +45,56 @@ function test() {
 
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   let aboutBrowser = gBrowser.selectedBrowser;
   aboutBrowser.addEventListener("load", function () {
     aboutBrowser.removeEventListener("load", arguments.callee, true);
 
-    let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-             getService(Ci.nsIWindowWatcher);
     function observer(aSubject, aTopic, aData) {
       if (aTopic != "domwindowopened")
         return;
 
-      ww.unregisterNotification(observer);
+      Services.ww.unregisterNotification(observer);
 
       let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
       win.addEventListener("load", function () {
         win.removeEventListener("load", arguments.callee, false);
 
         let browser = win.gBrowser;
         browser.addEventListener("load", function () {
           browser.removeEventListener("load", arguments.callee, true);
           
           // view source window is loaded, proceed with the rest of the test
           step1();
         }, true);
       }, false);
     }
-    ww.registerNotification(observer);
+    Services.ww.registerNotification(observer);
 
     openViewSource();
 
     function openViewSource() {
       // invoke the View Source command
       document.getElementById("View:PageSource").doCommand();
     }
 
     function step1() {
       function observer(aSubject, aTopic, aData) {
         if (aTopic == "domwindowclosed") {
           ok(true, "Entering the private browsing mode should close the view source window");
-          ww.unregisterNotification(observer);
+          Services.ww.unregisterNotification(observer);
 
           step2();
         }
         else if (aTopic == "domwindowopened")
           ok(false, "Entering the private browsing mode should not open any view source window");
       }
-      ww.registerNotification(observer);
+      Services.ww.registerNotification(observer);
 
       gBrowser.addTabsProgressListener({
         onLocationChange: function() {},
         onProgressChange: function() {},
         onSecurityChange: function() {},
         onStatusChange: function() {},
         onRefreshAttempted: function() {},
         onStateChange: function(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
@@ -119,49 +117,49 @@ function test() {
         step4();
     }
 
     function step4() {
       function observer(aSubject, aTopic, aData) {
         if (aTopic != "domwindowopened")
           return;
 
-        ww.unregisterNotification(observer);
+        Services.ww.unregisterNotification(observer);
 
         let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
         win.addEventListener("load", function () {
           win.removeEventListener("load", arguments.callee, false);
 
           let browser = win.gBrowser;
           browser.addEventListener("load", function () {
             browser.removeEventListener("load", arguments.callee, true);
             
             // view source window inside private browsing mode opened
             step5();
           }, true);
         }, false);
       }
-      ww.registerNotification(observer);
+      Services.ww.registerNotification(observer);
 
       openViewSource();
     }
 
     function step5() {
       let events = 0;
 
       function observer(aSubject, aTopic, aData) {
         if (aTopic == "domwindowclosed") {
           ok(true, "Leaving the private browsing mode should close the existing view source window");
           if (++events == 2)
-            ww.unregisterNotification(observer);
+            Services.ww.unregisterNotification(observer);
         }
         else if (aTopic == "domwindowopened") {
           ok(true, "Leaving the private browsing mode should restore the previous view source window");
           if (++events == 2)
-            ww.unregisterNotification(observer);
+            Services.ww.unregisterNotification(observer);
 
           let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
           win.addEventListener("load", function () {
             win.removeEventListener("load", arguments.callee, false);
 
             let browser = win.gBrowser;
             browser.addEventListener("load", function () {
               browser.removeEventListener("load", arguments.callee, true);
@@ -172,16 +170,16 @@ function test() {
               // cleanup
               win.close();
               gBrowser.removeCurrentTab();
               finish();
             }, true);
           }, false);
         }
       }
-      ww.registerNotification(observer);
+      Services.ww.registerNotification(observer);
 
       // exit private browsing mode
       pb.privateBrowsingEnabled = false;
     }
   }, true);
   aboutBrowser.loadURI("about:");
 }
--- a/browser/components/search/test/browser_415700.js
+++ b/browser/components/search/test/browser_415700.js
@@ -28,20 +28,17 @@
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-var gSS = Cc["@mozilla.org/browser/search-service;1"].
-           getService(Ci.nsIBrowserSearchService);
-var gObs = Cc["@mozilla.org/observer-service;1"].
-           getService(Ci.nsIObserverService);
+var gSS = Services.search;
 
 function observers(aSubject, aTopic, aData) {
   switch (aData) {
     case "engine-added":
       test2();
       break;
     case "engine-current":
       test3();
@@ -49,17 +46,17 @@ function observers(aSubject, aTopic, aDa
     case "engine-removed":
       test4();
       break;
   }
 }
 
 function test() {
   waitForExplicitFinish();
-  gObs.addObserver(observers, "browser-search-engine-modified", false);
+  Services.obs.addObserver(observers, "browser-search-engine-modified", false);
 
   gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/testEngine.xml",
                 Ci.nsISearchEngine.DATA_XML, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC",
                 false);
 }
 
 function test2() {
   var engine = gSS.getEngineByName("Foo");
@@ -78,11 +75,11 @@ function test3() {
   gSS.removeEngine(engine);
 }
 
 function test4() {
   var engine = gSS.currentEngine;
   ok(engine, "An engine is present.");
   isnot(engine.name, "Foo", "Current engine reset after removal");
 
-  gObs.removeObserver(observers, "browser-search-engine-modified");
+  Services.obs.removeObserver(observers, "browser-search-engine-modified");
   finish();
 }
--- a/browser/components/search/test/browser_426329.js
+++ b/browser/components/search/test/browser_426329.js
@@ -3,41 +3,38 @@ function test() {
 
   var searchBar = BrowserSearch.searchBar;
   var searchButton = document.getAnonymousElementByAttribute(searchBar,
                      "anonid", "search-go-button");
   ok(searchButton, "got search-go-button");
 
   searchBar.value = "test";
 
-  var obs = Cc["@mozilla.org/observer-service;1"].
-            getService(Ci.nsIObserverService);
-  var ss = Cc["@mozilla.org/browser/search-service;1"].
-           getService(Ci.nsIBrowserSearchService);
+  var ss = Services.search;
 
   function observer(aSub, aTopic, aData) {
     switch (aData) {
       case "engine-added":
         var engine = ss.getEngineByName("Bug 426329");
         ok(engine, "Engine was added.");
         //XXX Bug 493051
         //ss.currentEngine = engine;
         break;
       case "engine-current":
         ok(ss.currentEngine.name == "Bug 426329", "currentEngine set");
         testReturn();
         break;
       case "engine-removed":
-        obs.removeObserver(observer, "browser-search-engine-modified");
+        Services.obs.removeObserver(observer, "browser-search-engine-modified");
         finish();
         break;
     }
   }
 
-  obs.addObserver(observer, "browser-search-engine-modified", false);
+  Services.obs.addObserver(observer, "browser-search-engine-modified", false);
   ss.addEngine("http://mochi.test:8888/browser/browser/components/search/test/426329.xml",
                Ci.nsISearchEngine.DATA_XML, "data:image/x-icon,%00",
                false);
 
   var preSelectedBrowser, preTabNo;
   function init() {
     preSelectedBrowser = gBrowser.selectedBrowser;
     preTabNo = gBrowser.mTabs.length;
--- a/browser/components/search/test/browser_483086.js
+++ b/browser/components/search/test/browser_483086.js
@@ -28,59 +28,58 @@
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-let gSS = Cc["@mozilla.org/browser/search-service;1"].
-           getService(Ci.nsIBrowserSearchService);
+let gSS = Services.search;
 let gObs = Cc["@mozilla.org/observer-service;1"].
            getService(Ci.nsIObserverService);
 
 function test() {
   waitForExplicitFinish();
 
   function observer(aSubject, aTopic, aData) {
     switch (aData) {
       case "engine-added":
         let engine = gSS.getEngineByName("483086a");
         ok(engine, "Test engine 1 installed");
         isnot(engine.searchForm, "foo://example.com",
               "Invalid SearchForm URL dropped");
         gSS.removeEngine(engine);
         break;
       case "engine-removed":
-        gObs.removeObserver(observer, "browser-search-engine-modified");
+        Services.obs.removeObserver(observer, "browser-search-engine-modified");
         test2();
         break;
     }
   }
 
-  gObs.addObserver(observer, "browser-search-engine-modified", false);
+  Services.obs.addObserver(observer, "browser-search-engine-modified", false);
   gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/483086-1.xml",
                 Ci.nsISearchEngine.DATA_XML, "data:image/x-icon;%00",
                 false);
 }
 
 function test2() {
   function observer(aSubject, aTopic, aData) {
     switch (aData) {
       case "engine-added":
         let engine = gSS.getEngineByName("483086b");
         ok(engine, "Test engine 2 installed");
         is(engine.searchForm, "http://example.com", "SearchForm is correct");
         gSS.removeEngine(engine);
         break;
       case "engine-removed":  
-        gObs.removeObserver(observer, "browser-search-engine-modified");
+        Services.obs.removeObserver(observer, "browser-search-engine-modified");
         finish();
         break;
     }
   }
 
-  gObs.addObserver(observer, "browser-search-engine-modified", false);
+  Services.obs.addObserver(observer, "browser-search-engine-modified", false);
   gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/483086-2.xml",
                 Ci.nsISearchEngine.DATA_XML, "data:image/x-icon;%00",
                 false);
 }
--- a/browser/components/sessionstore/test/browser/browser_248970_a.js
+++ b/browser/components/sessionstore/test/browser/browser_248970_a.js
@@ -58,23 +58,21 @@ function test() {
   function getSessionstorejsModificationTime() {
     let file = getSessionstoreFile();
     if (file.exists())
       return file.lastModifiedTime;
     else
       return -1;
   }
 
-  let os = Cc["@mozilla.org/observer-service;1"].
-           getService(Ci.nsIObserverService);
   function waitForFileExistence(aMessage, aDoNext) {
     const TOPIC = "sessionstore-state-write-complete";
-    os.addObserver(function (aSubject, aTopic, aData) {
+    Services.obs.addObserver(function (aSubject, aTopic, aData) {
       // Remove the observer so we do not leak.
-      os.removeObserver(arguments.callee, TOPIC);
+      Services.obs.removeObserver(arguments.callee, TOPIC);
 
       // Check that the file exists.
       ok(getSessionstoreFile().exists(), aMessage);
 
       // Run our next set of work.
       aDoNext();
     }, TOPIC, false);
   }
--- a/browser/components/sessionstore/test/browser/browser_354894.js
+++ b/browser/components/sessionstore/test/browser/browser_354894.js
@@ -104,19 +104,17 @@
  * nsSessionStore restore a window next time it gets a chance and will post
  * notifications. The latter won't.
  */
 
 function browserWindowsCount(expected, msg) {
   if (typeof expected == "number")
     expected = [expected, expected];
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   is(count, expected[0], msg + " (nsIWindowMediator)");
   let state = Cc["@mozilla.org/browser/sessionstore;1"]
                 .getService(Ci.nsISessionStore)
                 .getBrowserState();
@@ -167,18 +165,16 @@ function test() {
     observing[aTopic]++;
 
     // handle some tests
     if (++hitCount == 1) {
       // Test 6
       aCancel.QueryInterface(Ci.nsISupportsPRBool).data = true;
     }
   }
-  let observerService = Cc["@mozilla.org/observer-service;1"].
-                        getService(Ci.nsIObserverService);
 
   /**
    * Helper: Sets prefs as the testsuite requires
    * @note Will be reset in cleanTestSuite just before finishing the tests
    */
   function setPrefs() {
     gPrefService.setIntPref("browser.startup.page", 3);
     gPrefService.setBoolPref(
@@ -189,30 +185,30 @@ function test() {
   }
 
   /**
    * Helper: Sets up this testsuite
    */
   function setupTestsuite(testFn) {
     // Register our observers
     for (let o in observing)
-      observerService.addObserver(observer, o, false);
+      Services.obs.addObserver(observer, o, false);
 
     // Make the main test window not count as a browser window any longer
     oldWinType = document.documentElement.getAttribute("windowtype");
     document.documentElement.setAttribute("windowtype", "navigator:testrunner");
   }
 
   /**
    * Helper: Cleans up behind the testsuite
    */
   function cleanupTestsuite(callback) {
     // Finally remove observers again
     for (let o in observing)
-      observerService.removeObserver(observer, o, false);
+      Services.obs.removeObserver(observer, o, false);
 
     // Reset the prefs we touched
     [
       "browser.startup.page",
       "browser.privatebrowsing.keep_current_session"
     ].forEach(function (pref) {
       if (gPrefService.prefHasUserValue(pref))
         gPrefService.clearUserPref(pref);
--- a/browser/components/sessionstore/test/browser/browser_394759.js
+++ b/browser/components/sessionstore/test/browser/browser_394759.js
@@ -32,19 +32,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
--- a/browser/components/sessionstore/test/browser/browser_394759_privatebrowsing.js
+++ b/browser/components/sessionstore/test/browser/browser_394759_privatebrowsing.js
@@ -34,19 +34,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
@@ -69,20 +67,18 @@ function test() {
     }],
     _closedWindows: []
   });
   ss.setBrowserState(blankState);
 
   // Wait for the sessionstore.js file to be written before going on.
   // Note: we don't wait for the complete event, since if asyncCopy fails we
   // would timeout.
-  let os = Cc["@mozilla.org/observer-service;1"].
-           getService(Ci.nsIObserverService);
-  os.addObserver(function (aSubject, aTopic, aData) {
-    os.removeObserver(arguments.callee, aTopic);
+  Services.obs.addObserver(function (aSubject, aTopic, aData) {
+    Services.obs.removeObserver(arguments.callee, aTopic);
     info("sessionstore.js is being written");
     executeSoon(continue_test);
   }, "sessionstore-state-write", false);
 
   // Remove the sessionstore.js file before setting the interval to 0
   let profilePath = Cc["@mozilla.org/file/directory_service;1"].
                     getService(Ci.nsIProperties).
                     get("ProfD", Ci.nsIFile);
--- a/browser/components/sessionstore/test/browser/browser_423132.js
+++ b/browser/components/sessionstore/test/browser/browser_423132.js
@@ -31,19 +31,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
--- a/browser/components/sessionstore/test/browser/browser_448741.js
+++ b/browser/components/sessionstore/test/browser/browser_448741.js
@@ -34,17 +34,16 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   /** Test for Bug 448741 **/
 
   // test setup
   let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
-  let os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
   waitForExplicitFinish();
 
   let uniqueName = "bug 448741";
   let uniqueValue = "as good as unique: " + Date.now();
 
   // set a unique value on a new, blank tab
   var tab = gBrowser.addTab();
   tab.linkedBrowser.stop();
@@ -66,32 +65,32 @@ function test() {
           delete tabData.extData[uniqueName];
           valueWasCleaned = true;
         }
       });
     });
 
     ok(valueWasCleaned, "found and removed the specific tab value");
     aSubject.data = uneval(state);
-    os.removeObserver(cleaningObserver, aTopic, false);
+    Services.obs.removeObserver(cleaningObserver, aTopic, false);
   }
 
   // make sure that all later observers don't see that value any longer
   function checkingObserver(aSubject, aTopic, aData) {
     ok(valueWasCleaned && aSubject instanceof Ci.nsISupportsString,
        "ready to check the cleaned state?");
     ok(aSubject.data.indexOf(uniqueValue) == -1, "data no longer contains our value?");
 
     // clean up
     gBrowser.removeTab(tab);
-    os.removeObserver(checkingObserver, aTopic, false);
+    Services.obs.removeObserver(checkingObserver, aTopic, false);
     if (gPrefService.prefHasUserValue("browser.sessionstore.interval"))
       gPrefService.clearUserPref("browser.sessionstore.interval");
     finish();
   }
 
   // last added observers are invoked first
-  os.addObserver(checkingObserver, "sessionstore-state-write", false);
-  os.addObserver(cleaningObserver, "sessionstore-state-write", false);
+  Services.obs.addObserver(checkingObserver, "sessionstore-state-write", false);
+  Services.obs.addObserver(cleaningObserver, "sessionstore-state-write", false);
 
   // trigger an immediate save operation
   gPrefService.setIntPref("browser.sessionstore.interval", 0);
 }
--- a/browser/components/sessionstore/test/browser/browser_461634.js
+++ b/browser/components/sessionstore/test/browser/browser_461634.js
@@ -32,19 +32,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
--- a/browser/components/sessionstore/test/browser/browser_464199.js
+++ b/browser/components/sessionstore/test/browser/browser_464199.js
@@ -31,19 +31,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
--- a/browser/components/sessionstore/test/browser/browser_465223.js
+++ b/browser/components/sessionstore/test/browser/browser_465223.js
@@ -31,19 +31,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
--- a/browser/components/sessionstore/test/browser/browser_477657.js
+++ b/browser/components/sessionstore/test/browser/browser_477657.js
@@ -31,19 +31,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
--- a/browser/components/sessionstore/test/browser/browser_480148.js
+++ b/browser/components/sessionstore/test/browser/browser_480148.js
@@ -31,19 +31,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
--- a/browser/components/sessionstore/test/browser/browser_490040.js
+++ b/browser/components/sessionstore/test/browser/browser_490040.js
@@ -32,36 +32,30 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
   /** Test for Bug 490040 **/
   is(browserWindowsCount(), 1, "Only one browser window should be open initially");
 
   let ss = Cc["@mozilla.org/browser/sessionstore;1"].
            getService(Ci.nsISessionStore);
-  let os = Cc["@mozilla.org/observer-service;1"].
-           getService(Ci.nsIObserverService);
-  let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-           getService(Ci.nsIWindowWatcher);
 
   waitForExplicitFinish();
 
   function testWithState(aState) {
     // Ensure we can store the window if needed.
     let curClosedWindowCount = ss.getClosedWindowCount();
     gPrefService.setIntPref("browser.sessionstore.max_windows_undo",
                             curClosedWindowCount + 1);
@@ -93,34 +87,34 @@ function test() {
               }
               ss.setWindowState(theWin, JSON.stringify(aState.windowState),
                                 true);
             });
           }, false);
           break;
 
         case "domwindowclosed":
-          ww.unregisterNotification(windowObserver);
+          Services.ww.unregisterNotification(windowObserver);
           // Use executeSoon to ensure this happens after SS observer.
           executeSoon(function () {
             is(ss.getClosedWindowCount(),
                curClosedWindowCount + (aState.shouldBeAdded ? 1 : 0),
                "That window should " + (aState.shouldBeAdded ? "" : "not ") +
                "be restorable");
             executeSoon(runNextTest);
           });
           break;
       }
     }
-    ww.registerNotification(windowObserver);
-    ww.openWindow(null,
-                  location,
-                  "_blank",
-                  "chrome,all,dialog=no",
-                  null);
+    Services.ww.registerNotification(windowObserver);
+    Services.ww.openWindow(null,
+                           location,
+                           "_blank",
+                           "chrome,all,dialog=no",
+                           null);
   }
 
   // Only windows with open tabs are restorable. Windows where a lone tab is
   // detached may have _closedTabs, but is left with just an empty tab.
   let states = [
     {
       shouldBeAdded: true,
       windowState: {
--- a/browser/components/sessionstore/test/browser/browser_491168.js
+++ b/browser/components/sessionstore/test/browser/browser_491168.js
@@ -31,33 +31,30 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
   /** Test for Bug 491168 **/
   is(browserWindowsCount(), 1, "Only one browser window should be open initially");
 
   // test setup
   let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
-  let ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
 
   waitForExplicitFinish();
 
   const REFERRER1 = "http://example.org/?" + Date.now();
   const REFERRER2 = "http://example.org/?" + Math.random();
 
   let tab = gBrowser.addTab();
   gBrowser.selectedTab = tab;
@@ -86,11 +83,11 @@ function test() {
         gBrowser.removeTab(newTab);
 
         is(browserWindowsCount(), 1, "Only one browser window should be open eventually");
         finish();
       }, true);
     }, true);
   },true);
 
-  let referrerURI = ioService.newURI(REFERRER1, null, null);
+  let referrerURI = Services.io.newURI(REFERRER1, null, null);
   browser.loadURI("http://example.org", referrerURI, null);
 }
--- a/browser/components/sessionstore/test/browser/browser_491577.js
+++ b/browser/components/sessionstore/test/browser/browser_491577.js
@@ -32,19 +32,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
--- a/browser/components/sessionstore/test/browser/browser_493467.js
+++ b/browser/components/sessionstore/test/browser/browser_493467.js
@@ -31,19 +31,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
--- a/browser/components/sessionstore/test/browser/browser_495495.js
+++ b/browser/components/sessionstore/test/browser/browser_495495.js
@@ -31,19 +31,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
--- a/browser/components/sessionstore/test/browser/browser_514751.js
+++ b/browser/components/sessionstore/test/browser/browser_514751.js
@@ -31,19 +31,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
--- a/browser/components/sessionstore/test/browser/browser_522545.js
+++ b/browser/components/sessionstore/test/browser/browser_522545.js
@@ -32,19 +32,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
--- a/browser/components/sessionstore/test/browser/browser_524745.js
+++ b/browser/components/sessionstore/test/browser/browser_524745.js
@@ -32,34 +32,30 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function browserWindowsCount() {
   let count = 0;
-  let e = Cc["@mozilla.org/appshell/window-mediator;1"]
-            .getService(Ci.nsIWindowMediator)
-            .getEnumerator("navigator:browser");
+  let e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   return count;
 }
 
 function test() {
   /** Test for Bug 524745 **/
   is(browserWindowsCount(), 1, "Only one browser window should be open initially");
 
   let ss = Cc["@mozilla.org/browser/sessionstore;1"].
            getService(Ci.nsISessionStore);
-  let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-           getService(Ci.nsIWindowWatcher);
   let uniqKey = "bug524745";
   let uniqVal = Date.now();
 
   waitForExplicitFinish();
 
   let window_B = openDialog(location, "_blank", "chrome,all,dialog=no");
   window_B.addEventListener("load", function(aEvent) {
     window_B.removeEventListener("load", arguments.callee, false);
--- a/browser/components/sessionstore/test/browser/browser_526613.js
+++ b/browser/components/sessionstore/test/browser/browser_526613.js
@@ -36,25 +36,21 @@
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   /** Test for Bug 526613 **/
   
   // test setup
   let ss = Cc["@mozilla.org/browser/sessionstore;1"].
            getService(Ci.nsISessionStore);
-  let os = Cc["@mozilla.org/observer-service;1"].
-           getService(Ci.nsIObserverService);
-  let wm = Cc["@mozilla.org/appshell/window-mediator;1"].
-           getService(Ci.nsIWindowMediator);
   waitForExplicitFinish();
 
   function browserWindowsCount(expected) {
     let count = 0;
-    let e = wm.getEnumerator("navigator:browser");
+    let e = Services.wm.getEnumerator("navigator:browser");
     while (e.hasMoreElements()) {
       if (!e.getNext().closed)
         ++count;
     }
     is(count, expected,
        "number of open browser windows according to nsIWindowMediator");
     let state = ss.getBrowserState();
     info(state);
@@ -82,30 +78,30 @@ function test() {
     is(aTopic, "sessionstore-browser-state-restored",
        "The sessionstore-browser-state-restored notification was observed");
 
     if (pass++ == 1) {
       browserWindowsCount(2);
 
       // let the first window be focused (see above)
       function pollMostRecentWindow() {
-        if (wm.getMostRecentWindow("navigator:browser") == window) {
+        if (Services.wm.getMostRecentWindow("navigator:browser") == window) {
           ss.setBrowserState(oldState);
         } else {
           info("waiting for the current window to become active");
           setTimeout(pollMostRecentWindow, 0);
           window.focus(); //XXX Why is this needed?
         }
       }
       pollMostRecentWindow();
     }
     else {
       browserWindowsCount(1);
       ok(!window.closed, "Restoring the old state should have left this window open");
-      os.removeObserver(observer, "sessionstore-browser-state-restored");
+      Services.obs.removeObserver(observer, "sessionstore-browser-state-restored");
       finish();
     }
   }
-  os.addObserver(observer, "sessionstore-browser-state-restored", false);
+  Services.obs.addObserver(observer, "sessionstore-browser-state-restored", false);
 
   // set browser to test state
   ss.setBrowserState(JSON.stringify(testState));
 }
--- a/browser/components/sessionstore/test/browser/browser_528776.js
+++ b/browser/components/sessionstore/test/browser/browser_528776.js
@@ -1,14 +1,13 @@
 var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
-var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
 
 function browserWindowsCount(expected) {
   var count = 0;
-  var e = wm.getEnumerator("navigator:browser");
+  var e = Services.wm.getEnumerator("navigator:browser");
   while (e.hasMoreElements()) {
     if (!e.getNext().closed)
       ++count;
   }
   is(count, expected,
      "number of open browser windows according to nsIWindowMediator");
   is(JSON.parse(ss.getBrowserState()).windows.length, expected,
      "number of open browser windows according to getBrowserState");
--- a/browser/components/wintaskbar/WindowsPreviewPerTab.jsm
+++ b/browser/components/wintaskbar/WindowsPreviewPerTab.jsm
@@ -175,16 +175,22 @@ function PreviewController(win, tab) {
 
 PreviewController.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsITaskbarPreviewController,
                                          Ci.nsIDOMEventListener]),
   destroy: function () {
     this.linkedBrowser.removeEventListener("pageshow", this, false);
     this.linkedBrowser.removeEventListener("DOMTitleChanged", this, false);
     this.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
+
+    // Break cycles, otherwise we end up leaking the window with everything
+    // attached to it.
+    delete this.win;
+    delete this.preview;
+    delete this.dirtyRegion;
   },
   get wrappedJSObject() {
     return this;
   },
 
   get dirtyRects() {
     let rectstream = this.dirtyRegion.getRects();
     if (!rectstream)
@@ -378,17 +384,16 @@ function TabWindow(win) {
   this.tabbrowser = win.gBrowser;
 
   this.previews = [];
 
   for (let i = 0; i < this.events.length; i++)
     this.tabbrowser.tabContainer.addEventListener(this.events[i], this, false);
   this.tabbrowser.addTabsProgressListener(this);
 
-
   AeroPeek.windows.push(this);
   let tabs = this.tabbrowser.tabs;
   for (let i = 0; i < tabs.length; i++)
     this.newTab(tabs[i]);
 
   this.updateTabOrdering();
   AeroPeek.checkPreviewCount();
 }
@@ -397,16 +402,18 @@ TabWindow.prototype = {
   _enabled: false,
   events: ["TabOpen", "TabClose", "TabSelect", "TabMove"],
 
   destroy: function () {
     this._destroying = true;
 
     let tabs = this.tabbrowser.tabs;
 
+    this.tabbrowser.removeTabsProgressListener(this);
+
     for (let i = 0; i < this.events.length; i++)
       this.tabbrowser.tabContainer.removeEventListener(this.events[i], this, false);
 
     for (let i = 0; i < tabs.length; i++)
       this.removeTab(tabs[i]);
 
     let idx = AeroPeek.windows.indexOf(this.win.gTaskbarTabGroup);
     AeroPeek.windows.splice(idx, 1);
@@ -584,16 +591,27 @@ var AeroPeek = {
 
     this.cacheLifespan = this.prefs.getIntPref(CACHE_EXPIRATION_TIME_PREF_NAME);
 
     this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME);
 
     this.enabled = this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME);
   },
 
+  destroy: function destroy() {
+    this._enabled = false;
+
+    this.prefs.removeObserver(TOGGLE_PREF_NAME, this);
+    this.prefs.removeObserver(DISABLE_THRESHOLD_PREF_NAME, this);
+    this.prefs.removeObserver(CACHE_EXPIRATION_TIME_PREF_NAME, this);
+
+    if (this.cacheTimer)
+      this.cacheTimer.cancel();
+  },
+
   get enabled() {
     return this._enabled;
   },
 
   set enabled(enable) {
     if (this._enabled == enable)
       return;
 
@@ -631,17 +649,20 @@ var AeroPeek = {
   },
 
   onCloseWindow: function (win) {
     // This occurs when the taskbar service is not available (xp, vista)
     if (!this.available)
       return;
 
     win.gTaskbarTabGroup.destroy();
-    win.gTaskbarTabGroup = null;
+    delete win.gTaskbarTabGroup;
+
+    if (this.windows.length == 0)
+      this.destroy();
   },
 
   resetCacheTimer: function () {
     this.cacheTimer.cancel();
     this.cacheTimer.init(this, 1000*this.cacheLifespan, Ci.nsITimer.TYPE_ONE_SHOT);
   },
 
   //// nsIObserver
--- a/browser/fuel/test/browser_Application.js
+++ b/browser/fuel/test/browser_Application.js
@@ -1,22 +1,17 @@
-const Ci = Components.interfaces;
-const Cc = Components.classes;
-
 // This listens for the next opened window and checks it is of the right url.
 // opencallback is called when the new window is fully loaded
 // closecallback is called when the window is closed
 function WindowOpenListener(url, opencallback, closecallback) {
   this.url = url;
   this.opencallback = opencallback;
   this.closecallback = closecallback;
 
-  var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
-           getService(Ci.nsIWindowMediator);
-  wm.addListener(this);
+  Services.wm.addListener(this);
 }
 
 WindowOpenListener.prototype = {
   url: null,
   opencallback: null,
   closecallback: null,
   window: null,
   domwindow: null,
@@ -42,19 +37,17 @@ WindowOpenListener.prototype = {
                            .getInterface(Ci.nsIDOMWindowInternal);
     this.domwindow.addEventListener("load", this, false);
   },
 
   onCloseWindow: function(window) {
     if (this.window != window)
       return;
 
-    var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
-             getService(Ci.nsIWindowMediator);
-    wm.removeListener(this);
+    Services.wm.removeListener(this);
     this.opencallback = null;
     this.window = null;
     this.domwindow = null;
 
     // Let the window close complete
     executeSoon(this.closecallback);
     this.closecallback = null;
   }
--- a/browser/fuel/test/browser_ApplicationQuitting.js
+++ b/browser/fuel/test/browser_ApplicationQuitting.js
@@ -1,18 +1,17 @@
 function test() {
   function quitRequestObserver(aSubject, aTopic, aData) {
     ok(aTopic == "quit-application-requested" &&
        aSubject instanceof Components.interfaces.nsISupportsPRBool,
        "Received a quit request we're going to deny");
     aSubject.data = true;
   }
-  
+
   // ensure that we don't accidentally quit
-  let os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
-  os.addObserver(quitRequestObserver, "quit-application-requested", false);
-  
+  Services.obs.addObserver(quitRequestObserver, "quit-application-requested", false);
+
   ok(!Application.quit(),    "Tried to quit - and didn't succeed");
   ok(!Application.restart(), "Tried to restart - and didn't succeed");
-  
+
   // clean up
-  os.removeObserver(quitRequestObserver, "quit-application-requested", false);
+  Services.obs.removeObserver(quitRequestObserver, "quit-application-requested", false);
 }
--- a/browser/fuel/test/browser_Bookmarks.js
+++ b/browser/fuel/test/browser_Bookmarks.js
@@ -1,17 +1,13 @@
-const Ci = Components.interfaces;
-const Cc = Components.classes;
-
 var gLastFolderAction = "";
 var gLastBookmarkAction = "";
 
 function url(spec) {
-  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
-  return ios.newURI(spec, null, null);
+  return Services.io.newURI(spec, null, null);
 }
 
 function test() {
   // Some very basic tests on the tags root
   var tags = Application.bookmarks.tags;
   ok(tags, "Check access to bookmark tags root");
   ok(!tags.parent, "Check tags parent (should be null)");
 
--- a/configure.in
+++ b/configure.in
@@ -127,17 +127,17 @@ GTK2_VERSION=2.10.0
 WINDRES_VERSION=2.14.90
 W32API_VERSION=3.8
 GNOMEVFS_VERSION=2.0
 GNOMEUI_VERSION=2.2.0
 GCONF_VERSION=1.2.1
 GIO_VERSION=2.0
 STARTUP_NOTIFICATION_VERSION=0.8
 DBUS_VERSION=0.60
-SQLITE_VERSION=3.6.23
+SQLITE_VERSION=3.6.23.1
 LIBNOTIFY_VERSION=0.4
 
 MSMANIFEST_TOOL=
 
 dnl Set various checks
 dnl ========================================================
 MISSING_X=
 AC_PROG_AWK
@@ -6590,16 +6590,23 @@ if test $MOZ_PLATFORM_MAEMO; then
       fi
 
       PKG_CHECK_MODULES(LIBHILDONFM,hildon-fm-2, _LIB_FOUND=1, _LIB_FOUND=)
       MOZ_PLATFORM_MAEMO_LIBS="$MOZ_PLATFORM_MAEMO_LIBS $LIBHILDONFM_LIBS"
       MOZ_PLATFORM_MAEMO_CFLAGS="$MOZ_PLATFORM_MAEMO_CFLAGS $LIBHILDONFM_CFLAGS"
       if test -z "$_LIB_FOUND"; then
          AC_MSG_ERROR([Hildon FM-2 is required when building for Maemo])
       fi
+
+      PKG_CHECK_MODULES(LIBLOCATION,liblocation, _LIB_FOUND=1, _LIB_FOUND=)
+      MOZ_PLATFORM_MAEMO_LIBS="$MOZ_PLATFORM_MAEMO_LIBS $LIBLOCATION_LIBS"
+      MOZ_PLATFORM_MAEMO_CFLAGS="$MOZ_PLATFORM_MAEMO_CFLAGS $LIBLOCATION_CFLAGS"
+      if test -z "$_LIB_FOUND"; then
+         AC_MSG_ERROR([liblocation is required when building for Maemo])
+      fi
    fi
 
    AC_SUBST(MOZ_PLATFORM_MAEMO_LIBS)
    AC_SUBST(MOZ_PLATFORM_MAEMO_CFLAGS)
 fi
 
 dnl ========================================================
 dnl = faststripe theme
--- a/content/canvas/src/CustomQS_WebGL.h
+++ b/content/canvas/src/CustomQS_WebGL.h
@@ -305,22 +305,22 @@ nsICanvasRenderingContextWebGL_TexSubIma
     nsresult rv;
 
     nsICanvasRenderingContextWebGL *self;
     xpc_qsSelfRef selfref;
     js::AutoValueRooter tvr(cx);
     if (!xpc_qsUnwrapThis(cx, obj, nsnull, &self, &selfref.ptr, tvr.addr(), nsnull))
         return JS_FALSE;
 
-    if (argc < 7 || (argc > 7 && argc < 9))
+    if (argc < 7)
         return xpc_qsThrow(cx, NS_ERROR_XPC_NOT_ENOUGH_ARGS);
 
     jsval *argv = JS_ARGV(cx, vp);
 
-    int32 intargs[8];
+    int32 intargs[9];
 
     // convert the first six args, they must be ints
     for (jsuint i = 0; i < 6; ++i) {
         if (!JS_ValueToECMAInt32(cx, argv[i], &intargs[i]))
             return JS_FALSE;
     }
 
     if (JSVAL_IS_OBJECT(argv[6])) {
--- a/content/events/src/nsContentEventHandler.cpp
+++ b/content/events/src/nsContentEventHandler.cpp
@@ -783,17 +783,20 @@ nsContentEventHandler::OnQueryCharacterA
   if (rootWidget != aEvent->widget) {
     eventOnRoot.refPoint += aEvent->widget->WidgetToScreenOffset();
     eventOnRoot.refPoint -= rootWidget->WidgetToScreenOffset();
   }
   nsPoint ptInRoot =
     nsLayoutUtils::GetEventCoordinatesRelativeTo(&eventOnRoot, rootFrame);
 
   nsIFrame* targetFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot);
-  if (!targetFrame || targetFrame->GetType() != nsGkAtoms::textFrame) {
+  if (!targetFrame || targetFrame->GetType() != nsGkAtoms::textFrame ||
+      !targetFrame->GetContent() ||
+      !nsContentUtils::ContentIsDescendantOf(targetFrame->GetContent(),
+                                             mRootContent)) {
     // there is no character at the point.
     aEvent->mReply.mOffset = nsQueryContentEvent::NOT_FOUND;
     aEvent->mSucceeded = PR_TRUE;
     return NS_OK;
   }
   nsPoint ptInTarget = ptInRoot - targetFrame->GetOffsetTo(rootFrame);
   nsTextFrame* textframe = static_cast<nsTextFrame*>(targetFrame);
   nsIFrame::ContentOffsets offsets =
--- a/content/html/content/public/nsHTMLAudioElement.h
+++ b/content/html/content/public/nsHTMLAudioElement.h
@@ -30,16 +30,19 @@
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
+#if !defined(nsHTMLAudioElement_h__)
+#define nsHTMLAudioElement_h__
+
 #include "nsIDOMHTMLAudioElement.h"
 #include "nsIJSNativeInitializer.h"
 #include "nsHTMLMediaElement.h"
 
 typedef PRUint16 nsMediaNetworkState;
 typedef PRUint16 nsMediaReadyState;
 
 class nsHTMLAudioElement : public nsHTMLMediaElement,
@@ -69,8 +72,10 @@ public:
   NS_DECL_NSIDOMHTMLAUDIOELEMENT
 
   // nsIJSNativeInitializer
   NS_IMETHOD Initialize(nsISupports* aOwner, JSContext* aContext,
                         JSObject* aObj, PRUint32 argc, jsval* argv);
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
 };
+
+#endif
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -30,16 +30,19 @@
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
+#if !defined(nsHTMLMediaElement_h__)
+#define nsHTMLMediaElement_h__
+
 #include "nsIDOMHTMLMediaElement.h"
 #include "nsGenericHTMLElement.h"
 #include "nsMediaDecoder.h"
 #include "nsIChannel.h"
 #include "nsThreadUtils.h"
 #include "nsIDOMRange.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsILoadGroup.h"
@@ -533,8 +536,10 @@ protected:
   PRPackedBool mHasSelfReference;
 
   // PR_TRUE if we've received a notification that the engine is shutting
   // down.
   PRPackedBool mShuttingDown;
 
   nsRefPtr<gfxASurface> mPrintSurface;
 };
+
+#endif
--- a/content/html/content/public/nsHTMLVideoElement.h
+++ b/content/html/content/public/nsHTMLVideoElement.h
@@ -30,16 +30,19 @@
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
+#if !defined(nsHTMLVideoElement_h__)
+#define nsHTMLVideoElement_h__
+
 #include "nsIDOMHTMLVideoElement.h"
 #include "nsHTMLMediaElement.h"
 
 class nsHTMLVideoElement : public nsHTMLMediaElement,
                            public nsIDOMHTMLVideoElement
 {
 public:
   nsHTMLVideoElement(nsINodeInfo *aNodeInfo, PRBool aFromParser = PR_FALSE);
@@ -71,8 +74,10 @@ public:
   virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
 
   // Returns the current video frame width and height.
   // If there is no video frame, returns the given default size.
   nsIntSize GetVideoSize(nsIntSize defaultSize);
 };
+
+#endif
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -79,16 +79,17 @@
 #include "nsContentPolicyUtils.h"
 #include "nsContentErrors.h"
 #include "nsCrossSiteListenerProxy.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsICachingChannel.h"
 #include "nsLayoutUtils.h"
 #include "nsVideoFrame.h"
 #include "BasicLayers.h"
+#include <limits>
 
 #ifdef MOZ_OGG
 #include "nsOggDecoder.h"
 #endif
 #ifdef MOZ_WAVE
 #include "nsWaveDecoder.h"
 #endif
 
@@ -814,17 +815,17 @@ NS_IMETHODIMP nsHTMLMediaElement::SetCur
   AddRemoveSelfReference();
 
   return rv;
 }
 
 /* readonly attribute float duration; */
 NS_IMETHODIMP nsHTMLMediaElement::GetDuration(float *aDuration)
 {
-  *aDuration =  mDecoder ? mDecoder->GetDuration() : 0.0;
+  *aDuration = mDecoder ? mDecoder->GetDuration() : std::numeric_limits<float>::quiet_NaN();
   return NS_OK;
 }
 
 /* readonly attribute boolean paused; */
 NS_IMETHODIMP nsHTMLMediaElement::GetPaused(PRBool *aPaused)
 {
   *aPaused = mPaused;
 
--- a/content/html/document/src/nsHTMLDocument.cpp
+++ b/content/html/document/src/nsHTMLDocument.cpp
@@ -1844,17 +1844,17 @@ nsHTMLDocument::SetCookie(const nsAStrin
 
 // XXX TBI: accepting arguments to the open method.
 nsresult
 nsHTMLDocument::OpenCommon(const nsACString& aContentType, PRBool aReplace)
 {
   if (!IsHTML() || mDisableDocWrite) {
     // No calling document.open() on XHTML
 
-    return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   PRBool loadAsHtml5 = nsHtml5Module::sEnabled;
 
   nsresult rv = NS_OK;
 
   // If we already have a parser we ignore the document.open call.
   if (mParser) {
@@ -2094,17 +2094,17 @@ nsHTMLDocument::Clear()
 }
 
 NS_IMETHODIMP
 nsHTMLDocument::Close()
 {
   if (!IsHTML()) {
     // No calling document.close() on XHTML!
 
-    return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   nsresult rv = NS_OK;
 
   if (mParser && mWriteState == eDocumentOpened) {
     mPendingScripts.RemoveElement(GenerateParserKey());
 
     mWriteState = mPendingScripts.IsEmpty() ? eDocumentClosed : ePendingClose;
@@ -2159,17 +2159,17 @@ nsHTMLDocument::WriteCommon(const nsAStr
 {
   mTooDeepWriteRecursion =
     (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
   NS_ENSURE_STATE(!mTooDeepWriteRecursion);
 
   if (!IsHTML() || mDisableDocWrite) {
     // No calling document.write*() on XHTML!
 
-    return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   nsresult rv = NS_OK;
 
   void *key = GenerateParserKey();
   if (mWriteState == eDocumentClosed ||
       (mWriteState == ePendingClose &&
        !mPendingScripts.Contains(key)) ||
--- a/content/html/document/src/nsHTMLFragmentContentSink.cpp
+++ b/content/html/document/src/nsHTMLFragmentContentSink.cpp
@@ -129,17 +129,16 @@ public:
   nsIContent* GetCurrentContent();
   PRInt32 PushContent(nsIContent *aContent);
   nsIContent* PopContent();
 
   virtual nsresult AddAttributes(const nsIParserNode& aNode,
                                  nsIContent* aContent);
 
   nsresult AddText(const nsAString& aString);
-  nsresult AddTextToContent(nsIContent* aContent, const nsAString& aText);
   nsresult FlushText();
 
   void ProcessBaseTag(nsIContent* aContent);
   void AddBaseTagInfo(nsIContent* aContent);
 
   nsresult Init();
 
   PRPackedBool mAllContent;
@@ -759,36 +758,16 @@ nsHTMLFragmentContentSink::AddText(const
     offset += amount;
     addLen -= amount;
   }
 
   return NS_OK;
 }
 
 nsresult
-nsHTMLFragmentContentSink::AddTextToContent(nsIContent* aContent, const nsAString& aText) {
-  NS_ASSERTION(aContent !=nsnull, "can't add text w/o a content");
-
-  nsresult result=NS_OK;
-
-  if(aContent) {
-    if (!aText.IsEmpty()) {
-      nsCOMPtr<nsIContent> text;
-      result = NS_NewTextNode(getter_AddRefs(text), mNodeInfoManager);
-      if (NS_SUCCEEDED(result)) {
-        text->SetText(aText, PR_TRUE);
-
-        result = aContent->AppendChildTo(text, PR_FALSE);
-      }
-    }
-  }
-  return result;
-}
-
-nsresult
 nsHTMLFragmentContentSink::FlushText()
 {
   if (0 == mTextLength) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIContent> content;
   nsresult rv = NS_NewTextNode(getter_AddRefs(content), mNodeInfoManager);
--- a/content/html/document/test/test_bug332848.xhtml
+++ b/content/html/document/test/test_bug332848.xhtml
@@ -23,33 +23,41 @@ https://bugzilla.mozilla.org/show_bug.cg
 // parseChecker will become true if we keep parsing after calling close().
 var parseChecker = false;
 
 function test() {
   try {
     document.open();
     is(0, 1, "document.open succeeded");
   } catch (e) {
-    is (e.code, DOMException.INVALID_ACCESS_ERR,
+    is (e.code, DOMException.INVALID_STATE_ERR,
         "Wrong exception from document.open");
   }
 
   try {
     document.write("aaa");
     is(0, 1, "document.write succeeded");
   } catch (e) {
-    is (e.code, DOMException.INVALID_ACCESS_ERR,
+    is (e.code, DOMException.INVALID_STATE_ERR,
+        "Wrong exception from document.write");
+  }
+
+  try {
+    document.writeln("aaa");
+    is(0, 1, "document.write succeeded");
+  } catch (e) {
+    is (e.code, DOMException.INVALID_STATE_ERR,
         "Wrong exception from document.write");
   }
 
   try {
     document.close();
     is(0, 1, "document.close succeeded");
   } catch (e) {
-    is (e.code, DOMException.INVALID_ACCESS_ERR,
+    is (e.code, DOMException.INVALID_STATE_ERR,
         "Wrong exception from document.close");
   }
 }
 
 function loadTest() {
   is(parseChecker, true, "Parsing stopped");
   test();
   SimpleTest.finish();
--- a/content/media/nsAudioStream.cpp
+++ b/content/media/nsAudioStream.cpp
@@ -40,16 +40,19 @@
 #include "prlog.h"
 #include "prmem.h"
 #include "nsAutoPtr.h"
 #include "nsAudioStream.h"
 #include "nsAlgorithm.h"
 extern "C" {
 #include "sydneyaudio/sydney_audio.h"
 }
+#include "mozilla/TimeStamp.h"
+
+using mozilla::TimeStamp;
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gAudioStreamLog = nsnull;
 #endif
 
 #define FAKE_BUFFER_SIZE 176400
 
 void nsAudioStream::InitLibrary()
@@ -63,17 +66,18 @@ void nsAudioStream::ShutdownLibrary()
 {
 }
 
 nsAudioStream::nsAudioStream() :
   mVolume(1.0),
   mAudioHandle(0),
   mRate(0),
   mChannels(0),
-  mFormat(FORMAT_S16_LE)
+  mFormat(FORMAT_S16_LE),
+  mPaused(PR_FALSE)
 {
 }
 
 nsAudioStream::~nsAudioStream()
 {
   Shutdown();
 }
 
@@ -105,23 +109,24 @@ void nsAudioStream::Shutdown()
 {
   if (!mAudioHandle) 
     return;
 
   sa_stream_destroy(static_cast<sa_stream_t*>(mAudioHandle));
   mAudioHandle = nsnull;
 }
 
-void nsAudioStream::Write(const void* aBuf, PRUint32 aCount)
+void nsAudioStream::Write(const void* aBuf, PRUint32 aCount, PRBool aBlocking)
 {
   NS_ABORT_IF_FALSE(aCount % mChannels == 0,
                     "Buffer size must be divisible by channel count");
+  NS_ASSERTION(!mPaused, "Don't write audio when paused, you'll block");
 
   PRUint32 offset = mBufferOverflow.Length();
-  PRInt32 count = aCount + offset;
+  PRUint32 count = aCount + offset;
 
   if (!mAudioHandle)
     return;
 
   nsAutoArrayPtr<short> s_data(new short[count]);
 
   if (s_data) {
     for (PRUint32 i=0; i < offset; ++i) {
@@ -163,31 +168,38 @@ void nsAudioStream::Write(const void* aB
               32767 :
               short(scaled_value);
           }
         }
         break;
       }
     }
 
-    PRInt32 available = Available();
-    if (available < count) {
-      mBufferOverflow.AppendElements(s_data.get() + available, (count - available));
-      count = available;
+    if (!aBlocking) {
+      // We're running in non-blocking mode, crop the data to the amount 
+      // which is available in the audio buffer, and save the rest for
+      // subsequent calls.
+      PRUint32 available = Available();
+      if (available < count) {
+        mBufferOverflow.AppendElements(s_data.get() + available, (count - available));
+        count = available;
+      }
     }
 
     if (sa_stream_write(static_cast<sa_stream_t*>(mAudioHandle),
-       s_data.get(), count * sizeof(short)) != SA_SUCCESS) {
+                        s_data.get(),
+                        count * sizeof(short)) != SA_SUCCESS)
+    {
       PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsAudioStream: sa_stream_write error"));
       Shutdown();
     }
   }
 }
 
-PRInt32 nsAudioStream::Available()
+PRUint32 nsAudioStream::Available()
 {
   // If the audio backend failed to open, lie and say we'll accept some
   // data.
   if (!mAudioHandle)
     return FAKE_BUFFER_SIZE;
 
   size_t s = 0; 
   if (sa_stream_get_write_size(static_cast<sa_stream_t*>(mAudioHandle), &s) != SA_SUCCESS)
@@ -220,38 +232,38 @@ void nsAudioStream::Drain()
         Shutdown();
   }
 }
 
 void nsAudioStream::Pause()
 {
   if (!mAudioHandle)
     return;
-
+  mPaused = PR_TRUE;
   sa_stream_pause(static_cast<sa_stream_t*>(mAudioHandle));
 }
 
 void nsAudioStream::Resume()
 {
   if (!mAudioHandle)
     return;
-
+  mPaused = PR_FALSE;
   sa_stream_resume(static_cast<sa_stream_t*>(mAudioHandle));
 }
 
-float nsAudioStream::GetPosition()
+PRInt64 nsAudioStream::GetPosition()
 {
   if (!mAudioHandle)
-    return -1.0;
+    return -1;
 
   sa_position_t positionType = SA_POSITION_WRITE_SOFTWARE;
 #if defined(XP_WIN)
   positionType = SA_POSITION_WRITE_HARDWARE;
 #endif
   PRInt64 position = 0;
   if (sa_stream_get_position(static_cast<sa_stream_t*>(mAudioHandle),
                              positionType, &position) == SA_SUCCESS) {
-    return (position / float(mRate) / mChannels / sizeof(short));
+    return ((1000 * position) / mRate / mChannels / sizeof(short));
   }
 
-  return -1.0;
+  return -1;
 }
 
--- a/content/media/nsAudioStream.h
+++ b/content/media/nsAudioStream.h
@@ -71,46 +71,55 @@ class nsAudioStream
   void Init(PRInt32 aNumChannels, PRInt32 aRate, SampleFormat aFormat);
 
   // Closes the stream. All future use of the stream is an error.
   void Shutdown();
 
   // Write sound data to the audio hardware.  aBuf is an array of samples in
   // the format specified by mFormat of length aCount.  aCount should be
   // evenly divisible by the number of channels in this audio stream.
-  void Write(const void* aBuf, PRUint32 aCount);
+  // When aBlocking is PR_TRUE, we'll block until the write has completed,
+  // otherwise we'll buffer any data we can't write immediately, and write
+  // it in a later call.
+  void Write(const void* aBuf, PRUint32 aCount, PRBool aBlocking);
 
   // Return the number of sound samples that can be written to the audio device
   // without blocking.
-  PRInt32 Available();
+  PRUint32 Available();
 
   // Set the current volume of the audio playback. This is a value from
   // 0 (meaning muted) to 1 (meaning full volume).
   void SetVolume(float aVolume);
 
   // Block until buffered audio data has been consumed.
   void Drain();
 
   // Pause audio playback
   void Pause();
 
   // Resume audio playback
   void Resume();
 
-  // Return the position in seconds of the sample being played by the
+  // Return the position in milliseconds of the sample being played by the
   // audio hardware.
-  float GetPosition();
+  PRInt64 GetPosition();
+
+  // Returns PR_TRUE when the audio stream is paused.
+  PRBool IsPaused() { return mPaused; }
 
  private:
   double mVolume;
   void* mAudioHandle;
   int mRate;
   int mChannels;
 
   SampleFormat mFormat;
 
   // When a Write() request is made, and the number of samples
   // requested to be written exceeds the buffer size of the audio
   // backend, the remaining samples are stored in this variable. They
   // will be written on the next Write() request.
   nsTArray<short> mBufferOverflow;
+
+  // PR_TRUE if this audio stream is paused.
+  PRPackedBool mPaused;
 };
 #endif
--- a/content/media/ogg/Makefile.in
+++ b/content/media/ogg/Makefile.in
@@ -44,21 +44,25 @@ include $(DEPTH)/config/autoconf.mk
 MODULE		= content
 LIBRARY_NAME	= gkconogg_s
 LIBXUL_LIBRARY 	= 1
 
 
 EXPORTS		+= \
 		nsChannelReader.h \
 		nsOggDecoder.h \
+		nsOggCodecState.h \
 		$(NULL)
 
 CPPSRCS		= \
 		nsChannelReader.cpp \
 		nsOggDecoder.cpp \
+		nsOggCodecState.cpp \
+		nsOggReader.cpp \
+		nsOggPlayStateMachine.cpp \
 		$(NULL)
 
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 INCLUDES	+= \
 		-I$(srcdir)/../../base/src \
new file mode 100644
--- /dev/null
+++ b/content/media/ogg/nsOggCodecState.cpp
@@ -0,0 +1,509 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#include "nsDebug.h"
+#include "nsOggCodecState.h"
+#include "nsOggDecoder.h"
+#include <string.h>
+#include "nsTraceRefcnt.h"
+#include "nsOggHacks.h"
+
+/*
+   The maximum height and width of the video. Used for
+   sanitizing the memory allocation of the RGB buffer.
+   The maximum resolution we anticipate encountering in the
+   wild is 2160p - 3840x2160 pixels.
+*/
+#define MAX_VIDEO_WIDTH  4000
+#define MAX_VIDEO_HEIGHT 3000
+
+// Adds two 64bit numbers, retuns PR_TRUE if addition succeeded, or PR_FALSE
+// if addition would result in an overflow.
+static PRBool AddOverflow(PRInt64 a, PRInt64 b, PRInt64& aResult);
+
+// 64 bit integer multiplication with overflow checking. Returns PR_TRUE
+// if the multiplication was successful, or PR_FALSE if the operation resulted
+// in an integer overflow.
+static PRBool MulOverflow(PRInt64 a, PRInt64 b, PRInt64& aResult);
+
+// Defined in nsOggReader.cpp.
+extern PRBool MulOverflow32(PRUint32 a, PRUint32 b, PRUint32& aResult);
+
+
+nsOggCodecState*
+nsOggCodecState::Create(ogg_page* aPage)
+{
+  nsAutoPtr<nsOggCodecState> codecState;
+  if (aPage->body_len > 6 && memcmp(aPage->body+1, "theora", 6) == 0) {
+    codecState = new nsTheoraState(aPage);
+  } else if (aPage->body_len > 6 && memcmp(aPage->body+1, "vorbis", 6) == 0) {
+    codecState = new nsVorbisState(aPage);
+  } else if (aPage->body_len > 8 && memcmp(aPage->body, "fishead\0", 8) == 0) {
+    codecState = new nsSkeletonState(aPage);
+  } else {
+    codecState = new nsOggCodecState(aPage);
+  }
+  return codecState->nsOggCodecState::Init() ? codecState.forget() : nsnull;
+}
+
+nsOggCodecState::nsOggCodecState(ogg_page* aBosPage) :
+  mPacketCount(0),
+  mSerial(ogg_page_serialno(aBosPage)),
+  mActive(PR_FALSE),
+  mDoneReadingHeaders(PR_FALSE)
+{
+  MOZ_COUNT_CTOR(nsOggCodecState);
+  memset(&mState, 0, sizeof(ogg_stream_state));
+}
+
+nsOggCodecState::~nsOggCodecState() {
+  MOZ_COUNT_DTOR(nsOggCodecState);
+  int ret = ogg_stream_clear(&mState);
+  NS_ASSERTION(ret == 0, "ogg_stream_clear failed");
+}
+
+nsresult nsOggCodecState::Reset() {
+  if (ogg_stream_reset(&mState) != 0) {
+    return NS_ERROR_FAILURE;
+  }
+  mBuffer.Erase();
+  return NS_OK;
+}
+
+PRBool nsOggCodecState::Init() {
+  int ret = ogg_stream_init(&mState, mSerial);
+  return ret == 0;
+}
+
+void nsPageQueue::Append(ogg_page* aPage) {
+  ogg_page* p = new ogg_page();
+  p->header_len = aPage->header_len;
+  p->body_len = aPage->body_len;
+  p->header = new unsigned char[p->header_len + p->body_len];
+  p->body = p->header + p->header_len;
+  memcpy(p->header, aPage->header, p->header_len);
+  memcpy(p->body, aPage->body, p->body_len);
+  nsDeque::Push(p);
+}
+
+PRBool nsOggCodecState::PageInFromBuffer() {
+  if (mBuffer.IsEmpty())
+    return PR_FALSE;
+  ogg_page *p = mBuffer.PeekFront();
+  int ret = ogg_stream_pagein(&mState, p);
+  NS_ENSURE_TRUE(ret == 0, PR_FALSE);
+  mBuffer.PopFront();
+  delete p->header;
+  delete p;
+  return PR_TRUE;
+}
+
+nsTheoraState::nsTheoraState(ogg_page* aBosPage) :
+  nsOggCodecState(aBosPage),
+  mSetup(0),
+  mCtx(0),
+  mFrameDuration(0),
+  mFrameRate(0),
+  mAspectRatio(0)
+{
+  MOZ_COUNT_CTOR(nsTheoraState);
+  th_info_init(&mInfo);
+  th_comment_init(&mComment);
+}
+
+nsTheoraState::~nsTheoraState() {
+  MOZ_COUNT_DTOR(nsTheoraState);
+  th_setup_free(mSetup);
+  th_decode_free(mCtx);
+  th_comment_clear(&mComment);
+  th_info_clear(&mInfo);
+}
+
+PRBool nsTheoraState::Init() {
+  if (!mActive)
+    return PR_FALSE;
+  mCtx = th_decode_alloc(&mInfo, mSetup);
+  if (mCtx == NULL) {
+    return mActive = PR_FALSE;
+  }
+
+  PRUint32 n = mInfo.fps_numerator;
+  PRUint32 d = mInfo.fps_denominator;
+
+  mFrameRate = (n == 0 || d == 0) ?
+    0.0 : static_cast<float>(n) / static_cast<float>(d);
+
+  PRUint32 c;
+  if (!MulOverflow32(1000, d, c)) {
+    return mActive = PR_FALSE;
+  }
+  mFrameDuration = c / n;
+
+  n = mInfo.aspect_numerator;
+  d = mInfo.aspect_denominator;
+  mAspectRatio = (n == 0 || d == 0) ?
+    1.0 : static_cast<float>(n) / static_cast<float>(d);
+
+  // Ensure the frame isn't larger than our prescribed maximum.
+  PRUint32 pixels;
+  if (!MulOverflow32(mInfo.pic_width, mInfo.pic_height, pixels) ||
+      pixels > MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT ||
+      pixels == 0)
+  {
+    return mActive = PR_FALSE;
+  }
+
+  return PR_TRUE;
+}
+
+PRBool
+nsTheoraState::DecodeHeader(ogg_packet* aPacket)
+{
+  mPacketCount++;
+  int ret = th_decode_headerin(&mInfo,
+                               &mComment,
+                               &mSetup,
+                               aPacket);
+ 
+  // We must determine when we've read the last header packet.
+  // th_decode_headerin() does not tell us when it's read the last header, so
+  // we must keep track of the headers externally.
+  //
+  // There are 3 header packets, the Identification, Comment, and Setup
+  // headers, which must be in that order. If they're out of order, the file
+  // is invalid. If we've successfully read a header, and it's the setup
+  // header, then we're done reading headers. The first byte of each packet
+  // determines it's type as follows:
+  //    0x80 -> Identification header
+  //    0x81 -> Comment header
+  //    0x82 -> Setup header
+  // See http://www.theora.org/doc/Theora.pdf Chapter 6, "Bitstream Headers",
+  // for more details of the Ogg/Theora containment scheme.
+  PRBool isSetupHeader = aPacket->bytes > 0 && aPacket->packet[0] == 0x82;
+  if (ret < 0 || mPacketCount > 3) {
+    // We've received an error, or the first three packets weren't valid
+    // header packets, assume bad input, and don't activate the bitstream.
+    mDoneReadingHeaders = PR_TRUE;
+  } else if (ret > 0 && isSetupHeader && mPacketCount == 3) {
+    // Successfully read the three header packets.
+    mDoneReadingHeaders = PR_TRUE;
+    mActive = PR_TRUE;
+  }
+  return mDoneReadingHeaders;
+}
+
+PRInt64
+nsTheoraState::Time(PRInt64 granulepos) {
+  if (granulepos < 0 || !mActive || mInfo.fps_numerator == 0) {
+    return -1;
+  }
+  PRInt64 t = 0;
+  PRInt64 frameno = th_granule_frame(mCtx, granulepos);
+  if (!AddOverflow(frameno, 1, t))
+    return -1;
+  if (!MulOverflow(t, 1000, t))
+    return -1;
+  if (!MulOverflow(t, mInfo.fps_denominator, t))
+    return -1;
+  return t / mInfo.fps_numerator;
+}
+
+PRInt64 nsTheoraState::StartTime(PRInt64 granulepos) {
+  if (granulepos < 0 || !mActive || mInfo.fps_numerator == 0) {
+    return -1;
+  }
+  PRInt64 t = 0;
+  PRInt64 frameno = th_granule_frame(mCtx, granulepos);
+  if (!MulOverflow(frameno, 1000, t))
+    return -1;
+  if (!MulOverflow(t, mInfo.fps_denominator, t))
+    return -1;
+  return t / mInfo.fps_numerator;
+}
+
+PRInt64
+nsTheoraState::MaxKeyframeOffset()
+{
+  // Determine the maximum time in milliseconds by which a key frame could
+  // offset for the theora bitstream. Theora granulepos encode time as:
+  // ((key_frame_number << granule_shift) + frame_offset).
+  // Therefore the maximum possible time by which any frame could be offset
+  // from a keyframe is the duration of (1 << granule_shift) - 1) frames.
+  PRInt64 frameDuration;
+  PRInt64 keyframeDiff;
+
+  PRInt64 shift = mInfo.keyframe_granule_shift;
+
+  // Max number of frames keyframe could possibly be offset.
+  keyframeDiff = (1 << shift) - 1;
+
+  // Length of frame in ms.
+  PRInt64 d = 0; // d will be 0 if multiplication overflows.
+  MulOverflow(1000, mInfo.fps_denominator, d);
+  frameDuration = d / mInfo.fps_numerator;
+
+  // Total time in ms keyframe can be offset from any given frame.
+  return frameDuration * keyframeDiff;
+}
+
+nsresult nsVorbisState::Reset()
+{
+  nsresult res = NS_OK;
+  if (mActive && vorbis_synthesis_restart(&mDsp) != 0) {
+    res = NS_ERROR_FAILURE;
+  }
+  if (NS_FAILED(nsOggCodecState::Reset())) {
+    return NS_ERROR_FAILURE;
+  }
+  return res;
+}
+
+nsVorbisState::nsVorbisState(ogg_page* aBosPage) :
+  nsOggCodecState(aBosPage)
+{
+  MOZ_COUNT_CTOR(nsVorbisState);
+  vorbis_info_init(&mInfo);
+  vorbis_comment_init(&mComment);
+  memset(&mDsp, 0, sizeof(vorbis_dsp_state));
+  memset(&mBlock, 0, sizeof(vorbis_block));
+}
+
+nsVorbisState::~nsVorbisState() {
+  MOZ_COUNT_DTOR(nsVorbisState);
+  vorbis_block_clear(&mBlock);
+  vorbis_dsp_clear(&mDsp);
+  vorbis_info_clear(&mInfo);
+  vorbis_comment_clear(&mComment);
+}
+
+PRBool nsVorbisState::DecodeHeader(ogg_packet* aPacket) {
+  mPacketCount++;
+  int ret = vorbis_synthesis_headerin(&mInfo,
+                                      &mComment,
+                                      aPacket);
+  // We must determine when we've read the last header packet.
+  // vorbis_synthesis_headerin() does not tell us when it's read the last
+  // header, so we must keep track of the headers externally.
+  //
+  // There are 3 header packets, the Identification, Comment, and Setup
+  // headers, which must be in that order. If they're out of order, the file
+  // is invalid. If we've successfully read a header, and it's the setup
+  // header, then we're done reading headers. The first byte of each packet
+  // determines it's type as follows:
+  //    0x1 -> Identification header
+  //    0x3 -> Comment header
+  //    0x5 -> Setup header
+  // For more details of the Vorbis/Ogg containment scheme, see the Vorbis I
+  // Specification, Chapter 4, Codec Setup and Packet Decode:
+  // http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-580004
+
+  PRBool isSetupHeader = aPacket->bytes > 0 && aPacket->packet[0] == 0x5;
+
+  if (ret < 0 || mPacketCount > 3) {
+    // We've received an error, or the first three packets weren't valid
+    // header packets, assume bad input, and don't activate the bitstream.
+    mDoneReadingHeaders = PR_TRUE;
+  } else if (ret == 0 && isSetupHeader && mPacketCount == 3) {
+    // Successfully read the three header packets, activate the bitstream.
+    mDoneReadingHeaders = PR_TRUE;
+    mActive = PR_TRUE;
+  }
+  return mDoneReadingHeaders;
+}
+
+PRBool nsVorbisState::Init()
+{
+  if (!mActive)
+    return PR_FALSE;
+
+  int ret = vorbis_synthesis_init(&mDsp, &mInfo);
+  if (ret != 0) {
+    NS_WARNING("vorbis_synthesis_init() failed initializing vorbis bitstream");
+    return mActive = PR_FALSE;
+  }
+  ret = vorbis_block_init(&mDsp, &mBlock);
+  if (ret != 0) {
+    NS_WARNING("vorbis_block_init() failed initializing vorbis bitstream");
+    if (mActive) {
+      vorbis_dsp_clear(&mDsp);
+    }
+    return mActive = PR_FALSE;
+  }
+  return PR_TRUE;
+}
+
+PRInt64 nsVorbisState::Time(PRInt64 granulepos) {
+  if (granulepos == -1 || !mActive || mDsp.vi->rate == 0) {
+    return -1;
+  }
+  PRInt64 t = 0;
+  MulOverflow(1000, granulepos, t);
+  return t / mDsp.vi->rate;
+}
+
+nsSkeletonState::nsSkeletonState(ogg_page* aBosPage)
+  : nsOggCodecState(aBosPage)
+{
+  MOZ_COUNT_CTOR(nsSkeletonState);
+}
+
+nsSkeletonState::~nsSkeletonState()
+{
+  MOZ_COUNT_DTOR(nsSkeletonState);
+}
+
+PRBool nsSkeletonState::DecodeHeader(ogg_packet* aPacket)
+{
+  if (aPacket->e_o_s) {
+    mActive = PR_TRUE;
+    mDoneReadingHeaders = PR_TRUE;
+  }
+  return mDoneReadingHeaders;
+}
+
+// Adds two 64bit numbers, retuns PR_TRUE if addition succeeded, or PR_FALSE
+// if addition would result in an overflow.
+static PRBool AddOverflow(PRInt64 a, PRInt64 b, PRInt64& aResult) {
+  if (b < 1) {
+    if (PR_INT64_MIN - b <= a) {
+      aResult = a + b;
+      return PR_TRUE;
+    }
+  } else if (PR_INT64_MAX - b >= a) {
+    aResult = a + b;
+    return PR_TRUE;
+  }
+  return PR_FALSE;
+}
+
+// 64 bit integer multiplication with overflow checking. Returns PR_TRUE
+// if the multiplication was successful, or PR_FALSE if the operation resulted
+// in an integer overflow.
+static PRBool MulOverflow(PRInt64 a, PRInt64 b, PRInt64& aResult) {
+  // We break a multiplication a * b into of sign_a * sign_b * abs(a) * abs(b)
+  //
+  // This is equivalent to:
+  //
+  // (sign_a * sign_b) * ((a_hi * 2^32) + a_lo) * ((b_hi * 2^32) + b_lo)
+  //
+  // Which is equivalent to:
+  //
+  // (sign_a * sign_b) *
+  // ((a_hi * b_hi << 64) +
+  //  (a_hi * b_lo << 32) + (a_lo * b_hi << 32) +
+  //   a_lo * b_lo)
+  //
+  // So to check if a*b overflows, we must check each sub part of the above
+  // sum.
+  //
+  // Note: -1 * PR_INT64_MIN == PR_INT64_MIN ; we can't negate PR_INT64_MIN!
+  // Note: Shift of negative numbers is undefined.
+  //
+  // Figure out the sign after multiplication. Then we can just work with
+  // unsigned numbers.
+  PRInt64 sign = (!(a < 0) == !(b < 0)) ? 1 : -1;
+
+  PRInt64 abs_a = (a < 0) ? -a : a;
+  PRInt64 abs_b = (b < 0) ? -b : b;
+
+  if (abs_a < 0) {
+    NS_ASSERTION(a == PR_INT64_MIN, "How else can this happen?");
+    if (b == 0 || b == 1) {
+      aResult = a * b;
+      return PR_TRUE;
+    } else {
+      return PR_FALSE;
+    }
+  }
+
+  if (abs_b < 0) {
+    NS_ASSERTION(b == PR_INT64_MIN, "How else can this happen?");
+    if (a == 0 || a == 1) {
+      aResult = a * b;
+      return PR_TRUE;
+    } else {
+      return PR_FALSE;
+    }
+  }
+
+  NS_ASSERTION(abs_a >= 0 && abs_b >= 0, "abs values must be non-negative");
+
+  PRInt64 a_hi = abs_a >> 32;
+  PRInt64 a_lo = abs_a & 0xFFFFFFFF;
+  PRInt64 b_hi = abs_b >> 32;
+  PRInt64 b_lo = abs_b & 0xFFFFFFFF;
+
+  NS_ASSERTION((a_hi<<32) + a_lo == abs_a, "Partition must be correct");
+  NS_ASSERTION((b_hi<<32) + b_lo == abs_b, "Partition must be correct");
+
+  // In the sub-equation (a_hi * b_hi << 64), if a_hi or b_hi
+  // are non-zero, this will overflow as it's shifted by 64.
+  // Abort if this overflows.
+  if (a_hi != 0 && b_hi != 0) {
+    return PR_FALSE;
+  }
+
+  // We can now assume that either a_hi or b_hi is 0.
+  NS_ASSERTION(a_hi == 0 || b_hi == 0, "One of these must be 0");
+
+  // Next we calculate:
+  // (a_hi * b_lo << 32) + (a_lo * b_hi << 32)
+  // We can factor this as:
+  // (a_hi * b_lo + a_lo * b_hi) << 32
+  PRInt64 q = a_hi * b_lo + a_lo * b_hi;
+  if (q > PR_INT32_MAX) {
+    // q will overflow when we shift by 32; abort.
+    return PR_FALSE;
+  }
+  q <<= 32;
+
+  // Both a_lo and b_lo are less than INT32_MAX, so can't overflow.
+  PRUint64 lo = a_lo * b_lo;
+  if (lo > PR_INT64_MAX) {
+    return PR_FALSE;
+  }
+
+  // Add the final result. We must check for overflow during addition.
+  if (!AddOverflow(q, static_cast<PRInt64>(lo), aResult)) {
+    return PR_FALSE;
+  }
+
+  aResult *= sign;
+  NS_ASSERTION(a * b == aResult, "We didn't overflow, but result is wrong!");
+  return PR_TRUE;
+}
new file mode 100644
--- /dev/null
+++ b/content/media/ogg/nsOggCodecState.h
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: ML 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#if !defined(nsOggCodecState_h_)
+#define nsOggCodecState_h_
+
+#include <ogg/ogg.h>
+#include <theora/theoradec.h>
+#include <vorbis/codec.h>
+#include <nsDeque.h>
+
+class OggPageDeallocator : public nsDequeFunctor {
+  virtual void* operator() (void* aPage) {
+    ogg_page* p = static_cast<ogg_page*>(aPage);
+    delete p->header;
+    delete p;
+    return nsnull;
+  }
+};
+
+// A queue of ogg_pages. When we read a page, and it's not from the bitstream
+// which we're looking for a page for, we buffer the page in the nsOggCodecState,
+// rather than pushing it immediately into the ogg_stream_state object. This
+// is because if we're skipping up to the next keyframe in very large frame
+// sized videos, there may be several megabytes of data between keyframes,
+// and the ogg_stream_state would end up resizing its buffer every time we
+// added a new 4K page to the bitstream, which kills performance on Windows.
+class nsPageQueue : private nsDeque {
+public:
+  nsPageQueue() : nsDeque(new OggPageDeallocator()) {}
+  ~nsPageQueue() { Erase(); }
+  PRBool IsEmpty() { return nsDeque::GetSize() == 0; }
+  void Append(ogg_page* aPage);
+  ogg_page* PopFront() { return static_cast<ogg_page*>(nsDeque::PopFront()); }
+  ogg_page* PeekFront() { return static_cast<ogg_page*>(nsDeque::PeekFront()); }
+  void Erase() { nsDeque::Erase(); }
+};
+
+// Encapsulates the data required for decoding an ogg bitstream and for
+// converting granulepos to timestamps.
+class nsOggCodecState {
+ public:
+  // Ogg types we know about
+  enum CodecType {
+    TYPE_VORBIS=0,
+    TYPE_THEORA=1,
+    TYPE_SKELETON=2,
+    TYPE_UNKNOWN=3
+  };
+
+ public:
+  nsOggCodecState(ogg_page* aBosPage);
+  virtual ~nsOggCodecState();
+  
+  // Factory for creating nsCodecStates.
+  static nsOggCodecState* Create(ogg_page* aPage);
+  
+  virtual CodecType GetType() { return TYPE_UNKNOWN; }
+  
+  // Reads a header packet. Returns PR_TRUE when last header has been read.
+  virtual PRBool DecodeHeader(ogg_packet* aPacket) {
+    return (mDoneReadingHeaders = PR_TRUE);
+  }
+
+  // Returns the end time that a granulepos represents.
+  virtual PRInt64 Time(PRInt64 granulepos) { return -1; }
+
+  // Returns the start time that a granulepos represents.
+  virtual PRInt64 StartTime(PRInt64 granulepos) { return -1; }
+
+  // Initializes the codec state.
+  virtual PRBool Init();
+
+  // Returns PR_TRUE when this bitstream has finished reading all its
+  // header packets.
+  PRBool DoneReadingHeaders() { return mDoneReadingHeaders; }
+
+  // Deactivates the bitstream. Only the primary video and audio bitstreams
+  // should be active.
+  void Deactivate() { mActive = PR_FALSE; }
+
+  // Resets decoding state.
+  virtual nsresult Reset();
+
+  // Clones a page and adds it to our buffer of pages which we'll insert to
+  // the bitstream at a later time (using PageInFromBuffer()). Memory stored in
+  // cloned pages is freed when Reset() or PageInFromBuffer() are called.
+  inline void AddToBuffer(ogg_page* aPage) { mBuffer.Append(aPage); }
+
+  // Returns PR_TRUE if we had a buffered page and we successfully inserted it
+  // into the bitstream.
+  PRBool PageInFromBuffer();
+
+public:
+
+  // Number of packets read.  
+  PRUint64 mPacketCount;
+
+  // Serial number of the bitstream.
+  PRUint32 mSerial;
+
+  // Ogg specific state.
+  ogg_stream_state mState;
+
+  // Buffer of pages which we've not yet inserted into the ogg_stream_state.
+  nsPageQueue mBuffer;
+
+  // Is the bitstream active; whether we're decoding and playing this bitstream.
+  PRPackedBool mActive;
+  
+  // PR_TRUE when all headers packets have been read.
+  PRPackedBool mDoneReadingHeaders;
+};
+
+class nsVorbisState : public nsOggCodecState {
+public:
+  nsVorbisState(ogg_page* aBosPage);
+  virtual ~nsVorbisState();
+
+  virtual CodecType GetType() { return TYPE_VORBIS; }
+  virtual PRBool DecodeHeader(ogg_packet* aPacket);
+  virtual PRInt64 Time(PRInt64 granulepos);
+  virtual PRBool Init();
+  virtual nsresult Reset();
+
+  vorbis_info mInfo;
+  vorbis_comment mComment;
+  vorbis_dsp_state mDsp;
+  vorbis_block mBlock;
+};
+
+class nsTheoraState : public nsOggCodecState {
+public:
+  nsTheoraState(ogg_page* aBosPage);
+  virtual ~nsTheoraState();
+
+  virtual CodecType GetType() { return TYPE_THEORA; }
+  virtual PRBool DecodeHeader(ogg_packet* aPacket);
+  virtual PRInt64 Time(PRInt64 granulepos);
+  virtual PRInt64 StartTime(PRInt64 granulepos);
+  virtual PRBool Init();
+
+  // Returns the maximum number of milliseconds which a keyframe can be offset
+  // from any given interframe.
+  PRInt64 MaxKeyframeOffset();
+
+  th_info mInfo;
+  th_comment mComment;
+  th_setup_info *mSetup;
+  th_dec_ctx* mCtx;
+
+  // Frame duration in ms.
+  PRUint32 mFrameDuration;
+
+  // Number of frames per second.
+  float mFrameRate;
+
+  float mAspectRatio;
+};
+
+class nsSkeletonState : public nsOggCodecState {
+public:
+  nsSkeletonState(ogg_page* aBosPage);
+  virtual ~nsSkeletonState();
+  virtual CodecType GetType() { return TYPE_SKELETON; }
+  virtual PRBool DecodeHeader(ogg_packet* aPacket);
+  virtual PRInt64 Time(PRInt64 granulepos) { return -1; }
+  virtual PRBool Init() { return PR_TRUE; }
+};
+
+#endif
--- a/content/media/ogg/nsOggDecoder.cpp
+++ b/content/media/ogg/nsOggDecoder.cpp
@@ -16,2157 +16,237 @@
  * The Original Code is Mozilla code.
  *
  * The Initial Developer of the Original Code is the Mozilla Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2007
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-#include "nsOggDecoder.h"
-
+#include "nsOggPlayStateMachine.h"
 #include <limits>
-#include "prmem.h"
-#include "nsIFrame.h"
-#include "nsIDocument.h"
-#include "nsThreadUtils.h"
-#include "nsIDOMHTMLMediaElement.h"
 #include "nsNetUtil.h"
 #include "nsAudioStream.h"
-#include "nsChannelReader.h"
 #include "nsHTMLVideoElement.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
-#include "nsAutoLock.h"
 #include "nsTArray.h"
-#include "nsNetUtil.h"
-
-using mozilla::TimeDuration;
-using mozilla::TimeStamp;
-using namespace mozilla::layers;
+#include "nsOggCodecState.h"
+#include "nsOggDecoder.h"
+#include "nsOggReader.h"
 
 #ifdef PR_LOGGING
-static PRLogModuleInfo* gOggDecoderLog;
+PRLogModuleInfo* gOggDecoderLog;
 #define LOG(type, msg) PR_LOG(gOggDecoderLog, type, msg)
 #else
 #define LOG(type, msg)
 #endif
 
-/* 
-   The maximum height and width of the video. Used for
-   sanitizing the memory allocation of the RGB buffer.
-   The maximum resolution we anticipate encountering in the
-   wild is 2160p - 3840x2160 pixels.
-*/
-#define MAX_VIDEO_WIDTH  4000
-#define MAX_VIDEO_HEIGHT 3000
-
-// The number of entries in oggplay buffer list.  This value is totally
-// arbitrary.  Note that the actual number of video/audio frames buffered is
-// twice this, because the current implementation releases OggPlay's buffer
-// entries and stores references or copies of the underlying data in the
-// FrameQueue.
-#define OGGPLAY_BUFFER_SIZE 5
-
-// The number of frames to read before audio callback is called.
-// This value is the one used by the oggplay examples.
-#define OGGPLAY_FRAMES_PER_CALLBACK 2048
-
-// Offset into Ogg buffer containing audio information. This value
-// is the one used by the oggplay examples.
-#define OGGPLAY_AUDIO_OFFSET 250L
-
-// Wait this number of seconds when buffering, then leave and play
-// as best as we can if the required amount of data hasn't been
-// retrieved.
-#define BUFFERING_WAIT 15
-
-// The amount of data to retrieve during buffering is computed based
-// on the download rate. BUFFERING_MIN_RATE is the minimum download
-// rate to be used in that calculation to help avoid constant buffering
-// attempts at a time when the average download rate has not stabilised.
-#define BUFFERING_MIN_RATE 50000
-#define BUFFERING_RATE(x) ((x)< BUFFERING_MIN_RATE ? BUFFERING_MIN_RATE : (x))
-
-// The number of seconds of buffer data before buffering happens
-// based on current playback rate.
-#define BUFFERING_SECONDS_LOW_WATER_MARK 1
-
-// The minimum size buffered byte range inside which we'll consider
-// trying a bounded-seek. When we seek, we first try to seek inside all
-// buffered ranges larger than this, and if they all fail we fall back to
-// an unbounded seek over the whole media. 64K is approximately 16 pages.
-#define MIN_BOUNDED_SEEK_SIZE (64 * 1024)
-
-class nsOggStepDecodeEvent;
-
-/* 
-  All reading (including seeks) from the nsMediaStream are done on the
-  decoding thread. The decoder thread is informed before closing that
-  the stream is about to close via the Shutdown
-  event. oggplay_prepare_for_close is called before sending the
-  shutdown event to tell liboggplay to shutdown.
-
-  This call results in oggplay internally not calling any
-  read/write/seek/tell methods, and returns a value that results in
-  stopping the decoder thread.
-
-  oggplay_close is called in the destructor which results in the media
-  stream being closed. This is how the nsMediaStream contract that no
-  read/seeking must occur during or after Close is called is enforced.
-
-  This object keeps pointers to the nsOggDecoder and nsChannelReader
-  objects.  Since the lifetime of nsOggDecodeStateMachine is
-  controlled by nsOggDecoder it will never have a stale reference to
-  these objects. The reader is destroyed by the call to oggplay_close
-  which is done in the destructor so again this will never be a stale
-  reference.
-
-  All internal state is synchronised via the decoder monitor. NotifyAll
-  on the monitor is called when the state of the state machine is changed
-  by the main thread. The following changes to state cause a notify:
-
-    mState and data related to that state changed (mSeekTime, etc)
-    Ogg Metadata Loaded
-    First Frame Loaded  
-    Frame decoded    
-    
-  See nsOggDecoder.h for more details.
-*/
-class nsOggDecodeStateMachine : public nsRunnable
-{
-  friend class nsOggStepDecodeEvent;
-public:
-  // Object to hold the decoded data from a frame
-  class FrameData {
-  public:
-    FrameData() :
-      mVideoHeader(nsnull),
-      mVideoWidth(0),
-      mVideoHeight(0),
-      mUVWidth(0),
-      mUVHeight(0),
-      mDecodedFrameTime(0.0),
-      mTime(0.0)
-    {
-      MOZ_COUNT_CTOR(FrameData);
-    }
-
-    ~FrameData()
-    {
-      MOZ_COUNT_DTOR(FrameData);
-      ClearVideoHeader();
-    }
-
-    void ClearVideoHeader() {
-      if (mVideoHeader) {
-        oggplay_callback_info_unlock_item(mVideoHeader);
-        mVideoHeader = nsnull;
-      }
-    }
-
-    // Write the audio data from the frame to the Audio stream.
-    void Write(nsAudioStream* aStream)
-    {
-      aStream->Write(mAudioData.Elements(), mAudioData.Length());
-      mAudioData.Clear(); 
-    }
-
-    void SetVideoHeader(OggPlayDataHeader* aVideoHeader)
-    {
-      NS_ABORT_IF_FALSE(!mVideoHeader, "Frame already owns a video header");
-      mVideoHeader = aVideoHeader;
-      oggplay_callback_info_lock_item(mVideoHeader);
-    }
-
-    // The position in the stream where this frame ended, in bytes
-    PRInt64 mEndStreamPosition;
-    OggPlayDataHeader* mVideoHeader;
-    nsTArray<float> mAudioData;
-    int mVideoWidth;
-    int mVideoHeight;
-    int mUVWidth;
-    int mUVHeight;
-    float mDecodedFrameTime;
-    float mTime;
-    OggPlayStreamInfo mState;
-  };
-
-  // A queue of decoded video frames. 
-  class FrameQueue
-  {
-  public:
-    FrameQueue() :
-      mHead(0),
-      mTail(0),
-      mCount(0)
-    {
-    }
-
-    void Push(FrameData* frame)
-    {
-      NS_ASSERTION(!IsFull(), "FrameQueue is full");
-      mQueue[mTail] = frame;
-      mTail = (mTail+1) % OGGPLAY_BUFFER_SIZE;
-      ++mCount;
-    }
-
-    FrameData* Peek() const
-    {
-      NS_ASSERTION(mCount > 0, "FrameQueue is empty");
-
-      return mQueue[mHead];
-    }
-
-    FrameData* Pop()
-    {
-      NS_ASSERTION(mCount, "FrameQueue is empty");
-
-      FrameData* result = mQueue[mHead];
-      mHead = (mHead + 1) % OGGPLAY_BUFFER_SIZE;
-      --mCount;
-      return result;
-    }
-
-    PRBool IsEmpty() const
-    {
-      return mCount == 0;
-    }
-
-    PRUint32 GetCount() const
-    {
-      return mCount;
-    }
-
-    PRBool IsFull() const
-    {
-      return mCount == OGGPLAY_BUFFER_SIZE;
-    }
-
-    PRUint32 ResetTimes(float aPeriod)
-    {
-      PRUint32 frames = 0;
-      if (mCount > 0) {
-        PRUint32 current = mHead;
-        do {
-          mQueue[current]->mTime = frames * aPeriod;
-          frames += 1;
-          current = (current + 1) % OGGPLAY_BUFFER_SIZE;
-        } while (current != mTail);
-      }
-      return frames;
-    }
-
-  private:
-    FrameData* mQueue[OGGPLAY_BUFFER_SIZE];
-    PRUint32 mHead;
-    PRUint32 mTail;
-    // This isn't redundant with mHead/mTail, since when mHead == mTail
-    // it's ambiguous whether the queue is full or empty
-    PRUint32 mCount;
-  };
-
-  // Enumeration for the valid states
-  enum State {
-    DECODER_STATE_DECODING_METADATA,
-    DECODER_STATE_DECODING,
-    DECODER_STATE_SEEKING,
-    DECODER_STATE_BUFFERING,
-    DECODER_STATE_COMPLETED,
-    DECODER_STATE_SHUTDOWN
-  };
-
-  nsOggDecodeStateMachine(nsOggDecoder* aDecoder);
-  ~nsOggDecodeStateMachine();
-
-  // Cause state transitions. These methods obtain the decoder monitor
-  // to synchronise the change of state, and to notify other threads
-  // that the state has changed.
-  void Shutdown();
-  void Decode();
-  void Seek(float aTime);
-  void StopStepDecodeThread(nsAutoMonitor* aMonitor);
-
-  NS_IMETHOD Run();
-
-  PRBool HasAudio()
-  {
-    NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "HasAudio() called during invalid state");
-    PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-    return mAudioTrack != -1;
-  }
-
-  // Decode one frame of data, returning the OggPlay error code. Must
-  // be called only when the current state > DECODING_METADATA. The decode 
-  // monitor MUST NOT be locked during this call since it can take a long
-  // time. liboggplay internally handles locking.
-  // Any return value apart from those below is mean decoding cannot continue.
-  // E_OGGPLAY_CONTINUE       = One frame decoded and put in buffer list
-  // E_OGGPLAY_USER_INTERRUPT = One frame decoded, buffer list is now full
-  // E_OGGPLAY_TIMEOUT        = No frames decoded, timed out
-  OggPlayErrorCode DecodeFrame();
-
-  // Handle any errors returned by liboggplay when decoding a frame.
-  // Since this function can change the decoding state it must be called
-  // with the decoder lock held.
-  void HandleDecodeErrors(OggPlayErrorCode r);
-
-  // Returns the next decoded frame of data. The caller is responsible
-  // for freeing the memory returned. This function must be called
-  // only when the current state > DECODING_METADATA. The decode
-  // monitor lock does not need to be locked during this call since
-  // liboggplay internally handles locking.
-  FrameData* NextFrame();
-
-  // Play a frame of decoded video. The decode monitor is obtained
-  // internally by this method for synchronisation.
-  void PlayFrame();
-
-  // Play the video data from the given frame. The decode monitor
-  // must be locked when calling this method.
-  void PlayVideo(FrameData* aFrame);
-
-  // Plays the audio for the frame, plus any outstanding audio data
-  // buffered by nsAudioStream and not yet written to the
-  // hardware. The audio data for the frame is cleared out so
-  // subsequent calls with the same frame do not re-write the data.
-  // The decode monitor must be locked when calling this method.
-  void PlayAudio(FrameData* aFrame);
-
-  // Called from the main thread to get the current frame time. The decoder
-  // monitor must be obtained before calling this.
-  float GetCurrentTime();
-
-  // Called from the main thread to get the duration. The decoder monitor
-  // must be obtained before calling this. It is in units of milliseconds.
-  PRInt64 GetDuration();
-
-  // Called from the main thread to set the duration of the media resource
-  // if it is able to be obtained via HTTP headers. The decoder monitor
-  // must be obtained before calling this.
-  void SetDuration(PRInt64 aDuration);
-
-  // Called from the main thread to set whether the media resource can
-  // be seeked. The decoder monitor must be obtained before calling this.
-  void SetSeekable(PRBool aSeekable);
-
-  // Set the audio volume. The decoder monitor must be obtained before
-  // calling this.
-  void SetVolume(float aVolume);
-
-  // Clear the flag indicating that a playback position change event
-  // is currently queued. This is called from the main thread and must
-  // be called with the decode monitor held.
-  void ClearPositionChangeFlag();
-
-  // Called by decoder and main thread.
-  nsHTMLMediaElement::NextFrameStatus GetNextFrameStatus();
-
-  // Must be called with the decode monitor held. Can be called by main
-  // thread.
-  PRBool HaveNextFrameData() const {
-    return !mDecodedFrames.IsEmpty() &&
-      (mDecodedFrames.Peek()->mDecodedFrameTime > mCurrentFrameTime ||
-       mDecodedFrames.GetCount() > 1);
-  }
-
-  // Must be called with the decode monitor held. Can be called by main
-  // thread.
-  PRBool IsBuffering() const {
-    PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-    return mState == nsOggDecodeStateMachine::DECODER_STATE_BUFFERING;
-  }
-
-  // Must be called with the decode monitor held. Can be called by main
-  // thread.
-  PRBool IsSeeking() const {
-    PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-    return mState == nsOggDecodeStateMachine::DECODER_STATE_SEEKING;
-  }
-
-protected:
-
-  // Decodes from the current position until encountering a frame with time
-  // greater or equal to aSeekTime.
-  PRBool DecodeToFrame(nsAutoMonitor& aMonitor,
-                       float aSeekTime);
-
-  // Convert the OggPlay frame information into a format used by Gecko
-  // (RGB for video, float for sound, etc).The decoder monitor must be
-  // acquired in the scope of calls to these functions. They must be
-  // called only when the current state > DECODING_METADATA.
-  void HandleVideoData(FrameData* aFrame, int aTrackNum, OggPlayDataHeader* aVideoHeader);
-  void HandleAudioData(FrameData* aFrame, OggPlayAudioData* aAudioData, int aSize);
-
-  void UpdateReadyState() {
-    PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-
-    nsCOMPtr<nsIRunnable> event;
-    switch (GetNextFrameStatus()) {
-      case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING:
-        event = NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, NextFrameUnavailableBuffering);
-        break;
-      case nsHTMLMediaElement::NEXT_FRAME_AVAILABLE:
-        event = NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, NextFrameAvailable);
-        break;
-      case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE:
-        event = NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, NextFrameUnavailable);
-        break;
-      default:
-        PR_NOT_REACHED("unhandled frame state");
-    }
-
-    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-  }
-
-  // These methods can only be called on the decoding thread.
-  void LoadOggHeaders(nsChannelReader* aReader);
-
-  // Initializes and opens the audio stream. Called from the decode
-  // thread only. Must be called with the decode monitor held.
-  void OpenAudioStream();
-
-  // Closes and releases resources used by the audio stream. Called
-  // from the decode thread only. Must be called with the decode
-  // monitor held.
-  void CloseAudioStream();
-
-  // Start playback of audio, either by opening or resuming the audio
-  // stream. Must be called with the decode monitor held.
-  void StartAudio();
-
-  // Stop playback of audio, either by closing or pausing the audio
-  // stream. Must be called with the decode monitor held.
-  void StopAudio();
-
-  // Start playback of media. Must be called with the decode monitor held.
-  // This opens or re-opens the audio stream for playback to start.
-  void StartPlayback();
-
-  // Stop playback of media. Must be called with the decode monitor held.
-  // This actually closes the audio stream and releases any OS resources.
-  void StopPlayback();
-
-  // Pause playback of media. Must be called with the decode monitor held.
-  // This does not close the OS based audio stream - it suspends it to be
-  // resumed later.
-  void PausePlayback();
-
-  // Resume playback of media. Must be called with the decode monitor held.
-  // This resumes a paused audio stream.
-  void ResumePlayback();
-
-  // Update the playback position. This can result in a timeupdate event
-  // and an invalidate of the frame being dispatched asynchronously if
-  // there is no such event currently queued.
-  // Only called on the decoder thread. Must be called with
-  // the decode monitor held.
-  void UpdatePlaybackPosition(float aTime);
-
-  // Takes decoded frames from liboggplay's internal buffer and
-  // places them in our frame queue. Must be called with the decode
-  // monitor held.
-  void QueueDecodedFrames();
-
-  // Seeks the OggPlay to aTime, inside buffered byte ranges in aReader's
-  // media stream.
-  nsresult Seek(float aTime, nsChannelReader* aReader);
-
-  // Sets the current video and audio track to active in liboggplay.
-  // Called from the decoder thread only.
-  void SetTracksActive();
-
-private:
-  // *****
-  // The follow fields are only accessed by the decoder thread
-  // *****
-
-  // The decoder object that created this state machine. The decoder
-  // always outlives us since it controls our lifetime.
-  nsOggDecoder* mDecoder;
-
-  // The OggPlay handle. Synchronisation of calls to oggplay functions
-  // are handled by liboggplay. We control the lifetime of this
-  // object, destroying it in our destructor.
-  OggPlay* mPlayer;
-
-  // Frame data containing decoded video/audio for the frame the
-  // current frame and the previous frame. Always accessed with monitor
-  // held. Written only via the decoder thread, but can be tested on
-  // main thread via HaveNextFrameData.
-  FrameQueue mDecodedFrames;
-
-  // The time that playback started from the system clock. This is used
-  // for synchronising frames.  It is reset after a seek as the mTime member
-  // of FrameData is reset to start at 0 from the first frame after a seek.
-  // Accessed only via the decoder thread.
-  TimeStamp mPlayStartTime;
-
-  // The time that playback was most recently paused, either via
-  // buffering or pause. This is used to compute mPauseDuration for
-  // a/v sync adjustments.  Accessed only via the decoder thread.
-  TimeStamp mPauseStartTime;
-
-  // The total time that has been spent in completed pauses (via
-  // 'pause' or buffering). This is used to adjust for these
-  // pauses when computing a/v synchronisation. Accessed only via the
-  // decoder thread.
-  TimeDuration mPauseDuration;
-
-  // PR_TRUE if the media is playing and the decoder has started
-  // the sound and adjusted the sync time for pauses. PR_FALSE
-  // if the media is paused and the decoder has stopped the sound
-  // and adjusted the sync time for pauses. Accessed only via the
-  // decoder thread.
-  PRPackedBool mPlaying;
-
-  // Number of seconds of data video/audio data held in a frame.
-  // Accessed only via the decoder thread.
-  double mCallbackPeriod;
-
-  // Video data. These are initially set when the metadata is loaded.
-  // They are only accessed from the decoder thread.
-  PRInt32 mVideoTrack;
-  float   mFramerate;
-  float   mAspectRatio;
-
-  // Audio data. These are initially set when the metadata is loaded.
-  // They are only accessed from the decoder thread.
-  PRInt32 mAudioRate;
-  PRInt32 mAudioChannels;
-  PRInt32 mAudioTrack;
-
-  // Time that buffering started. Used for buffering timeout and only
-  // accessed in the decoder thread.
-  TimeStamp mBufferingStart;
-
-  // Download position where we should stop buffering. Only
-  // accessed in the decoder thread.
-  PRInt64 mBufferingEndOffset;
-
-  // The last decoded video frame. Used for computing the sleep period
-  // between frames for a/v sync.  Read/Write from the decode thread only.
-  PRUint64 mLastFrame;
-
-  // The decoder position of the end of the last decoded video frame.
-  // Read/Write from the decode thread only.
-  PRInt64 mLastFramePosition;
-
-  // Thread that steps through decoding each frame using liboggplay. Only accessed
-  // via the decode thread.
-  nsCOMPtr<nsIThread> mStepDecodeThread;
-
-  // *****
-  // The follow fields are accessed by the decoder thread or
-  // the main thread.
-  // *****
-
-  // The decoder monitor must be obtained before modifying this state.
-  // NotifyAll on the monitor must be called when the state is changed by
-  // the main thread so the decoder thread can wake up.
-  State mState;
-
-  // Position to seek to when the seek state transition occurs. The
-  // decoder monitor lock must be obtained before reading or writing
-  // this value.
-  float mSeekTime;
-
-  // The audio stream resource. Used on the decode thread and the
-  // main thread (Via the SetVolume call). Synchronisation via
-  // mDecoder monitor.
-  nsAutoPtr<nsAudioStream> mAudioStream;
-
-  // The time of the current frame in seconds. This is referenced from
-  // 0.0 which is the initial start of the stream. Set by the decode
-  // thread, and read-only from the main thread to get the current
-  // time value. Synchronised via decoder monitor.
-  float mCurrentFrameTime;
-
-  // The presentation times of the first frame that was decoded. This is 
-  // the start time of the frame. This is subtracted from each frames'
-  // timestamp, so that playback appears to start at time 0 and end at
-  // time mDuration. Read/Written from the decode thread, read from the 
-  // main thread. Synchronised via decoder monitor.
-  float mPlaybackStartTime;
-
-  // Volume of playback. 0.0 = muted. 1.0 = full volume. Read/Written
-  // from the decode and main threads. Synchronised via decoder
-  // monitor.
-  float mVolume;
-
-  // Duration of the media resource. It is accessed from the decoder and main
-  // threads. Synchronised via decoder monitor. It is in units of
-  // milliseconds.
-  PRInt64 mDuration;
-
-  // PR_TRUE if the media resource can be seeked. Accessed from the decoder
-  // and main threads. Synchronised via decoder monitor.
-  PRPackedBool mSeekable;
-
-  // PR_TRUE if an event to notify about a change in the playback
-  // position has been queued, but not yet run. It is set to PR_FALSE when
-  // the event is run. This allows coalescing of these events as they can be
-  // produced many times per second. Synchronised via decoder monitor.
-  PRPackedBool mPositionChangeQueued;
-
-  // PR_TRUE if the step decode loop thread has finished decoding. It is
-  // written by the step decode thread and read and written by the state
-  // machine thread (but only written by the state machine thread while
-  // the step decode thread is not running).
-  // Synchronised via decoder monitor.
-  PRPackedBool mDecodingCompleted;
-
-  // PR_TRUE if the step decode loop thread should exit now. It is
-  // written by the state machine thread and read by the step decode thread.
-  // Synchronised via decoder monitor.
-  PRPackedBool mExitStepDecodeThread;
-
-  // PR_TRUE if the step decode loop has indicated that we need to buffer.
-  // Accessed by the step decode thread and the decode state machine thread.
-  // Synchronised via the decoder monitor.
-  PRPackedBool mBufferExhausted;
-
-  // PR_TRUE if mDuration has a value obtained from an HTTP header.
-  // Read/Written from the decode and main threads. Synchronised via the
-  // decoder monitor.
-  PRPackedBool mGotDurationFromHeader;
-};
-
-// Event that gets posted to the thread that is responsible for decoding
-// Ogg frames. Decodes each frame of an Ogg file. Locking of liboggplay 
-// is managed by liboggplay. The thread is created when the frames first
-// need to be decoded and is shutdown when decoding is not needed (either
-// completed, or seeking).
-class nsOggStepDecodeEvent : public nsRunnable {
-private:
-  // Since the lifetime of this event loop is controlled by the
-  // decode state machine object, it is safe to keep an 
-  // unreferenced counted pointer to it, so we can inform 
-  // it when we've finished decoding.
-  nsOggDecodeStateMachine* mDecodeStateMachine;
-
-  // The lifetime of this player is managed by the decode state
-  // machine thread. This event is created and destroyed before
-  // the mPlayer object itself is deleted. 
-  OggPlay* mPlayer;
-
-public:
-  nsOggStepDecodeEvent(nsOggDecodeStateMachine* machine, OggPlay* player) : 
-    mDecodeStateMachine(machine), mPlayer(player) {}
-  
-  // Return true if we are in a state where the decoder should not be running.
-  PRBool InStopDecodingState() {
-    PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecodeStateMachine->mDecoder->GetMonitor());
-    return 
-      mDecodeStateMachine->mState != nsOggDecodeStateMachine::DECODER_STATE_DECODING &&
-      mDecodeStateMachine->mState != nsOggDecodeStateMachine::DECODER_STATE_BUFFERING;
-  }
-  
-  // This method will block on oggplay_step_decoding when oggplay's
-  // internal buffers are full. It is unblocked by the decode
-  // state machine thread via a call to oggplay_prepare_for_close
-  // during the shutdown protocol. It is unblocked during seeking
-  // by release frames from liboggplay's frame queue.
-  NS_IMETHOD Run() {
-    OggPlayErrorCode r = E_OGGPLAY_TIMEOUT;
-    nsAutoMonitor mon(mDecodeStateMachine->mDecoder->GetMonitor());
-    nsOggDecoder* decoder = mDecodeStateMachine->mDecoder;
-    NS_ASSERTION(!mDecodeStateMachine->mDecodingCompleted,
-                 "State machine should have cleared this flag");
-
-    while (!mDecodeStateMachine->mExitStepDecodeThread &&
-           !InStopDecodingState() &&
-           (r == E_OGGPLAY_TIMEOUT ||
-            r == E_OGGPLAY_USER_INTERRUPT ||
-            r == E_OGGPLAY_CONTINUE)) {
-      if (mDecodeStateMachine->mBufferExhausted) {
-        mon.Wait();
-      } else {
-        // decoder and decoder->mReader are never null here because
-        // they are non-null through the lifetime of the state machine
-        // thread, which includes the lifetime of this thread.
-        PRInt64 initialDownloadPosition =
-          decoder->mReader->Stream()->GetCachedDataEnd(decoder->mDecoderPosition);
-
-        mon.Exit();
-        r = oggplay_step_decoding(mPlayer);
-        mon.Enter();
-
-        mDecodeStateMachine->HandleDecodeErrors(r);
-
-        // Check whether decoding the last frame required us to read data
-        // that wasn't available at the start of the frame. That means
-        // we should probably start buffering.
-        if (decoder->mDecoderPosition > initialDownloadPosition) {
-          mDecodeStateMachine->mBufferExhausted = PR_TRUE;
-        }
-
-        // If PlayFrame is waiting, wake it up so we can run the
-        // decoder loop and move frames from the oggplay queue to our
-        // queue. Also needed to wake up the decoder loop that waits
-        // for a frame to be ready to display.
-        mon.NotifyAll();
-      }
-    }
-
-    mDecodeStateMachine->mDecodingCompleted = PR_TRUE;
-    return NS_OK;
-  }
-};
-
-nsOggDecodeStateMachine::nsOggDecodeStateMachine(nsOggDecoder* aDecoder) :
-  mDecoder(aDecoder),
-  mPlayer(0),
-  mPlayStartTime(),
-  mPauseStartTime(),
-  mPauseDuration(0),
-  mPlaying(PR_FALSE),
-  mCallbackPeriod(1.0),
-  mVideoTrack(-1),
-  mFramerate(0.0),
-  mAspectRatio(1.0),
-  mAudioRate(0),
-  mAudioChannels(0),
-  mAudioTrack(-1),
-  mBufferingStart(),
-  mBufferingEndOffset(0),
-  mLastFrame(0),
-  mLastFramePosition(-1),
-  mState(DECODER_STATE_DECODING_METADATA),
-  mSeekTime(0.0),
-  mCurrentFrameTime(0.0),
-  mPlaybackStartTime(0.0), 
-  mVolume(1.0),
-  mDuration(-1),
-  mSeekable(PR_TRUE),
-  mPositionChangeQueued(PR_FALSE),
-  mDecodingCompleted(PR_FALSE),
-  mExitStepDecodeThread(PR_FALSE),
-  mBufferExhausted(PR_FALSE),
-  mGotDurationFromHeader(PR_FALSE)
-{
-}
-
-nsOggDecodeStateMachine::~nsOggDecodeStateMachine()
-{
-  while (!mDecodedFrames.IsEmpty()) {
-    delete mDecodedFrames.Pop();
-  }
-  oggplay_close(mPlayer);
-}
-
-OggPlayErrorCode nsOggDecodeStateMachine::DecodeFrame()
-{
-  OggPlayErrorCode r = oggplay_step_decoding(mPlayer);
-  return r;
-}
-
-void nsOggDecodeStateMachine::HandleDecodeErrors(OggPlayErrorCode aErrorCode)
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-
-  if (aErrorCode != E_OGGPLAY_TIMEOUT &&
-      aErrorCode != E_OGGPLAY_OK &&
-      aErrorCode != E_OGGPLAY_USER_INTERRUPT &&
-      aErrorCode != E_OGGPLAY_CONTINUE) {
-    mState = DECODER_STATE_SHUTDOWN;
-    nsCOMPtr<nsIRunnable> event =
-      NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, DecodeError);
-    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-  }
-}
-
-nsOggDecodeStateMachine::FrameData* nsOggDecodeStateMachine::NextFrame()
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  OggPlayCallbackInfo** info = oggplay_buffer_retrieve_next(mPlayer);
-  if (!info)
-    return nsnull;
-
-  FrameData* frame = new FrameData();
-  if (!frame) {
-    return nsnull;
-  }
-
-  frame->mTime = mCallbackPeriod * mLastFrame;
-  frame->mEndStreamPosition = mDecoder->mDecoderPosition;
-  mLastFrame += 1;
-
-  if (mLastFramePosition >= 0) {
-    NS_ASSERTION(frame->mEndStreamPosition >= mLastFramePosition,
-                 "Playback positions must not decrease without an intervening reset");
-    TimeStamp base = mPlayStartTime;
-    if (base.IsNull()) {
-      // It doesn't really matter what 'base' is, so just use 'now' if
-      // we haven't started playback.
-      base = TimeStamp::Now();
-    }
-    mDecoder->mPlaybackStatistics.Start(
-        base + TimeDuration::FromMilliseconds(NS_round(frame->mTime*1000)));
-    mDecoder->mPlaybackStatistics.AddBytes(frame->mEndStreamPosition - mLastFramePosition);
-    mDecoder->mPlaybackStatistics.Stop(
-        base + TimeDuration::FromMilliseconds(NS_round(mCallbackPeriod*mLastFrame*1000)));
-    mDecoder->UpdatePlaybackRate();
-  }
-  mLastFramePosition = frame->mEndStreamPosition;
-
-  int num_tracks = oggplay_get_num_tracks(mPlayer);
-  float audioTime = -1.0;
-  float videoTime = -1.0;
-
-  if (mVideoTrack != -1 &&
-      num_tracks > mVideoTrack &&
-      oggplay_callback_info_get_type(info[mVideoTrack]) == OGGPLAY_YUV_VIDEO) {
-    OggPlayDataHeader** headers = oggplay_callback_info_get_headers(info[mVideoTrack]);
-    if (headers[0]) {
-      videoTime = ((float)oggplay_callback_info_get_presentation_time(headers[0]))/1000.0;
-      HandleVideoData(frame, mVideoTrack, headers[0]);
-    }
-  }
-
-  // If the audio stream has finished, but there's still video frames to
-  // be rendered, we need to send blank audio data to the audio hardware,
-  // so that the audio clock, which maintains the presentation time, keeps
-  // incrementing.
-  PRBool needSilence = PR_FALSE;
-
-  if (mAudioTrack != -1 && num_tracks > mAudioTrack) {
-    OggPlayDataType type = oggplay_callback_info_get_type(info[mAudioTrack]);
-    needSilence = (type == OGGPLAY_INACTIVE);
-    if (type == OGGPLAY_FLOATS_AUDIO) {
-      OggPlayDataHeader** headers = oggplay_callback_info_get_headers(info[mAudioTrack]);
-      if (headers[0]) {
-        audioTime = ((float)oggplay_callback_info_get_presentation_time(headers[0]))/1000.0;
-        int required = oggplay_callback_info_get_required(info[mAudioTrack]);
-        for (int j = 0; j < required; ++j) {
-          int size = oggplay_callback_info_get_record_size(headers[j]);
-          OggPlayAudioData* audio_data = oggplay_callback_info_get_audio_data(headers[j]);
-          HandleAudioData(frame, audio_data, size);
-        }
-      }
-    }
-  }
-
-  if (needSilence) {
-    // Write silence to keep audio clock moving for av sync
-    size_t count = mAudioChannels * mAudioRate * mCallbackPeriod;
-    // count must be evenly divisble by number of channels.
-    count = mAudioChannels * PRInt32(NS_ceil(mAudioRate*mCallbackPeriod));
-    float* data = frame->mAudioData.AppendElements(count);
-    if (data) {
-      memset(data, 0, sizeof(float)*count);
-    }
-  }
-
-  // Pick one stream to act as the reference track to indicate if the
-  // stream has ended, seeked, etc.
-  if (videoTime >= 0) {
-    frame->mState = oggplay_callback_info_get_stream_info(info[mVideoTrack]);
-    frame->mDecodedFrameTime = videoTime;
-  } else if (audioTime >= 0) {
-    frame->mState = oggplay_callback_info_get_stream_info(info[mAudioTrack]);
-    frame->mDecodedFrameTime = audioTime;
-  } else {
-    NS_WARNING("Encountered frame with no audio or video data");
-    frame->mState = OGGPLAY_STREAM_UNINITIALISED;
-    frame->mDecodedFrameTime = 0.0;
-  }
-
-  oggplay_buffer_release(mPlayer, info);
-  return frame;
-}
-
-void nsOggDecodeStateMachine::HandleVideoData(FrameData* aFrame, int aTrackNum, OggPlayDataHeader* aVideoHeader) {
-  if (!aVideoHeader)
-    return;
-
-  int y_width = 0;
-  int y_height = 0;
-  oggplay_get_video_y_size(mPlayer, aTrackNum, &y_width, &y_height);
-  int uv_width = 0;
-  int uv_height = 0;
-  oggplay_get_video_uv_size(mPlayer, aTrackNum, &uv_width, &uv_height);
-
-  if (y_width >= MAX_VIDEO_WIDTH || y_height >= MAX_VIDEO_HEIGHT) {
-    return;
-  }
-
-  aFrame->mVideoWidth = y_width;
-  aFrame->mVideoHeight = y_height;
-  aFrame->mUVWidth = uv_width;
-  aFrame->mUVHeight = uv_height;
-  aFrame->SetVideoHeader(aVideoHeader);
-}
-
-void nsOggDecodeStateMachine::HandleAudioData(FrameData* aFrame, OggPlayAudioData* aAudioData, int aSize) {
-  // 'aSize' is number of samples. Multiply by number of channels to
-  // get the actual number of floats being sent.
-  int size = aSize * mAudioChannels;
-
-  aFrame->mAudioData.AppendElements(reinterpret_cast<float*>(aAudioData), size);
-}
-
-void nsOggDecodeStateMachine::PlayFrame() {
-  // Play a frame of video and/or audio data.
-  // If we are playing we open the audio stream if needed
-  // If there are decoded frames in the queue a single frame
-  // is popped off and played if it is time for that frame
-  // to display. 
-  // If it is not time yet to display the frame, we either
-  // continue decoding frames, or wait until it is time for
-  // the frame to display if the queue is full.
-  //
-  // If the decode state is not PLAYING then we just exit
-  // so we can continue decoding frames. If the queue is
-  // full we wait for a state change.
-  nsAutoMonitor mon(mDecoder->GetMonitor());
-
-  if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
-    if (!mPlaying) {
-      ResumePlayback();
-    }
-
-    if (!mDecodedFrames.IsEmpty()) {
-      FrameData* frame = mDecodedFrames.Peek();
-      if (frame->mState == OGGPLAY_STREAM_JUST_SEEKED) {
-        // After returning from a seek all mTime members of
-        // FrameData start again from a time position of 0.
-        // Reset the play start time.
-        mPlayStartTime = TimeStamp::Now();
-        mPauseDuration = TimeDuration(0);
-        frame->mState = OGGPLAY_STREAM_INITIALISED;
-      }
-
-      double time;
-      PRUint32 hasAudio = frame->mAudioData.Length();
-      for (;;) {
-        // Even if the frame has had its audio data written we call
-        // PlayAudio to ensure that any data we have buffered in the
-        // nsAudioStream is written to the hardware.
-        PlayAudio(frame);
-        double hwtime = mAudioStream && hasAudio ? mAudioStream->GetPosition() : -1.0;
-        time = hwtime < 0.0 ?
-          (TimeStamp::Now() - mPlayStartTime - mPauseDuration).ToSeconds() :
-          hwtime;
-        // Resynchronize the system clock against the audio clock.
-        if (hwtime >= 0.0) {
-          mPlayStartTime = TimeStamp::Now();
-          mPlayStartTime -= TimeDuration::FromMilliseconds(hwtime * 1000.0);
-          mPauseDuration = TimeDuration(0);
-        }
-        // Is it time for the next frame?  Using an integer here avoids f.p.
-        // rounding errors that can cause multiple 0ms waits (Bug 495352)
-        PRInt64 wait = PRInt64((frame->mTime - time)*1000);
-        if (wait <= 0)
-          break;
-        mon.Wait(PR_MillisecondsToInterval(wait));
-        if (mState == DECODER_STATE_SHUTDOWN)
-          return;
-      }
-
-      mDecodedFrames.Pop();
-      QueueDecodedFrames();
-
-      // Skip frames up to the one we should be showing.
-      while (!mDecodedFrames.IsEmpty() && time >= mDecodedFrames.Peek()->mTime) {
-        LOG(PR_LOG_DEBUG, ("%p Skipping frame time %f with audio at time %f", mDecoder, mDecodedFrames.Peek()->mTime, time));
-        PlayAudio(frame);
-        delete frame;
-        frame = mDecodedFrames.Peek();
-        mDecodedFrames.Pop();
-      }
-      if (time < frame->mTime + mCallbackPeriod) {
-        PlayAudio(frame);
-        PlayVideo(frame);
-        mDecoder->mPlaybackPosition = frame->mEndStreamPosition;
-        UpdatePlaybackPosition(frame->mDecodedFrameTime);
-        delete frame;
-      }
-      else {
-        PlayAudio(frame);
-        delete frame;
-        frame = 0;
-      }
-    }
-  }
-  else {
-    if (mPlaying) {
-      PausePlayback();
-    }
-
-    if (mState == DECODER_STATE_DECODING) {
-      mon.Wait();
-      if (mState == DECODER_STATE_SHUTDOWN) {
-        return;
-      }
-    }
-  }
-}
-
-static void ToARGBHook(const PlanarYCbCrImage::Data& aData, PRUint8* aOutput)
-{
-  OggPlayYUVChannels yuv;
-  NS_ASSERTION(aData.mYStride == aData.mYSize.width,
-               "Stride not supported");
-  NS_ASSERTION(aData.mCbCrStride == aData.mCbCrSize.width,
-               "Stride not supported");
-  yuv.ptry = aData.mYChannel;
-  yuv.ptru = aData.mCbChannel;
-  yuv.ptrv = aData.mCrChannel;
-  yuv.uv_width = aData.mCbCrSize.width;
-  yuv.uv_height = aData.mCbCrSize.height;
-  yuv.y_width = aData.mYSize.width;
-  yuv.y_height = aData.mYSize.height;
-
-  OggPlayRGBChannels rgb;
-  rgb.ptro = aOutput;
-  rgb.rgb_width = aData.mYSize.width;
-  rgb.rgb_height = aData.mYSize.height;
-
-  oggplay_yuv2bgra(&yuv, &rgb);  
-}
-
-void nsOggDecodeStateMachine::PlayVideo(FrameData* aFrame)
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  if (aFrame && aFrame->mVideoHeader) {
-    ImageContainer* container = mDecoder->GetImageContainer();
-    // Currently our Ogg decoder only knows how to output to PLANAR_YCBCR
-    // format.
-    Image::Format format = Image::PLANAR_YCBCR;
-    nsRefPtr<Image> image;
-    if (container) {
-      image = container->CreateImage(&format, 1);
-    }
-    if (image) {
-      NS_ASSERTION(image->GetFormat() == Image::PLANAR_YCBCR,
-                   "Wrong format?");
-      PlanarYCbCrImage* videoImage = static_cast<PlanarYCbCrImage*>(image.get());
-
-      // XXX this is only temporary until we get YUV code in the layer
-      // system.
-      videoImage->SetRGBConverter(ToARGBHook);
-
-      OggPlayVideoData* videoData = oggplay_callback_info_get_video_data(aFrame->mVideoHeader);
-      PlanarYCbCrImage::Data data;
-      data.mYChannel = videoData->y;
-      data.mYSize = gfxIntSize(aFrame->mVideoWidth, aFrame->mVideoHeight);
-      data.mYStride = data.mYSize.width;
-      data.mCbChannel = videoData->u;
-      data.mCrChannel = videoData->v;
-      data.mCbCrSize = gfxIntSize(aFrame->mUVWidth, aFrame->mUVHeight);
-      data.mCbCrStride = data.mCbCrSize.width;
-      videoImage->SetData(data);
-
-      mDecoder->SetVideoData(data.mYSize, mAspectRatio, image);
-    }
-
-    // Don't play the frame's video data more than once.
-    aFrame->ClearVideoHeader();
-  }
-}
-
-void nsOggDecodeStateMachine::PlayAudio(FrameData* aFrame)
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  if (!mAudioStream)
-    return;
-
-  aFrame->Write(mAudioStream);
-}
-
-void nsOggDecodeStateMachine::OpenAudioStream()
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  mAudioStream = new nsAudioStream();
-  if (!mAudioStream) {
-    LOG(PR_LOG_ERROR, ("%p Could not create audio stream", mDecoder));
-  }
-  else {
-    mAudioStream->Init(mAudioChannels, mAudioRate, nsAudioStream::FORMAT_FLOAT32);
-    mAudioStream->SetVolume(mVolume);
-  }
-}
-
-void nsOggDecodeStateMachine::CloseAudioStream()
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  if (mAudioStream) {
-    mAudioStream->Shutdown();
-    mAudioStream = nsnull;
-  }
-}
-
-void nsOggDecodeStateMachine::StartAudio()
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  if (HasAudio()) {
-    OpenAudioStream();
-  }
-}
-
-void nsOggDecodeStateMachine::StopAudio()
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  if (HasAudio()) {
-    CloseAudioStream();
-  }
-}
-
-void nsOggDecodeStateMachine::StartPlayback()
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  StartAudio();
-  mPlaying = PR_TRUE;
-
-  // If this is the very first play, then set the initial start time
-  if (mPlayStartTime.IsNull()) {
-    mPlayStartTime = TimeStamp::Now();
-  }
-
-  // If we have been paused previously, then compute duration spent paused
-  if (!mPauseStartTime.IsNull()) {
-    mPauseDuration += TimeStamp::Now() - mPauseStartTime;
-    // Null out mPauseStartTime
-    mPauseStartTime = TimeStamp();
-  }
-}
-
-void nsOggDecodeStateMachine::StopPlayback()
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  mLastFrame = mDecodedFrames.ResetTimes(mCallbackPeriod);
-  StopAudio();
-  mPlaying = PR_FALSE;
-  mPauseStartTime = TimeStamp();
-  mPauseDuration = 0;
-  mPlayStartTime = TimeStamp();
-}
-
-void nsOggDecodeStateMachine::PausePlayback()
-{
-  if (!mAudioStream) {
-    StopPlayback();
-    return;
-  }
-  mAudioStream->Pause();
-  mPlaying = PR_FALSE;
-  mPauseStartTime = TimeStamp::Now();
-}
-
-void nsOggDecodeStateMachine::ResumePlayback()
-{
- if (!mAudioStream) {
-    StartPlayback();
-    return;
- }
- 
- mAudioStream->Resume();
- mPlaying = PR_TRUE;
-
- // Compute duration spent paused
- if (!mPauseStartTime.IsNull()) {
-   mPauseDuration += TimeStamp::Now() - mPauseStartTime;
-   // Null out mPauseStartTime
-   mPauseStartTime = TimeStamp();
- }
-}
-
-void nsOggDecodeStateMachine::UpdatePlaybackPosition(float aTime)
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  mCurrentFrameTime = aTime - mPlaybackStartTime;
-  if (!mPositionChangeQueued) {
-    mPositionChangeQueued = PR_TRUE;
-    nsCOMPtr<nsIRunnable> event =
-      NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackPositionChanged);
-    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-  }
-}
-
-void nsOggDecodeStateMachine::QueueDecodedFrames()
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  FrameData* frame;
-  while (!mDecodedFrames.IsFull() && (frame = NextFrame())) {
-    PRUint32 oldFrameCount = mDecodedFrames.GetCount();
-    mDecodedFrames.Push(frame);
-    if (oldFrameCount < 2) {
-      // Transitioning from 0 to 1 frames or from 1 to 2 frames could
-      // affect HaveNextFrameData and hence what UpdateReadyStateForData does.
-      // This could change us from HAVE_CURRENT_DATA to HAVE_FUTURE_DATA
-      // (or even HAVE_ENOUGH_DATA), so we'd better trigger an
-      // update to the ready state.
-      UpdateReadyState();
-    }
-  }
-}
-
-void nsOggDecodeStateMachine::ClearPositionChangeFlag()
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  mPositionChangeQueued = PR_FALSE;
-}
-
-nsHTMLMediaElement::NextFrameStatus nsOggDecodeStateMachine::GetNextFrameStatus()
-{
-  nsAutoMonitor mon(mDecoder->GetMonitor());
-  if (IsBuffering() || IsSeeking()) {
-    return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING;
-  } else if (HaveNextFrameData()) {
-    return nsHTMLMediaElement::NEXT_FRAME_AVAILABLE;
-  }
-  return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE;
-}
-
-void nsOggDecodeStateMachine::SetVolume(float volume)
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  if (mAudioStream) {
-    mAudioStream->SetVolume(volume);
-  }
-
-  mVolume = volume;
-}
-
-float nsOggDecodeStateMachine::GetCurrentTime()
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  return mCurrentFrameTime;
-}
-
-PRInt64 nsOggDecodeStateMachine::GetDuration()
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  return mDuration;
-}
-
-void nsOggDecodeStateMachine::SetDuration(PRInt64 aDuration)
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  mDuration = aDuration;
-}
-
-void nsOggDecodeStateMachine::SetSeekable(PRBool aSeekable)
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-  mSeekable = aSeekable;
-}
-
-void nsOggDecodeStateMachine::Shutdown()
-{
-  // oggplay_prepare_for_close cannot be undone. Once called, the
-  // mPlayer object cannot decode any more frames. Once we've entered
-  // the shutdown state here there's no going back.
-  nsAutoMonitor mon(mDecoder->GetMonitor());
-
-  // Change state before issuing shutdown request to threads so those
-  // threads can start exiting cleanly during the Shutdown call.
-  LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder));
-  mState = DECODER_STATE_SHUTDOWN;
-  mon.NotifyAll();
-
-  if (mPlayer) {
-    // This will unblock the step decode loop in the
-    // StepDecode thread. The thread can then be safely
-    // shutdown.
-    oggplay_prepare_for_close(mPlayer);
-  }
-}
-
-void nsOggDecodeStateMachine::Decode()
-{
-  // When asked to decode, switch to decoding only if
-  // we are currently buffering.
-  nsAutoMonitor mon(mDecoder->GetMonitor());
-  if (mState == DECODER_STATE_BUFFERING) {
-    LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder));
-    mState = DECODER_STATE_DECODING;
-    mon.NotifyAll();
-  }
-}
-
-void nsOggDecodeStateMachine::Seek(float aTime)
-{
-  nsAutoMonitor mon(mDecoder->GetMonitor());
-  // nsOggDecoder::mPlayState should be SEEKING while we seek, and
-  // in that case nsOggDecoder shouldn't be calling us.
-  NS_ASSERTION(mState != DECODER_STATE_SEEKING,
-               "We shouldn't already be seeking");
-  mSeekTime = aTime + mPlaybackStartTime;
-  float duration = static_cast<float>(mDuration) / 1000.0;
-  NS_ASSERTION(mSeekTime >= 0 && mSeekTime <= duration,
-               "Can only seek in range [0,duration]");
-  LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder, aTime));
-  mState = DECODER_STATE_SEEKING;
-}
-
-class ByteRange {
-public:
-  ByteRange() : mStart(-1), mEnd(-1) {}
-  ByteRange(PRInt64 aStart, PRInt64 aEnd) : mStart(aStart), mEnd(aEnd) {}
-  PRInt64 mStart, mEnd;
-};
-
-static void GetBufferedBytes(nsMediaStream* aStream, nsTArray<ByteRange>& aRanges)
-{
-  PRInt64 startOffset = 0;
-  while (PR_TRUE) {
-    PRInt64 endOffset = aStream->GetCachedDataEnd(startOffset);
-    if (endOffset == startOffset) {
-      // Uncached at startOffset.
-      endOffset = aStream->GetNextCachedData(startOffset);
-      if (endOffset == -1) {
-        // Uncached at startOffset until endOffset of stream, or we're at
-        // the end of stream.
-        break;
-      }
-    } else {
-      // Bytes [startOffset..endOffset] are cached.
-      PRInt64 cachedLength = endOffset - startOffset;
-      // Only bother trying to seek inside ranges greater than
-      // MIN_BOUNDED_SEEK_SIZE, so that the bounded seek is unlikely to
-      // read outside of the range when finding Ogg page boundaries.
-      if (cachedLength > MIN_BOUNDED_SEEK_SIZE) {
-        aRanges.AppendElement(ByteRange(startOffset, endOffset));
-      }
-    }
-    startOffset = endOffset;
-  }
-}
-
-nsresult nsOggDecodeStateMachine::Seek(float aTime, nsChannelReader* aReader)
-{
-  LOG(PR_LOG_DEBUG, ("%p About to seek OggPlay to %fms", mDecoder, aTime));
-  nsMediaStream* stream = aReader->Stream(); 
-  nsAutoTArray<ByteRange, 16> ranges;
-  stream->Pin();
-  GetBufferedBytes(stream, ranges);
-  PRInt64 rv = -1;
-  for (PRUint32 i = 0; rv < 0 && i < ranges.Length(); i++) {
-    rv = oggplay_seek_to_keyframe(mPlayer,
-                                  ogg_int64_t(aTime * 1000),
-                                  ranges[i].mStart,
-                                  ranges[i].mEnd);
-  }
-  stream->Unpin();
-
-  if (rv < 0) {
-    // Could not seek in a buffered range, fall back to seeking over the
-    // entire media.
-    rv = oggplay_seek_to_keyframe(mPlayer,
-                                  ogg_int64_t(aTime * 1000),
-                                  0,
-                                  stream->GetLength());
-  }
-
-  LOG(PR_LOG_DEBUG, ("%p Finished seeking OggPlay", mDecoder));
-
-  return (rv < 0) ? NS_ERROR_FAILURE : NS_OK;
-}
-
-PRBool nsOggDecodeStateMachine::DecodeToFrame(nsAutoMonitor& aMonitor,
-                                              float aTime)
-{
-  // Drop frames before the target time.
-  float target = aTime - mCallbackPeriod / 2.0;
-  FrameData* frame = nsnull;
-  OggPlayErrorCode r;
-  mLastFrame = 0;
-
-  // Some of the audio data from previous frames actually belongs
-  // to this frame and later frames. So rescue that data and stuff
-  // it into the first frame.
-  float audioTime = 0;
-  nsTArray<float> audioData;
-  do {
-    if (frame) {
-      audioData.AppendElements(frame->mAudioData);
-      audioTime += frame->mAudioData.Length() /
-        (float)mAudioRate / (float)mAudioChannels;
-    }
-    do {
-      aMonitor.Exit();
-      r = DecodeFrame();
-      aMonitor.Enter();
-    } while (mState != DECODER_STATE_SHUTDOWN && r == E_OGGPLAY_TIMEOUT);
-
-    HandleDecodeErrors(r);
-
-    if (mState == DECODER_STATE_SHUTDOWN)
-      break;
-
-    FrameData* nextFrame = NextFrame();
-    if (!nextFrame)
-      break;
-
-    delete frame;
-    frame = nextFrame;
-  } while (frame->mDecodedFrameTime < target);
-
-  if (mState == DECODER_STATE_SHUTDOWN) {
-    delete frame;
-    return PR_TRUE;
-  }
-
-  NS_ASSERTION(frame != nsnull, "No frame after decode!");
-  if (frame) {
-    if (audioTime > frame->mTime) {
-      // liboggplay gave us more data than expected, we need to prepend
-      // the extra data to the current frame to keep audio in sync.
-      audioTime -= frame->mTime;
-      // numExtraSamples must be evenly divisble by number of channels.
-      size_t numExtraSamples = mAudioChannels *
-        PRInt32(NS_ceil(mAudioRate*audioTime));
-      float* data = audioData.Elements() + audioData.Length() - numExtraSamples;
-      float* dst = frame->mAudioData.InsertElementsAt(0, numExtraSamples);
-      memcpy(dst, data, numExtraSamples * sizeof(float));
-    }
-
-    mLastFrame = 0;
-    frame->mTime = 0;
-    frame->mState = OGGPLAY_STREAM_JUST_SEEKED;
-    mDecodedFrames.Push(frame);
-    UpdatePlaybackPosition(frame->mDecodedFrameTime);
-    PlayVideo(frame);
-  }
-
-  return r == E_OGGPLAY_OK;
-}
-
-void nsOggDecodeStateMachine::StopStepDecodeThread(nsAutoMonitor* aMonitor)
-{
-  PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
-
-  if (!mStepDecodeThread)
-    return;
-
-  if (!mDecodingCompleted) {
-    // Break the step-decode thread out of the decoding loop. First
-    // set the exit flag so it will exit the loop.
-    mExitStepDecodeThread = PR_TRUE;
-    // Remove liboggplay frame buffer so that the step-decode thread
-    // can unblock in liboggplay.
-    delete NextFrame();
-    // Now notify to wake it up if it's waiting on the monitor.
-    aMonitor->NotifyAll();
-  }
-
-  aMonitor->Exit();
-  mStepDecodeThread->Shutdown();
-  aMonitor->Enter();
-  mStepDecodeThread = nsnull;
-}
-
-nsresult nsOggDecodeStateMachine::Run()
-{
-  nsChannelReader* reader = mDecoder->GetReader();
-  NS_ENSURE_TRUE(reader, NS_ERROR_NULL_POINTER);
-  while (PR_TRUE) {
-    nsAutoMonitor mon(mDecoder->GetMonitor());
-    switch(mState) {
-    case DECODER_STATE_SHUTDOWN:
-      if (mPlaying) {
-        StopPlayback();
-      }
-      StopStepDecodeThread(&mon);
-      NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
-                   "How did we escape from the shutdown state???");
-      return NS_OK;
-
-    case DECODER_STATE_DECODING_METADATA:
-      {
-        mon.Exit();
-        LoadOggHeaders(reader);
-        mon.Enter();
-      
-        OggPlayErrorCode r = E_OGGPLAY_TIMEOUT;
-        while (mState != DECODER_STATE_SHUTDOWN && r == E_OGGPLAY_TIMEOUT) {
-          mon.Exit();
-          r = DecodeFrame();
-          mon.Enter();
-        }
-
-        HandleDecodeErrors(r);
-
-        if (mState == DECODER_STATE_SHUTDOWN)
-          continue;
-
-        mLastFrame = 0;
-        FrameData* frame = NextFrame();
-        if (frame) {
-          mDecodedFrames.Push(frame);
-          mDecoder->mPlaybackPosition = frame->mEndStreamPosition;
-          mPlaybackStartTime = frame->mDecodedFrameTime;
-          UpdatePlaybackPosition(frame->mDecodedFrameTime);
-          // Now that we know the start offset, we can tell the channel
-          // reader the last frame time.
-          if (mGotDurationFromHeader) {
-            // Duration was in HTTP header, so the last frame time is
-            // start frame time + duration.
-            reader->SetLastFrameTime((PRInt64)(mPlaybackStartTime * 1000) + mDuration);
-          }
-          else if (mDuration != -1) {
-            // Got duration by seeking to end and getting timestamp of last
-            // page; mDuration holds the timestamp of the end of the last page.
-            reader->SetLastFrameTime(mDuration);
-            // Duration needs to be corrected so it's the length of media, not
-            // the last frame's end time. Note mPlaybackStartTime is
-            // presentation time, which is the start-time of the frame.
-            mDuration -= (PRInt64)(mPlaybackStartTime * 1000);
-          }
-          PlayVideo(frame);
-        }
-
-        // Inform the element that we've loaded the Ogg metadata and the
-        // first frame.
-        nsCOMPtr<nsIRunnable> metadataLoadedEvent = 
-          NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, MetadataLoaded); 
-        NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
-
-        if (mState == DECODER_STATE_DECODING_METADATA) {
-          if (r == E_OGGPLAY_OK) {
-            LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to COMPLETED", mDecoder));
-            mState = DECODER_STATE_COMPLETED;
-          } else {
-            LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder));
-            mState = DECODER_STATE_DECODING;
-          }
-        }
-      }
-      break;
-
-    case DECODER_STATE_DECODING:
-      {
-        // If there is no step decode thread,  start it. It may not be running
-        // due to us having completed and then restarted playback, seeking, 
-        // or if this is the initial play.
-        if (!mStepDecodeThread) {
-          nsresult rv = NS_NewThread(getter_AddRefs(mStepDecodeThread));
-          if (NS_FAILED(rv)) {
-            mState = DECODER_STATE_SHUTDOWN;
-            continue;
-          }
-
-          mBufferExhausted = PR_FALSE;
-          mDecodingCompleted = PR_FALSE;
-          mExitStepDecodeThread = PR_FALSE;
-          nsCOMPtr<nsIRunnable> event = new nsOggStepDecodeEvent(this, mPlayer);
-          mStepDecodeThread->Dispatch(event, NS_DISPATCH_NORMAL);
-        }
-
-        // Get the decoded frames and store them in our queue of decoded frames
-        QueueDecodedFrames();
-        while (mDecodedFrames.IsEmpty() && !mDecodingCompleted &&
-               !mBufferExhausted) {
-          if (mPlaying) {
-            PausePlayback();
-          }
-          mon.Wait();
-          if (mState != DECODER_STATE_DECODING)
-            break;
-          QueueDecodedFrames();
-        }
-
-        if (mState != DECODER_STATE_DECODING)
-          continue;
-
-        if (mDecodingCompleted) {
-          LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING to COMPLETED", mDecoder));
-          mState = DECODER_STATE_COMPLETED;
-          StopStepDecodeThread(&mon);
-          continue;
-        }
-
-        // Show at least the first frame if we're not playing
-        // so we have a poster frame on initial load and after seek.
-        if (!mPlaying && !mDecodedFrames.IsEmpty()) {
-          PlayVideo(mDecodedFrames.Peek());
-        }
-
-        if (mBufferExhausted &&
-            mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING &&
-            !mDecoder->mReader->Stream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
-            !mDecoder->mReader->Stream()->IsSuspendedByCache()) {
-          // There is at most one frame in the queue and there's
-          // more data to load. Let's buffer to make sure we can play a
-          // decent amount of video in the future.
-          if (mPlaying) {
-            PausePlayback();
-          }
-
-          // We need to tell the element that buffering has started.
-          // We can't just directly send an asynchronous runnable that
-          // eventually fires the "waiting" event. The problem is that
-          // there might be pending main-thread events, such as "data
-          // received" notifications, that mean we're not actually still
-          // buffering by the time this runnable executes. So instead
-          // we just trigger UpdateReadyStateForData; when it runs, it
-          // will check the current state and decide whether to tell
-          // the element we're buffering or not.
-          UpdateReadyState();
-
-          mBufferingStart = TimeStamp::Now();
-          PRPackedBool reliable;
-          double playbackRate = mDecoder->ComputePlaybackRate(&reliable);
-          mBufferingEndOffset = mDecoder->mDecoderPosition +
-            BUFFERING_RATE(playbackRate) * BUFFERING_WAIT;
-          mState = DECODER_STATE_BUFFERING;
-          if (mPlaying) {
-            PausePlayback();
-          }
-          LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING to BUFFERING", mDecoder));
-        } else {
-          if (mBufferExhausted) {
-            // This will wake up the step decode thread and force it to
-            // call oggplay_step_decoding at least once. This guarantees
-            // we make progress.
-            mBufferExhausted = PR_FALSE;
-            mon.NotifyAll();
-          }
-          PlayFrame();
-        }
-      }
-      break;
-
-    case DECODER_STATE_SEEKING:
-      {
-        // During the seek, don't have a lock on the decoder state,
-        // otherwise long seek operations can block the main thread.
-        // The events dispatched to the main thread are SYNC calls.
-        // These calls are made outside of the decode monitor lock so
-        // it is safe for the main thread to makes calls that acquire
-        // the lock since it won't deadlock. We check the state when
-        // acquiring the lock again in case shutdown has occurred
-        // during the time when we didn't have the lock.
-        StopStepDecodeThread(&mon);
-        if (mState == DECODER_STATE_SHUTDOWN)
-          continue;
-
-        float seekTime = mSeekTime;
-        mDecoder->StopProgressUpdates();
-
-        StopPlayback();
-
-        // Remove all frames decoded prior to seek from the queue
-        while (!mDecodedFrames.IsEmpty()) {
-          delete mDecodedFrames.Pop();
-        }
-        // SeekingStarted will do a UpdateReadyStateForData which will
-        // inform the element and its users that we have no frames
-        // to display
-
-        mon.Exit();
-        nsCOMPtr<nsIRunnable> startEvent = 
-          NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStarted);
-        NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
-        
-        nsresult res = Seek(seekTime, reader);
-
-        // Reactivate all tracks. Liboggplay deactivates tracks when it
-        // reads to the end of stream, but they must be reactivated in order
-        // to start reading from them again.
-        SetTracksActive();
-
-        mon.Enter();
-        mDecoder->StartProgressUpdates();
-        mLastFramePosition = mDecoder->mPlaybackPosition;
-        if (mState == DECODER_STATE_SHUTDOWN)
-          continue;
-
-        PRBool atEnd = PR_FALSE;
-        if (NS_SUCCEEDED(res)) {
-          atEnd = DecodeToFrame(mon, seekTime);
-          // mSeekTime should not have changed. While we seek, mPlayState
-          // should always be PLAY_STATE_SEEKING and no-one will call
-          // nsOggDecoderStateMachine::Seek.
-          NS_ASSERTION(seekTime == mSeekTime, "No-one should have changed mSeekTime");
-          if (mState == DECODER_STATE_SHUTDOWN) {
-            continue;
-          }
-
-          if (!atEnd) {
-            OggPlayErrorCode r;
-            // Now try to decode another frame to see if we're at the end.
-            do {
-              mon.Exit();
-              r = DecodeFrame();
-              mon.Enter();
-            } while (mState != DECODER_STATE_SHUTDOWN && r == E_OGGPLAY_TIMEOUT);
-            HandleDecodeErrors(r);
-            if (mState == DECODER_STATE_SHUTDOWN)
-              continue;
-            atEnd = r == E_OGGPLAY_OK;
-          }
-          QueueDecodedFrames();
-        }
-
-        // Change state to DECODING or COMPLETED now. SeekingStopped will
-        // call nsOggDecodeStateMachine::Seek to reset our state to SEEKING
-        // if we need to seek again.
-        nsCOMPtr<nsIRunnable> stopEvent;
-        if (!atEnd && mDecodedFrames.GetCount() > 1) {
-          LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %f) to DECODING",
-                             mDecoder, seekTime));
-          stopEvent = NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStopped);
-          mState = DECODER_STATE_DECODING;
-        } else {
-          LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %f) to COMPLETED",
-                             mDecoder, seekTime));
-          stopEvent = NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStoppedAtEnd);
-          mState = DECODER_STATE_COMPLETED;
-        }
-        mon.NotifyAll();
-
-        mon.Exit();
-        NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);        
-        mon.Enter();
-      }
-      break;
-
-    case DECODER_STATE_BUFFERING:
-      {
-        TimeStamp now = TimeStamp::Now();
-        if (now - mBufferingStart < TimeDuration::FromSeconds(BUFFERING_WAIT) &&
-            mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition) < mBufferingEndOffset &&
-            !mDecoder->mReader->Stream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
-            !mDecoder->mReader->Stream()->IsSuspendedByCache()) {
-          LOG(PR_LOG_DEBUG, 
-              ("%p In buffering: buffering data until %d bytes available or %f seconds", mDecoder,
-               PRUint32(mBufferingEndOffset - mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition)),
-               BUFFERING_WAIT - (now - mBufferingStart).ToSeconds()));
-          mon.Wait(PR_MillisecondsToInterval(1000));
-          if (mState == DECODER_STATE_SHUTDOWN)
-            continue;
-        } else {
-          LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder));
-          mState = DECODER_STATE_DECODING;
-        }
-
-        if (mState != DECODER_STATE_BUFFERING) {
-          mBufferExhausted = PR_FALSE;
-          // Notify to allow blocked decoder thread to continue
-          mon.NotifyAll();
-          UpdateReadyState();
-          if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
-            if (!mPlaying) {
-              ResumePlayback();
-            }
-          }
-        }
-
-        break;
-      }
-
-    case DECODER_STATE_COMPLETED:
-      {
-        // Get all the remaining decoded frames in the liboggplay buffer and
-        // place them in the frame queue.
-        QueueDecodedFrames();
-
-        // Play the remaining frames in the frame queue
-        while (mState == DECODER_STATE_COMPLETED &&
-               !mDecodedFrames.IsEmpty()) {
-          PlayFrame();
-          if (mState == DECODER_STATE_COMPLETED) {
-            // Wait for the time of one frame so we don't tight loop
-            // and we need to release the monitor so timeupdate and
-            // invalidate's on the main thread can occur.
-            mon.Wait(PR_MillisecondsToInterval(PRInt64(mCallbackPeriod*1000)));
-            QueueDecodedFrames();
-          }
-        }
-
-        if (mState != DECODER_STATE_COMPLETED)
-          continue;
-
-        if (mAudioStream) {
-          mon.Exit();
-          LOG(PR_LOG_DEBUG, ("%p Begin nsAudioStream::Drain", mDecoder));
-          mAudioStream->Drain();
-          LOG(PR_LOG_DEBUG, ("%p End nsAudioStream::Drain", mDecoder));
-          mon.Enter();
-
-          // After the drain call the audio stream is unusable. Close it so that
-          // next time audio is used a new stream is created. The StopPlayback
-          // call also resets the playing flag so audio is restarted correctly.
-          StopPlayback();
-
-          if (mState != DECODER_STATE_COMPLETED)
-            continue;
-        }
-        
-        if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
-          // We were playing, we need to move the current time to the end of
-          // media, and send an 'ended' event.
-          mCurrentFrameTime += mCallbackPeriod;
-          if (mDuration >= 0) {
-            mCurrentFrameTime = PR_MAX(mCurrentFrameTime, mDuration / 1000.0);
-          }
-
-          mon.Exit();
-          nsCOMPtr<nsIRunnable> event =
-            NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackEnded);
-          NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
-          mon.Enter();
-        }
-
-        while (mState == DECODER_STATE_COMPLETED) {
-          mon.Wait();
-        }
-      }
-      break;
-    }
-  }
-
-  return NS_OK;
-}
-
-// Initialize our OggPlay struct with the specified limit on video size.
-static OggPlay*
-OggPlayOpen(OggPlayReader* reader,
-            int max_frame_pixels)
-{
-  OggPlay *me = NULL;
-  int r;
-
-  if ((me = oggplay_new_with_reader(reader)) == NULL) {
-    return NULL;
-  }
-
-  r = oggplay_set_max_video_frame_pixels(me, max_frame_pixels);
-  if (r != E_OGGPLAY_OK) {
-    oggplay_close(me);
-    return NULL;
-  }
-
-  do {
-    r = oggplay_initialise(me, 0);
-  } while (r == E_OGGPLAY_TIMEOUT);
-
-  if (r != E_OGGPLAY_OK) {
-    oggplay_close(me);
-    return NULL;
-  }
-
-  return me;
-}
-
-void nsOggDecodeStateMachine::LoadOggHeaders(nsChannelReader* aReader) 
-{
-  LOG(PR_LOG_DEBUG, ("%p Loading Ogg Headers", mDecoder));
-  mPlayer = OggPlayOpen(aReader, MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT);
-  if (!mPlayer) {
-    nsAutoMonitor mon(mDecoder->GetMonitor());
-    mState = DECODER_STATE_SHUTDOWN;
-    HandleDecodeErrors(E_OGGPLAY_UNINITIALISED);
-    return;
-  }
-  LOG(PR_LOG_DEBUG, ("%p There are %d tracks", mDecoder, oggplay_get_num_tracks(mPlayer)));
-
-  for (int i = 0; i < oggplay_get_num_tracks(mPlayer); ++i) {
-    LOG(PR_LOG_DEBUG, ("%p Tracks %d: %s", mDecoder, i, oggplay_get_track_typename(mPlayer, i)));
-    if (mVideoTrack == -1 && oggplay_get_track_type(mPlayer, i) == OGGZ_CONTENT_THEORA) {
-      oggplay_set_callback_num_frames(mPlayer, i, 1);
-      mVideoTrack = i;
-
-      int fpsd, fpsn;
-      oggplay_get_video_fps(mPlayer, i, &fpsd, &fpsn);
-      mFramerate = fpsd == 0 ? 0.0 : float(fpsn)/float(fpsd);
-      mCallbackPeriod = 1.0 / mFramerate;
-      LOG(PR_LOG_DEBUG, ("%p Frame rate: %f", mDecoder, mFramerate));
-
-      int aspectd, aspectn;
-      // this can return E_OGGPLAY_UNINITIALISED if the video has
-      // no aspect ratio data. We assume 1.0 in that case.
-      OggPlayErrorCode r =
-        oggplay_get_video_aspect_ratio(mPlayer, i, &aspectd, &aspectn);
-      mAspectRatio = r == E_OGGPLAY_OK && aspectd > 0 ?
-          float(aspectn)/float(aspectd) : 1.0;
-
-      int y_width;
-      int y_height;
-      oggplay_get_video_y_size(mPlayer, i, &y_width, &y_height);
-      mDecoder->SetVideoData(gfxIntSize(y_width, y_height), mAspectRatio,
-                             nsnull);
-    }
-    else if (mAudioTrack == -1 && oggplay_get_track_type(mPlayer, i) == OGGZ_CONTENT_VORBIS) {
-      mAudioTrack = i;
-      oggplay_set_offset(mPlayer, i, OGGPLAY_AUDIO_OFFSET);
-      oggplay_get_audio_samplerate(mPlayer, i, &mAudioRate);
-      oggplay_get_audio_channels(mPlayer, i, &mAudioChannels);
-      LOG(PR_LOG_DEBUG, ("%p samplerate: %d, channels: %d", mDecoder, mAudioRate, mAudioChannels));
-    }
-  }
-
-  if (mVideoTrack == -1 && mAudioTrack == -1) {
-    nsAutoMonitor mon(mDecoder->GetMonitor());
-    HandleDecodeErrors(E_OGGPLAY_UNINITIALISED);
-    return;
-  }
-
-  SetTracksActive();
-
-  if (mVideoTrack == -1) {
-    oggplay_set_callback_num_frames(mPlayer, mAudioTrack, OGGPLAY_FRAMES_PER_CALLBACK);
-    mCallbackPeriod = 1.0 / (float(mAudioRate) / OGGPLAY_FRAMES_PER_CALLBACK);
-  }
-  LOG(PR_LOG_DEBUG, ("%p Callback Period: %f", mDecoder, mCallbackPeriod));
-
-  oggplay_use_buffer(mPlayer, OGGPLAY_BUFFER_SIZE);
-
-  // Get the duration from the Ogg file. We only do this if the
-  // content length of the resource is known as we need to seek
-  // to the end of the file to get the last time field. We also
-  // only do this if the resource is seekable and if we haven't
-  // already obtained the duration via an HTTP header.
-  {
-    nsAutoMonitor mon(mDecoder->GetMonitor());
-    mGotDurationFromHeader = (mDuration != -1);
-    if (mState != DECODER_STATE_SHUTDOWN &&
-        aReader->Stream()->GetLength() >= 0 &&
-        mSeekable &&
-        mDuration == -1) {
-      mDecoder->StopProgressUpdates();
-      // Don't hold the monitor during the duration
-      // call as it can issue seek requests
-      // and blocks until these are completed.
-      mon.Exit();
-      PRInt64 d = oggplay_get_duration(mPlayer);
-      oggplay_seek(mPlayer, 0);
-      mon.Enter();
-      mDuration = d;
-      mDecoder->StartProgressUpdates();
-      mDecoder->UpdatePlaybackRate();
-    }
-    if (mState == DECODER_STATE_SHUTDOWN)
-      return;
-  }
-}
-
-void nsOggDecodeStateMachine::SetTracksActive()
-{
-  if (mVideoTrack != -1 && 
-      oggplay_set_track_active(mPlayer, mVideoTrack) < 0)  {
-    LOG(PR_LOG_ERROR, ("%p Could not set track %d active", mDecoder, mVideoTrack));
-  }
-
-  if (mAudioTrack != -1 && 
-      oggplay_set_track_active(mPlayer, mAudioTrack) < 0)  {
-    LOG(PR_LOG_ERROR, ("%p Could not set track %d active", mDecoder, mAudioTrack));
-  }
-}
-
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsOggDecoder, nsIObserver)
 
 void nsOggDecoder::Pause() 
 {
-  nsAutoMonitor mon(mMonitor);
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  MonitorAutoEnter mon(mMonitor);
   if (mPlayState == PLAY_STATE_SEEKING || mPlayState == PLAY_STATE_ENDED) {
     mNextState = PLAY_STATE_PAUSED;
     return;
   }
 
   ChangeState(PLAY_STATE_PAUSED);
 }
 
 void nsOggDecoder::SetVolume(float volume)
 {
-  nsAutoMonitor mon(mMonitor);
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mInitialVolume = volume;
-
   if (mDecodeStateMachine) {
     mDecodeStateMachine->SetVolume(volume);
   }
 }
 
 float nsOggDecoder::GetDuration()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (mDuration >= 0) {
      return static_cast<float>(mDuration) / 1000.0;
   }
-
   return std::numeric_limits<float>::quiet_NaN();
 }
 
 nsOggDecoder::nsOggDecoder() :
-  nsMediaDecoder(),
+  mMonitor("media.decoder"),
   mDecoderPosition(0),
   mPlaybackPosition(0),
   mCurrentTime(0.0),
   mInitialVolume(0.0),
   mRequestedSeekTime(-1.0),
   mDuration(-1),
   mSeekable(PR_TRUE),
-  mReader(nsnull),
-  mMonitor(nsnull),
   mPlayState(PLAY_STATE_PAUSED),
   mNextState(PLAY_STATE_PAUSED),
   mResourceLoaded(PR_FALSE),
   mIgnoreProgressData(PR_FALSE)
 {
   MOZ_COUNT_CTOR(nsOggDecoder);
-
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 #ifdef PR_LOGGING
   if (!gOggDecoderLog) {
     gOggDecoderLog = PR_NewLogModule("nsOggDecoder");
   }
 #endif
 }
 
 PRBool nsOggDecoder::Init(nsHTMLMediaElement* aElement)
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (!nsMediaDecoder::Init(aElement))
     return PR_FALSE;
 
-  mMonitor = nsAutoMonitor::NewMonitor("media.decoder");
-  if (!mMonitor)
-    return PR_FALSE;
-
   nsContentUtils::RegisterShutdownObserver(this);
-
-  mReader = new nsChannelReader();
-  NS_ENSURE_TRUE(mReader, PR_FALSE);
-
   mImageContainer = aElement->GetImageContainer();
-
   return PR_TRUE;
 }
 
 void nsOggDecoder::Stop()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread");
 
   // The decode thread must die before the state machine can die.
   // The state machine must die before the reader.
   // The state machine must die before the decoder.
-  if (mDecodeThread)
-    mDecodeThread->Shutdown();
+  if (mStateMachineThread)
+    mStateMachineThread->Shutdown();
 
-  mDecodeThread = nsnull;
+  mStateMachineThread = nsnull;
   mDecodeStateMachine = nsnull;
-  mReader = nsnull;
 }
 
 void nsOggDecoder::Shutdown()
 {
-  NS_ASSERTION(NS_IsMainThread(), 
-               "nsOggDecoder::Shutdown called on non-main thread");  
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   
   if (mShuttingDown)
     return;
 
   mShuttingDown = PR_TRUE;
 
   // This changes the decoder state to SHUTDOWN and does other things
   // necessary to unblock the state machine thread if it's blocked, so
   // the asynchronous shutdown in nsDestroyStateMachine won't deadlock.
   if (mDecodeStateMachine) {
     mDecodeStateMachine->Shutdown();
   }
 
   // Force any outstanding seek and byterange requests to complete
   // to prevent shutdown from deadlocking.
-  if (mReader) {
-    mReader->Stream()->Close();
+  if (mStream) {
+    mStream->Close();
   }
 
   ChangeState(PLAY_STATE_SHUTDOWN);
   nsMediaDecoder::Shutdown();
 
-  // We can't destroy mDecodeStateMachine until mDecodeThread is shut down.
+  // We can't destroy mDecodeStateMachine until mStateMachineThread is shut down.
   // It's unsafe to Shutdown() the decode thread here, as
   // nsIThread::Shutdown() may run events, such as JS event handlers,
   // and we could be running at an unsafe time such as during element
   // destruction.
   // So we destroy the decoder on the main thread in an asynchronous event.
   // See bug 468721.
   nsCOMPtr<nsIRunnable> event =
     NS_NEW_RUNNABLE_METHOD(nsOggDecoder, this, Stop);
   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
 
   nsContentUtils::UnregisterShutdownObserver(this);
 }
 
 nsOggDecoder::~nsOggDecoder()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   MOZ_COUNT_DTOR(nsOggDecoder);
-  nsAutoMonitor::DestroyMonitor(mMonitor);
 }
 
 nsresult nsOggDecoder::Load(nsMediaStream* aStream,
                             nsIStreamListener** aStreamListener)
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (aStreamListener) {
     *aStreamListener = nsnull;
   }
 
   {
     // Hold the lock while we do this to set proper lock ordering
     // expectations for dynamic deadlock detectors: decoder lock(s)
     // should be grabbed before the cache lock
-    nsAutoMonitor mon(mMonitor);
+    MonitorAutoEnter mon(mMonitor);
 
     nsresult rv = aStream->Open(aStreamListener);
     if (NS_FAILED(rv)) {
       delete aStream;
       return rv;
     }
 
-    mReader->Init(aStream);
+    mStream = aStream;
   }
 
-  nsresult rv = NS_NewThread(getter_AddRefs(mDecodeThread));
+  nsresult rv = NS_NewThread(getter_AddRefs(mStateMachineThread));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mDecodeStateMachine = new nsOggDecodeStateMachine(this);
+  mDecodeStateMachine = new nsOggPlayStateMachine(this);
+  if (NS_FAILED(mDecodeStateMachine->Init())) {
+    return NS_ERROR_FAILURE;
+  }
   {
-    nsAutoMonitor mon(mMonitor);
+    MonitorAutoEnter mon(mMonitor);
     mDecodeStateMachine->SetSeekable(mSeekable);
     mDecodeStateMachine->SetDuration(mDuration);
   }
 
   ChangeState(PLAY_STATE_LOADING);
 
-  return mDecodeThread->Dispatch(mDecodeStateMachine, NS_DISPATCH_NORMAL);
+  return mStateMachineThread->Dispatch(mDecodeStateMachine, NS_DISPATCH_NORMAL);
 }
 
 nsresult nsOggDecoder::Play()
 {
-  nsAutoMonitor mon(mMonitor);
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  MonitorAutoEnter mon(mMonitor);
   if (mPlayState == PLAY_STATE_SEEKING) {
     mNextState = PLAY_STATE_PLAYING;
     return NS_OK;
   }
   if (mPlayState == PLAY_STATE_ENDED)
     return Seek(0);
 
   ChangeState(PLAY_STATE_PLAYING);
 
   return NS_OK;
 }
 
 nsresult nsOggDecoder::Seek(float aTime)
 {
-  nsAutoMonitor mon(mMonitor);
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  MonitorAutoEnter mon(mMonitor);
 
   if (aTime < 0.0)
     return NS_ERROR_FAILURE;
 
   mRequestedSeekTime = aTime;
 
   // If we are already in the seeking state, then setting mRequestedSeekTime
   // above will result in the new seek occurring when the current seek
@@ -2180,107 +260,109 @@ nsresult nsOggDecoder::Seek(float aTime)
     ChangeState(PLAY_STATE_SEEKING);
   }
 
   return NS_OK;
 }
 
 nsresult nsOggDecoder::PlaybackRateChanged()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 float nsOggDecoder::GetCurrentTime()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   return mCurrentTime;
 }
 
 nsMediaStream* nsOggDecoder::GetCurrentStream()
 {
-  return mReader ? mReader->Stream() : nsnull;
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  return mStream;
 }
 
 already_AddRefed<nsIPrincipal> nsOggDecoder::GetCurrentPrincipal()
 {
-  if (!mReader)
-    return nsnull;
-  return mReader->Stream()->GetCurrentPrincipal();
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  return mStream ? mStream->GetCurrentPrincipal() : nsnull;
 }
 
 void nsOggDecoder::MetadataLoaded()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (mShuttingDown)
     return;
 
   // Only inform the element of MetadataLoaded if not doing a load() in order
   // to fulfill a seek, otherwise we'll get multiple metadataloaded events.
   PRBool notifyElement = PR_TRUE;
   {
-    nsAutoMonitor mon(mMonitor);
+    MonitorAutoEnter mon(mMonitor);
     mDuration = mDecodeStateMachine ? mDecodeStateMachine->GetDuration() : -1;
     notifyElement = mNextState != PLAY_STATE_SEEKING;
   }
 
   if (mElement && notifyElement) {
     // Make sure the element and the frame (if any) are told about
     // our new size.
     Invalidate();
     mElement->MetadataLoaded();
   }
 
   if (!mResourceLoaded) {
     StartProgress();
-  }
-  else if (mElement)
-  {
+  } else if (mElement) {
     // Resource was loaded during metadata loading, when progress
     // events are being ignored. Fire the final progress event.
     mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
   }
- 
+
   // Only inform the element of FirstFrameLoaded if not doing a load() in order
   // to fulfill a seek, otherwise we'll get multiple loadedfirstframe events.
-  PRBool resourceIsLoaded = !mResourceLoaded && mReader &&
-    mReader->Stream()->IsDataCachedToEndOfStream(mDecoderPosition);
+  PRBool resourceIsLoaded = !mResourceLoaded && mStream &&
+    mStream->IsDataCachedToEndOfStream(mDecoderPosition);
   if (mElement && notifyElement) {
     mElement->FirstFrameLoaded(resourceIsLoaded);
   }
 
   // The element can run javascript via events
-  // before reaching here, so only change the 
+  // before reaching here, so only change the
   // state if we're still set to the original
   // loading state.
-  nsAutoMonitor mon(mMonitor);
+  MonitorAutoEnter mon(mMonitor);
   if (mPlayState == PLAY_STATE_LOADING) {
     if (mRequestedSeekTime >= 0.0) {
       ChangeState(PLAY_STATE_SEEKING);
-    }
-    else {
+    } else {
       ChangeState(mNextState);
     }
   }
 
   if (resourceIsLoaded) {
     ResourceLoaded();
   }
 }
 
 void nsOggDecoder::ResourceLoaded()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
   // Don't handle ResourceLoaded if we are shutting down, or if
   // we need to ignore progress data due to seeking (in the case
   // that the seek results in reaching end of file, we get a bogus call
   // to ResourceLoaded).
   if (mShuttingDown)
     return;
 
   {
     // If we are seeking or loading then the resource loaded notification we get
     // should be ignored, since it represents the end of the seek request.
-    nsAutoMonitor mon(mMonitor);
+    MonitorAutoEnter mon(mMonitor);
     if (mIgnoreProgressData || mResourceLoaded || mPlayState == PLAY_STATE_LOADING)
       return;
 
     Progress(PR_FALSE);
 
     mResourceLoaded = PR_TRUE;
     StopProgress();
   }
@@ -2289,27 +371,29 @@ void nsOggDecoder::ResourceLoaded()
   if (mElement) {
     mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
     mElement->ResourceLoaded();
   }
 }
 
 void nsOggDecoder::NetworkError()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (mShuttingDown)
     return;
 
   if (mElement)
     mElement->NetworkError();
 
   Shutdown();
 }
 
 void nsOggDecoder::DecodeError()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (mShuttingDown)
     return;
 
   if (mElement)
     mElement->DecodeError();
 
   Shutdown();
 }
@@ -2337,35 +421,37 @@ void nsOggDecoder::PlaybackEnded()
     mElement->PlaybackEnded();
   }
 }
 
 NS_IMETHODIMP nsOggDecoder::Observe(nsISupports *aSubjet,
                                     const char *aTopic,
                                     const PRUnichar *someData)
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
     Shutdown();
   }
 
   return NS_OK;
 }
 
 nsMediaDecoder::Statistics
 nsOggDecoder::GetStatistics()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   Statistics result;
 
-  nsAutoMonitor mon(mMonitor);
-  if (mReader) {
+  MonitorAutoEnter mon(mMonitor);
+  if (mStream) {
     result.mDownloadRate = 
-      mReader->Stream()->GetDownloadRate(&result.mDownloadRateReliable);
+      mStream->GetDownloadRate(&result.mDownloadRateReliable);
     result.mDownloadPosition =
-      mReader->Stream()->GetCachedDataEnd(mDecoderPosition);
-    result.mTotalBytes = mReader->Stream()->GetLength();
+      mStream->GetCachedDataEnd(mDecoderPosition);
+    result.mTotalBytes = mStream->GetLength();
     result.mPlaybackRate = ComputePlaybackRate(&result.mPlaybackRateReliable);
     result.mDecoderPosition = mDecoderPosition;
     result.mPlaybackPosition = mPlaybackPosition;
   } else {
     result.mDownloadRate = 0;
     result.mDownloadRateReliable = PR_TRUE;
     result.mPlaybackRate = 0;
     result.mPlaybackRateReliable = PR_TRUE;
@@ -2375,83 +461,92 @@ nsOggDecoder::GetStatistics()
     result.mTotalBytes = 0;
   }
 
   return result;
 }
 
 double nsOggDecoder::ComputePlaybackRate(PRPackedBool* aReliable)
 {
-  PRInt64 length = mReader ? mReader->Stream()->GetLength() : -1;
+  GetMonitor().AssertCurrentThreadIn();
+  NS_ASSERTION(NS_IsMainThread() || IsThread(mStateMachineThread),
+               "Should be on main or state machine thread.");
+
+  PRInt64 length = mStream ? mStream->GetLength() : -1;
   if (mDuration >= 0 && length >= 0) {
     *aReliable = PR_TRUE;
     return double(length)*1000.0/mDuration;
   }
   return mPlaybackStatistics.GetRateAtLastStop(aReliable);
 }
 
 void nsOggDecoder::UpdatePlaybackRate()
 {
-  if (!mReader)
+  NS_ASSERTION(NS_IsMainThread() || IsThread(mStateMachineThread),
+               "Should be on main or state machine thread.");
+  GetMonitor().AssertCurrentThreadIn();
+  if (!mStream)
     return;
   PRPackedBool reliable;
   PRUint32 rate = PRUint32(ComputePlaybackRate(&reliable));
   if (reliable) {
     // Avoid passing a zero rate
-    rate = PR_MAX(rate, 1);
+    rate = NS_MAX(rate, 1u);
   } else {
     // Set a minimum rate of 10,000 bytes per second ... sometimes we just
     // don't have good data
-    rate = PR_MAX(rate, 10000);
+    rate = NS_MAX(rate, 10000u);
   }
-  mReader->Stream()->SetPlaybackRate(rate);
+  mStream->SetPlaybackRate(rate);
 }
 
 void nsOggDecoder::NotifySuspendedStatusChanged()
 {
-  NS_ASSERTION(NS_IsMainThread(), 
-               "nsOggDecoder::NotifyDownloadSuspended called on non-main thread");
-  if (!mReader)
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  if (!mStream)
     return;
-  if (mReader->Stream()->IsSuspendedByCache() && mElement) {
+  if (mStream->IsSuspendedByCache() && mElement) {
     // if this is an autoplay element, we need to kick off its autoplaying
     // now so we consume data and hopefully free up cache space
     mElement->NotifyAutoplayDataReady();
   }
 }
 
 void nsOggDecoder::NotifyBytesDownloaded()
 {
-  NS_ASSERTION(NS_IsMainThread(),
-               "nsOggDecoder::NotifyBytesDownloaded called on non-main thread");   
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   UpdateReadyStateForData();
   Progress(PR_FALSE);
 }
 
 void nsOggDecoder::NotifyDownloadEnded(nsresult aStatus)
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
   if (aStatus == NS_BINDING_ABORTED)
     return;
 
   {
-    nsAutoMonitor mon(mMonitor);
+    MonitorAutoEnter mon(mMonitor);
     UpdatePlaybackRate();
   }
 
   if (NS_SUCCEEDED(aStatus)) {
     ResourceLoaded();
   } else if (aStatus != NS_BASE_STREAM_CLOSED) {
     NetworkError();
   }
   UpdateReadyStateForData();
 }
 
 void nsOggDecoder::NotifyBytesConsumed(PRInt64 aBytes)
 {
-  nsAutoMonitor mon(mMonitor);
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mDecodeStateMachine->OnStateMachineThread() || mDecodeStateMachine->OnDecodeThread(),
+               "Should be on play state machine or decode thread.");
   if (!mIgnoreProgressData) {
     mDecoderPosition += aBytes;
   }
 }
 
 void nsOggDecoder::NextFrameUnavailableBuffering()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
@@ -2487,21 +582,23 @@ void nsOggDecoder::UpdateReadyStateForDa
 
   nsHTMLMediaElement::NextFrameStatus frameStatus =
     mDecodeStateMachine->GetNextFrameStatus();
   mElement->UpdateReadyStateForData(frameStatus);
 }
 
 void nsOggDecoder::SeekingStopped()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
   if (mShuttingDown)
     return;
 
   {
-    nsAutoMonitor mon(mMonitor);
+    MonitorAutoEnter mon(mMonitor);
 
     // An additional seek was requested while the current seek was
     // in operation.
     if (mRequestedSeekTime >= 0.0)
       ChangeState(PLAY_STATE_SEEKING);
     else
       ChangeState(mNextState);
   }
@@ -2511,22 +608,24 @@ void nsOggDecoder::SeekingStopped()
     mElement->SeekCompleted();
   }
 }
 
 // This is called when seeking stopped *and* we're at the end of the
 // media.
 void nsOggDecoder::SeekingStoppedAtEnd()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
   if (mShuttingDown)
     return;
 
   PRBool fireEnded = PR_FALSE;
   {
-    nsAutoMonitor mon(mMonitor);
+    MonitorAutoEnter mon(mMonitor);
 
     // An additional seek was requested while the current seek was
     // in operation.
     if (mRequestedSeekTime >= 0.0) {
       ChangeState(PLAY_STATE_SEEKING);
     } else {
       fireEnded = mNextState != PLAY_STATE_PLAYING;
       ChangeState(fireEnded ? PLAY_STATE_ENDED : mNextState);
@@ -2539,37 +638,37 @@ void nsOggDecoder::SeekingStoppedAtEnd()
     if (fireEnded) {
       mElement->PlaybackEnded();
     }
   }
 }
 
 void nsOggDecoder::SeekingStarted()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (mShuttingDown)
     return;
 
   if (mElement) {
     UpdateReadyStateForData();
     mElement->SeekStarted();
   }
 }
 
 void nsOggDecoder::ChangeState(PlayState aState)
 {
-  NS_ASSERTION(NS_IsMainThread(), 
-               "nsOggDecoder::ChangeState called on non-main thread");   
-  nsAutoMonitor mon(mMonitor);
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");   
+  MonitorAutoEnter mon(mMonitor);
 
   if (mNextState == aState) {
     mNextState = PLAY_STATE_PAUSED;
   }
 
   if (mPlayState == PLAY_STATE_SHUTDOWN) {
-    mon.NotifyAll();
+    mMonitor.NotifyAll();
     return;
   }
 
   mPlayState = aState;
   switch (aState) {
   case PLAY_STATE_PAUSED:
     /* No action needed */
     break;
@@ -2588,30 +687,31 @@ void nsOggDecoder::ChangeState(PlayState
     break;
   case PLAY_STATE_ENDED:
     /* No action needed */
     break;
   case PLAY_STATE_SHUTDOWN:
     /* No action needed */
     break;
   }
-  mon.NotifyAll();
+  mMonitor.NotifyAll();
 }
 
 void nsOggDecoder::PlaybackPositionChanged()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (mShuttingDown)
     return;
 
   float lastTime = mCurrentTime;
 
   // Control the scope of the monitor so it is not
   // held while the timeupdate and the invalidate is run.
   {
-    nsAutoMonitor mon(mMonitor);
+    MonitorAutoEnter mon(mMonitor);
 
     if (mDecodeStateMachine) {
       mCurrentTime = mDecodeStateMachine->GetCurrentTime();
       mDecodeStateMachine->ClearPositionChangeFlag();
     }
   }
 
   // Invalidate the frame so any video data is displayed.
@@ -2620,70 +720,89 @@ void nsOggDecoder::PlaybackPositionChang
   // frame has reflowed and the size updated beforehand.
   Invalidate();
 
   if (mElement && lastTime != mCurrentTime) {
     mElement->DispatchSimpleEvent(NS_LITERAL_STRING("timeupdate"));
   }
 }
 
+void nsOggDecoder::DurationChanged()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  MonitorAutoEnter mon(mMonitor);
+  PRInt64 oldDuration = mDuration;
+  mDuration = mDecodeStateMachine ? mDecodeStateMachine->GetDuration() : -1;
+  if (mElement && oldDuration != mDuration) {
+    LOG(PR_LOG_DEBUG, ("%p duration changed to %lldms", this, mDuration));
+    mElement->DispatchSimpleEvent(NS_LITERAL_STRING("durationchange"));
+  }
+}
+
 void nsOggDecoder::SetDuration(PRInt64 aDuration)
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mDuration = aDuration;
   if (mDecodeStateMachine) {
-    nsAutoMonitor mon(mMonitor);
+    MonitorAutoEnter mon(mMonitor);
     mDecodeStateMachine->SetDuration(mDuration);
     UpdatePlaybackRate();
   }
 }
 
 void nsOggDecoder::SetSeekable(PRBool aSeekable)
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mSeekable = aSeekable;
   if (mDecodeStateMachine) {
-    nsAutoMonitor mon(mMonitor);
+    MonitorAutoEnter mon(mMonitor);
     mDecodeStateMachine->SetSeekable(aSeekable);
   }
 }
 
 PRBool nsOggDecoder::GetSeekable()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   return mSeekable;
 }
 
 void nsOggDecoder::Suspend()
 {
-  if (mReader) {
-    mReader->Stream()->Suspend(PR_TRUE);
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  if (mStream) {
+    mStream->Suspend(PR_TRUE);
   }
 }
 
 void nsOggDecoder::Resume()
 {
-  if (mReader) {
-    mReader->Stream()->Resume();
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  if (mStream) {
+    mStream->Resume();
   }
 }
 
 void nsOggDecoder::StopProgressUpdates()
 {
+  NS_ASSERTION(IsThread(mStateMachineThread), "Should be on state machine thread.");
   mIgnoreProgressData = PR_TRUE;
-  if (mReader) {
-    mReader->Stream()->SetReadMode(nsMediaCacheStream::MODE_METADATA);
+  if (mStream) {
+    mStream->SetReadMode(nsMediaCacheStream::MODE_METADATA);
   }
 }
 
 void nsOggDecoder::StartProgressUpdates()
 {
+  NS_ASSERTION(IsThread(mStateMachineThread), "Should be on state machine thread.");
   mIgnoreProgressData = PR_FALSE;
-  if (mReader) {
-    mReader->Stream()->SetReadMode(nsMediaCacheStream::MODE_PLAYBACK);
-    mDecoderPosition = mPlaybackPosition = mReader->Stream()->Tell();
+  if (mStream) {
+    mStream->SetReadMode(nsMediaCacheStream::MODE_PLAYBACK);
+    mDecoderPosition = mPlaybackPosition = mStream->Tell();
   }
 }
 
 void nsOggDecoder::MoveLoadsToBackground()
 {
-  if (mReader) {
-    mReader->Stream()->MoveLoadsToBackground();
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  if (mStream) {
+    mStream->MoveLoadsToBackground();
   }
 }
-
--- a/content/media/ogg/nsOggDecoder.h
+++ b/content/media/ogg/nsOggDecoder.h
@@ -16,80 +16,93 @@
  * The Original Code is Mozilla code.
  *
  * The Initial Developer of the Original Code is the Mozilla Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2007
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 /*
-Each video element has two threads. The first thread, called the Decode thread,
-owns the resources for downloading and reading the video file. It goes through the
-file, prcessing any decoded theora and vorbis data. It handles the sending of the
-audio data to the sound device and the presentation of the video data at the correct
-frame rate.
+Each video element has three threads. 
+
+  1) The state machine thread owns the resources for downloading and reading
+     the media file. It controls the lifetime of the other two threads
+     and renders the video data at the correct time during playback.
 
-The second thread is the step decode thread. It uses OggPlay to decode the video and
-audio data. It indirectly uses an nsMediaStream to do the file reading and seeking via 
-Oggplay. 
+  2) The Audio thread writes the decoded audio data to the audio
+     hardware.  This is done in a seperate thread to ensure that the
+     audio hardware gets a constant stream of data without
+     interruption due to decoding or diplay. At some point
+     libsydneyaudio will be refactored to have a callback interface
+     where it asks for data and an extra thread will no longer be
+     needed.
 
-All file reads and seeks must occur on these two threads. Synchronisation is done via
-liboggplay internal mutexes to ensure that access to the liboggplay structures is
-done correctly in the presence of the threads.
+  3) The decode thread. This thread reads from the media stream and decodes
+     the Theora and Vorbis data. It places the decoded data in a queue 
+     for the other threads to pull from.
 
-The step decode thread is created and destroyed in the decode thread. When decoding
-needs to be done it is created and event dispatched to it to start the decode loop.
-This event exits when decoding is completed or no longer required (during seeking
-or shutdown).
+All file reads and seeks must occur on either the state machine thread
+or the decode thread. Synchronisation is done via a monitor owned by
+nsOggDecoder.
+
+The decode thread and the audio thread are created and destroyed in the
+state machine thread. When playback needs to occur they are created and
+events dispatched to them to start them. These events exit when
+decoding is completed or no longer required (during seeking or
+shutdown).
     
-When the decode thread is created an event is dispatched to it. The event
-runs for the lifetime of the playback of the resource. The decode thread
-synchronises with the main thread via a single monitor held by the 
-nsOggDecoder object.
+All threads have one event that is dispatched to it and that event
+runs for the lifetime of the playback of the resource. State shared
+between them is synchronised with the main thread via a monitor
+held by the nsOggDecoder object. The decode thread also has its own
+monitor to ensure that its internal state is independent of the other
+threads, and to ensure that it's not hogging the monitor while decoding.
 
-The event contains a Run method which consists of an infinite loop
-that checks the state that the state machine is in and processes
-operations on that state.
+The events consist of a Run method which is an infinite loop that
+perform the threads operation and checks the state that the state
+machine is in and processes operations on that state.
 
-The nsOggDecodeStateMachine class is the event that gets dispatched to
-the decode thread. It has the following states:
+The nsOggPlayStateMachine class is the event that gets dispatched to
+the state machine thread. It has the following states:
 
 DECODING_METADATA
   The Ogg headers are being loaded, and things like framerate, etc are
   being determined, and the first frame of audio/video data is being decoded.
 DECODING
-  Video/Audio frames are being decoded.
+  The decode and audio threads are started and video frames displayed at
+  the required time. 
 SEEKING
   A seek operation is in progress.
 BUFFERING
   Decoding is paused while data is buffered for smooth playback.
 COMPLETED
-  The resource has completed decoding. 
+  The resource has completed decoding, but not finished playback. 
 SHUTDOWN
   The decoder object is about to be destroyed.
 
 The following result in state transitions.
 
 Shutdown()
-  Clean up any resources the nsOggDecodeStateMachine owns.
+  Clean up any resources the nsOggPlayStateMachine owns.
 Decode()
   Start decoding video frames.
 Buffer
   This is not user initiated. It occurs when the
   available data in the stream drops below a certain point.
 Complete
   This is not user initiated. It occurs when the
   stream is completely decoded.
@@ -158,17 +171,17 @@ player PLAYING   decoder DECODING, BUFFE
 player PAUSED    decoder DECODING, BUFFERING, SEEKING, COMPLETED
 player SEEKING   decoder SEEKING
 player COMPLETED decoder SHUTDOWN
 player SHUTDOWN  decoder SHUTDOWN
 
 The general sequence of events with these objects is:
 
 1) The video element calls Load on nsMediaDecoder. This creates the
-   decode thread and starts the channel for downloading the file. It
+   state machine thread and starts the channel for downloading the file. It
    instantiates and starts the Decode state machine. The high level
    LOADING state is entered, which results in the decode state machine
    to start decoding metadata. These are the headers that give the
    video size, framerate, etc.  It returns immediately to the calling
    video element.
 
 2) When the Ogg metadata has been loaded by the decode thread it will
    call a method on the video element object to inform it that this
@@ -183,110 +196,98 @@ 3) When the first frame of Ogg data has 
 
    This results in the high level state changing to PLAYING or PAUSED
    depending on any user action that may have occurred.
 
    The decode thread, while in the DECODING state, plays audio and
    video, if the correct frame time comes around and the decoder
    play state is PLAYING.
    
-a/v synchronisation is done by a combination of liboggplay and the
-Decoder state machine. liboggplay ensures that a decoded frame of data
-has both the audio samples and the YUV data for that period of time.
+a/v synchronisation is handled by the stete machine thread. It examines the
+audio playback time and compares this to the next frame in the queue
+of frames. If it is time to play the video frame it is then displayed.
 
-When a frame is decoded by the decode state machine it converts the
-YUV encoded video to RGB and copies the sound data to an internal
-FrameData object. This is stored in a queue of available decoded frames.
-Included in the FrameData object is the time that that frame should
-be displayed.
+Frame skipping is done in the following ways:
 
-The display state machine keeps track of the time since the last frame it
-played. After decoding a frame it checks if it is time to display the next
-item in the decoded frame queue. If so, it pops the item off the queue
-and displays it.
+  1) The state machine thread will skip all frames in the video queue whose
+     display time is less than the current audio time. This ensures
+     the correct frame for the current time is always displayed.
 
-Ideally a/v sync would take into account the actual audio clock of the
-audio hardware for the sync rather than using the system clock.
-Unfortunately getting valid time data out of the audio hardware has proven
-to be unreliable across platforms (and even distributions in Linux) depending
-on audio hardware, audio backend etc. The current approach works fine in practice
-and is a compromise until this issue can be sorted. The plan is to eventually
-move to synchronising using the audio hardware.
+  2) The decode thread will stop decoding interframes and read to the
+     next keyframe if it determines that decoding the remaining
+     interframes will cause playback issues. It detects this by:
+       a) If the amount of audio data in the audio queue drops
+          below a threshold whereby audio may start to skip.
+       b) If the video queue drops below a threshold where it
+          will be decoding video data that won't be displayed due
+          to the decode thread dropping the frame immediately.
 
-To prevent audio skipping and framerate dropping it is very important to
-make sure no blocking occurs during the decoding process and minimise
-expensive time operations at the time a frame is to be displayed. This is
-managed by immediately converting video data to RGB on decode (an expensive
-operation to do at frame display time) and checking if the sound device will
-not block before writing sound data to it.
+YCbCr conversion is done on the decode thread when it is time to display
+the video frame. This means frames that are skipped will not have the
+YCbCr conversion done, improving playback.
 
-Shutdown needs to ensure that the event posted to the decode
-thread is completed. The decode thread can potentially block internally
-inside liboggplay when reading, seeking, or its internal buffers containing
-decoded data are full. When blocked in this manner a call from the main thread
-to Shutdown() will hang.  
+The decode thread pushes decoded audio and videos frames into two
+separate queues - one for audio and one for video. These are kept
+separate to make it easy to constantly feed audio data to the sound
+hardware while allowing frame skipping of video data. These queues are
+threadsafe, and neither the decode, audio, or state machine thread should
+be able to monopolize them, and cause starvation of the other threads.
 
-This is fixed with a protocol to ensure that the decode event cleanly
-completes. The nsMediaStream that the nsChannelReader uses has a
-Cancel() method. Calling this before Shutdown() will close any
-internal streams or listeners resulting in blocked i/o completing with
-an error, and all future i/o on the stream having an error.
-
-This causes the decode thread to exit and Shutdown() can occur.
+Both queues are bounded by a maximum size. When this size is reached
+the decode thread will no longer decode video or audio depending on the
+queue that has reached the threshold.
 
-If the decode thread is seeking then the same Cancel() operation
-causes an error to be returned from the seek call to liboggplay which
-exits out of the seek operation, and stops the seek state running on the
-decode thread.
+During playback the audio thread will be idle (via a Wait() on the
+monitor) if the audio queue is empty. Otherwise it constantly pops an
+item off the queue and plays it with a blocking write to the audio
+hardware (via nsAudioStream and libsydneyaudio).
 
-If the decode thread is blocked due to internal decode buffers being
-full, it is unblocked during the shutdown process by calling
-oggplay_prepare_for_close.
-
-In practice the OggPlay internal buffer should never fill as we retrieve and
-process the frame immediately on decoding.
+The decode thread idles if the video queue is empty or if it is
+not yet time to display the next frame.
 
 The Shutdown method on nsOggDecoder can spin the event loop as it waits
 for threads to complete. Spinning the event loop is a bad thing to happen
 during certain times like destruction of the media element. To work around
-this the Shutdown method does nothing by queue an event to the main thread
+this the Shutdown method does nothing but queue an event to the main thread
 to perform the actual Shutdown. This way the shutdown can occur at a safe
 time. 
 
 This means the owning object of a nsOggDecoder object *MUST* call Shutdown
 when destroying the nsOggDecoder object.
 */
 #if !defined(nsOggDecoder_h_)
 #define nsOggDecoder_h_
 
 #include "nsMediaDecoder.h"
 
 #include "nsISupports.h"
 #include "nsCOMPtr.h"
 #include "nsIThread.h"
 #include "nsIChannel.h"
-#include "nsChannelReader.h"
 #include "nsIObserver.h"
 #include "nsIFrame.h"
 #include "nsAutoPtr.h"
 #include "nsSize.h"
 #include "prlog.h"
-#include "prmon.h"
 #include "gfxContext.h"
 #include "gfxRect.h"
-#include "oggplay/oggplay.h"
+#include "nsMediaStream.h"
+#include "nsMediaDecoder.h"
+#include "mozilla/Monitor.h"
+
+using mozilla::Monitor;
 
 class nsAudioStream;
-class nsOggDecodeStateMachine;
-class nsOggStepDecodeEvent;
+class nsOggPlayStateMachine;
+class nsOggReader;
 
 class nsOggDecoder : public nsMediaDecoder
 {
-  friend class nsOggDecodeStateMachine;
-  friend class nsOggStepDecodeEvent;
+  friend class nsOggReader;
+  friend class nsOggPlayStateMachine;
 
   // ISupports
   NS_DECL_ISUPPORTS
 
   // nsIObserver
   NS_DECL_NSIOBSERVER
 
  public:
@@ -331,17 +332,18 @@ class nsOggDecoder : public nsMediaDecod
   virtual float GetDuration();
 
   virtual nsMediaStream* GetCurrentStream();
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
 
   virtual void NotifySuspendedStatusChanged();
   virtual void NotifyBytesDownloaded();
   virtual void NotifyDownloadEnded(nsresult aStatus);
-  // Called by nsChannelReader on the decoder thread
+  // Called by the decode thread to keep track of the number of bytes read
+  // from the resource.
   void NotifyBytesConsumed(PRInt64 aBytes);
 
   // Called when the video file has completed downloading.
   // Call on the main thread only.
   void ResourceLoaded();
 
   // Called if the media file encounters a network error.
   // Call on the main thread only.
@@ -361,51 +363,50 @@ class nsOggDecoder : public nsMediaDecod
   virtual void SetDuration(PRInt64 aDuration);
 
   // Set a flag indicating whether seeking is supported
   virtual void SetSeekable(PRBool aSeekable);
 
   // Return PR_TRUE if seeking is supported.
   virtual PRBool GetSeekable();
 
-  // Returns the channel reader.
-  nsChannelReader* GetReader() { return mReader; }
-
   virtual Statistics GetStatistics();
 
   // Suspend any media downloads that are in progress. Called by the
   // media element when it is sent to the bfcache. Call on the main
   // thread only.
   virtual void Suspend();
 
   // Resume any media downloads that have been suspended. Called by the
   // media element when it is restored from the bfcache. Call on the
   // main thread only.
   virtual void Resume();
 
   // Tells our nsMediaStream to put all loads in the background.
   virtual void MoveLoadsToBackground();
 
-  // Stop the state machine thread and drop references to the thread,
-  // state machine and channel reader.
+  // Stop the state machine thread and drop references to the thread and
+  // state machine.
   void Stop();
 
+  // Called by the state machine to notify the decoder that the duration
+  // has changed.
+  void DurationChanged();
+
 protected:
 
   // Returns the monitor for other threads to synchronise access to
   // state.
-  PRMonitor* GetMonitor() 
-  { 
+  Monitor& GetMonitor() { 
     return mMonitor; 
   }
 
   // Return the current state. Can be called on any thread. If called from
   // a non-main thread, the decoder monitor must be held.
-  PlayState GetState()
-  {
+  PlayState GetState() {
     return mPlayState;
   }
 
   // Stop updating the bytes downloaded for progress notifications. Called
   // when seeking to prevent wild changes to the progress notification.
   // Must be called with the decoder monitor held.
   void StopProgressUpdates();
 
@@ -491,34 +492,32 @@ private:
   // during decoder seek operations, but it's updated at the end when we
   // start playing back again.
   PRInt64 mPlaybackPosition;
   // Data needed to estimate playback data rate. The timeline used for
   // this estimate is "decode time" (where the "current time" is the
   // time of the last decoded video frame).
   nsChannelStatistics mPlaybackStatistics;
 
-  // Thread to handle decoding of Ogg data.
-  nsCOMPtr<nsIThread> mDecodeThread;
+  // Thread to manage playback state machine.
+  nsCOMPtr<nsIThread> mStateMachineThread;
 
   // The current playback position of the media resource in units of
   // seconds. This is updated approximately at the framerate of the
   // video (if it is a video) or the callback period of the audio.
   // It is read and written from the main thread only.
   float mCurrentTime;
 
   // Volume that playback should start at.  0.0 = muted. 1.0 = full
-  // volume.  Readable/Writeable from the main thread. Read from the
-  // audio thread when it is first started to get the initial volume
-  // level.
+  // volume.  Readable/Writeable from the main thread.
   float mInitialVolume;
 
   // Position to seek to when the seek notification is received by the
-  // decoding thread. Written by the main thread and read via the
-  // decoding thread. Synchronised using mPlayStateMonitor. If the
+  // decode thread. Written by the main thread and read via the
+  // decode thread. Synchronised using mMonitor. If the
   // value is negative then no seek has been requested. When a seek is
   // started this is reset to negative.
   float mRequestedSeekTime;
 
   // Duration of the media resource. Set to -1 if unknown.
   // Set when the Ogg metadata is loaded. Accessed on the main thread
   // only.
   PRInt64 mDuration;
@@ -526,36 +525,30 @@ private:
   // True if the media resource is seekable (server supports byte range
   // requests).
   PRPackedBool mSeekable;
 
   /******
    * The following member variables can be accessed from any thread.
    ******/
 
-  // The state machine object for handling the decoding via
-  // oggplay. It is safe to call methods of this object from other
-  // threads. Its internal data is synchronised on a monitor. The
-  // lifetime of this object is after mPlayState is LOADING and before
-  // mPlayState is SHUTDOWN. It is safe to access it during this
-  // period.
-  nsCOMPtr<nsOggDecodeStateMachine> mDecodeStateMachine;
+  // The state machine object for handling the decoding. It is safe to
+  // call methods of this object from other threads. Its internal data
+  // is synchronised on a monitor. The lifetime of this object is
+  // after mPlayState is LOADING and before mPlayState is SHUTDOWN. It
+  // is safe to access it during this period.
+  nsCOMPtr<nsOggPlayStateMachine> mDecodeStateMachine;
 
-  // OggPlay object used to read data from a channel. Created on main
-  // thread. Passed to liboggplay and the locking for multithreaded
-  // access is handled by that library. Some methods are called from
-  // the decoder thread, and the state machine for that thread keeps
-  // a pointer to this reader. This is safe as the only methods called
-  // are threadsafe (via the threadsafe nsMediaStream).
-  nsAutoPtr<nsChannelReader> mReader;
+  // Stream of media data.
+  nsAutoPtr<nsMediaStream> mStream;
 
   // Monitor for detecting when the video play state changes. A call
   // to Wait on this monitor will block the thread until the next
   // state change.
-  PRMonitor* mMonitor;
+  Monitor mMonitor;
 
   // Set to one of the valid play states. It is protected by the
   // monitor mMonitor. This monitor must be acquired when reading or
   // writing the state. Any change to the state on the main thread
   // must call NotifyAll on the monitor so the decode thread can wake up.
   PlayState mPlayState;
 
   // The state to change to after a seek or load operation. It must only
new file mode 100644
--- /dev/null
+++ b/content/media/ogg/nsOggHacks.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: ML 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsOggHacks_h
+#define nsOggHacks_h
+
+// This file contains stuff we'd rather put elsewhere, but which is
+// dependent on other changes which we don't want to wait for. We plan to
+// remove this file in the near future.
+
+
+// This belongs in prtypes.h
+/************************************************************************
+ * MACROS:      PR_INT64_MAX
+ *              PR_INT64_MIN
+ *              PR_UINT64_MAX
+ * DESCRIPTION:
+ *  The maximum and minimum values of a PRInt64 or PRUint64.
+************************************************************************/
+
+#define PR_INT64_MAX (~((PRInt64)(1) << 63))
+#define PR_INT64_MIN (-PR_INT64_MAX - 1)
+#define PR_UINT64_MAX (~(PRUint64)(0))
+
+
+// This belongs in xpcom/monitor/Monitor.h, once we've made
+// mozilla::Monitor non-reentrant.
+namespace mozilla {
+
+/**
+ * MonitorAutoExit
+ * Exit the Monitor when it enters scope, and enters it when it leaves 
+ * scope.
+ *
+ * MUCH PREFERRED to bare calls to Monitor.Exit and Enter.
+ */ 
+class NS_COM_GLUE NS_STACK_CLASS MonitorAutoExit
+{
+public:
+    /**
+     * Constructor
+     * The constructor releases the given lock.  The destructor
+     * acquires the lock. The lock must be held before constructing
+     * this object!
+     * 
+     * @param aMonitor A valid mozilla::Monitor* returned by 
+     *                 mozilla::Monitor::NewMonitor. It must be
+     *                 already locked.
+     **/
+    MonitorAutoExit(mozilla::Monitor &aMonitor) :
+        mMonitor(&aMonitor)
+    {
+        NS_ASSERTION(mMonitor, "null monitor");
+        mMonitor->AssertCurrentThreadIn();
+        mMonitor->Exit();
+    }
+    
+    ~MonitorAutoExit(void)
+    {
+        mMonitor->Enter();
+    }
+ 
+private:
+    MonitorAutoExit();
+    MonitorAutoExit(const MonitorAutoExit&);
+    MonitorAutoExit& operator =(const MonitorAutoExit&);
+    static void* operator new(size_t) CPP_THROW_NEW;
+    static void operator delete(void*);
+
+    mozilla::Monitor* mMonitor;
+};
+
+} // namespace mozilla
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/ogg/nsOggPlayStateMachine.cpp
@@ -0,0 +1,1392 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: ML 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include <limits>
+#include "nsAudioStream.h"
+#include "nsTArray.h"
+#include "nsOggDecoder.h"
+#include "nsOggReader.h"
+#include "nsOggPlayStateMachine.h"
+#include "oggplay/oggplay.h"
+#include "mozilla/mozalloc.h"
+#include "nsOggHacks.h"
+
+using namespace mozilla::layers;
+using mozilla::MonitorAutoExit;
+
+// Adds two 32bit unsigned numbers, retuns PR_TRUE if addition succeeded,
+// or PR_FALSE the if addition would result in an overflow.
+static PRBool AddOverflow(PRUint32 a, PRUint32 b, PRUint32& aResult);
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gOggDecoderLog;
+#define LOG(type, msg) PR_LOG(gOggDecoderLog, type, msg)
+#else
+#define LOG(type, msg)
+#endif
+
+// Wait this number of seconds when buffering, then leave and play
+// as best as we can if the required amount of data hasn't been
+// retrieved.
+#define BUFFERING_WAIT 30
+
+// The amount of data to retrieve during buffering is computed based
+// on the download rate. BUFFERING_MIN_RATE is the minimum download
+// rate to be used in that calculation to help avoid constant buffering
+// attempts at a time when the average download rate has not stabilised.
+#define BUFFERING_MIN_RATE 50000
+#define BUFFERING_RATE(x) ((x)< BUFFERING_MIN_RATE ? BUFFERING_MIN_RATE : (x))
+
+// The frame rate to use if there is no video data in the resource to
+// be played.
+#define AUDIO_FRAME_RATE 25.0
+
+nsOggPlayStateMachine::nsOggPlayStateMachine(nsOggDecoder* aDecoder) :
+  mDecoder(aDecoder),
+  mState(DECODER_STATE_DECODING_METADATA),
+  mAudioMonitor("media.audiostream"),
+  mCbCrSize(0),
+  mPlayDuration(0),
+  mBufferingEndOffset(0),
+  mStartTime(-1),
+  mEndTime(-1),
+  mSeekTime(0),
+  mCurrentFrameTime(0),
+  mAudioStartTime(-1),
+  mAudioEndTime(-1),
+  mVideoFrameTime(-1),
+  mVolume(1.0),
+  mSeekable(PR_TRUE),
+  mPositionChangeQueued(PR_FALSE),
+  mAudioCompleted(PR_FALSE),
+  mBufferExhausted(PR_FALSE),
+  mGotDurationFromHeader(PR_FALSE),
+  mStopDecodeThreads(PR_TRUE)
+{
+  MOZ_COUNT_CTOR(nsOggPlayStateMachine);
+}
+
+nsOggPlayStateMachine::~nsOggPlayStateMachine()
+{
+  MOZ_COUNT_DTOR(nsOggPlayStateMachine);
+}
+
+void nsOggPlayStateMachine::DecodeLoop()
+{
+  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+  PRBool videoPlaying = PR_FALSE;
+  PRBool audioPlaying = PR_FALSE;
+  {
+    MonitorAutoEnter mon(mDecoder->GetMonitor());
+    videoPlaying = HasVideo();
+    audioPlaying = HasAudio();
+  }
+
+  // We want to "pump" the decode until we've got a few frames/samples decoded
+  // before we consider whether decode is falling behind.
+  PRBool audioPump = PR_TRUE;
+  PRBool videoPump = PR_TRUE;
+
+  // If the video decode is falling behind the audio, we'll start dropping the
+  // inter-frames up until the next keyframe which is at or before the current
+  // playback position. skipToNextKeyframe is PR_TRUE if we're currently
+  // skipping up to the next keyframe.
+  PRBool skipToNextKeyframe = PR_FALSE;
+
+  // If we have fewer than videoKeyframeSkipThreshold decoded frames, and
+  // we're not "pumping video", we'll skip the video up to the next keyframe
+  // which is at or after the current playback position.
+  const unsigned videoKeyframeSkipThreshold = 1;
+
+  // Once we've decoded more than videoPumpThreshold video frames, we'll
+  // no longer be considered to be "pumping video".
+  const unsigned videoPumpThreshold = 5;
+
+  // If we've got more than videoWaitThreshold decoded video frames waiting in
+  // the video queue, we will not decode any more video frames until they've
+  // been consumed by the play state machine thread.
+  const unsigned videoWaitThreshold = 10;
+
+  // After the audio decode fills with more than audioPumpThresholdMs ms
+  // of decoded audio, we'll start to check whether the audio or video decode
+  // is falling behind.
+  const unsigned audioPumpThresholdMs = 250;
+
+  // If audio queue has less than this many ms of decoded audio, we won't risk
+  // trying to decode the video, we'll skip decoding video up to the next
+  // keyframe.
+  const unsigned lowAudioThresholdMs = 100;
+
+  // If more than this many ms of decoded audio is queued, we'll hold off
+  // decoding more audio.
+  const unsigned audioWaitThresholdMs = 2000;
+
+  // Main decode loop.
+  while (videoPlaying || audioPlaying) {
+    PRBool audioWait = !audioPlaying;
+    PRBool videoWait = !videoPlaying;
+    {
+      // Wait for more data to download if we've exhausted all our
+      // buffered data.
+      MonitorAutoEnter mon(mDecoder->GetMonitor());
+      while (!mStopDecodeThreads &&
+             mBufferExhausted &&
+             mState != DECODER_STATE_SHUTDOWN)
+      {
+        mon.Wait();
+      }
+      if (mState == DECODER_STATE_SHUTDOWN || mStopDecodeThreads)
+        break;
+    }
+
+    PRUint32 videoQueueSize = mReader->mVideoQueue.GetSize();
+    // Don't decode any more frames if we've filled our buffers.
+    // Limits memory consumption.
+    if (videoQueueSize > videoWaitThreshold) {
+      videoWait = PR_TRUE;
+    }
+
+    // We don't want to consider skipping to the next keyframe if we've
+    // only just started up the decode loop, so wait until we've decoded
+    // some frames before allowing the keyframe skip.
+    if (videoPump && videoQueueSize >= videoPumpThreshold) {
+      videoPump = PR_FALSE;
+    }
+    if (!videoPump &&
+        videoPlaying &&
+        videoQueueSize < videoKeyframeSkipThreshold)
+    {
+      skipToNextKeyframe = PR_TRUE;
+    }
+
+    PRInt64 initialDownloadPosition = 0;
+    PRInt64 currentTime = 0;
+    {
+      MonitorAutoEnter mon(mDecoder->GetMonitor());
+      initialDownloadPosition =
+        mDecoder->mStream->GetCachedDataEnd(mDecoder->mDecoderPosition);
+      currentTime = mCurrentFrameTime + mStartTime;
+    }
+
+    // Determine how much audio data is decoded ahead of the current playback
+    // position.
+    int audioQueueSize = mReader->mAudioQueue.GetSize();
+    PRInt64 audioDecoded = mReader->mAudioQueue.Duration();
+
+    // Don't decode any audio if the audio decode is way ahead, or if we're
+    // skipping to the next video keyframe and the audio is marginally ahead.
+    if (audioDecoded > audioWaitThresholdMs ||
+        (skipToNextKeyframe && audioDecoded > audioPumpThresholdMs)) {
+      audioWait = PR_TRUE;
+    }
+    if (audioPump && audioDecoded > audioPumpThresholdMs) {
+      audioPump = PR_FALSE;
+    }
+    if (!audioPump && audioPlaying && audioDecoded < lowAudioThresholdMs) {
+      skipToNextKeyframe = PR_TRUE;
+    }
+
+    if (videoPlaying && !videoWait) {
+      videoPlaying = mReader->DecodeVideoPage(skipToNextKeyframe, currentTime);
+      {
+        MonitorAutoEnter mon(mDecoder->GetMonitor());
+        if (mDecoder->mDecoderPosition >= initialDownloadPosition) {
+          mBufferExhausted = PR_TRUE;
+        }
+      }
+    }
+    {
+      MonitorAutoEnter mon(mDecoder->GetMonitor());
+      initialDownloadPosition =
+        mDecoder->mStream->GetCachedDataEnd(mDecoder->mDecoderPosition);
+      mDecoder->GetMonitor().NotifyAll();
+    }
+
+    if (audioPlaying && !audioWait) {
+      audioPlaying = mReader->DecodeAudioPage();
+      {
+        MonitorAutoEnter mon(mDecoder->GetMonitor());
+        if (mDecoder->mDecoderPosition >= initialDownloadPosition) {
+          mBufferExhausted = PR_TRUE;
+        }
+      }
+    }
+
+    {
+      MonitorAutoEnter mon(mDecoder->GetMonitor());
+
+      if (!IsPlaying() &&
+          (!audioWait || !videoWait) &&
+          (videoQueueSize < 2 || audioQueueSize < 2))
+      {
+        // Transitioning from 0 to 1 frames or from 1 to 2 frames could
+        // affect HaveNextFrameData and hence what UpdateReadyStateForData does.
+        // This could change us from HAVE_CURRENT_DATA to HAVE_FUTURE_DATA
+        // (or even HAVE_ENOUGH_DATA), so we'd better trigger an
+        // update to the ready state. We only need to do this if we're
+        // not playing; if we're playing the playback code will post an update
+        // whenever it advances a frame.
+        UpdateReadyState();
+      }
+
+      if (mState == DECODER_STATE_SHUTDOWN || mStopDecodeThreads) {
+        break;
+      }
+      if ((!HasAudio() || (audioWait && audioPlaying)) &&
+          (!HasVideo() || (videoWait && videoPlaying)))
+      {
+        // All active bitstreams' decode is well ahead of the playback
+        // position, we may as well wait have for the playback to catch up.
+        mon.Wait();
+      }
+    }
+  }
+
+  {
+    MonitorAutoEnter mon(mDecoder->GetMonitor());
+    if (!mStopDecodeThreads &&
+        mState != DECODER_STATE_SHUTDOWN &&
+        mState != DECODER_STATE_SEEKING)
+    {
+      mState = DECODER_STATE_COMPLETED;
+      mDecoder->GetMonitor().NotifyAll();
+    }
+  }
+  LOG(PR_LOG_DEBUG, ("Shutting down DecodeLoop this=%p", this));
+}
+
+PRBool nsOggPlayStateMachine::IsPlaying()
+{
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  return !mPlayStartTime.IsNull();
+}
+
+void nsOggPlayStateMachine::AudioLoop()
+{
+  NS_ASSERTION(OnAudioThread(), "Should be on audio thread.");
+  LOG(PR_LOG_DEBUG, ("Begun audio thread/loop"));
+  {
+    MonitorAutoEnter mon(mDecoder->GetMonitor());
+    mAudioCompleted = PR_FALSE;
+  }
+  PRInt64 audioStartTime = -1;
+  while (1) {
+
+    // Wait while we're not playing, and we're not shutting down, or we're 
+    // playing and we've got no audio to play.
+    {
+      MonitorAutoEnter mon(mDecoder->GetMonitor());
+      NS_ASSERTION(mState != DECODER_STATE_DECODING_METADATA,
+                   "Should have meta data before audio started playing.");
+      while (mState != DECODER_STATE_SHUTDOWN &&
+             !mStopDecodeThreads &&
+             (!IsPlaying() ||
+              mState == DECODER_STATE_BUFFERING ||
+              (mReader->mAudioQueue.GetSize() == 0 &&
+               !mReader->mAudioQueue.AtEndOfStream())))
+      {
+        mon.Wait();
+      }
+
+      // If we're shutting down, break out and exit the audio thread.
+      if (mState == DECODER_STATE_SHUTDOWN ||
+          mStopDecodeThreads ||
+          mReader->mAudioQueue.AtEndOfStream())
+      {
+        break;
+      }
+    }
+
+    NS_ASSERTION(mReader->mAudioQueue.GetSize() > 0,
+                 "Should have data to play");
+    nsAutoPtr<SoundData> sound(mReader->mAudioQueue.PopFront());
+    {
+      MonitorAutoEnter mon(mDecoder->GetMonitor());
+      NS_ASSERTION(IsPlaying(), "Should be playing");
+      // Awaken the decode loop if it's waiting for space to free up in the
+      // audio queue.
+      mDecoder->GetMonitor().NotifyAll();
+    }
+
+    if (audioStartTime == -1) {
+      // Remember the presentation time of the first audio sample we play.
+      // We add this to the position/played duration of the audio stream to
+      // determine the audio clock time. Used for A/V sync.
+      MonitorAutoEnter mon(mDecoder->GetMonitor());
+      mAudioStartTime = audioStartTime = sound->mTime;
+      LOG(PR_LOG_DEBUG, ("First audio sample has timestamp %lldms", mAudioStartTime));
+    }
+
+    {
+      MonitorAutoEnter audioMon(mAudioMonitor);
+      if (mAudioStream) {
+        // The state machine could have paused since we've released the decoder
+        // monitor and acquired the audio monitor. Rather than acquire both
+        // monitors, the audio stream also maintains whether its paused or not.
+        // This prevents us from doing a blocking write while holding the audio
+        // monitor while paused; we would block, and the state machine won't be 
+        // able to acquire the audio monitor in order to resume or destroy the
+        // audio stream.
+        if (!mAudioStream->IsPaused()) {
+          mAudioStream->Write(sound->mAudioData,
+                              sound->AudioDataLength(),
+                              PR_TRUE);
+          mAudioEndTime = sound->mTime + sound->mDuration;
+        } else {
+          mReader->mAudioQueue.PushFront(sound);
+          sound.forget();
+        }
+      }
+    }
+    sound = nsnull;
+
+    if (mReader->mAudioQueue.AtEndOfStream()) {
+      // Last sample pushed to audio hardware, wait for the audio to finish,
+      // before the audio thread terminates.
+      MonitorAutoEnter audioMon(mAudioMonitor);
+      if (mAudioStream) {
+        mAudioStream->Drain();
+      }
+      LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder));
+    }
+  }
+  {
+    MonitorAutoEnter mon(mDecoder->GetMonitor());
+    mAudioCompleted = PR_TRUE;
+  }
+  LOG(PR_LOG_DEBUG, ("Audio stream finished playing, audio thread exit"));
+}
+
+nsresult nsOggPlayStateMachine::Init()
+{
+  mReader = new nsOggReader(this);
+  return mReader->Init();
+}
+
+void nsOggPlayStateMachine::StopPlayback(eStopMode aMode)
+{
+  NS_ASSERTION(IsThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  // Reset mPlayStartTime before we pause/shutdown the nsAudioStream. This is
+  // so that if the audio loop is about to write audio, it will have the chance
+  // to check to see if we're paused and not write the audio. If not, the
+  // audio thread can block in the write, and we deadlock trying to acquire
+  // the audio monitor upon resume playback.
+  if (IsPlaying()) {
+    mPlayDuration += TimeStamp::Now() - mPlayStartTime;
+    mPlayStartTime = TimeStamp();
+  }
+  if (HasAudio()) {
+    MonitorAutoExit exitMon(mDecoder->GetMonitor());
+    MonitorAutoEnter audioMon(mAudioMonitor);
+    if (mAudioStream) {
+      if (aMode == AUDIO_PAUSE) {
+        mAudioStream->Pause();
+      } else if (aMode == AUDIO_SHUTDOWN) {
+        mAudioStream->Shutdown();
+        mAudioStream = nsnull;
+      }
+    }
+  }
+}
+
+void nsOggPlayStateMachine::StartPlayback()
+{
+  NS_ASSERTION(IsThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
+  NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+  LOG(PR_LOG_DEBUG, ("%p StartPlayback", mDecoder));
+  if (HasAudio()) {
+    MonitorAutoExit exitMon(mDecoder->GetMonitor());
+    MonitorAutoEnter audioMon(mAudioMonitor);
+    if (mAudioStream) {
+      // We have an audiostream, so it must have been paused the last time
+      // StopPlayback() was called.
+      mAudioStream->Resume();
+    } else {
+      // No audiostream, create one.
+      mAudioStream = new nsAudioStream();
+      mAudioStream->Init(mInfo.mAudioChannels,
+                         mInfo.mAudioRate,
+                         nsAudioStream::FORMAT_FLOAT32);
+      mAudioStream->SetVolume(mVolume);
+    }
+  }
+  mPlayStartTime = TimeStamp::Now();
+  mDecoder->GetMonitor().NotifyAll();
+}
+
+void nsOggPlayStateMachine::UpdatePlaybackPosition(PRInt64 aTime)
+{
+  NS_ASSERTION(IsThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  NS_ASSERTION(mStartTime >= 0, "Should have positive mStartTime");
+  mCurrentFrameTime = aTime - mStartTime;
+  NS_ASSERTION(mCurrentFrameTime >= 0, "CurrentTime should be positive!");
+  if (aTime > mEndTime) {
+    NS_ASSERTION(mCurrentFrameTime > GetDuration(),
+                 "CurrentTime must be after duration if aTime > endTime!");
+    mEndTime = aTime;
+    nsCOMPtr<nsIRunnable> event =
+      NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, DurationChanged);
+    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+  }
+  if (!mPositionChangeQueued) {
+    mPositionChangeQueued = PR_TRUE;
+    nsCOMPtr<nsIRunnable> event =
+      NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackPositionChanged);
+    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+  }
+}
+
+void nsOggPlayStateMachine::ClearPositionChangeFlag()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  mPositionChangeQueued = PR_FALSE;
+}
+
+nsHTMLMediaElement::NextFrameStatus nsOggPlayStateMachine::GetNextFrameStatus()
+{
+  MonitorAutoEnter mon(mDecoder->GetMonitor());
+  if (IsBuffering() || IsSeeking()) {
+    return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING;
+  } else if (HaveNextFrameData()) {
+    return nsHTMLMediaElement::NEXT_FRAME_AVAILABLE;
+  }
+  return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE;
+}
+
+void nsOggPlayStateMachine::SetVolume(float volume)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  {
+    MonitorAutoEnter audioMon(mAudioMonitor);
+    if (mAudioStream) {
+      mAudioStream->SetVolume(volume);
+    }
+  }
+  {
+    MonitorAutoEnter mon(mDecoder->GetMonitor());
+    mVolume = volume;
+  }
+}
+
+float nsOggPlayStateMachine::GetCurrentTime()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  return (float)mCurrentFrameTime / 1000.0;
+}
+
+PRInt64 nsOggPlayStateMachine::GetDuration()
+{
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  if (mEndTime == -1 || mStartTime == -1)
+    return -1;
+  return mEndTime - mStartTime;
+}
+
+void nsOggPlayStateMachine::SetDuration(PRInt64 aDuration)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  if (mStartTime != -1) {
+    mEndTime = mStartTime + aDuration;
+  } else {
+    mStartTime = 0;
+    mEndTime = aDuration;
+  }
+}
+
+void nsOggPlayStateMachine::SetSeekable(PRBool aSeekable)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  mSeekable = aSeekable;
+}
+
+void nsOggPlayStateMachine::Shutdown()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
+  // Once we've entered the shutdown state here there's no going back.
+  MonitorAutoEnter mon(mDecoder->GetMonitor());
+
+  // Change state before issuing shutdown request to threads so those
+  // threads can start exiting cleanly during the Shutdown call.
+  LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder));
+  mState = DECODER_STATE_SHUTDOWN;
+  mDecoder->GetMonitor().NotifyAll();
+}
+
+void nsOggPlayStateMachine::Decode()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  // When asked to decode, switch to decoding only if
+  // we are currently buffering.
+  MonitorAutoEnter mon(mDecoder->GetMonitor());
+  if (mState == DECODER_STATE_BUFFERING) {
+    LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder));
+    mState = DECODER_STATE_DECODING;
+    mDecoder->GetMonitor().NotifyAll();
+  }
+}
+
+void nsOggPlayStateMachine::ResetPlayback()
+{
+  NS_ASSERTION(IsThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
+  mVideoFrameTime = -1;
+  mAudioStartTime = -1;
+  mAudioEndTime = -1;
+  mAudioCompleted = PR_FALSE;
+}
+
+void nsOggPlayStateMachine::Seek(float aTime)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  MonitorAutoEnter mon(mDecoder->GetMonitor());
+  // nsOggDecoder::mPlayState should be SEEKING while we seek, and
+  // in that case nsOggDecoder shouldn't be calling us.
+  NS_ASSERTION(mState != DECODER_STATE_SEEKING,
+               "We shouldn't already be seeking");
+  NS_ASSERTION(mState >= DECODER_STATE_DECODING,
+               "We should have loaded metadata");
+  double t = aTime * 1000.0;
+  if (t > PR_INT64_MAX) {
+    // Prevent integer overflow.
+    return;
+  }
+
+  mSeekTime = static_cast<PRInt64>(t) + mStartTime;
+  NS_ASSERTION(mSeekTime >= mStartTime && mSeekTime <= mEndTime,
+               "Can only seek in range [0,duration]");
+
+  // Bound the seek time to be inside the media range.
+  NS_ASSERTION(mStartTime != -1, "Should know start time by now");
+  NS_ASSERTION(mEndTime != -1, "Should know end time by now");
+  mSeekTime = NS_MIN(mSeekTime, mEndTime);
+  mSeekTime = NS_MAX(mStartTime, mSeekTime);
+  LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder, aTime));
+  mState = DECODER_STATE_SEEKING;
+}
+
+void nsOggPlayStateMachine::StopDecodeThreads()
+{
+  NS_ASSERTION(IsThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+  mStopDecodeThreads = PR_TRUE;
+  mDecoder->GetMonitor().NotifyAll();
+  if (mDecodeThread) {
+    {
+      MonitorAutoExit exitMon(mDecoder->GetMonitor());
+      mDecodeThread->Shutdown();
+    }
+    mDecodeThread = nsnull;
+  }
+  if (mAudioThread) {
+    {
+      MonitorAutoExit exitMon(mDecoder->GetMonitor());
+      mAudioThread->Shutdown();
+    }
+    mAudioThread = nsnull;
+  }
+}
+
+nsresult
+nsOggPlayStateMachine::StartDecodeThreads()
+{
+  NS_ASSERTION(IsThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+  mStopDecodeThreads = PR_FALSE;
+  if (!mDecodeThread && mState < DECODER_STATE_COMPLETED) {
+    nsresult rv = NS_NewThread(getter_AddRefs(mDecodeThread));
+    if (NS_FAILED(rv)) {
+      mState = DECODER_STATE_SHUTDOWN;
+      return rv;
+    }
+    nsCOMPtr<nsIRunnable> event =
+      NS_NEW_RUNNABLE_METHOD(nsOggPlayStateMachine, this, DecodeLoop);
+    mDecodeThread->Dispatch(event, NS_DISPATCH_NORMAL);
+  }
+  if (HasAudio() && !mAudioThread) {
+    nsresult rv = NS_NewThread(getter_AddRefs(mAudioThread));
+    if (NS_FAILED(rv)) {
+      mState = DECODER_STATE_SHUTDOWN;
+      return rv;
+    }
+    nsCOMPtr<nsIRunnable> event =
+      NS_NEW_RUNNABLE_METHOD(nsOggPlayStateMachine, this, AudioLoop);
+    mAudioThread->Dispatch(event, NS_DISPATCH_NORMAL);
+  }
+  return NS_OK;
+}
+
+nsresult nsOggPlayStateMachine::Run()
+{
+  NS_ASSERTION(IsThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
+  nsMediaStream* stream = mDecoder->mStream;
+  NS_ENSURE_TRUE(stream, NS_ERROR_NULL_POINTER);
+
+  while (PR_TRUE) {
+    MonitorAutoEnter mon(mDecoder->GetMonitor());
+    switch (mState) {
+    case DECODER_STATE_SHUTDOWN:
+      if (IsPlaying()) {
+        StopPlayback(AUDIO_SHUTDOWN);
+      }
+      StopDecodeThreads();
+      NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
+                   "How did we escape from the shutdown state???");
+      return NS_OK;
+
+    case DECODER_STATE_DECODING_METADATA:
+      {
+        LoadOggHeaders();
+        if (mState == DECODER_STATE_SHUTDOWN) {
+          continue;
+        }
+
+        VideoData* videoData = FindStartTime();
+        if (videoData) {
+          MonitorAutoExit exitMon(mDecoder->GetMonitor());
+          RenderVideoFrame(videoData);
+        }
+
+        // Start the decode threads, so that we can pre buffer the streams.
+        // and calculate the start time in order to determine the duration.
+        if (NS_FAILED(StartDecodeThreads())) {
+          continue;
+        }
+
+        NS_ASSERTION(mStartTime != -1, "Must have start time");
+        NS_ASSERTION((!HasVideo() && !HasAudio()) ||
+                     !mSeekable || mEndTime != -1,
+                     "Active seekable media should have end time");
+        NS_ASSERTION(!mSeekable || GetDuration() != -1, "Seekable media should have duration");
+        LOG(PR_LOG_DEBUG, ("%p Media goes from %lldms to %lldms (duration %lldms) seekable=%d",
+                           mDecoder, mStartTime, mEndTime, GetDuration(), mSeekable));
+
+        if (mState == DECODER_STATE_SHUTDOWN)
+          continue;
+
+        // Inform the element that we've loaded the Ogg metadata and the
+        // first frame.
+        nsCOMPtr<nsIRunnable> metadataLoadedEvent =
+          NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, MetadataLoaded);
+        NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
+
+        if (mState == DECODER_STATE_DECODING_METADATA) {
+          LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder));
+          mState = DECODER_STATE_DECODING;
+        }
+
+        // Start playback.
+        if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
+          if (!IsPlaying()) {
+            StartPlayback();
+          }
+        }
+      }
+      break;
+
+    case DECODER_STATE_DECODING:
+      {
+        if (NS_FAILED(StartDecodeThreads())) {
+          continue;
+        }
+
+        AdvanceFrame();
+
+        if (mState != DECODER_STATE_DECODING)
+          continue;
+
+        if (mBufferExhausted &&
+            mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING &&
+            !mDecoder->mStream->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
+            !mDecoder->mStream->IsSuspendedByCache()) {
+          // There is at most one frame in the queue and there's
+          // more data to load. Let's buffer to make sure we can play a
+          // decent amount of video in the future.
+          if (IsPlaying()) {
+            StopPlayback(AUDIO_PAUSE);
+            mDecoder->GetMonitor().NotifyAll();
+          }
+
+          // We need to tell the element that buffering has started.
+          // We can't just directly send an asynchronous runnable that
+          // eventually fires the "waiting" event. The problem is that
+          // there might be pending main-thread events, such as "data
+          // received" notifications, that mean we're not actually still
+          // buffering by the time this runnable executes. So instead
+          // we just trigger UpdateReadyStateForData; when it runs, it
+          // will check the current state and decide whether to tell
+          // the element we're buffering or not.
+          UpdateReadyState();
+
+          mBufferingStart = TimeStamp::Now();
+          PRPackedBool reliable;
+          double playbackRate = mDecoder->ComputePlaybackRate(&reliable);
+          mBufferingEndOffset = mDecoder->mDecoderPosition +
+              BUFFERING_RATE(playbackRate) * BUFFERING_WAIT;
+          mState = DECODER_STATE_BUFFERING;
+          LOG(PR_LOG_DEBUG, ("Changed state from DECODING to BUFFERING"));
+        } else {
+          if (mBufferExhausted) {
+            // This will wake up the decode thread and force it to try to
+            // decode video and audio. This guarantees we make progress.
+            mBufferExhausted = PR_FALSE;
+            mDecoder->GetMonitor().NotifyAll();
+          }
+        }
+
+      }
+      break;
+
+    case DECODER_STATE_SEEKING:
+      {
+        // During the seek, don't have a lock on the decoder state,
+        // otherwise long seek operations can block the main thread.
+        // The events dispatched to the main thread are SYNC calls.
+        // These calls are made outside of the decode monitor lock so
+        // it is safe for the main thread to makes calls that acquire
+        // the lock since it won't deadlock. We check the state when
+        // acquiring the lock again in case shutdown has occurred
+        // during the time when we didn't have the lock.
+        PRInt64 seekTime = mSeekTime;
+        mDecoder->StopProgressUpdates();
+
+        StopPlayback(AUDIO_SHUTDOWN);
+        StopDecodeThreads();
+        ResetPlayback();
+
+        // SeekingStarted will do a UpdateReadyStateForData which will
+        // inform the element and its users that we have no frames
+        // to display
+        {
+          MonitorAutoExit exitMon(mDecoder->GetMonitor());
+          nsCOMPtr<nsIRunnable> startEvent =
+            NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStarted);
+          NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
+        }
+        if (mCurrentFrameTime != mSeekTime - mStartTime) {
+          nsresult res;
+          {
+            MonitorAutoExit exitMon(mDecoder->GetMonitor());
+            // Now perform the seek. We must not hold the state machine monitor
+            // while we seek, since the seek decodes.
+            res = mReader->Seek(seekTime, mStartTime, mEndTime);
+          }
+          if (NS_SUCCEEDED(res)){
+            SoundData* audio = HasAudio() ? mReader->mAudioQueue.PeekFront() : nsnull;
+            if (audio) {
+              mPlayDuration = TimeDuration::FromMilliseconds(audio->mTime);
+            }
+            if (HasVideo()) {
+              nsAutoPtr<VideoData> video(mReader->mVideoQueue.PeekFront());
+              if (video) {
+                RenderVideoFrame(video);
+                if (!audio) {
+                  NS_ASSERTION(video->mTime <= seekTime &&
+                               seekTime <= video->mTime + mInfo.mCallbackPeriod,
+                               "Seek target should lie inside the first frame after seek");
+                  mPlayDuration = TimeDuration::FromMilliseconds(seekTime);
+                }
+              }
+              mReader->mVideoQueue.PopFront();
+            }
+            UpdatePlaybackPosition(seekTime);
+          }
+        }
+        mDecoder->StartProgressUpdates();
+        if (mState == DECODER_STATE_SHUTDOWN)
+          continue;
+
+        // Try to decode another frame to detect if we're at the end...
+        LOG(PR_LOG_DEBUG, ("Seek completed, mCurrentFrameTime=%lld\n", mCurrentFrameTime));
+
+        // Change state to DECODING or COMPLETED now. SeekingStopped will
+        // call nsOggPlayStateMachine::Seek to reset our state to SEEKING
+        // if we need to seek again.
+        
+        nsCOMPtr<nsIRunnable> stopEvent;
+        if (mCurrentFrameTime == mEndTime) {
+          LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lldms) to COMPLETED",
+                             mDecoder, seekTime));
+          stopEvent = NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStoppedAtEnd);
+          mState = DECODER_STATE_COMPLETED;
+        } else {
+          LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lldms) to DECODING",
+                             mDecoder, seekTime));
+          stopEvent = NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStopped);
+          mState = DECODER_STATE_DECODING;
+        }
+        mBufferExhausted = PR_FALSE;
+        mDecoder->GetMonitor().NotifyAll();
+
+        {
+          MonitorAutoExit exitMon(mDecoder->GetMonitor());
+          NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
+        }
+      }
+      break;
+
+    case DECODER_STATE_BUFFERING:
+      {
+        TimeStamp now = TimeStamp::Now();
+        if (now - mBufferingStart < TimeDuration::FromSeconds(BUFFERING_WAIT) &&
+            mDecoder->mStream->GetCachedDataEnd(mDecoder->mDecoderPosition) < mBufferingEndOffset &&
+            !mDecoder->mStream->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
+            !mDecoder->mStream->IsSuspendedByCache()) {
+          LOG(PR_LOG_DEBUG,
+              ("In buffering: buffering data until %d bytes available or %f seconds",
+               PRUint32(mBufferingEndOffset - mDecoder->mStream->GetCachedDataEnd(mDecoder->mDecoderPosition)),
+               BUFFERING_WAIT - (now - mBufferingStart).ToSeconds()));
+          Wait(1000);
+          if (mState == DECODER_STATE_SHUTDOWN)
+            continue;
+        } else {
+          LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder));
+          LOG(PR_LOG_DEBUG, ("%p Buffered for %lf seconds",
+                             mDecoder,
+                             (TimeStamp::Now() - mBufferingStart).ToSeconds()));
+          mState = DECODER_STATE_DECODING;
+        }
+
+        if (mState != DECODER_STATE_BUFFERING) {
+          mBufferExhausted = PR_FALSE;
+          // Notify to allow blocked decoder thread to continue
+          mDecoder->GetMonitor().NotifyAll();
+          UpdateReadyState();
+          if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
+            if (!IsPlaying()) {
+              StartPlayback();
+            }
+          }
+        }
+
+        break;
+      }
+
+    case DECODER_STATE_COMPLETED:
+      {
+        if (NS_FAILED(StartDecodeThreads())) {
+          continue;
+        }
+
+        // Play the remaining media.
+        while (mState == DECODER_STATE_COMPLETED &&
+               (mReader->mVideoQueue.GetSize() > 0 ||
+                (HasAudio() && !mAudioCompleted)))
+        {
+          AdvanceFrame();
+        }
+
+        if (mAudioStream) {
+          // Close the audop stream so that next time audio is used a new stream
+          // is created. The StopPlayback call also resets the IsPlaying() state
+          // so audio is restarted correctly.
+          StopPlayback(AUDIO_SHUTDOWN);
+        }
+
+        if (mState != DECODER_STATE_COMPLETED)
+          continue;
+
+        LOG(PR_LOG_DEBUG, ("Shutting down the state machine thread"));
+        StopDecodeThreads();
+
+        if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
+          PRInt64 videoTime = HasVideo() ? (mVideoFrameTime + mInfo.mCallbackPeriod) : 0;
+          PRInt64 clockTime = NS_MAX(mEndTime, NS_MAX(videoTime, GetAudioClock()));
+          UpdatePlaybackPosition(clockTime);
+          {
+            MonitorAutoExit exitMon(mDecoder->GetMonitor());
+            nsCOMPtr<nsIRunnable> event =
+              NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackEnded);
+            NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
+          }
+        }
+
+        while (mState == DECODER_STATE_COMPLETED) {
+          mDecoder->GetMonitor().Wait();
+        }
+      }
+      break;
+    }
+  }
+
+  return NS_OK;
+}
+
+static void ToARGBHook(const PlanarYCbCrImage::Data& aData, PRUint8* aOutput)
+{
+  OggPlayYUVChannels yuv;
+  NS_ASSERTION(aData.mYStride == aData.mYSize.width,
+               "Stride not supported");
+  NS_ASSERTION(aData.mCbCrStride == aData.mCbCrSize.width,
+               "Stride not supported");
+  yuv.ptry = aData.mYChannel;
+  yuv.ptru = aData.mCbChannel;
+  yuv.ptrv = aData.mCrChannel;
+  yuv.uv_width = aData.mCbCrSize.width;
+  yuv.uv_height = aData.mCbCrSize.height;
+  yuv.y_width = aData.mYSize.width;
+  yuv.y_height = aData.mYSize.height;
+
+  OggPlayRGBChannels rgb;
+  rgb.ptro = aOutput;
+  rgb.rgb_width = aData.mYSize.width;
+  rgb.rgb_height = aData.mYSize.height;
+
+  oggplay_yuv2bgra(&yuv, &rgb);  
+}
+
+void nsOggPlayStateMachine::RenderVideoFrame(VideoData* aData)
+{
+  NS_ASSERTION(IsThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
+
+  if (aData->mDuplicate) {
+    return;
+  }
+
+  unsigned xSubsample = (aData->mBuffer[1].width != 0) ?
+    (aData->mBuffer[0].width / aData->mBuffer[1].width) : 0;
+
+  unsigned ySubsample = (aData->mBuffer[1].height != 0) ?
+    (aData->mBuffer[0].height / aData->mBuffer[1].height) : 0;
+
+  if (xSubsample == 0 || ySubsample == 0) {
+    // We can't perform yCbCr to RGB, so we can't render the frame...
+    return;
+  }
+  NS_ASSERTION(mInfo.mPicture.width != 0 && mInfo.mPicture.height != 0,
+               "We can only render non-zero-sized video");
+
+  unsigned cbCrStride = mInfo.mPicture.width / xSubsample;
+  unsigned cbCrHeight = mInfo.mPicture.height / ySubsample;
+
+  // Ensure the picture size specified in the headers can be extracted out of
+  // the frame we've been supplied without indexing out of bounds.
+  PRUint32 picXLimit;
+  PRUint32 picYLimit;
+  if (!AddOverflow(mInfo.mPicture.x, mInfo.mPicture.width, picXLimit) ||
+      picXLimit > PR_ABS(aData->mBuffer[0].stride) ||
+      !AddOverflow(mInfo.mPicture.y, mInfo.mPicture.height, picYLimit) ||
+      picYLimit > PR_ABS(aData->mBuffer[0].height))
+  {
+    // The specified picture dimensions can't be contained inside the video
+    // frame, we'll stomp memory if we try to copy it. Fail.
+    return;
+  }
+
+  unsigned cbCrSize = PR_ABS(aData->mBuffer[0].stride * aData->mBuffer[0].height) +
+                      PR_ABS(aData->mBuffer[1].stride * aData->mBuffer[1].height) * 2;
+  if (cbCrSize != mCbCrSize) {
+    mCbCrSize = cbCrSize;
+    mCbCrBuffer = static_cast<unsigned char*>(moz_xmalloc(cbCrSize));
+    if (!mCbCrBuffer) {
+      // Malloc failed...
+      NS_WARNING("Malloc failure allocating YCbCr->RGB buffer");
+      return;
+    }
+  }
+
+  unsigned char* data = mCbCrBuffer.get();
+
+  unsigned char* y = data;
+  unsigned char* cb = y + (mInfo.mPicture.width * PR_ABS(aData->mBuffer[0].height));
+  unsigned char* cr = cb + (cbCrStride * PR_ABS(aData->mBuffer[1].height));
+
+  unsigned char* p = y;
+  unsigned yStride = mInfo.mPicture.width;
+  unsigned char* q = aData->mBuffer[0].data + mInfo.mPicture.x +
+                     aData->mBuffer[0].stride * mInfo.mPicture.y;
+  for(unsigned i=0; i < mInfo.mPicture.height; ++i) {
+    NS_ASSERTION(q + mInfo.mPicture.width <
+                 aData->mBuffer[0].data + aData->mBuffer[0].stride * aData->mBuffer[0].height,
+                 "Y read must be in bounds");
+    NS_ASSERTION(p + mInfo.mPicture.width < data + mCbCrSize,
+                 "Memory copy 1 will stomp");
+    memcpy(p, q, mInfo.mPicture.width);
+    p += mInfo.mPicture.width;
+    q += aData->mBuffer[0].stride;
+  }
+
+  unsigned xo = xSubsample ? (mInfo.mPicture.x / xSubsample) : 0;
+  unsigned yo = ySubsample ? aData->mBuffer[1].stride * (mInfo.mPicture.y / ySubsample) : 0;
+
+  unsigned cbCrOffset = xo+yo;
+  p = cb;
+  q = aData->mBuffer[1].data + cbCrOffset;
+  unsigned char* p2 = cr;
+  unsigned char* q2 = aData->mBuffer[2].data + cbCrOffset;
+#ifdef DEBUG
+  unsigned char* buffer1Limit =
+    aData->mBuffer[1].data + aData->mBuffer[1].stride * aData->mBuffer[1].height;
+  unsigned char* buffer2Limit =
+    aData->mBuffer[2].data + aData->mBuffer[2].stride * aData->mBuffer[2].height;
+#endif
+  for(unsigned i=0; i < cbCrHeight; ++i) {
+    NS_ASSERTION(q + cbCrStride <= buffer1Limit,
+                 "Cb source read must be within bounds");
+    NS_ASSERTION(q2 + cbCrStride <= buffer2Limit,
+                 "Cr source read must be within bounds");
+    NS_ASSERTION(p + cbCrStride < data + mCbCrSize,
+                 "Cb write destination must be within bounds");
+    NS_ASSERTION(p2 + cbCrStride < data + mCbCrSize,
+                 "Cr write destination must be within bounds");
+    memcpy(p, q, cbCrStride);
+    memcpy(p2, q2, cbCrStride);
+    p += cbCrStride;
+    p2 += cbCrStride;
+    q += aData->mBuffer[1].stride;
+    q2 += aData->mBuffer[2].stride;
+  }
+
+  ImageContainer* container = mDecoder->GetImageContainer();
+  // Currently our Ogg decoder only knows how to output to PLANAR_YCBCR
+  // format.
+  Image::Format format = Image::PLANAR_YCBCR;
+  nsRefPtr<Image> image;
+  if (container) {
+    image = container->CreateImage(&format, 1);
+  }
+  if (image) {
+    NS_ASSERTION(image->GetFormat() == Image::PLANAR_YCBCR,
+                 "Wrong format?");
+    PlanarYCbCrImage* videoImage = static_cast<PlanarYCbCrImage*>(image.get());
+    // XXX this is only temporary until we get YCbCr code in the layer
+    // system.
+    videoImage->SetRGBConverter(ToARGBHook);
+    PlanarYCbCrImage::Data data;
+    data.mYChannel = y;
+    data.mYSize = gfxIntSize(mInfo.mPicture.width, mInfo.mPicture.height);
+    data.mYStride = mInfo.mPicture.width;
+    data.mCbChannel = cb;
+    data.mCrChannel = cr;
+    data.mCbCrSize = gfxIntSize(cbCrStride, cbCrHeight);
+    data.mCbCrStride = cbCrStride;
+    videoImage->SetData(data);
+    mDecoder->SetVideoData(data.mYSize, mInfo.mAspectRatio, image);
+  }
+}
+
+PRInt64
+nsOggPlayStateMachine::GetAudioClock()
+{
+  NS_ASSERTION(IsThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
+  if (!mAudioStream || !HasAudio())
+    return -1;
+  PRInt64 t = mAudioStream->GetPosition();
+  return (t == -1) ? -1 : t + mAudioStartTime;
+}
+
+void nsOggPlayStateMachine::AdvanceFrame()
+{
+  NS_ASSERTION(IsThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  // When it's time to display a frame, decode the frame and display it.
+  if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
+    if (!IsPlaying()) {
+      StartPlayback();
+      mDecoder->GetMonitor().NotifyAll();
+    }
+
+    if (HasAudio() && mAudioStartTime == -1 && !mAudioCompleted) {
+      // We've got audio (so we should sync off the audio clock), but we've
+      // not played a sample on the audio thread, so we can't get a time
+      // from the audio clock. Just wait and then return, to give the audio
+      // clock time to tick.
+      Wait(mInfo.mCallbackPeriod);
+      return;
+    }
+
+    // Determine the clock time. If we've got audio, and we've not reached
+    // the end of the audio, use the audio clock. However if we've finished
+    // audio, or don't have audio, use the system clock.
+    PRInt64 clock_time = -1;
+    PRInt64 audio_time = GetAudioClock();
+    if (HasAudio() && !mAudioCompleted && audio_time != -1) {
+      clock_time = audio_time;
+      // Resync against the audio clock, while we're trusting the
+      // audio clock. This ensures no "drift", particularly on Linux.
+      mPlayStartTime = TimeStamp::Now() - TimeDuration::FromMilliseconds(clock_time);
+    } else {
+      // Sound is disabled on this system. Sync to the system clock.
+      TimeDuration t = TimeStamp::Now() - mPlayStartTime + mPlayDuration;
+      clock_time = (PRInt64)(1000 * t.ToSeconds());
+      // Ensure the clock can never go backwards.
+      NS_ASSERTION(mCurrentFrameTime <= clock_time, "Clock should go forwards");
+      clock_time = NS_MAX(mCurrentFrameTime, clock_time) + mStartTime;
+    }
+
+    NS_ASSERTION(clock_time >= mStartTime, "Should have positive clock time.");
+    nsAutoPtr<VideoData> videoData;
+    if (mReader->mVideoQueue.GetSize() > 0) {
+      VideoData* data = mReader->mVideoQueue.PeekFront();
+      while (clock_time >= data->mTime) {
+        mVideoFrameTime = data->mTime;
+        videoData = data;
+        mReader->mVideoQueue.PopFront();
+        if (mReader->mVideoQueue.GetSize() == 0)
+          break;
+        data = mReader->mVideoQueue.PeekFront();
+      }
+    }
+
+    if (videoData) {
+      // Decode one frame and display it
+      NS_ASSERTION(videoData->mTime >= mStartTime, "Should have positive frame time");
+      {
+        MonitorAutoExit exitMon(mDecoder->GetMonitor());
+        // If we have video, we want to increment the clock in steps of the frame
+        // duration.
+        RenderVideoFrame(videoData);
+      }
+      mDecoder->GetMonitor().NotifyAll();
+      videoData = nsnull;
+    }
+
+    // Cap the current time to the larger of the audio and video end time.
+    // This ensures that if we're running off the system clock, we don't
+    // advance the clock to after the media end time.
+    if (mVideoFrameTime != -1 || mAudioEndTime != -1) {
+      // These will be non -1 if we've displayed a video frame, or played an audio sample.
+      clock_time = NS_MIN(clock_time, NS_MAX(mVideoFrameTime, mAudioEndTime));
+      if (clock_time - mStartTime > mCurrentFrameTime) {
+        // Only update the playback position if the clock time is greater
+        // than the previous playback position. The audio clock can
+        // sometimes report a time less than its previously reported in
+        // some situations, and we need to gracefully handle that.
+        UpdatePlaybackPosition(clock_time);
+      }
+    }
+
+    // If the number of audio/video samples queued has changed, either by
+    // this function popping and playing a video sample, or by the audio
+    // thread popping and playing an audio sample, we may need to update our
+    // ready state. Post an update to do so.
+    UpdateReadyState();
+
+    Wait(mInfo.mCallbackPeriod);
+  } else {
+    if (IsPlaying()) {
+      StopPlayback(AUDIO_PAUSE);
+      mDecoder->GetMonitor().NotifyAll();
+    }
+
+    if (mState == DECODER_STATE_DECODING ||
+        mState == DECODER_STATE_COMPLETED) {
+      mDecoder->GetMonitor().Wait();
+    }
+  }
+}
+
+void nsOggPlayStateMachine::Wait(PRUint32 aMs) {
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+  TimeStamp end = TimeStamp::Now() + TimeDuration::FromMilliseconds(aMs);
+  TimeStamp now;
+  while ((now = TimeStamp::Now()) < end &&
+         mState != DECODER_STATE_SHUTDOWN &&
+         mState != DECODER_STATE_SEEKING)
+  {
+    TimeDuration d = end - now;
+    PRInt64 ms = d.ToSeconds() * 1000;
+    if (ms == 0) {
+      break;
+    }
+    NS_ASSERTION(ms <= aMs && ms > 0,
+                 "nsOggPlayStateMachine::Wait interval very wrong!");
+    mDecoder->GetMonitor().Wait(PR_MillisecondsToInterval(ms));
+  }
+}
+
+void nsOggPlayStateMachine::LoadOggHeaders()
+{
+  NS_ASSERTION(IsThread(mDecoder->mStateMachineThread),
+               "Should be on state machine thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  LOG(PR_LOG_DEBUG, ("Loading Ogg Headers"));
+
+  nsMediaStream* stream = mDecoder->mStream;
+
+  nsOggInfo info;
+  {
+    MonitorAutoExit exitMon(mDecoder->GetMonitor());
+    mReader->ReadOggHeaders(info);
+  }
+  mInfo = info;
+  mDecoder->StartProgressUpdates();
+
+  if (!mInfo.mHasVideo && !mInfo.mHasAudio) {
+    mState = DECODER_STATE_SHUTDOWN;      
+    nsCOMPtr<nsIRunnable> event =
+      NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, DecodeError);
+    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+    return;
+  }
+
+  if (!mInfo.mHasVideo) {
+    mInfo.mCallbackPeriod = 1000 / AUDIO_FRAME_RATE;
+  }
+  LOG(PR_LOG_DEBUG, ("%p Callback Period: %u", mDecoder, mInfo.mCallbackPeriod));
+
+  // TODO: Get the duration from Skeleton index, if available.
+
+  // Get the duration from the Ogg file. We only do this if the
+  // content length of the resource is known as we need to seek
+  // to the end of the file to get the last time field. We also
+  // only do this if the resource is seekable and if we haven't
+  // already obtained the duration via an HTTP header.
+  mGotDurationFromHeader = (GetDuration() != -1);
+  if (mState != DECODER_STATE_SHUTDOWN &&
+      stream->GetLength() >= 0 &&
+      mSeekable &&
+      mEndTime == -1)
+  {
+    mDecoder->StopProgressUpdates();
+    FindEndTime();
+    mDecoder->StartProgressUpdates();
+    mDecoder->UpdatePlaybackRate();
+  }
+}
+
+VideoData* nsOggPlayStateMachine::FindStartTime()
+{
+  NS_ASSERTION(IsThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+  PRInt64 startTime = 0;
+  mStartTime = 0;
+  VideoData* v = nsnull;
+  {
+    MonitorAutoExit exitMon(mDecoder->GetMonitor());
+    v = mReader->FindStartTime(mInfo.mDataOffset, startTime);
+  }
+  if (startTime != 0) {
+    mStartTime = startTime;
+    if (mGotDurationFromHeader) {
+      NS_ASSERTION(mEndTime != -1,
+                   "We should have mEndTime as supplied duration here");
+      // We were specified a duration from a Content-Duration HTTP header.
+      // Adjust mEndTime so that mEndTime-mStartTime matches the specified
+      // duration.
+      mEndTime = mStartTime + mEndTime;
+    }
+  }
+  LOG(PR_LOG_DEBUG, ("%p Media start time is %lldms", mDecoder, mStartTime));
+  return v;
+}
+
+void nsOggPlayStateMachine::FindEndTime() 
+{
+  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  nsMediaStream* stream = mDecoder->mStream;
+
+  // Seek to the end of file to find the length and duration.
+  PRInt64 length = stream->GetLength();
+  NS_ASSERTION(length > 0, "Must have a content length to get end time");
+
+  mEndTime = 0;
+  PRInt64 endTime = 0;
+  {
+    MonitorAutoExit exitMon(mDecoder->GetMonitor());
+    endTime = mReader->FindEndTime(length);
+  }
+  if (endTime != -1) {
+    mEndTime = endTime;
+  }
+
+  NS_ASSERTION(mInfo.mDataOffset > 0,
+               "Should have offset of first non-header page");
+  {
+    MonitorAutoExit exitMon(mDecoder->GetMonitor());
+    stream->Seek(nsISeekableStream::NS_SEEK_SET, mInfo.mDataOffset);
+  }
+  LOG(PR_LOG_DEBUG, ("%p Media end time is %lldms", mDecoder, mEndTime));   
+}
+
+void nsOggPlayStateMachine::UpdateReadyState() {
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+  nsCOMPtr<nsIRunnable> event;
+  switch (GetNextFrameStatus()) {
+    case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING:
+      event = NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, NextFrameUnavailableBuffering);
+      break;
+    case nsHTMLMediaElement::NEXT_FRAME_AVAILABLE:
+      event = NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, NextFrameAvailable);
+      break;
+    case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE:
+      event = NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, NextFrameUnavailable);
+      break;
+    default:
+      PR_NOT_REACHED("unhandled frame state");
+  }
+
+  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+}
+
+
+static PRBool AddOverflow(PRUint32 a, PRUint32 b, PRUint32& aResult) {
+  PRUint64 rl = static_cast<PRUint64>(a) + static_cast<PRUint64>(b);
+  if (rl > PR_UINT32_MAX) {
+    return PR_FALSE;
+  }
+  aResult = static_cast<PRUint32>(rl);
+  return true;
+}
new file mode 100644
--- /dev/null
+++ b/content/media/ogg/nsOggPlayStateMachine.h
@@ -0,0 +1,419 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: ML 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#if !defined(nsOggPlayStateMachine_h__)
+#define nsOggPlayStateMachine_h__
+
+#include "prmem.h"
+#include "nsThreadUtils.h"
+#include "nsOggReader.h"
+#include "nsOggDecoder.h"
+#include "nsHTMLMediaElement.h"
+#include "mozilla/Monitor.h"
+
+using mozilla::TimeDuration;
+using mozilla::TimeStamp;
+
+class nsOggDecoder;
+
+// Checks if we're on a specific thread or not. Used in assertions to
+// verify thread safety.
+static inline PRBool IsThread(nsIThread* aThread) {
+  return NS_GetCurrentThread() == aThread;
+}
+
+/*
+  The playback state machine class. This manages the decoding in the
+  nsOggReader on the decode thread, seeking and in-sync-playback on the
+  state machine thread, and controls the audio "push" thread.
+
+  All internal state is synchronised via the decoder monitor. NotifyAll
+  on the monitor is called when the state of the state machine is changed
+  by the main thread. The following changes to state cause a notify:
+
+    mState and data related to that state changed (mSeekTime, etc)
+    Ogg Metadata Loaded
+    First Frame Loaded
+    Frame decoded
+    data pushed or popped from the video and audio queues
+
+  See nsOggDecoder.h for more details.
+*/
+class nsOggPlayStateMachine : public nsRunnable
+{
+public:
+  // Enumeration for the valid states
+  enum State {
+    DECODER_STATE_DECODING_METADATA,
+    DECODER_STATE_DECODING,
+    DECODER_STATE_SEEKING,
+    DECODER_STATE_BUFFERING,
+    DECODER_STATE_COMPLETED,
+    DECODER_STATE_SHUTDOWN
+  };
+
+  nsOggPlayStateMachine(nsOggDecoder* aDecoder);
+  ~nsOggPlayStateMachine();
+
+  // Initializes the state machine, returns NS_OK on success, or
+  // NS_ERROR_FAILURE on failure.
+  nsresult Init();
+
+  // Cause state transitions. These methods obtain the decoder monitor
+  // to synchronise the change of state, and to notify other threads
+  // that the state has changed.
+  void Shutdown();
+  void Decode();
+
+  // Seeks to aTime seconds.
+  void Seek(float aTime);
+
+  // State machine thread run function. Polls the state, sends frames to be
+  // displayed at appropriate times, and generally manages the decode.
+  NS_IMETHOD Run();
+
+  // This is called on the state machine thread and audio thread.
+  // The decoder monitor must be obtained before calling this.
+  PRBool HasAudio() const {
+    mDecoder->GetMonitor().AssertCurrentThreadIn();
+    return mInfo.mHasAudio;
+  }
+
+  // This is called on the state machine thread and audio thread.
+  // The decoder monitor must be obtained before calling this.
+  PRBool HasVideo() const {
+    mDecoder->GetMonitor().AssertCurrentThreadIn();
+    return mInfo.mHasVideo;
+  }
+
+  // Returns the current playback position in seconds.
+  // Called from the main thread to get the current frame time. The decoder
+  // monitor must be obtained before calling this.
+  float GetCurrentTime();
+
+  // Called from the main thread to get the duration. The decoder monitor
+  // must be obtained before calling this. It is in units of milliseconds.
+  PRInt64 GetDuration();
+
+  // Called from the main thread to set the duration of the media resource
+  // if it is able to be obtained via HTTP headers. The decoder monitor
+  // must be obtained before calling this.
+  void SetDuration(PRInt64 aDuration);
+
+  // Called from the main thread to set whether the media resource can
+  // be seeked. The decoder monitor must be obtained before calling this.
+  void SetSeekable(PRBool aSeekable);
+
+  // Set the audio volume. The decoder monitor must be obtained before
+  // calling this.
+  void SetVolume(float aVolume);
+
+  // Clear the flag indicating that a playback position change event
+  // is currently queued. This is called from the main thread and must
+  // be called with the decode monitor held.
+  void ClearPositionChangeFlag();
+
+  // Should be called by main thread.
+  PRBool HaveNextFrameData() const {
+    PRUint32 audioQueueSize = mReader->mAudioQueue.GetSize();
+    return (mReader->mVideoQueue.GetSize() > 0 &&
+            (!HasAudio() || audioQueueSize > 0)) ||
+           audioQueueSize > 0;
+  }
+
+  // Must be called with the decode monitor held.
+  PRBool IsBuffering() const {
+    mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+    return mState == nsOggPlayStateMachine::DECODER_STATE_BUFFERING;
+  }
+
+  // Must be called with the decode monitor held.
+  PRBool IsSeeking() const {
+    mDecoder->GetMonitor().AssertCurrentThreadIn();
+
+    return mState == nsOggPlayStateMachine::DECODER_STATE_SEEKING;
+  }
+
+  // Functions used by assertions to ensure we're calling things
+  // on the appropriate threads.
+  PRBool OnStateMachineThread() {
+    return IsThread(mDecoder->mStateMachineThread);
+  }
+
+  PRBool OnDecodeThread() {
+    return IsThread(mDecodeThread);
+  }
+
+  PRBool OnAudioThread() {
+    return IsThread(mAudioThread);
+  }
+
+  // Decode loop, called on the decode thread.
+  void DecodeLoop();
+
+  // The decoder object that created this state machine. The decoder
+  // always outlives us since it controls our lifetime. This is accessed
+  // read only on the AV, state machine, audio and main thread.
+  nsOggDecoder* mDecoder;
+
+  // Update the playback position. This can result in a timeupdate event
+  // and an invalidate of the frame being dispatched asynchronously if
+  // there is no such event currently queued.
+  // Only called on the decoder thread. Must be called with
+  // the decode monitor held.
+  void UpdatePlaybackPosition(PRInt64 aTime);
+
+  nsHTMLMediaElement::NextFrameStatus GetNextFrameStatus();
+
+  // The decoder monitor must be obtained before modifying this state.
+  // NotifyAll on the monitor must be called when the state is changed by
+  // the main thread so the decoder thread can wake up.
+  // Accessed on state machine, audio, main, and AV thread. 
+  State mState;
+
+private:
+
+  // Waits on the decoder Monitor for aMs. If the decoder monitor is awoken
+  // by a Notify() call, we'll continue waiting, unless we've moved into
+  // shutdown state. This enables us to ensure that we wait for a specified
+  // time, and that the myriad of Notify()s we do an the decoder monitor
+  // don't cause the audio thread to be starved. The decoder monitor must
+  // be locked.
+  void Wait(PRUint32 aMs);
+
+  // Dispatches an asynchronous event to update the media element's ready state.
+  void UpdateReadyState();
+
+  // Resets playback timing data. Called when we seek, on the state machine
+  // thread.
+  void ResetPlayback();
+
+  // Returns the audio clock, if we have audio, or -1 if we don't.
+  // Called on the state machine thread.
+  PRInt64 GetAudioClock();
+
+  // Returns the presentation time of the first sample or frame in the media.
+  // If the media has video, it returns the first video frame. The decoder
+  // monitor must be held with exactly one lock count. Called on the state
+  // machine thread.
+  VideoData* FindStartTime();
+
+  // Finds the end time of the last page in the Ogg file, storing the value
+  // in mEndTime if successful. The decoder must be held with exactly one lock
+  // count. Called on the state machine thread.
+  void FindEndTime();
+
+  // Performs YCbCr to RGB conversion, and pushes the image down the
+  // rendering pipeline. Called on the state machine thread.
+  void RenderVideoFrame(VideoData* aData);
+
+  // If we have video, display a video frame if it's time for display has
+  // arrived, otherwise sleep until it's time for the next sample. Update
+  // the current frame time as appropriate, and trigger ready state update.
+  // The decoder monitor must be held with exactly one lock count. Called
+  // on the state machine thread.
+  void AdvanceFrame();
+
+  // Stops the decode threads. The decoder monitor must be held with exactly
+  // one lock count. Called on the state machine thread.
+  void StopDecodeThreads();
+
+  // Starts the decode threads. The decoder monitor must be held with exactly
+  // one lock count. Called on the state machine thread.
+  nsresult StartDecodeThreads();
+
+  // Reads the Ogg headers using the nsOggReader, and initializes playback.
+  // Called on the state machine thread. The decoder monitor must be held with
+  // exactly one lock count.
+  void LoadOggHeaders();
+
+  // The main loop for the audio thread. Sent to the thread as
+  // an NS_NEW_RUNNABLE_METHOD. This continually does blocking writes to
+  // to audio stream to play audio data.
+  void AudioLoop();
+
+  // Stop or pause playback of media. This has two modes, denoted by
+  // aMode being either AUDIO_PAUSE or AUDIO_SHUTDOWN.
+  //
+  // AUDIO_PAUSE: Suspends the audio stream to be resumed later.
+  // This does not close the OS based audio stream 
+  //
+  // AUDIO_SHUTDOWN: Closes and destroys the audio stream and
+  // releases any OS resources.
+  //
+  // The decoder monitor must be held with exactly one lock count. Called
+  // on the state machine thread.
+  enum eStopMode {AUDIO_PAUSE, AUDIO_SHUTDOWN};
+  void StopPlayback(eStopMode aMode);
+
+  // Resume playback of media. Must be called with the decode monitor held.
+  // This resumes a paused audio stream. The decoder monitor must be held with
+  // exactly one lock count. Called on the state machine thread.
+  void StartPlayback();
+
+  // Returns PR_TRUE if we're currently playing. The decoder monitor must
+  // be held.
+  PRBool IsPlaying();
+
+  // Stores presentation info about required for playback of the media.
+  nsOggInfo mInfo;
+
+  // Monitor on mAudioStream. This monitor must be held in order to delete
+  // or use the audio stream. This stops us destroying the audio stream
+  // while it's being used on another thread (typically when it's being
+  // written to on the audio thread).
+  Monitor mAudioMonitor;
+
+  // The reader, don't call its methods with the decoder monitor held.
+  // This is created in the play state machine's constructor, and destroyed
+  // in the play state machine's destructor.
+  nsAutoPtr<nsOggReader> mReader;
+
+  // The size of the decoded YCbCr frame.
+  // Accessed on state machine thread.
+  PRUint32 mCbCrSize;
+
+  // Accessed on state machine thread.
+  nsAutoArrayPtr<unsigned char> mCbCrBuffer;
+
+  // Thread for pushing audio onto the audio hardware.
+  // The "audio push thread".
+  nsCOMPtr<nsIThread> mAudioThread;
+
+  // Thread for decoding video in background. The "decode thread".
+  nsCOMPtr<nsIThread> mDecodeThread;
+
+  // The time that playback started from the system clock. This is used
+  // for timing the display of audio frames when there's no audio.
+  // Accessed only via the state machine thread.
+  TimeStamp mPlayStartTime;
+
+  // The amount of time we've spent playing already the media. The current
+  // playback position is therefore (mPlayDuration + (now - mPlayStartTime)).
+  // Accessed only via the state machine thread.
+  TimeDuration mPlayDuration;
+
+  // Time that buffering started. Used for buffering timeout and only
+  // accessed on the state machine thread.
+  TimeStamp mBufferingStart;
+
+  // Download position where we should stop buffering. Only
+  // accessed on the state machine thread.
+  PRInt64 mBufferingEndOffset;
+
+  // Start time of the media, in milliseconds. This is the presentation
+  // time of the first sample decoded from the media, and is used to calculate
+  // duration and as a bounds for seeking. Accessed on state machine and
+  // main thread. Access controlled by decoder monitor.
+  PRInt64 mStartTime;
+
+  // Time of the last page in the media, in milliseconds. This is the
+  // end time of the last sample in the media. Accessed on state
+  // machine and main thread. Access controlled by decoder monitor.
+  PRInt64 mEndTime;
+
+  // Position to seek to in milliseconds when the seek state transition occurs.
+  // The decoder monitor lock must be obtained before reading or writing
+  // this value. Accessed on main and state machine thread.
+  PRInt64 mSeekTime;
+
+  // The audio stream resource. Used on the state machine, audio, and main
+  // threads. You must hold the mAudioMonitor, and must NOT hold the decoder
+  // monitor when using the audio stream!
+  nsAutoPtr<nsAudioStream> mAudioStream;
+
+  // The time of the current frame in milliseconds. This is referenced from
+  // 0 which is the initial playback position. Set by the state machine
+  // thread, and read-only from the main thread to get the current
+  // time value. Synchronised via decoder monitor.
+  PRInt64 mCurrentFrameTime;
+
+  // The presentation time of the first audio sample that was played. We can
+  // add this to the audio stream position to determine the current audio time.
+  // Accessed on audio and state machine thread. Synchronized by decoder monitor.
+  PRInt64 mAudioStartTime;
+
+  // The end time of the last audio sample that's been pushed onto the audio
+  // hardware. This will approximately be the end time of the audio stream,
+  // unless another sample is pushed to the hardware.
+  PRInt64 mAudioEndTime;
+
+  // The presentation time of the last video frame which has been displayed.
+  // Accessed from the state machine thread.
+  PRInt64 mVideoFrameTime;
+  
+  // Volume of playback. 0.0 = muted. 1.0 = full volume. Read/Written
+  // from the state machine and main threads. Synchronised via decoder
+  // monitor.
+  float mVolume;
+
+  // PR_TRUE if the media resource can be seeked. Accessed from the state
+  // machine and main threads. Synchronised via decoder monitor.
+  PRPackedBool mSeekable;
+
+  // PR_TRUE if an event to notify about a change in the playback
+  // position has been queued, but not yet run. It is set to PR_FALSE when
+  // the event is run. This allows coalescing of these events as they can be
+  // produced many times per second. Synchronised via decoder monitor.
+  // Accessed on main and state machine threads.
+  PRPackedBool mPositionChangeQueued;
+
+  // PR_TRUE if the audio playback thread has finished. It is finished
+  // when either all the audio samples in the Vorbis bitstream have completed
+  // playing, or we've moved into shutdown state, and the threads are to be
+  // destroyed. Written by the audio playback thread and read and written by
+  // the state machine thread. Synchronised via decoder monitor.
+  PRPackedBool mAudioCompleted;
+
+  // PR_TRUE if the decode thread has indicated that we need to buffer.
+  // Accessed by the decode thread and the state machine thread.
+  // Synchronised via the decoder monitor.
+  PRPackedBool mBufferExhausted;
+
+  // PR_TRUE if mDuration has a value obtained from an HTTP header.
+  // Accessed on the state machine thread.
+  PRPackedBool mGotDurationFromHeader;
+    
+  // PR_FALSE while decode threads should be running. Accessed on audio, 
+  // state machine and decode threads. Syncrhonised by decoder monitor.
+  PRPackedBool mStopDecodeThreads;
+};
+
+
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/ogg/nsOggReader.cpp
@@ -0,0 +1,1540 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: ML 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISeekableStream.h"
+#include "nsClassHashtable.h"
+#include "nsTArray.h"
+#include "nsOggDecoder.h"
+#include "nsOggReader.h"
+#include "nsOggCodecState.h"
+#include "nsOggPlayStateMachine.h"
+#include "mozilla/mozalloc.h"
+#include "nsOggHacks.h"
+
+using mozilla::MonitorAutoExit;
+
+// Un-comment to enable logging of seek bisections.
+//#define SEEK_LOGGING
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gOggDecoderLog;
+#define LOG(type, msg) PR_LOG(gOggDecoderLog, type, msg)
+#ifdef SEEK_LOGGING
+#define SEEK_LOG(type, msg) PR_LOG(gOggDecoderLog, type, msg)
+#else
+#define SEEK_LOG(type, msg)
+#endif
+#else
+#define LOG(type, msg)
+#define SEEK_LOG(type, msg)
+#endif
+
+// Chunk size to read when reading Ogg files. Average Ogg page length
+// is about 4300 bytes, so we read the file in chunks larger than that.
+static const int PAGE_STEP = 8192;
+
+// 32 bit integer multiplication with overflow checking. Returns PR_TRUE
+// if the multiplication was successful, or PR_FALSE if the operation resulted
+// in an integer overflow.
+PRBool MulOverflow32(PRUint32 a, PRUint32 b, PRUint32& aResult) {
+  PRUint64 a64 = a;
+  PRUint64 b64 = b;
+  PRUint64 r64 = a64 * b64;
+  if (r64 > PR_UINT32_MAX)
+    return PR_FALSE;
+  aResult = static_cast<PRUint32>(r64);
+  return PR_TRUE;
+}
+
+VideoData* VideoData::Create(PRInt64 aTime,
+                             th_ycbcr_buffer aBuffer,
+                             PRBool aKeyframe,
+                             PRInt64 aGranulepos)
+{
+  nsAutoPtr<VideoData> v(new VideoData(aTime, aKeyframe, aGranulepos));
+  for (PRUint32 i=0; i < 3; ++i) {
+    PRUint32 size = 0;
+    if (!MulOverflow32(PR_ABS(aBuffer[i].height),
+                       PR_ABS(aBuffer[i].stride),
+                       size))
+    {
+      // Invalid frame size. Skip this plane. The plane will have 0
+      // dimensions, thanks to our constructor.
+      continue;
+    }
+    unsigned char* p = static_cast<unsigned char*>(moz_xmalloc(size));
+    if (!p) {
+      NS_WARNING("Failed to allocate memory for video frame");
+      return nsnull;
+    }
+    v->mBuffer[i].data = p;
+    v->mBuffer[i].width = aBuffer[i].width;
+    v->mBuffer[i].height = aBuffer[i].height;
+    v->mBuffer[i].stride = aBuffer[i].stride;
+    memcpy(v->mBuffer[i].data, aBuffer[i].data, size);
+  }
+  return v.forget();
+}
+
+nsOggReader::nsOggReader(nsOggPlayStateMachine* aStateMachine)
+  : mMonitor("media.oggreader"),
+    mPlayer(aStateMachine),
+    mTheoraState(nsnull),
+    mVorbisState(nsnull),
+    mPageOffset(0),
+    mDataOffset(0),
+    mTheoraGranulepos(-1),
+    mVorbisGranulepos(-1),
+    mCallbackPeriod(0)
+{
+  MOZ_COUNT_CTOR(nsOggReader);
+}
+
+nsOggReader::~nsOggReader()
+{
+  ResetDecode();
+  ogg_sync_clear(&mOggState);
+  MOZ_COUNT_DTOR(nsOggReader);
+}
+
+nsresult nsOggReader::Init() {
+  PRBool init = mCodecStates.Init();
+  NS_ASSERTION(init, "Failed to initialize mCodecStates");
+  if (!init) {
+    return NS_ERROR_FAILURE;
+  }
+  int ret = ogg_sync_init(&mOggState);
+  NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
+  return NS_OK;
+}
+
+nsresult nsOggReader::ResetDecode()
+{
+  nsresult res = NS_OK;
+
+  // Clear the Theora/Vorbis granulepos capture status, so that the next
+  // decode calls recaptures the granulepos.
+  mTheoraGranulepos = -1;
+  mVorbisGranulepos = -1;
+
+  mVideoQueue.Reset();
+  mAudioQueue.Reset();
+
+  MonitorAutoEnter mon(mMonitor);
+
+  // Discard any previously buffered packets/pages.
+  ogg_sync_reset(&mOggState);
+  if (mVorbisState && NS_FAILED(mVorbisState->Reset())) {
+    res = NS_ERROR_FAILURE;
+  }
+  if (mTheoraState && NS_FAILED(mTheoraState->Reset())) {
+    res = NS_ERROR_FAILURE;
+  }
+
+  return res;
+}
+
+nsresult nsOggReader::DecodeVorbis(nsTArray<SoundData*>& aChunks,
+                                   ogg_packet* aPacket)
+{
+  // Successfully read a packet.
+  if (vorbis_synthesis(&mVorbisState->mBlock, aPacket) != 0) {
+    return NS_ERROR_FAILURE;
+  }
+  if (vorbis_synthesis_blockin(&mVorbisState->mDsp,
+                               &mVorbisState->mBlock) != 0)
+  {
+    return NS_ERROR_FAILURE;
+  }
+
+  float** pcm = 0;
+  PRUint32 samples = 0;
+  PRUint32 channels = mVorbisState->mInfo.channels;
+  while ((samples = vorbis_synthesis_pcmout(&mVorbisState->mDsp, &pcm)) > 0) {
+    if (samples > 0) {
+      float* buffer = new float[samples * channels];
+      float* p = buffer;
+      for (PRUint32 i = 0; i < samples; ++i) {
+        for (PRUint32 j = 0; j < channels; ++j) {
+          *p++ = pcm[j][i];
+        }
+      }
+
+      PRInt64 duration = mVorbisState->Time((PRInt64)samples);
+      PRInt64 startTime = (mVorbisGranulepos != -1) ?
+        mVorbisState->Time(mVorbisGranulepos) : -1;
+      SoundData* s = new SoundData(startTime,
+                                   duration,
+                                   samples,
+                                   buffer,
+                                   channels);
+      if (mVorbisGranulepos != -1) {
+        mVorbisGranulepos += samples;
+      }
+      aChunks.AppendElement(s);
+    }
+    if (vorbis_synthesis_read(&mVorbisState->mDsp, samples) != 0) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+  return NS_OK;
+}
+
+// Decode page, calculate timestamps.
+PRBool nsOggReader::DecodeAudioPage()
+{
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mPlayer->OnStateMachineThread() || mPlayer->OnDecodeThread(),
+               "Should be on playback or decode thread.");
+  NS_ASSERTION(mVorbisState!=0, "Need Vorbis state to decode audio");
+  ogg_packet packet;
+  packet.granulepos = -1;
+
+  PRBool endOfStream = PR_FALSE;
+
+  nsAutoTArray<SoundData*, 64> chunks;
+  if (mVorbisGranulepos == -1) {
+    // Not captured Vorbis granulepos, read up until we get a granulepos, and
+    // back propagate the granulepos.
+
+    // We buffer the packets' pcm samples until we reach a packet with a granulepos.
+    // This will be the last packet in a page. Then using that granulepos to 
+    // calculate the packet's end time, we calculate all the packets' start times by
+    // subtracting their durations.
+
+    // Ensure we've got Vorbis packets; read one more Vorbis page if necessary.
+    while (packet.granulepos <= 0 && !endOfStream) {
+      if (!ReadOggPacket(mVorbisState, &packet)) {
+        endOfStream = PR_TRUE;
+        break;
+      }
+      if (packet.e_o_s != 0) {
+        // This packet marks the logical end of the Vorbis bitstream. It may
+        // still contain sound samples, so we must still decode it.
+        endOfStream = PR_TRUE;
+      }
+
+      if (NS_FAILED(DecodeVorbis(chunks, &packet))) {
+        NS_WARNING("Failed to decode Vorbis packet");
+      }
+    }
+
+    if (packet.granulepos > 0) {
+      // Successfully read up to a non -1 granulepos.
+      // Calculate the timestamps of the sound samples.
+      PRInt64 granulepos = packet.granulepos; // Represents end time of last sample.
+      mVorbisGranulepos = packet.granulepos;
+      for (int i = chunks.Length() - 1; i >= 0; --i) {
+        SoundData* s = chunks[i];
+        PRInt64 startGranule = granulepos - s->mSamples;
+        s->mTime = mVorbisState->Time(startGranule);
+        granulepos = startGranule;
+      }
+    }
+  } else {
+    // We have already captured the granulepos. The next packet's granulepos
+    // is its number of samples, plus the previous granulepos.
+    if (!ReadOggPacket(mVorbisState, &packet)) {
+      endOfStream = PR_TRUE;
+    } else {
+      // Successfully read a packet from the file. Decode it.
+      endOfStream = packet.e_o_s != 0;
+
+      // Try to decode any packet we've read.
+      if (NS_FAILED(DecodeVorbis(chunks, &packet))) {
+        NS_WARNING("Failed to decode Vorbis packet");
+      }
+
+      if (packet.granulepos != -1 && packet.granulepos != mVorbisGranulepos) {
+        // If the packet's granulepos doesn't match our running sample total,
+        // it's likely the bitstream has been damaged somehow, or perhaps
+        // oggz-chopped. Just assume the packet's granulepos is correct...
+        mVorbisGranulepos = packet.granulepos;
+      }
+    }
+  }
+
+  // We've successfully decoded some sound chunks. Push them onto the audio
+  // queue.
+  for (PRUint32 i = 0; i < chunks.Length(); ++i) {
+    mAudioQueue.Push(chunks[i]);
+  }
+
+  if (endOfStream) {
+    // We've encountered an end of bitstream packet, or we've hit the end of
+    // file while trying to decode, so inform the audio queue that there'll
+    // be no more samples.
+    mAudioQueue.Finish();
+    return PR_FALSE;
+  }
+
+  return PR_TRUE;
+}
+
+// Returns 1 if the Theora info struct is decoding a media of Theora
+// verion (maj,min,sub) or later, otherwise returns 0.
+static int
+TheoraVersion(th_info* info,
+              unsigned char maj,
+              unsigned char min,
+              unsigned char sub)
+{
+  ogg_uint32_t ver = (maj << 16) + (min << 8) + sub;
+  ogg_uint32_t th_ver = (info->version_major << 16) +
+                        (info->version_minor << 8) +
+                        info->version_subminor;
+  return (th_ver >= ver) ? 1 : 0;
+}
+
+// Ensures that all the VideoData in aFrames array are stored in increasing
+// order by timestamp. Used in assertions in debug builds.
+static PRBool
+AllFrameTimesIncrease(nsTArray<VideoData*>& aFrames)
+{
+  PRInt64 prevTime = -1;
+  PRInt64 prevGranulepos = -1;
+  for (PRUint32 i = 0; i < aFrames.Length(); i++) {
+    VideoData* f = aFrames[i];
+    if (f->mTime < prevTime) {
+      return PR_FALSE;
+    }
+    prevTime = f->mTime;
+    prevGranulepos = f->mGranulepos;
+  }
+  return PR_TRUE;
+}
+
+static void Clear(nsTArray<VideoData*>& aFrames) {
+  for (PRUint32 i = 0; i < aFrames.Length(); ++i) {
+    delete aFrames[i];
+  }
+  aFrames.Clear();
+}
+
+nsresult nsOggReader::DecodeTheora(nsTArray<VideoData*>& aFrames,
+                                   ogg_packet* aPacket)
+{
+  int ret = th_decode_packetin(mTheoraState->mCtx, aPacket, 0);
+  if (ret != 0 && ret != TH_DUPFRAME) {
+    return NS_ERROR_FAILURE;
+  }
+  PRInt64 time = (aPacket->granulepos != -1)
+    ? mTheoraState->StartTime(aPacket->granulepos) : -1;
+  if (ret == TH_DUPFRAME) {
+    aFrames.AppendElement(VideoData::CreateDuplicate(time,
+                                                     aPacket->granulepos));
+  } else if (ret == 0) {
+    th_ycbcr_buffer buffer;
+    ret = th_decode_ycbcr_out(mTheoraState->mCtx, buffer);
+    NS_ASSERTION(ret == 0, "th_decode_ycbcr_out failed");
+    PRBool isKeyframe = th_packet_iskeyframe(aPacket) == 1;
+    VideoData *v = VideoData::Create(time,
+                                     buffer,
+                                     isKeyframe,
+                                     aPacket->granulepos);
+    if (!v) {
+      NS_WARNING("Failed to allocate memory for video frame");
+      Clear(aFrames);
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+    aFrames.AppendElement(v);
+  }
+  return NS_OK;
+}
+
+PRBool nsOggReader::DecodeVideoPage(PRBool &aKeyframeSkip,
+                                    PRInt64 aTimeThreshold)
+{
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mPlayer->OnStateMachineThread() || mPlayer->OnDecodeThread(),
+               "Should be on state machine or AV thread.");
+  // We chose to keep track of the Theora granulepos ourselves, rather than
+  // rely on th_decode_packetin() to do it for us. This is because
+  // th_decode_packetin() simply works by incrementing a counter every time
+  // it's called, so if we drop frames and don't call it, subsequent granulepos
+  // will be wrong. Whenever we read a packet which has a granulepos, we use
+  // its granulepos, otherwise we increment the previous packet's granulepos.
+
+  nsAutoTArray<VideoData*, 8> frames;
+  ogg_packet packet;
+  PRBool endOfStream = PR_FALSE;
+  if (mTheoraGranulepos == -1) {
+    // We've not read a Theora packet with a granulepos, so we don't know what
+    // timestamp to assign to Theora frames we decode. This will only happen
+    // the first time we read, or after a seek. We must read and buffer up to
+    // the first Theora packet with a granulepos, and back-propagate its 
+    // granulepos to calculate the buffered frames' granulepos.
+    do {
+      if (!ReadOggPacket(mTheoraState, &packet)) {
+        // Failed to read another page, must be the end of file. We can't have
+        // already encountered an end of bitstream packet, else we wouldn't be
+        // here, so this bitstream must be missing its end of stream packet, or
+        // is otherwise corrupt (oggz-chop can output files like this). Inform
+        // the queue that there will be no more frames.
+        mVideoQueue.Finish();
+        return PR_FALSE;
+      }
+
+      if (packet.granulepos > 0) {
+        // We've found a packet with a granulepos, we can now determine the
+        // buffered packet's timestamps, as well as the timestamps for any
+        // packets we read subsequently.
+        mTheoraGranulepos = packet.granulepos;
+      }
+    
+      if (DecodeTheora(frames, &packet) == NS_ERROR_OUT_OF_MEMORY) {
+        NS_WARNING("Theora decode memory allocation failure!");
+        return PR_FALSE;
+      }
+
+    } while (packet.granulepos <= 0 && !endOfStream);
+
+    if (packet.granulepos > 0) {
+      // We have captured a granulepos. Backpropagate the granulepos
+      // to determine buffered packets' timestamps.
+      PRInt64 succGranulepos = packet.granulepos;
+      int version_3_2_1 = TheoraVersion(&mTheoraState->mInfo,3,2,1);
+      int shift = mTheoraState->mInfo.keyframe_granule_shift;
+      for (int i = frames.Length() - 2; i >= 0; --i) {
+        PRInt64 granulepos = succGranulepos;
+        if (frames[i]->mKeyframe) {
+          // This frame is a keyframe. It's granulepos is the previous granule
+          // number minus 1, shifted by granuleshift.
+          ogg_int64_t frame_index = th_granule_frame(mTheoraState->mCtx,
+                                                     granulepos);
+          granulepos = (frame_index + version_3_2_1 - 1) << shift;
+          // Theora 3.2.1+ granulepos store frame number [1..N], so granulepos
+          // should be > 0.
+          // Theora 3.2.0 granulepos store the frame index [0..(N-1)], so
+          // granulepos should be >= 0. 
+          NS_ASSERTION((version_3_2_1 && granulepos > 0) ||
+                       granulepos >= 0, "Should have positive granulepos");
+        } else {
+          // Packet is not a keyframe. It's granulepos depends on its successor
+          // packet...
+          if (frames[i+1]->mKeyframe) {
+            // The successor frame is a keyframe, so we can't just subtract 1
+            // from the "keyframe offset" part of its granulepos, as it
+            // doesn't have one! So fake it, take the keyframe offset as the