Merge latest green changeset from mozilla-inbound to mozilla-central
authorMatt Brubeck <mbrubeck@mozilla.com>
Mon, 05 Dec 2011 10:04:12 -0800
changeset 83099 fafaf614791f698e9f66902560206e37065eee39
parent 83031 cb70391c86d9e69a76971518496dbc22214c7c5d (current diff)
parent 83098 2f33758829d2e432e18d7f280e94cb936ae458a1 (diff)
child 83112 338fae43b9d0d174fb58e7a086f9f8de565c420c
child 84614 fefb15b96b0fc5f1ff538e890e47b9cacba657bc
child 111679 220dd5cad2ac17e53112582e5b007737ff77d586
push id519
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 00:38:35 +0000
treeherdermozilla-beta@788ea1ef610b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone11.0a1
first release with
nightly linux32
fafaf614791f / 11.0a1 / 20111206031117 / files
nightly linux64
fafaf614791f / 11.0a1 / 20111206031117 / files
nightly mac
fafaf614791f / 11.0a1 / 20111206031117 / files
nightly win32
fafaf614791f / 11.0a1 / 20111206031117 / files
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
Merge latest green changeset from mozilla-inbound to mozilla-central
accessible/tests/mochitest/test_elm_landmarks.html
accessible/tests/mochitest/test_elm_listbox.xul
accessible/tests/mochitest/test_elm_nsApplicationAcc.html
accessible/tests/mochitest/test_elm_plugin.html
config/autoconf.mk.in
content/canvas/test/webgl/disable-quickCheckAPI.patch
--- a/accessible/public/nsIAccessibleRole.idl
+++ b/accessible/public/nsIAccessibleRole.idl
@@ -783,14 +783,19 @@ interface nsIAccessibleRole : nsISupport
 
   /**
    * A note. Originally intended to be hidden until activated, but now also used
    * for things like html 'aside'.
    */
   const unsigned long ROLE_NOTE = 123;
 
   /**
+   * A figure. Used for things like HTML5 figure element.
+   */
+  const unsigned long ROLE_FIGURE = 124;
+
+  /**
    * It's not role actually. This constant is important to help ensure
    * nsRoleMap's are synchronized.
    */
-  const unsigned long ROLE_LAST_ENTRY = 124;
+  const unsigned long ROLE_LAST_ENTRY = 125;
 };
 
--- a/accessible/src/atk/nsRoleMap.h
+++ b/accessible/src/atk/nsRoleMap.h
@@ -164,11 +164,12 @@ static const PRUint32 atkRoleMap[] = {
     ATK_ROLE_IMAGE,               // nsIAccessibleRole::ROLE_IMAGE_MAP            116
     ATK_ROLE_LIST_ITEM,           // nsIAccessibleRole::ROLE_OPTION               117
     ATK_ROLE_LIST_ITEM,           // nsIAccessibleRole::ROLE_RICH_OPTION          118
     ATK_ROLE_LIST,                // nsIAccessibleRole::ROLE_LISTBOX              119
     ATK_ROLE_UNKNOWN,             // nsIAccessibleRole::ROLE_FLAT_EQUATION        120
     ATK_ROLE_TABLE_CELL,          // nsIAccessibleRole::ROLE_GRID_CELL            121
     ATK_ROLE_PANEL,               // nsIAccessibleRole::ROLE_EMBEDDED_OBJECT      122
     ATK_ROLE_SECTION,             // nsIAccessibleRole::ROLE_NOTE                 123
+    ATK_ROLE_PANEL,               // nsIAccessibleRole::ROLE_FIGURE               124
     kROLE_ATK_LAST_ENTRY          // nsIAccessibleRole::ROLE_LAST_ENTRY
 };
 
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -1600,17 +1600,30 @@ nsAccessibilityService::CreateAccessible
 }
 
 already_AddRefed<nsAccessible>
 nsAccessibilityService::CreateHTMLAccessibleByMarkup(nsIFrame* aFrame,
                                                      nsIContent* aContent,
                                                      nsIWeakReference* aWeakShell)
 {
   // This method assumes we're in an HTML namespace.
-  nsIAtom *tag = aContent->Tag();
+  nsIAtom* tag = aContent->Tag();
+  if (tag == nsGkAtoms::figcaption) {
+    nsAccessible* accessible =
+      new nsHTMLFigcaptionAccessible(aContent, aWeakShell);
+    NS_IF_ADDREF(accessible);
+    return accessible;
+  }
+
+  if (tag == nsGkAtoms::figure) {
+    nsAccessible* accessible = new nsHTMLFigureAccessible(aContent, aWeakShell);
+    NS_IF_ADDREF(accessible);
+    return accessible;
+  }
+
   if (tag == nsGkAtoms::legend) {
     nsAccessible* accessible = new nsHTMLLegendAccessible(aContent, aWeakShell);
     NS_IF_ADDREF(accessible);
     return accessible;
   }
 
   if (tag == nsGkAtoms::option) {
     nsAccessible* accessible = new nsHTMLSelectOptionAccessible(aContent,
--- a/accessible/src/base/nsAccessibilityService.h
+++ b/accessible/src/base/nsAccessibilityService.h
@@ -419,17 +419,18 @@ static const char kRoleNames[][20] = {
   "combobox option",     //ROLE_COMBOBOX_OPTION
   "image map",           //ROLE_IMAGE_MAP
   "listbox option",      //ROLE_OPTION
   "listbox rich option", //ROLE_RICH_OPTION
   "listbox",             //ROLE_LISTBOX
   "flat equation",       //ROLE_FLAT_EQUATION
   "gridcell",            //ROLE_GRID_CELL
   "embedded object",     //ROLE_EMBEDDED_OBJECT
-  "note"                 //ROLE_NOTE
+  "note",                //ROLE_NOTE
+  "figure"               //ROLE_FIGURE
 };
 
 /**
  * Map nsIAccessibleEvents constants to strings. Used by
  * nsIAccessibleRetrieval::getStringEventType() method.
  */
 static const char kEventTypeNames[][40] = {
   "unknown",                                 //
--- a/accessible/src/html/nsHTMLFormControlAccessible.cpp
+++ b/accessible/src/html/nsHTMLFormControlAccessible.cpp
@@ -788,8 +788,111 @@ nsHTMLLegendAccessible::RelationByType(P
   return rel;
 }
 
 PRUint32
 nsHTMLLegendAccessible::NativeRole()
 {
   return nsIAccessibleRole::ROLE_LABEL;
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// nsHTMLFigureAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+nsHTMLFigureAccessible::
+  nsHTMLFigureAccessible(nsIContent* aContent, nsIWeakReference* aShell) :
+  nsHyperTextAccessibleWrap(aContent, aShell)
+{
+}
+
+nsresult
+nsHTMLFigureAccessible::GetAttributesInternal(nsIPersistentProperties* aAttributes)
+{
+  nsresult rv = nsHyperTextAccessibleWrap::GetAttributesInternal(aAttributes);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Expose figure xml-role.
+  nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+                         NS_LITERAL_STRING("figure"));
+  return NS_OK;
+}
+
+PRUint32
+nsHTMLFigureAccessible::NativeRole()
+{
+  return nsIAccessibleRole::ROLE_FIGURE;
+}
+
+nsresult
+nsHTMLFigureAccessible::GetNameInternal(nsAString& aName)
+{
+  nsresult rv = nsHyperTextAccessibleWrap::GetNameInternal(aName);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!aName.IsEmpty())
+    return NS_OK;
+
+  nsIContent* captionContent = Caption();
+  if (captionContent) {
+    return nsTextEquivUtils::
+      AppendTextEquivFromContent(this, captionContent, &aName);
+  }
+
+  return NS_OK;
+}
+
+Relation
+nsHTMLFigureAccessible::RelationByType(PRUint32 aType)
+{
+  Relation rel = nsHyperTextAccessibleWrap::RelationByType(aType);
+  if (aType == nsIAccessibleRelation::RELATION_LABELLED_BY)
+    rel.AppendTarget(Caption());
+
+  return rel;
+}
+
+nsIContent*
+nsHTMLFigureAccessible::Caption() const
+{
+  for (nsIContent* childContent = mContent->GetFirstChild(); childContent;
+       childContent = childContent->GetNextSibling()) {
+    if (childContent->NodeInfo()->Equals(nsGkAtoms::figcaption,
+                                         mContent->GetNameSpaceID())) {
+      return childContent;
+    }
+  }
+
+  return nsnull;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsHTMLFigcaptionAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+nsHTMLFigcaptionAccessible::
+  nsHTMLFigcaptionAccessible(nsIContent* aContent, nsIWeakReference* aShell) :
+  nsHyperTextAccessibleWrap(aContent, aShell)
+{
+}
+
+PRUint32
+nsHTMLFigcaptionAccessible::NativeRole()
+{
+  return nsIAccessibleRole::ROLE_CAPTION;
+}
+
+Relation
+nsHTMLFigcaptionAccessible::RelationByType(PRUint32 aType)
+{
+  Relation rel = nsHyperTextAccessibleWrap::RelationByType(aType);
+  if (aType != nsIAccessibleRelation::RELATION_LABEL_FOR)
+    return rel;
+
+  nsAccessible* figure = Parent();
+  if (figure &&
+      figure->GetContent()->NodeInfo()->Equals(nsGkAtoms::figure,
+                                               mContent->GetNameSpaceID())) {
+    rel.AppendTarget(figure);
+  }
+
+  return rel;
+}
--- a/accessible/src/html/nsHTMLFormControlAccessible.h
+++ b/accessible/src/html/nsHTMLFormControlAccessible.h
@@ -223,9 +223,41 @@ class nsHTMLLegendAccessible : public ns
 public:
   nsHTMLLegendAccessible(nsIContent *aContent, nsIWeakReference *aShell);
 
   // nsAccessible
   virtual PRUint32 NativeRole();
   virtual Relation RelationByType(PRUint32 aType);
 };
 
-#endif  
+/**
+ * Accessible for HTML5 figure element.
+ */
+class nsHTMLFigureAccessible : public nsHyperTextAccessibleWrap
+{
+public:
+  nsHTMLFigureAccessible(nsIContent* aContent, nsIWeakReference* aShell);
+
+  // nsAccessible
+  virtual nsresult GetAttributesInternal(nsIPersistentProperties* aAttributes);
+  virtual nsresult GetNameInternal(nsAString& aName);
+  virtual PRUint32 NativeRole();
+  virtual Relation RelationByType(PRUint32 aType);
+
+protected:
+  nsIContent* Caption() const;
+};
+
+
+/**
+ * Accessible for HTML5 figcaption element.
+ */
+class nsHTMLFigcaptionAccessible : public nsHyperTextAccessibleWrap
+{
+public:
+  nsHTMLFigcaptionAccessible(nsIContent* aContent, nsIWeakReference* aShell);
+
+  // nsAccessible
+  virtual PRUint32 NativeRole();
+  virtual Relation RelationByType(PRUint32 aType);
+};
+
+#endif
--- a/accessible/src/mac/nsRoleMap.h
+++ b/accessible/src/mac/nsRoleMap.h
@@ -161,10 +161,11 @@ static const NSString* AXRoles [] = {
   NSAccessibilityImageRole,                     // ROLE_IMAGE_MAP
   NSAccessibilityRowRole,                       // ROLE_OPTION
   NSAccessibilityRowRole,                       // ROLE_RICH_OPTION
   NSAccessibilityListRole,                      // ROLE_LISTBOX
   NSAccessibilityUnknownRole,                   // ROLE_FLAT_EQUATION
   NSAccessibilityGroupRole,                     // ROLE_GRID_CELL
   NSAccessibilityGroupRole,                     // ROLE_EMBEDDED_OBJECT
   NSAccessibilityGroupRole,                     // ROLE_NOTE
+  NSAccessibilityGroupRole,                     // ROLE_FIGURE
   @"ROLE_LAST_ENTRY"                            // ROLE_LAST_ENTRY. bogus role that will never be shown (just marks the end of this array)!
 };
--- a/accessible/src/msaa/nsRoleMap.h
+++ b/accessible/src/msaa/nsRoleMap.h
@@ -441,12 +441,15 @@ static const WindowsRoleMapItem gWindows
   { ROLE_SYSTEM_CELL, ROLE_SYSTEM_CELL },
 
   // nsIAccessibleRole::ROLE_EMBEDDED_OBJECT
   { USE_ROLE_STRING, IA2_ROLE_EMBEDDED_OBJECT },
 
   // nsIAccessibleRole::ROLE_NOTE
   { USE_ROLE_STRING, IA2_ROLE_NOTE },
 
+  // nsIAccessibleRole::ROLE_FIGURE
+  { ROLE_SYSTEM_GROUPING, ROLE_SYSTEM_GROUPING },
+
   // nsIAccessibleRole::ROLE_LAST_ENTRY
   { ROLE_WINDOWS_LAST_ENTRY, ROLE_WINDOWS_LAST_ENTRY }
 };
 
--- a/accessible/tests/mochitest/Makefile.in
+++ b/accessible/tests/mochitest/Makefile.in
@@ -41,16 +41,17 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = accessible
 
 DIRS	= \
   actions \
   attributes \
   editabletext \
+  elm \
   events \
   focus \
   hyperlink \
   hypertext \
   name \
   relations \
   selectable \
   states \
@@ -89,20 +90,16 @@ include $(topsrcdir)/config/rules.mk
 		test_aria_role_equation.html \
 		test_aria_roles.html \
 		test_aria_roles.xul \
 		test_aria_token_attrs.html \
 		test_bug420863.html \
 		test_childAtPoint.html \
 		test_childAtPoint.xul \
 		test_descr.html \
-		test_elm_landmarks.html \
-		test_elm_listbox.xul \
-		test_elm_nsApplicationAcc.html \
-		test_elm_plugin.html \
 		test_nsIAccessibleDocument.html \
 		test_nsIAccessibleImage.html \
 		test_nsIAccessNode_utils.html \
 		test_nsOuterDocAccessible.html \
 		test_role_nsHyperTextAcc.html \
 		test_text_caret.html \
 		test_textboxes.html \
 		test_textboxes.xul \
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/elm/Makefile.in
@@ -0,0 +1,57 @@
+#
+# ***** 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.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Alexander Surkov <surkov.alexander@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 *****
+
+DEPTH		= ../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir  = accessible/elm
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_TEST_FILES =\
+		test_figure.html \
+		test_landmarks.html \
+		test_listbox.xul \
+		test_nsApplicationAcc.html \
+		test_plugin.html \
+		$(NULL)
+
+libs:: $(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/a11y/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_figure.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>HTML5 figure/figcaption tests</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../role.js"></script>
+  <script type="application/javascript"
+          src="../attributes.js"></script>
+  <script type="application/javascript"
+          src="../relations.js"></script>
+  <script type="application/javascript"
+          src="../name.js"></script>
+
+  <script type="application/javascript">
+
+    function doTest()
+    {
+      testRole("figure", ROLE_FIGURE);
+      testRole("figcaption", ROLE_CAPTION);
+
+      todo(false, "figure name gets extra whitespace in the end!");
+      testName("figure", "figure caption ");
+      testName("figcaption", null);
+
+      testRelation("figure", RELATION_LABELLED_BY, "figcaption");
+      testRelation("figcaption", RELATION_LABEL_FOR, "figure");
+
+      testAttrs("figure", {"xml-roles" : "figure"}, true);
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+</head>
+<body>
+
+  <a target="_blank"
+    title="Implement figure and figcaption accessibility"
+    href="https://bugzilla.mozilla.org/show_bug.cgi?id=658272">
+     Mozilla Bug 658272
+  </a><br/>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <figure id="figure">
+    <figcaption id="figcaption">figure caption</figcaption>
+  </figure>
+
+</body>
+</html>
rename from accessible/tests/mochitest/test_elm_landmarks.html
rename to accessible/tests/mochitest/elm/test_landmarks.html
--- a/accessible/tests/mochitest/test_elm_landmarks.html
+++ b/accessible/tests/mochitest/elm/test_landmarks.html
@@ -4,21 +4,21 @@
   <title>HTML landmark tests</title>
   <link rel="stylesheet" type="text/css"
         href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
-          src="common.js"></script>
+          src="../common.js"></script>
   <script type="application/javascript"
-          src="role.js"></script>
+          src="../role.js"></script>
   <script type="application/javascript"
-          src="attributes.js"></script>
+          src="../attributes.js"></script>
 
   <script type="application/javascript">
 
     function doTest()
     {
       testRole("nav", ROLE_SECTION);
       testRole("header", ROLE_HEADER);
       testRole("footer", ROLE_FOOTER);
rename from accessible/tests/mochitest/test_elm_listbox.xul
rename to accessible/tests/mochitest/elm/test_listbox.xul
--- a/accessible/tests/mochitest/test_elm_listbox.xul
+++ b/accessible/tests/mochitest/elm/test_listbox.xul
@@ -5,19 +5,19 @@
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="XUL listbox element test.">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
 
   <script type="application/javascript"
-          src="common.js"></script>
+          src="../common.js"></script>
   <script type="application/javascript"
-          src="role.js"></script>
+          src="../role.js"></script>
 
   <script type="application/javascript">
   <![CDATA[
     function doTest()
     {
       var id = "";
       var listbox = null, acc = null;
 
rename from accessible/tests/mochitest/test_elm_nsApplicationAcc.html
rename to accessible/tests/mochitest/elm/test_nsApplicationAcc.html
--- a/accessible/tests/mochitest/test_elm_nsApplicationAcc.html
+++ b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html
@@ -3,19 +3,19 @@
 <head>
   <title>application accessible name</title>
   <link rel="stylesheet" type="text/css" 
          href="chrome://mochikit/content/tests/SimpleTest/test.css" />
   <script type="application/javascript" 
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript" 
-          src="common.js"></script>
+          src="../common.js"></script>
   <script type="application/javascript" 
-          src="role.js"></script>
+          src="../role.js"></script>
 
   <script type="application/javascript">
     function doTest()
     {
         var accessible = getApplicationAccessible();
         if (!accessible) {
           SimpleTest.finish();
           return;
rename from accessible/tests/mochitest/test_elm_plugin.html
rename to accessible/tests/mochitest/elm/test_plugin.html
--- a/accessible/tests/mochitest/test_elm_plugin.html
+++ b/accessible/tests/mochitest/elm/test_plugin.html
@@ -4,21 +4,21 @@
   <title>Plugin tests</title>
   <link rel="stylesheet" type="text/css"
         href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
-          src="common.js"></script>
+          src="../common.js"></script>
   <script type="application/javascript"
-          src="role.js"></script>
+          src="../role.js"></script>
   <script type="application/javascript"
-          src="states.js"></script>
+          src="../states.js"></script>
 
   <script type="application/javascript">
 
     function doTest()
     {
       if (!WIN) {
         ok(true,
            "Nothing to test because accessible plugins are supported on Windows only");
--- a/accessible/tests/mochitest/role.js
+++ b/accessible/tests/mochitest/role.js
@@ -15,16 +15,17 @@ const ROLE_CHROME_WINDOW = nsIAccessible
 const ROLE_COMBOBOX = nsIAccessibleRole.ROLE_COMBOBOX;
 const ROLE_COMBOBOX_LIST = nsIAccessibleRole.ROLE_COMBOBOX_LIST;
 const ROLE_COMBOBOX_OPTION = nsIAccessibleRole.ROLE_COMBOBOX_OPTION;
 const ROLE_COLUMNHEADER = nsIAccessibleRole.ROLE_COLUMNHEADER;
 const ROLE_DIALOG = nsIAccessibleRole.ROLE_DIALOG;
 const ROLE_DOCUMENT = nsIAccessibleRole.ROLE_DOCUMENT;
 const ROLE_EMBEDDED_OBJECT = nsIAccessibleRole.ROLE_EMBEDDED_OBJECT;
 const ROLE_ENTRY = nsIAccessibleRole.ROLE_ENTRY;
+const ROLE_FIGURE = nsIAccessibleRole.ROLE_FIGURE;
 const ROLE_FOOTER = nsIAccessibleRole.ROLE_FOOTER;
 const ROLE_FLAT_EQUATION = nsIAccessibleRole.ROLE_FLAT_EQUATION;
 const ROLE_FORM = nsIAccessibleRole.ROLE_FORM;
 const ROLE_GRAPHIC = nsIAccessibleRole.ROLE_GRAPHIC;
 const ROLE_GRID_CELL = nsIAccessibleRole.ROLE_GRID_CELL;
 const ROLE_GROUPING = nsIAccessibleRole.ROLE_GROUPING;
 const ROLE_HEADER = nsIAccessibleRole.ROLE_HEADER;
 const ROLE_HEADING = nsIAccessibleRole.ROLE_HEADING;
--- a/browser/components/preferences/languages.js
+++ b/browser/components/preferences/languages.js
@@ -204,28 +204,30 @@ var gLanguagesDialog = {
   {
     var selectedID = this._availableLanguages.selectedItem.id;
     var preference = document.getElementById("intl.accept_languages");
     var arrayOfPrefs = preference.value.toLowerCase().split(/\s*,\s*/);
     for (var i = 0; i < arrayOfPrefs.length; ++i ){
       if (arrayOfPrefs[i] == selectedID)
         return;
     }
-      
+
     this._selectedItemID = selectedID;
-    
+
     if (preference.value == "") 
       preference.value = selectedID;
-    else
-      preference.value += "," + selectedID;
+    else {
+      arrayOfPrefs.unshift(selectedID);
+      preference.value = arrayOfPrefs.join(",");
+    }
   
     this._acceptLanguages[selectedID] = true;
     this._availableLanguages.selectedItem = null;
     
-    // Reuild the available list with the added item removed...
+    // Rebuild the available list with the added item removed...
     this._buildAvailableLanguageList(); 
     
     this._availableLanguages.setAttribute("label", this._availableLanguages.getAttribute("label2"));
   },
   
   removeLanguage: function ()
   {
     // Build the new preference value string.
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -131,16 +131,20 @@
 @BINPATH@/components/content_xslt.xpt
 @BINPATH@/components/content_xtf.xpt
 @BINPATH@/components/cookie.xpt
 @BINPATH@/components/directory.xpt
 @BINPATH@/components/docshell.xpt
 @BINPATH@/components/dom.xpt
 @BINPATH@/components/dom_apps.xpt
 @BINPATH@/components/dom_base.xpt
+#ifdef MOZ_B2G_RIL
+@BINPATH@/components/dom_telephony.xpt
+@BINPATH@/components/dom_telephony_worker.xpt
+#endif
 @BINPATH@/components/dom_battery.xpt
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
@@ -353,16 +357,22 @@
 @BINPATH@/components/satchel.manifest
 @BINPATH@/components/nsFormAutoComplete.js
 @BINPATH@/components/nsFormHistory.js
 @BINPATH@/components/nsInputListAutoComplete.js
 @BINPATH@/components/contentSecurityPolicy.manifest
 @BINPATH@/components/contentSecurityPolicy.js
 @BINPATH@/components/contentAreaDropListener.manifest
 @BINPATH@/components/contentAreaDropListener.js
+#ifdef MOZ_B2G_RIL
+@BINPATH@/components/nsTelephonyWorker.manifest
+@BINPATH@/components/nsTelephonyWorker.js
+@BINPATH@/components/Telephony.manifest
+@BINPATH@/components/Telephony.js
+#endif
 @BINPATH@/components/BrowserProfileMigrators.manifest
 @BINPATH@/components/ChromeProfileMigrator.js
 #ifdef XP_MACOSX
 @BINPATH@/components/libalerts_s.dylib
 #endif
 #ifdef MOZ_ENABLE_DBUS
 @BINPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@
 #endif
--- a/browser/installer/removed-files.in
+++ b/browser/installer/removed-files.in
@@ -63,16 +63,20 @@ components/nsCloseAllWindows.js
 components/nsDictionary.js
 components/nsExtensionManager.js
 components/nsInterfaceInfoToIDL.js
 components/nsScriptableIO.js
 components/nsUrlClassifierTable.js
 components/nsXmlRpcClient.js
 components/pluginGlue.js
 components/sidebar.xpt
+#ifdef MOZ_B2G_RIL
+components/dom_telephony.xpt
+components/dom_telephony_worker.xpt
+#endif
 components/WeaveCrypto.js
 components/WeaveCrypto.manifest
 components/xmlextras.xpt
 components/xpcom.xpt
 components/xpti.dat
 components/xptitemp.dat
 components/nsMicrosummaryService.js
 D3DCompiler_42.dll
@@ -225,16 +229,17 @@ extensions/testpilot@labs.mozilla.com/sk
 extensions/testpilot@labs.mozilla.com/skin/win/notification-tail-up.png
 extensions/testpilot@labs.mozilla.com/tests/test_data_store.js
 greprefs/all.js
 greprefs/security-prefs.js
 greprefs/xpinstall.js
 install.rdf
 modules/ISO8601DateUtils.jsm
 modules/JSON.jsm
+modules/SpatialNavigation.js
 modules/utils.js
 mozilla-runtime@BIN_SUFFIX@
 old-homepage-default.properties
 README.txt
 res/arrow.gif
 res/arrowd.gif
 res/broken-image.gif
 res/broken-image.png
@@ -907,16 +912,22 @@ xpicleanup@BIN_SUFFIX@
   components/nsUpdateTimerManager.js
   components/nsUrlClassifierLib.js
   components/nsUrlClassifierListManager.js
   components/nsURLFormatter.js
   components/nsWebHandlerApp.js
   components/PlacesProtocolHandler.js
   components/storage-Legacy.js
   components/storage-mozStorage.js
+#ifdef MOZ_B2G_RIL
+  components/nsTelephonyWorker.manifest
+  components/nsTelephonyWorker.js
+  components/Telephony.manifest
+  components/Telephony.js
+#endif
   components/txEXSLTRegExFunctions.js
   components/Weave.js
   components/WebContentConverter.js
   defaults/autoconfig/platform.js
   defaults/autoconfig/prefcalls.js
   defaults/pref/firefox-branding.js
   defaults/pref/firefox.js
   defaults/pref/firefox-l10n.js
@@ -1124,16 +1135,20 @@ xpicleanup@BIN_SUFFIX@
   components/content_xtf.xpt
   components/contentprefs.xpt
   components/cookie.xpt
   components/crashreporter.xpt
   components/directory.xpt
   components/docshell.xpt
   components/dom.xpt
   components/dom_base.xpt
+#ifdef MOZ_B2G_RIL
+  components/dom_telephony.xpt
+  components/dom_telephony_worker.xpt
+#endif
   components/dom_canvas.xpt
   components/dom_core.xpt
   components/dom_css.xpt
   components/dom_events.xpt
   components/dom_geolocation.xpt
   components/dom_html.xpt
   components/dom_json.xpt
   components/dom_loadsave.xpt
@@ -1293,16 +1308,17 @@ xpicleanup@BIN_SUFFIX@
   updater.app/Contents/MacOS/updater.ini
 #endif
 #ifdef XP_UNIX
   #ifndef XP_MACOSX
     chrome/icons/default/default.xpm
     components/libimgicon.so
     dictionaries/PL.aff
     dictionaries/PL.dic
+    icons/document.png
     icons/mozicon16.xpm
     icons/mozicon50.xpm
     plugins/libnullplugin.so
     readme.txt
   #endif
 #endif
 #ifndef XP_WIN
   res/fonts/mathfontSymbol.properties
--- a/build/mobile/devicemanagerADB.py
+++ b/build/mobile/devicemanagerADB.py
@@ -1,23 +1,25 @@
 import subprocess
 from devicemanager import DeviceManager, DMError
 import re
 import os
 import sys
+import tempfile
 
 class DeviceManagerADB(DeviceManager):
 
   def __init__(self, host = None, port = 20701, retrylimit = 5, packageName = None):
     self.host = host
     self.port = port
     self.retrylimit = retrylimit
     self.retries = 0
     self._sock = None
     self.useRunAs = False
+    self.useZip = False
     self.packageName = None
     if packageName == None:
       if os.getenv('USER'):
         packageName = 'org.mozilla.fennec_' + os.getenv('USER')
       else:
         packageName = 'org.mozilla.fennec_'
     self.Init(packageName)
 
@@ -26,16 +28,20 @@ class DeviceManagerADB(DeviceManager):
     # successful initialization even if, for example, adb is not installed.
     try:
       self.verifyADB()
       self.verifyRunAs(packageName)
     except:
       self.useRunAs = False
       self.packageName = None
     try:
+      self.verifyZip()
+    except:
+      self.useZip = False
+    try:
       # a test to see if we have root privs
       files = self.listFiles("/data/data")
       if (len(files) == 1):
         if (files[0].find("Permission denied") != -1):
           print "NOT running as root"
           raise Exception("not running as root")
     except:
       try:
@@ -98,41 +104,51 @@ class DeviceManagerADB(DeviceManager):
   # push localDir from host to remoteDir on the device
   # external function
   # returns:
   #  success: remoteDir
   #  failure: None
   def pushDir(self, localDir, remoteDir):
     # adb "push" accepts a directory as an argument, but if the directory
     # contains symbolic links, the links are pushed, rather than the linked
-    # files; we push file-by-file to get around this limitation
+    # files; we either zip/unzip or push file-by-file to get around this 
+    # limitation
     try:
-      if (not self.dirExists(remoteDir)):
-        self.mkDirs(remoteDir+"/x")
-      for root, dirs, files in os.walk(localDir, followlinks='true'):
-        relRoot = os.path.relpath(root, localDir)
-        for file in files:
-          localFile = os.path.join(root, file)
-          remoteFile = remoteDir + "/"
-          if (relRoot!="."):
-            remoteFile = remoteFile + relRoot + "/"
-          remoteFile = remoteFile + file
-          self.pushFile(localFile, remoteFile)
-        for dir in dirs:
-          targetDir = remoteDir + "/"
-          if (relRoot!="."):
-            targetDir = targetDir + relRoot + "/"
-          targetDir = targetDir + dir
-          if (not self.dirExists(targetDir)):
-            self.mkDir(targetDir)
+      if (self.useZip):
+        localZip = tempfile.mktemp()+".zip"
+        remoteZip = remoteDir + "/adbdmtmp.zip"
+        subprocess.check_output(["zip", "-r", localZip, '.'], cwd=localDir)
+        self.pushFile(localZip, remoteZip)
+        os.remove(localZip)
+        self.checkCmdAs(["shell", "unzip", "-o", remoteZip, "-d", remoteDir])
+        self.checkCmdAs(["shell", "rm", remoteZip])
+      else:
+        if (not self.dirExists(remoteDir)):
+          self.mkDirs(remoteDir+"/x")
+        for root, dirs, files in os.walk(localDir, followlinks='true'):
+          relRoot = os.path.relpath(root, localDir)
+          for file in files:
+            localFile = os.path.join(root, file)
+            remoteFile = remoteDir + "/"
+            if (relRoot!="."):
+              remoteFile = remoteFile + relRoot + "/"
+            remoteFile = remoteFile + file
+            self.pushFile(localFile, remoteFile)
+          for dir in dirs:
+            targetDir = remoteDir + "/"
+            if (relRoot!="."):
+              targetDir = targetDir + relRoot + "/"
+            targetDir = targetDir + dir
+            if (not self.dirExists(targetDir)):
+              self.mkDir(targetDir)
       self.checkCmdAs(["shell", "chmod", "777", remoteDir])
-      return True
+      return remoteDir
     except:
       print "pushing " + localDir + " to " + remoteDir + " failed"
-      return False
+      return None
 
   # external function
   # returns:
   #  success: True
   #  failure: False
   def dirExists(self, dirname):
     return self.isDir(dirname)
 
@@ -236,21 +252,35 @@ class DeviceManagerADB(DeviceManager):
   # external function
   # returns:
   #  success: output filename
   #  failure: None
   def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
     acmd = ["shell", "am","start"]
     cmd = ' '.join(cmd).strip()
     i = cmd.find(" ")
+    # SUT identifies the URL by looking for :\\ -- another strategy to consider
+    re_url = re.compile('^[http|file|chrome|about].*')
+    last = cmd.rfind(" ")
+    uri = ""
+    args = ""
+    if re_url.match(cmd[last:].strip()):
+      args = cmd[i:last].strip()
+      uri = cmd[last:].strip()
+    else:
+      args = cmd[i:].strip()
     acmd.append("-n")
     acmd.append(cmd[0:i] + "/.App")
     acmd.append("--es")
-    acmd.append("args")
-    acmd.append(cmd[i:])
+    if args != "":
+      acmd.append("args")
+      acmd.append(args)
+    if uri != "":
+      acmd.append("-d")
+      acmd.append(''.join(['\'',uri, '\'']));
     print acmd
     self.checkCmd(acmd)
     return outputFile;
 
   # external function
   # returns:
   #  success: output from testagent
   #  failure: None
@@ -573,8 +603,30 @@ class DeviceManagerADB(DeviceManager):
       self.checkCmd(["shell", "run-as", packageName, "cp", self.tmpDir + "/tmpfile", devroot + "/sanity"])
       if (self.fileExists(devroot + "/sanity/tmpfile")):
         print "will execute commands via run-as " + packageName
         self.packageName = packageName
         self.useRunAs = True
       self.checkCmd(["shell", "rm", devroot + "/tmp/tmpfile"])
       self.checkCmd(["shell", "run-as", packageName, "rm", "-r", devroot + "/sanity"])
       
+  def isUnzipAvailable(self):
+    data = self.runCmd(["shell", "unzip"]).stdout.read()
+    if (re.search('Usage', data)):
+      return True
+    else:
+      return False
+
+  def isLocalZipAvailable(self):
+    try:
+      subprocess.check_call(["zip", "-?"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    except:
+      return False
+    return True
+
+  def verifyZip(self):
+    # If "zip" can be run locally, and "unzip" can be run remotely, then pushDir
+    # can use these to push just one file per directory -- a significant
+    # optimization for large directories.
+    self.useZip = False
+    if (self.isUnzipAvailable() and self.isLocalZipAvailable()):
+      print "will use zip to push directories"
+      self.useZip = True
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -283,16 +283,18 @@ MOZ_ENABLE_GNOME_COMPONENT = @MOZ_ENABLE
 
 MOZ_ENABLE_GIO = @MOZ_ENABLE_GIO@
 MOZ_GIO_CFLAGS = @MOZ_GIO_CFLAGS@
 MOZ_GIO_LIBS = @MOZ_GIO_LIBS@
 
 MOZ_NATIVE_NSPR = @MOZ_NATIVE_NSPR@
 MOZ_NATIVE_NSS = @MOZ_NATIVE_NSS@
 
+MOZ_B2G_RIL = @MOZ_B2G_RIL@
+
 BUILD_CTYPES = @BUILD_CTYPES@
 
 COMPILE_ENVIRONMENT = @COMPILE_ENVIRONMENT@
 CROSS_COMPILE   = @CROSS_COMPILE@
 
 WCHAR_CFLAGS	= @WCHAR_CFLAGS@
 
 OS_CPPFLAGS	= @CPPFLAGS@
--- a/configure.in
+++ b/configure.in
@@ -4976,16 +4976,17 @@ cairo-android)
 
 cairo-gonk)
     AC_DEFINE(MOZ_WIDGET_GONK)
     MOZ_WIDGET_TOOLKIT=gonk
     TK_CFLAGS='$(MOZ_CAIRO_CFLAGS)'
     TK_LIBS='$(MOZ_CAIRO_LIBS)'
     MOZ_WEBGL=1
     MOZ_PDF_PRINTING=1
+    MOZ_B2G_RIL=1
     ;;
 
 esac
 
 AC_SUBST(MOZ_PDF_PRINTING)
 if test "$MOZ_PDF_PRINTING"; then
    PDF_SURFACE_FEATURE="#define CAIRO_HAS_PDF_SURFACE 1"
    AC_DEFINE(MOZ_PDF_PRINTING)
@@ -7569,16 +7570,28 @@ dnl ====================================
 dnl = Support for Quantify (Windows)
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(quantify,
 [  --enable-quantify       Enable Quantify support (Windows only) ],
     MOZ_QUANTIFY=1,
     MOZ_QUANTIFY= )
 
 dnl ========================================================
+dnl = Enable Radio Interface for B2G (Gonk usually)
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(b2g-ril,
+[  --enable-b2g-ril      Set compile flags necessary for testing B2G Radio Interface Layer via network sockets ],
+    MOZ_B2G_RIL=1,
+    MOZ_B2G_RIL= )
+if test -n "$MOZ_B2G_RIL"; then
+   AC_DEFINE(MOZ_B2G_RIL)
+fi
+AC_SUBST(MOZ_B2G_RIL)
+
+dnl ========================================================
 dnl = Support for demangling undefined symbols
 dnl ========================================================
 if test -z "$SKIP_LIBRARY_CHECKS"; then
     AC_LANG_SAVE
     AC_LANG_CPLUSPLUS
     AC_CHECK_FUNCS(__cxa_demangle, HAVE_DEMANGLE=1, HAVE_DEMANGLE=)
     AC_LANG_RESTORE
 fi
new file mode 100644
--- /dev/null
+++ b/content/base/crashtests/700512-worker.js
@@ -0,0 +1,7 @@
+onmessage = function(event) {
+  var blob = event.data;
+
+  blob.mozSlice(1, 5);
+
+  postMessage("done");
+}
new file mode 100644
--- /dev/null
+++ b/content/base/crashtests/700512.html
@@ -0,0 +1,16 @@
+<html class="reftest-wait">
+  <script type="text/javascript">
+    var worker = new Worker("700512-worker.js");
+
+    var bb = new MozBlobBuilder();
+
+    bb.append("foo");
+    bb.append("bar");
+
+    worker.onmessage = function() {
+      document.documentElement.removeAttribute("class");
+    }
+
+    worker.postMessage(bb.getBlob());
+  </script>
+</html>
--- a/content/base/crashtests/crashtests.list
+++ b/content/base/crashtests/crashtests.list
@@ -95,9 +95,10 @@ load 658845-1.svg
 load 667336-1.html
 load 679459.html
 load 679689-1.html
 load 682463.html
 load 693212.xhtml
 load 698974-1.html
 load 700090-1.html
 load 700090-2.html
+load 700512.html
 load xhr_html_nullresponse.html
--- a/content/base/public/nsIFrameLoader.idl
+++ b/content/base/public/nsIFrameLoader.idl
@@ -136,17 +136,17 @@ interface nsIContentViewManager : nsISup
                          [retval, array, size_is(aLength)] out nsIContentView aResult);
 
   /**
    * The root content view.
    */
   readonly attribute nsIContentView rootContentView;
 };
 
-[scriptable, uuid(12905a29-4246-475a-81d4-fc389197df02)]
+[scriptable, uuid(efc0b731-45dc-4189-8ffa-d3eeeb850977)]
 interface nsIFrameLoader : nsISupports
 {
   /**
    * Get the docshell from the frame loader.
    */
   readonly attribute nsIDocShell docShell;
 
   /**
@@ -253,16 +253,23 @@ interface nsIFrameLoader : nsISupports
 
   /**
    * With this event mode, it's the application's responsability to 
    * convert and forward events to the content process
    */
   const unsigned long EVENT_MODE_DONT_FORWARD_TO_CHILD = 0x00000001;
 
   attribute unsigned long eventMode;
+
+  /**
+   * If false, then the subdocument is not clipped to its CSS viewport, and the
+   * subdocument's viewport scrollbar(s) are not rendered.
+   * Defaults to true.
+   */
+  attribute boolean clipSubdocument;
 };
 
 native alreadyAddRefed_nsFrameLoader(already_AddRefed<nsFrameLoader>);
 
 [scriptable, uuid(5879040e-83e9-40e3-b2bb-5ddf43b76e47)]
 interface nsIFrameLoaderOwner : nsISupports
 {
   /**
--- a/content/base/src/nsFrameLoader.cpp
+++ b/content/base/src/nsFrameLoader.cpp
@@ -138,24 +138,22 @@ public:
     if (base_win) {
       base_win->Destroy();
     }
     return NS_OK;
   }
   nsRefPtr<nsIDocShell> mDocShell;
 };
 
-static void InvalidateFrame(nsIFrame* aFrame)
+static void InvalidateFrame(nsIFrame* aFrame, PRUint32 aFlags)
 {
+  if (!aFrame)
+    return;
   nsRect rect = nsRect(nsPoint(0, 0), aFrame->GetRect().Size());
-  // NB: we pass INVALIDATE_NO_THEBES_LAYERS here to keep view
-  // semantics the same for both in-process and out-of-process
-  // <browser>.  This is just a transform of the layer subtree in
-  // both.
-  aFrame->InvalidateWithFlags(rect, nsIFrame::INVALIDATE_NO_THEBES_LAYERS);
+  aFrame->InvalidateWithFlags(rect, aFlags);
 }
 
 NS_IMPL_ISUPPORTS1(nsContentView, nsIContentView)
 
 bool
 nsContentView::IsRoot() const
 {
   return mScrollId == FrameMetrics::ROOT_SCROLL_ID;
@@ -184,18 +182,21 @@ nsContentView::Update(const ViewConfig& 
   }
 
   if (RenderFrameParent* rfp = mFrameLoader->GetCurrentRemoteFrame()) {
     rfp->ContentViewScaleChanged(this);
   }
 
   // XXX could be clever here and compute a smaller invalidation
   // rect
-  nsIFrame* frame = mFrameLoader->GetPrimaryFrameOfOwningContent();
-  InvalidateFrame(frame);
+  // NB: we pass INVALIDATE_NO_THEBES_LAYERS here to keep view
+  // semantics the same for both in-process and out-of-process
+  // <browser>.  This is just a transform of the layer subtree in
+  // both.
+  InvalidateFrame(mFrameLoader->GetPrimaryFrameOfOwningContent(), nsIFrame::INVALIDATE_NO_THEBES_LAYERS);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsContentView::ScrollTo(float aXpx, float aYpx)
 {
   ViewConfig config(mConfig);
   config.mScrollOffset = nsPoint(nsPresContext::CSSPixelsToAppUnits(aXpx),
@@ -323,16 +324,17 @@ nsFrameLoader::nsFrameLoader(Element* aO
   , mNeedsAsyncDestroy(false)
   , mInSwap(false)
   , mInShow(false)
   , mHideCalled(false)
   , mNetworkCreated(aNetworkCreated)
   , mDelayRemoteDialogs(false)
   , mRemoteBrowserShown(false)
   , mRemoteFrame(false)
+  , mClipSubdocument(true)
   , mCurrentRemoteFrame(nsnull)
   , mRemoteBrowser(nsnull)
   , mRenderMode(RENDER_MODE_DEFAULT)
   , mEventMode(EVENT_MODE_NORMAL_DISPATCH)
 {
 }
 
 nsFrameLoader*
@@ -1688,17 +1690,21 @@ nsFrameLoader::GetRenderMode(PRUint32* a
 NS_IMETHODIMP
 nsFrameLoader::SetRenderMode(PRUint32 aRenderMode)
 {
   if (aRenderMode == mRenderMode) {
     return NS_OK;
   }
 
   mRenderMode = aRenderMode;
-  InvalidateFrame(GetPrimaryFrameOfOwningContent());
+  // NB: we pass INVALIDATE_NO_THEBES_LAYERS here to keep view
+  // semantics the same for both in-process and out-of-process
+  // <browser>.  This is just a transform of the layer subtree in
+  // both.
+  InvalidateFrame(GetPrimaryFrameOfOwningContent(), nsIFrame::INVALIDATE_NO_THEBES_LAYERS);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::GetEventMode(PRUint32* aEventMode)
 {
   *aEventMode = mEventMode;
   return NS_OK;
@@ -1706,16 +1712,48 @@ nsFrameLoader::GetEventMode(PRUint32* aE
 
 NS_IMETHODIMP
 nsFrameLoader::SetEventMode(PRUint32 aEventMode)
 {
   mEventMode = aEventMode;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsFrameLoader::GetClipSubdocument(bool* aResult)
+{
+  *aResult = mClipSubdocument;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::SetClipSubdocument(bool aClip)
+{
+  mClipSubdocument = aClip;
+  nsIFrame* frame = GetPrimaryFrameOfOwningContent();
+  if (frame) {
+    InvalidateFrame(frame, 0);
+    frame->PresContext()->PresShell()->
+      FrameNeedsReflow(frame, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+    nsSubDocumentFrame* subdocFrame = do_QueryFrame(frame);
+    if (subdocFrame) {
+      nsIFrame* subdocRootFrame = subdocFrame->GetSubdocumentRootFrame();
+      if (subdocRootFrame) {
+        nsIFrame* subdocRootScrollFrame = subdocRootFrame->PresContext()->PresShell()->
+          GetRootScrollFrame();
+        if (subdocRootScrollFrame) {
+          frame->PresContext()->PresShell()->
+            FrameNeedsReflow(frame, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+        }
+      }
+    }
+  }
+  return NS_OK;
+}
+
 nsIntSize
 nsFrameLoader::GetSubDocumentSize(const nsIFrame *aIFrame)
 {
   nsSize docSizeAppUnits;
   nsPresContext* presContext = aIFrame->PresContext();
   nsCOMPtr<nsIDOMHTMLFrameElement> frameElem = 
     do_QueryInterface(aIFrame->GetContent());
   if (frameElem) {
--- a/content/base/src/nsFrameLoader.h
+++ b/content/base/src/nsFrameLoader.h
@@ -282,16 +282,18 @@ public:
   {
     mCurrentRemoteFrame = aFrame;
   }
   nsFrameMessageManager* GetFrameMessageManager() { return mMessageManager; }
 
   mozilla::dom::Element* GetOwnerContent() { return mOwnerContent; }
   void SetOwnerContent(mozilla::dom::Element* aContent);
 
+  bool ShouldClipSubdocument() { return mClipSubdocument; }
+
 private:
 
   bool ShouldUseRemoteProcess();
 
   /**
    * If we are an IPC frame, set mRemoteFrame. Otherwise, create and
    * initialize mDocShell.
    */
@@ -333,17 +335,19 @@ private:
   bool mHideCalled : 1;
   // True when the object is created for an element which the parser has
   // created using NS_FROM_PARSER_NETWORK flag. If the element is modified,
   // it may lose the flag.
   bool mNetworkCreated : 1;
 
   bool mDelayRemoteDialogs : 1;
   bool mRemoteBrowserShown : 1;
-  bool mRemoteFrame;
+  bool mRemoteFrame : 1;
+  bool mClipSubdocument : 1;
+
   // XXX leaking
   nsCOMPtr<nsIObserver> mChildHost;
   RenderFrameParent* mCurrentRemoteFrame;
   TabParent* mRemoteBrowser;
 
   // See nsIFrameLoader.idl.  Short story, if !(mRenderMode &
   // RENDER_MODE_ASYNC_SCROLL), all the fields below are ignored in
   // favor of what content tells.
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -231,23 +231,16 @@ WebGLContext::WebGLContext()
     mActiveTexture = 0;
     mWebGLError = LOCAL_GL_NO_ERROR;
     mPixelStoreFlipY = false;
     mPixelStorePremultiplyAlpha = false;
     mPixelStoreColorspaceConversion = BROWSER_DEFAULT_WEBGL;
 
     mShaderValidation = true;
 
-    mMapBuffers.Init();
-    mMapTextures.Init();
-    mMapPrograms.Init();
-    mMapShaders.Init();
-    mMapFramebuffers.Init();
-    mMapRenderbuffers.Init();
-
     mBlackTexturesAreInitialized = false;
     mFakeBlackStatus = DoNotNeedFakeBlack;
 
     mVertexAttrib0Vector[0] = 0;
     mVertexAttrib0Vector[1] = 0;
     mVertexAttrib0Vector[2] = 0;
     mVertexAttrib0Vector[3] = 1;
     mFakeVertexAttrib0BufferObjectVector[0] = 0;
@@ -309,107 +302,48 @@ WebGLContext::WebGLContext()
 WebGLContext::~WebGLContext()
 {
     DestroyResourcesAndContext();
     WebGLMemoryReporter::RemoveWebGLContext(this);
     TerminateRobustnessTimer();
     mContextRestorer = nsnull;
 }
 
-static PLDHashOperator
-DeleteTextureFunction(const PRUint32& aKey, WebGLTexture *aValue, void *aData)
-{
-    gl::GLContext *gl = (gl::GLContext *) aData;
-    NS_ASSERTION(!aValue->Deleted(), "Texture is still in mMapTextures, but is deleted?");
-    GLuint name = aValue->GLName();
-    gl->fDeleteTextures(1, &name);
-    aValue->Delete();
-    return PL_DHASH_NEXT;
-}
-
-static PLDHashOperator
-DeleteBufferFunction(const PRUint32& aKey, WebGLBuffer *aValue, void *aData)
-{
-    gl::GLContext *gl = (gl::GLContext *) aData;
-    NS_ASSERTION(!aValue->Deleted(), "Buffer is still in mMapBuffers, but is deleted?");
-    GLuint name = aValue->GLName();
-    gl->fDeleteBuffers(1, &name);
-    aValue->Delete();
-    return PL_DHASH_NEXT;
-}
-
-static PLDHashOperator
-DeleteFramebufferFunction(const PRUint32& aKey, WebGLFramebuffer *aValue, void *aData)
-{
-    gl::GLContext *gl = (gl::GLContext *) aData;
-    NS_ASSERTION(!aValue->Deleted(), "Framebuffer is still in mMapFramebuffers, but is deleted?");
-    GLuint name = aValue->GLName();
-    gl->fDeleteFramebuffers(1, &name);
-    aValue->Delete();
-    return PL_DHASH_NEXT;
-}
-
-static PLDHashOperator
-DeleteRenderbufferFunction(const PRUint32& aKey, WebGLRenderbuffer *aValue, void *aData)
-{
-    gl::GLContext *gl = (gl::GLContext *) aData;
-    NS_ASSERTION(!aValue->Deleted(), "Renderbuffer is still in mMapRenderbuffers, but is deleted?");
-    GLuint name = aValue->GLName();
-    gl->fDeleteRenderbuffers(1, &name);
-    aValue->Delete();
-    return PL_DHASH_NEXT;
-}
-
-static PLDHashOperator
-DeleteProgramFunction(const PRUint32& aKey, WebGLProgram *aValue, void *aData)
-{
-    gl::GLContext *gl = (gl::GLContext *) aData;
-    NS_ASSERTION(!aValue->Deleted(), "Program is still in mMapPrograms, but is deleted?");
-    GLuint name = aValue->GLName();
-    gl->fDeleteProgram(name);
-    aValue->Delete();
-    return PL_DHASH_NEXT;
-}
-
-static PLDHashOperator
-DeleteShaderFunction(const PRUint32& aKey, WebGLShader *aValue, void *aData)
-{
-    gl::GLContext *gl = (gl::GLContext *) aData;
-    NS_ASSERTION(!aValue->Deleted(), "Shader is still in mMapShaders, but is deleted?");
-    GLuint name = aValue->GLName();
-    gl->fDeleteShader(name);
-    aValue->Delete();
-    return PL_DHASH_NEXT;
-}
-
 void
 WebGLContext::DestroyResourcesAndContext()
 {
     if (!gl)
         return;
 
     gl->MakeCurrent();
 
-    mMapTextures.EnumerateRead(DeleteTextureFunction, gl);
-    mMapTextures.Clear();
+    mBound2DTextures.Clear();
+    mBoundCubeMapTextures.Clear();
+    mBoundArrayBuffer = nsnull;
+    mBoundElementArrayBuffer = nsnull;
+    mCurrentProgram = nsnull;
+    mBoundFramebuffer = nsnull;
+    mBoundRenderbuffer = nsnull;
 
-    mMapBuffers.EnumerateRead(DeleteBufferFunction, gl);
-    mMapBuffers.Clear();
-
-    mMapPrograms.EnumerateRead(DeleteProgramFunction, gl);
-    mMapPrograms.Clear();
+    mAttribBuffers.Clear();
 
-    mMapShaders.EnumerateRead(DeleteShaderFunction, gl);
-    mMapShaders.Clear();
-
-    mMapFramebuffers.EnumerateRead(DeleteFramebufferFunction, gl);
-    mMapFramebuffers.Clear();
-
-    mMapRenderbuffers.EnumerateRead(DeleteRenderbufferFunction, gl);
-    mMapRenderbuffers.Clear();
+    while (mTextures.Length())
+        mTextures.Last()->DeleteOnce();
+    while (mBuffers.Length())
+        mBuffers.Last()->DeleteOnce();
+    while (mRenderbuffers.Length())
+        mRenderbuffers.Last()->DeleteOnce();
+    while (mFramebuffers.Length())
+        mFramebuffers.Last()->DeleteOnce();
+    while (mShaders.Length())
+        mShaders.Last()->DeleteOnce();
+    while (mPrograms.Length())
+        mPrograms.Last()->DeleteOnce();
+    while (mUniformLocations.Length())
+        mUniformLocations.Last()->DeleteOnce();
 
     if (mBlackTexturesAreInitialized) {
         gl->fDeleteTextures(1, &mBlackTexture2D);
         gl->fDeleteTextures(1, &mBlackTextureCubeMap);
         mBlackTexturesAreInitialized = false;
     }
 
     if (mFakeVertexAttrib0BufferObject) {
@@ -1265,101 +1199,93 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(WebGLBuffer)
 NS_IMPL_RELEASE(WebGLBuffer)
 
 DOMCI_DATA(WebGLBuffer, WebGLBuffer)
 
 NS_INTERFACE_MAP_BEGIN(WebGLBuffer)
-  NS_INTERFACE_MAP_ENTRY(WebGLBuffer)
   NS_INTERFACE_MAP_ENTRY(nsIWebGLBuffer)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(WebGLBuffer)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(WebGLTexture)
 NS_IMPL_RELEASE(WebGLTexture)
 
 DOMCI_DATA(WebGLTexture, WebGLTexture)
 
 NS_INTERFACE_MAP_BEGIN(WebGLTexture)
-  NS_INTERFACE_MAP_ENTRY(WebGLTexture)
   NS_INTERFACE_MAP_ENTRY(nsIWebGLTexture)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(WebGLTexture)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(WebGLProgram)
 NS_IMPL_RELEASE(WebGLProgram)
 
 DOMCI_DATA(WebGLProgram, WebGLProgram)
 
 NS_INTERFACE_MAP_BEGIN(WebGLProgram)
-  NS_INTERFACE_MAP_ENTRY(WebGLProgram)
   NS_INTERFACE_MAP_ENTRY(nsIWebGLProgram)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(WebGLProgram)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(WebGLShader)
 NS_IMPL_RELEASE(WebGLShader)
 
 DOMCI_DATA(WebGLShader, WebGLShader)
 
 NS_INTERFACE_MAP_BEGIN(WebGLShader)
-  NS_INTERFACE_MAP_ENTRY(WebGLShader)
   NS_INTERFACE_MAP_ENTRY(nsIWebGLShader)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(WebGLShader)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(WebGLFramebuffer)
 NS_IMPL_RELEASE(WebGLFramebuffer)
 
 DOMCI_DATA(WebGLFramebuffer, WebGLFramebuffer)
 
 NS_INTERFACE_MAP_BEGIN(WebGLFramebuffer)
-  NS_INTERFACE_MAP_ENTRY(WebGLFramebuffer)
   NS_INTERFACE_MAP_ENTRY(nsIWebGLFramebuffer)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(WebGLFramebuffer)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(WebGLRenderbuffer)
 NS_IMPL_RELEASE(WebGLRenderbuffer)
 
 DOMCI_DATA(WebGLRenderbuffer, WebGLRenderbuffer)
 
 NS_INTERFACE_MAP_BEGIN(WebGLRenderbuffer)
-  NS_INTERFACE_MAP_ENTRY(WebGLRenderbuffer)
   NS_INTERFACE_MAP_ENTRY(nsIWebGLRenderbuffer)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(WebGLRenderbuffer)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(WebGLUniformLocation)
 NS_IMPL_RELEASE(WebGLUniformLocation)
 
 DOMCI_DATA(WebGLUniformLocation, WebGLUniformLocation)
 
 NS_INTERFACE_MAP_BEGIN(WebGLUniformLocation)
-  NS_INTERFACE_MAP_ENTRY(WebGLUniformLocation)
   NS_INTERFACE_MAP_ENTRY(nsIWebGLUniformLocation)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(WebGLUniformLocation)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(WebGLActiveInfo)
 NS_IMPL_RELEASE(WebGLActiveInfo)
 
 DOMCI_DATA(WebGLActiveInfo, WebGLActiveInfo)
 
 NS_INTERFACE_MAP_BEGIN(WebGLActiveInfo)
-  NS_INTERFACE_MAP_ENTRY(WebGLActiveInfo)
   NS_INTERFACE_MAP_ENTRY(nsIWebGLActiveInfo)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(WebGLActiveInfo)
 NS_INTERFACE_MAP_END
 
 #define NAME_NOT_SUPPORTED(base) \
 NS_IMETHODIMP base::GetName(WebGLuint *aName) \
 { return NS_ERROR_NOT_IMPLEMENTED; } \
@@ -1374,30 +1300,27 @@ NAME_NOT_SUPPORTED(WebGLFramebuffer)
 NAME_NOT_SUPPORTED(WebGLRenderbuffer)
 
 NS_IMPL_ADDREF(WebGLExtension)
 NS_IMPL_RELEASE(WebGLExtension)
 
 DOMCI_DATA(WebGLExtension, WebGLExtension)
 
 NS_INTERFACE_MAP_BEGIN(WebGLExtension)
-  NS_INTERFACE_MAP_ENTRY(WebGLExtension)
   NS_INTERFACE_MAP_ENTRY(nsIWebGLExtension)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(WebGLExtension)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(WebGLExtensionStandardDerivatives)
 NS_IMPL_RELEASE(WebGLExtensionStandardDerivatives)
 
 DOMCI_DATA(WebGLExtensionStandardDerivatives, WebGLExtensionStandardDerivatives)
 
 NS_INTERFACE_MAP_BEGIN(WebGLExtensionStandardDerivatives)
-  //NS_INTERFACE_MAP_ENTRY(WebGLExtensionStandardDerivatives)
-  //NS_INTERFACE_MAP_ENTRY(WebGLExtension)
   NS_INTERFACE_MAP_ENTRY(nsIWebGLExtensionStandardDerivatives)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, WebGLExtension)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(WebGLExtensionStandardDerivatives)
 NS_INTERFACE_MAP_END_INHERITING(WebGLExtension)
 
 NS_IMPL_ADDREF(WebGLExtensionLoseContext)
 NS_IMPL_RELEASE(WebGLExtensionLoseContext)
 
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -90,18 +90,18 @@ namespace mozilla {
 class WebGLTexture;
 class WebGLBuffer;
 class WebGLProgram;
 class WebGLShader;
 class WebGLFramebuffer;
 class WebGLRenderbuffer;
 class WebGLUniformLocation;
 class WebGLExtension;
+class WebGLVertexAttribData;
 
-template<int PreallocatedOwnersCapacity> class WebGLZeroingObject;
 class WebGLContextBoundObject;
 
 enum FakeBlackStatus { DoNotNeedFakeBlack, DoNeedFakeBlack, DontKnowIfNeedFakeBlack };
 
 struct VertexAttrib0Status {
     enum { Default, EmulatedUninitializedArray, EmulatedInitializedArray };
 };
 
@@ -120,196 +120,324 @@ struct WebGLTexelPremultiplicationOp {
 
 int GetWebGLTexelFormat(GLenum format, GLenum type);
 
 inline bool is_pot_assuming_nonnegative(WebGLsizei x)
 {
     return (x & (x-1)) == 0;
 }
 
-class WebGLObjectBaseRefPtr
+/* Each WebGL object class WebGLFoo wants to:
+ *  - inherit WebGLRefCountedObject<WebGLFoo>
+ *  - implement a Delete() method
+ *  - have its destructor call DeleteOnce()
+ * 
+ * This base class provides two features to WebGL object types:
+ * 1. support for OpenGL object reference counting
+ * 2. support for OpenGL deletion statuses
+ *
+ ***** 1. OpenGL object reference counting *****
+ *
+ * WebGL objects such as WebGLTexture's really have two different refcounts:
+ * the XPCOM refcount, that is directly exposed to JavaScript, and the OpenGL
+ * refcount.
+ *
+ * For example, when in JavaScript one does: var newname = existingTexture;
+ * that increments the XPCOM refcount, but doesn't affect the OpenGL refcount.
+ * When one attaches the texture to a framebuffer object, that does increment
+ * its OpenGL refcount (and also its XPCOM refcount, to prevent the regular
+ * XPCOM refcounting mechanism from destroying objects prematurely).
+ *
+ * The actual OpenGL refcount is opaque to us (it's internal to the OpenGL
+ * implementation) but is affects the WebGL semantics that we have to implement:
+ * for example, a WebGLTexture that is attached to a WebGLFramebuffer must not
+ * be actually deleted, even if deleteTexture has been called on it, and even
+ * if JavaScript doesn't have references to it anymore. We can't just rely on
+ * OpenGL to keep alive the underlying OpenGL texture for us, for a variety of
+ * reasons, most importantly: we'd need to know when OpenGL objects are actually
+ * deleted, and OpenGL doesn't notify us about that, so we would have to query
+ * status very often with glIsXxx calls which isn't practical.
+ *
+ * This means that we have to keep track of the OpenGL refcount ourselves,
+ * in addition to the XPCOM refcount.
+ *
+ * This class implements such a refcount, see the mWebGLRefCnt
+ * member. In order to avoid name clashes (with regular XPCOM refcounting)
+ * in the derived class, we prefix members with 'WebGL', whence the names
+ * WebGLAddRef, WebGLRelease, etc.
+ *
+ * In practice, WebGLAddRef and WebGLRelease are only called from the
+ * WebGLRefPtr class.
+ *
+ ***** 2. OpenGL deletion statuses *****
+ *
+ * In OpenGL, an object can go through 3 different deletion statuses during its
+ * lifetime, which correspond to the 3 enum values for DeletionStatus in this class:
+ *  - the Default status, which it has from its creation to when the
+ *    suitable glDeleteXxx function is called on it;
+ *  - the DeleteRequested status, which is has from when the suitable glDeleteXxx
+ *    function is called on it to when it is no longer referenced by other OpenGL
+ *    objects. For example, a texture that is attached to a non-current FBO
+ *    will enter that status when glDeleteTexture is called on it. For objects
+ *    with that status, GL_DELETE_STATUS queries return true, but glIsXxx
+ *    functions still return true.
+ *  - the Deleted status, which is the status of objects on which the
+ *    suitable glDeleteXxx function has been called, and that are not referenced
+ *    by other OpenGL objects.
+ *
+ * This state is stored in the mDeletionStatus member of this class.
+ *
+ * When the GL refcount hits zero, if the status is DeleteRequested then we call
+ * the Delete() method on the derived class and the status becomes Deleted. This is
+ * what the MaybeDelete() function does.
+ * 
+ * The DeleteOnce() function implemented here is a helper to ensure that we don't
+ * call Delete() twice on the same object. Since the derived class' destructor
+ * needs to call DeleteOnce() which calls Delete(), we can't allow either to be
+ * virtual. Strictly speaking, we could let them be virtual if the derived class
+ * were final, but that would be impossible to enforce and would lead to strange
+ * bugs if it were subclassed.
+ *
+ * This WebGLRefCountedObject class takes the Derived type
+ * as template parameter, as a means to allow DeleteOnce to call Delete()
+ * on the Derived class, without either method being virtual. This is a common
+ * C++ pattern known as the "curiously recursive template pattern (CRTP)".
+ */
+template<typename Derived>
+class WebGLRefCountedObject
 {
-protected:
-    template<int PreallocatedOwnersCapacity>
-    friend class WebGLZeroingObject;
+public:
+    enum DeletionStatus { Default, DeleteRequested, Deleted };
 
-    WebGLObjectBaseRefPtr()
-        : mRawPtr(0)
-    {
+    WebGLRefCountedObject()
+      : mDeletionStatus(Default)
+    { }
+
+    ~WebGLRefCountedObject() {
+        NS_ABORT_IF_FALSE(mWebGLRefCnt == 0, "destroying WebGL object still referenced by other WebGL objects");
+        NS_ABORT_IF_FALSE(mDeletionStatus == Deleted, "Derived class destructor must call DeleteOnce()");
     }
 
-    WebGLObjectBaseRefPtr(nsISupports *rawPtr)
-        : mRawPtr(rawPtr)
-    {
+    // called by WebGLRefPtr
+    void WebGLAddRef() {
+        ++mWebGLRefCnt;
+    }
+
+    // called by WebGLRefPtr
+    void WebGLRelease() {
+        NS_ABORT_IF_FALSE(mWebGLRefCnt > 0, "releasing WebGL object with WebGL refcnt already zero");
+        --mWebGLRefCnt;
+        MaybeDelete();
+    }
+
+    // this is the function that WebGL.deleteXxx() functions want to call
+    void RequestDelete() {
+        if (mDeletionStatus == Default)
+            mDeletionStatus = DeleteRequested;
+        MaybeDelete();
     }
 
-    void Zero() {
-        if (mRawPtr) {
-            // Note: RemoveRefOwner isn't called here, because
-            // the entire owner array will be cleared.
-            mRawPtr->Release();
-            mRawPtr = 0;
+    bool IsDeleted() const {
+        return mDeletionStatus == Deleted;
+    }
+
+    bool IsDeleteRequested() const {
+        return mDeletionStatus != Default;
+    }
+
+    void DeleteOnce() {
+        if (mDeletionStatus != Deleted) {
+            static_cast<Derived*>(this)->Delete();
+            mDeletionStatus = Deleted;
+        }
+    }
+
+private:
+    void MaybeDelete() {
+        if (mWebGLRefCnt == 0 &&
+            mDeletionStatus == DeleteRequested)
+        {
+            DeleteOnce();
         }
     }
 
 protected:
-    nsISupports *mRawPtr;
+    nsAutoRefCnt mWebGLRefCnt;
+    DeletionStatus mDeletionStatus;
 };
 
-template <class T>
-class WebGLObjectRefPtr
-    : public WebGLObjectBaseRefPtr
+/* This WebGLRefPtr class is meant to be used for references between WebGL objects.
+ * For example, a WebGLProgram holds WebGLRefPtr's to the WebGLShader's attached
+ * to it.
+ *
+ * Why the need for a separate refptr class? The only special thing that WebGLRefPtr
+ * does is that it increments and decrements the WebGL refcount of
+ * WebGLRefCountedObject's, in addition to incrementing and decrementing the
+ * usual XPCOM refcount.
+ *
+ * This means that by using a WebGLRefPtr instead of a nsRefPtr, you ensure that
+ * the WebGL refcount is incremented, which means that the object will be kept
+ * alive by this reference even if the matching webgl.deleteXxx() function is
+ * called on it.
+ */
+template<typename T>
+class WebGLRefPtr
 {
 public:
-    typedef T element_type;
-
-    WebGLObjectRefPtr()
+    WebGLRefPtr()
+        : mRawPtr(0)
     { }
 
-    WebGLObjectRefPtr(const WebGLObjectRefPtr<T>& aSmartPtr)
-        : WebGLObjectBaseRefPtr(aSmartPtr.mRawPtr)
+    WebGLRefPtr(const WebGLRefPtr<T>& aSmartPtr)
+        : mRawPtr(aSmartPtr.mRawPtr)
     {
-        if (mRawPtr) {
-            RawPtr()->AddRef();
-            RawPtr()->AddRefOwner(this);
-        }
+        AddRefOnPtr(mRawPtr);
     }
 
-    WebGLObjectRefPtr(T *aRawPtr)
-        : WebGLObjectBaseRefPtr(aRawPtr)
+    WebGLRefPtr(T *aRawPtr)
+        : mRawPtr(aRawPtr)
     {
-        if (mRawPtr) {
-            RawPtr()->AddRef();
-            RawPtr()->AddRefOwner(this);
-        }
+        AddRefOnPtr(mRawPtr);
     }
 
-    WebGLObjectRefPtr(const already_AddRefed<T>& aSmartPtr)
-        : WebGLObjectBaseRefPtr(aSmartPtr.mRawPtr)
-          // construct from |dont_AddRef(expr)|
-    {
-        if (mRawPtr) {
-            RawPtr()->AddRef();
-            RawPtr()->AddRefOwner(this);
-        }
+    ~WebGLRefPtr() {
+        ReleasePtr(mRawPtr);
     }
 
-    ~WebGLObjectRefPtr() {
-        if (mRawPtr) {
-            RawPtr()->RemoveRefOwner(this);
-            RawPtr()->Release();
-        }
-    }
-
-    WebGLObjectRefPtr<T>&
-    operator=(const WebGLObjectRefPtr<T>& rhs)
+    WebGLRefPtr<T>&
+    operator=(const WebGLRefPtr<T>& rhs)
     {
-        assign_with_AddRef(static_cast<T*>(rhs.mRawPtr));
+        assign_with_AddRef(rhs.mRawPtr);
         return *this;
     }
 
-    WebGLObjectRefPtr<T>&
+    WebGLRefPtr<T>&
     operator=(T* rhs)
     {
         assign_with_AddRef(rhs);
         return *this;
     }
 
-    WebGLObjectRefPtr<T>&
-    operator=(const already_AddRefed<T>& rhs)
-    {
-        assign_assuming_AddRef(static_cast<T*>(rhs.mRawPtr));
-        return *this;
-    }
-
     T* get() const {
-        return const_cast<T*>(static_cast<T*>(mRawPtr));
+        return static_cast<T*>(mRawPtr);
     }
 
     operator T*() const {
         return get();
     }
 
     T* operator->() const {
-        NS_PRECONDITION(mRawPtr != 0, "You can't dereference a NULL WebGLObjectRefPtr with operator->()!");
+        NS_ABORT_IF_FALSE(mRawPtr != 0, "You can't dereference a NULL WebGLRefPtr with operator->()!");
         return get();
     }
 
     T& operator*() const {
-        NS_PRECONDITION(mRawPtr != 0, "You can't dereference a NULL WebGLObjectRefPtr with operator*()!");
+        NS_ABORT_IF_FALSE(mRawPtr != 0, "You can't dereference a NULL WebGLRefPtr with operator*()!");
         return *get();
     }
 
 private:
-    T* RawPtr() { return static_cast<T*>(mRawPtr); }
+
+    static void AddRefOnPtr(T* rawPtr) {
+        if (rawPtr) {
+            rawPtr->WebGLAddRef();
+            rawPtr->AddRef();
+        }
+    }
+
+    static void ReleasePtr(T* rawPtr) {
+        if (rawPtr) {
+            rawPtr->WebGLRelease(); // must be done first before Release(), as Release() might actually destroy the object
+            rawPtr->Release();
+        }
+    }
 
     void assign_with_AddRef(T* rawPtr) {
-        if (rawPtr) {
-            rawPtr->AddRef();
-            rawPtr->AddRefOwner(this);
-        }
-
+        AddRefOnPtr(rawPtr);
         assign_assuming_AddRef(rawPtr);
     }
 
     void assign_assuming_AddRef(T* newPtr) {
-        T* oldPtr = RawPtr();
+        T* oldPtr = mRawPtr;
         mRawPtr = newPtr;
-        if (oldPtr) {
-            oldPtr->RemoveRefOwner(this);
-            oldPtr->Release();
-        }
+        ReleasePtr(oldPtr);
     }
+
+protected:
+    T *mRawPtr;
 };
 
-class WebGLBuffer;
+typedef PRUint64 WebGLMonotonicHandle;
 
-struct WebGLVertexAttribData {
-    // note that these initial values are what GL initializes vertex attribs to
-    WebGLVertexAttribData()
-        : buf(0), stride(0), size(4), byteOffset(0),
-          type(LOCAL_GL_FLOAT), enabled(false), normalized(false)
-    { }
+/* WebGLFastArray offers a fast array for the use case where all what one needs is to append
+ * and remove elements. Removal is fast because the array is always kept sorted with respect
+ * to "monotonic handles". Appending an element returns such a "monotonic handle" which the
+ * user needs to keep for future use for when it will want to remove the element.
+ */
+template<typename ElementType>
+class WebGLFastArray
+{
+    struct Entry {
+        ElementType mElement;
+        WebGLMonotonicHandle mMonotonicHandle;
 
-    WebGLObjectRefPtr<WebGLBuffer> buf;
-    WebGLuint stride;
-    WebGLuint size;
-    GLuint byteOffset;
-    GLenum type;
-    bool enabled;
-    bool normalized;
+        Entry(ElementType elem, WebGLMonotonicHandle monotonicHandle)
+            : mElement(elem), mMonotonicHandle(monotonicHandle)
+        {}
 
-    GLuint componentSize() const {
-        switch(type) {
-            case LOCAL_GL_BYTE:
-                return sizeof(GLbyte);
-                break;
-            case LOCAL_GL_UNSIGNED_BYTE:
-                return sizeof(GLubyte);
-                break;
-            case LOCAL_GL_SHORT:
-                return sizeof(GLshort);
-                break;
-            case LOCAL_GL_UNSIGNED_SHORT:
-                return sizeof(GLushort);
-                break;
-            // XXX case LOCAL_GL_FIXED:
-            case LOCAL_GL_FLOAT:
-                return sizeof(GLfloat);
-                break;
-            default:
-                NS_ERROR("Should never get here!");
-                return 0;
-        }
+        struct Comparator {
+            bool Equals(const Entry& a, const Entry& b) const {
+                return a.mMonotonicHandle == b.mMonotonicHandle;
+            }
+            bool LessThan(const Entry& a, const Entry& b) const {
+                return a.mMonotonicHandle < b.mMonotonicHandle;
+            }
+        };
+    };
+
+public:
+    WebGLFastArray()
+        : mCurrentMonotonicHandle(0) // CheckedInt already does it, this is just defensive coding
+    {}
+
+    ElementType operator[](size_t index) const {
+        return mArray[index].mElement;
     }
 
-    GLuint actualStride() const {
-        if (stride) return stride;
-        return size * componentSize();
+    size_t Length() const {
+        return mArray.Length();
+    }
+
+    ElementType Last() const {
+        return operator[](Length() - 1);
+    }
+
+    WebGLMonotonicHandle AppendElement(ElementType elem)
+    {
+        WebGLMonotonicHandle monotonicHandle = NextMonotonicHandle();
+        mArray.AppendElement(Entry(elem, monotonicHandle));
+        return monotonicHandle;
     }
+
+    void RemoveElement(WebGLMonotonicHandle monotonicHandle)
+    {
+        mArray.RemoveElementSorted(Entry(ElementType(), monotonicHandle),
+                                   typename Entry::Comparator());
+    }
+
+private:
+    WebGLMonotonicHandle NextMonotonicHandle() {
+        ++mCurrentMonotonicHandle;
+        if (!mCurrentMonotonicHandle.valid())
+            NS_RUNTIMEABORT("ran out of monotonic ids!");
+        return mCurrentMonotonicHandle.value();
+    }
+
+    nsTArray<Entry> mArray;
+    CheckedInt<WebGLMonotonicHandle> mCurrentMonotonicHandle;
 };
 
 struct WebGLContextOptions {
     // these are defaults
     WebGLContextOptions()
         : alpha(true), depth(true), stencil(false),
           premultipliedAlpha(true), antialias(true),
           preserveDrawingBuffer(false)
@@ -515,16 +643,19 @@ protected:
     bool mInvalidated;
     bool mResetLayer;
     bool mVerbose;
     bool mOptionsFrozen;
     bool mMinCapability;
     bool mDisableExtensions;
     bool mHasRobustness;
 
+    template<typename WebGLObjectType>
+    void DeleteWebGLObjectsArray(nsTArray<WebGLObjectType>& array);
+
     WebGLuint mActiveTexture;
     WebGLenum mWebGLError;
 
     // whether shader validation is supported
     bool mShaderValidation;
 
     // some GL constants
     PRInt32 mGLMaxVertexAttribs;
@@ -675,42 +806,36 @@ protected:
 
     void MaybeRestoreContext();
     void ForceLoseContext();
     void ForceRestoreContext();
 
     // the buffers bound to the current program's attribs
     nsTArray<WebGLVertexAttribData> mAttribBuffers;
 
-    // the textures bound to any sampler uniforms
-    nsTArray<WebGLObjectRefPtr<WebGLTexture> > mUniformTextures;
-
-    // textures bound to 
-    nsTArray<WebGLObjectRefPtr<WebGLTexture> > mBound2DTextures;
-    nsTArray<WebGLObjectRefPtr<WebGLTexture> > mBoundCubeMapTextures;
+    nsTArray<WebGLRefPtr<WebGLTexture> > mBound2DTextures;
+    nsTArray<WebGLRefPtr<WebGLTexture> > mBoundCubeMapTextures;
 
-    WebGLObjectRefPtr<WebGLBuffer> mBoundArrayBuffer;
-    WebGLObjectRefPtr<WebGLBuffer> mBoundElementArrayBuffer;
-    // note nsRefPtr -- this stays alive even after being deleted,
-    // and is only explicitly removed from the current state via
-    // a call to UseProgram.
-    nsRefPtr<WebGLProgram> mCurrentProgram;
+    WebGLRefPtr<WebGLBuffer> mBoundArrayBuffer;
+    WebGLRefPtr<WebGLBuffer> mBoundElementArrayBuffer;
+
+    WebGLRefPtr<WebGLProgram> mCurrentProgram;
 
     PRUint32 mMaxFramebufferColorAttachments;
 
-    nsRefPtr<WebGLFramebuffer> mBoundFramebuffer;
-    nsRefPtr<WebGLRenderbuffer> mBoundRenderbuffer;
+    WebGLRefPtr<WebGLFramebuffer> mBoundFramebuffer;
+    WebGLRefPtr<WebGLRenderbuffer> mBoundRenderbuffer;
 
-    // lookup tables for GL name -> object wrapper
-    nsRefPtrHashtable<nsUint32HashKey, WebGLTexture> mMapTextures;
-    nsRefPtrHashtable<nsUint32HashKey, WebGLBuffer> mMapBuffers;
-    nsRefPtrHashtable<nsUint32HashKey, WebGLProgram> mMapPrograms;
-    nsRefPtrHashtable<nsUint32HashKey, WebGLShader> mMapShaders;
-    nsRefPtrHashtable<nsUint32HashKey, WebGLFramebuffer> mMapFramebuffers;
-    nsRefPtrHashtable<nsUint32HashKey, WebGLRenderbuffer> mMapRenderbuffers;
+    WebGLFastArray<WebGLTexture*> mTextures;
+    WebGLFastArray<WebGLBuffer*> mBuffers;
+    WebGLFastArray<WebGLProgram*> mPrograms;
+    WebGLFastArray<WebGLShader*> mShaders;
+    WebGLFastArray<WebGLRenderbuffer*> mRenderbuffers;
+    WebGLFastArray<WebGLFramebuffer*> mFramebuffers;
+    WebGLFastArray<WebGLUniformLocation*> mUniformLocations;
 
     // PixelStore parameters
     PRUint32 mPixelStorePackAlignment, mPixelStoreUnpackAlignment, mPixelStoreColorspaceConversion;
     bool mPixelStoreFlipY, mPixelStorePremultiplyAlpha;
 
     FakeBlackStatus mFakeBlackStatus;
 
     WebGLuint mBlackTexture2D, mBlackTextureCubeMap;
@@ -745,55 +870,21 @@ public:
     // console logging helpers
     static void LogMessage(const char *fmt, ...);
     static void LogMessage(const char *fmt, va_list ap);
     void LogMessageIfVerbose(const char *fmt, ...);
     void LogMessageIfVerbose(const char *fmt, va_list ap);
 
     friend class WebGLTexture;
     friend class WebGLFramebuffer;
+    friend class WebGLRenderbuffer;
     friend class WebGLProgram;
-};
-
-// this class is a mixin for the named type wrappers, and is used
-// by WebGLObjectRefPtr to tell the object who holds references, so that
-// we can zero them out appropriately when the object is deleted, because
-// it will be unbound in the GL.
-//
-// PreallocatedOwnersCapacity is the preallocated capacity for the array of refptrs to owners.
-// Having some minimal preallocated capacity is an important optimization, see bug 522193. In this
-// bug, a benchmark was using WebGLBuffer with a number of owners oscillating between 0 and 2.
-// At this time mRefOwners was a nsTArray, and the too frequent reallocations were slowing us down.
-template<int PreallocatedOwnersCapacity>
-class WebGLZeroingObject
-{
-public:
-    WebGLZeroingObject()
-    { }
-
-    void AddRefOwner(WebGLObjectBaseRefPtr *owner) {
-        mRefOwners.AppendElement(owner);
-    }
-
-    void RemoveRefOwner(WebGLObjectBaseRefPtr *owner) {
-        mRefOwners.RemoveElement(owner);
-    }
-
-    void ZeroOwners() {
-        WebGLObjectBaseRefPtr **owners = mRefOwners.Elements();
-        
-        for (PRUint32 i = 0; i < mRefOwners.Length(); i++) {
-            owners[i]->Zero();
-        }
-
-        mRefOwners.Clear();
-    }
-
-protected:
-    nsAutoTArray<WebGLObjectBaseRefPtr *, PreallocatedOwnersCapacity> mRefOwners;
+    friend class WebGLBuffer;
+    friend class WebGLShader;
+    friend class WebGLUniformLocation;
 };
 
 // this class is a mixin for GL objects that have dimensions
 // that we need to track.
 class WebGLRectangleObject
 {
 protected:
     WebGLRectangleObject()
@@ -846,52 +937,95 @@ public:
             mContextGeneration == other->Generation();
     }
 
 protected:
     WebGLContext *mContext;
     PRUint32 mContextGeneration;
 };
 
-#define WEBGLBUFFER_PRIVATE_IID \
-    {0xd69f22e9, 0x6f98, 0x48bd, {0xb6, 0x94, 0x34, 0x17, 0xed, 0x06, 0x11, 0xab}}
-class WebGLBuffer :
-    public nsIWebGLBuffer,
-    public WebGLZeroingObject<8>, // almost never has more than 8 owners
-    public WebGLContextBoundObject
+struct WebGLVertexAttribData {
+    // note that these initial values are what GL initializes vertex attribs to
+    WebGLVertexAttribData()
+        : buf(0), stride(0), size(4), byteOffset(0),
+          type(LOCAL_GL_FLOAT), enabled(false), normalized(false)
+    { }
+
+    WebGLRefPtr<WebGLBuffer> buf;
+    WebGLuint stride;
+    WebGLuint size;
+    GLuint byteOffset;
+    GLenum type;
+    bool enabled;
+    bool normalized;
+
+    GLuint componentSize() const {
+        switch(type) {
+            case LOCAL_GL_BYTE:
+                return sizeof(GLbyte);
+                break;
+            case LOCAL_GL_UNSIGNED_BYTE:
+                return sizeof(GLubyte);
+                break;
+            case LOCAL_GL_SHORT:
+                return sizeof(GLshort);
+                break;
+            case LOCAL_GL_UNSIGNED_SHORT:
+                return sizeof(GLushort);
+                break;
+            // XXX case LOCAL_GL_FIXED:
+            case LOCAL_GL_FLOAT:
+                return sizeof(GLfloat);
+                break;
+            default:
+                NS_ERROR("Should never get here!");
+                return 0;
+        }
+    }
+
+    GLuint actualStride() const {
+        if (stride) return stride;
+        return size * componentSize();
+    }
+};
+
+class WebGLBuffer
+    : public nsIWebGLBuffer
+    , public WebGLRefCountedObject<WebGLBuffer>
+    , public WebGLContextBoundObject
 {
 public:
-    NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLBUFFER_PRIVATE_IID)
-
-    WebGLBuffer(WebGLContext *context, WebGLuint name) :
-        WebGLContextBoundObject(context),
-        mName(name), mDeleted(false), mHasEverBeenBound(false),
-        mByteLength(0), mTarget(LOCAL_GL_NONE), mData(nsnull)
-    {}
+    WebGLBuffer(WebGLContext *context)
+        : WebGLContextBoundObject(context)
+        , mHasEverBeenBound(false)
+        , mByteLength(0)
+        , mTarget(LOCAL_GL_NONE)
+        , mData(nsnull)
+    {
+        mContext->MakeContextCurrent();
+        mContext->gl->fGenBuffers(1, &mGLName);
+        mMonotonicHandle = mContext->mBuffers.AppendElement(this);
+    }
 
     ~WebGLBuffer() {
-        Delete();
+        DeleteOnce();
     }
 
     void Delete() {
-        if (mDeleted)
-            return;
-        ZeroOwners();
-
+        mContext->MakeContextCurrent();
+        mContext->gl->fDeleteBuffers(1, &mGLName);
         free(mData);
         mData = nsnull;
-
-        mDeleted = true;
         mByteLength = 0;
+        mContext->mBuffers.RemoveElement(mMonotonicHandle);
     }
 
-    bool Deleted() const { return mDeleted; }
     bool HasEverBeenBound() { return mHasEverBeenBound; }
     void SetHasEverBeenBound(bool x) { mHasEverBeenBound = x; }
-    GLuint GLName() const { return mName; }
+    GLuint GLName() const { return mGLName; }
     GLuint ByteLength() const { return mByteLength; }
     GLenum Target() const { return mTarget; }
     const void *Data() const { return mData; }
 
     void SetByteLength(GLuint byteLength) { mByteLength = byteLength; }
     void SetTarget(GLenum target) { mTarget = target; }
 
     // element array buffers are the only buffers for which we need to keep a copy of the data.
@@ -965,80 +1099,83 @@ public:
         mHasCachedMaxUshortElement = true;
         mCachedMaxUshortElement = FindMaxElementInSubArray<GLshort>(mByteLength>>1, 0);
         return mCachedMaxUshortElement;
       }
     }
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLBUFFER
+
 protected:
-    WebGLuint mName;
-    bool mDeleted;
+
+    WebGLuint mGLName;
     bool mHasEverBeenBound;
     GLuint mByteLength;
     GLenum mTarget;
+    WebGLMonotonicHandle mMonotonicHandle;
 
     PRUint8 mCachedMaxUbyteElement;
     bool mHasCachedMaxUbyteElement;
     PRUint16 mCachedMaxUshortElement;
     bool mHasCachedMaxUshortElement;
 
     void* mData; // in the case of an Element Array Buffer, we keep a copy.
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(WebGLBuffer, WEBGLBUFFER_PRIVATE_IID)
-
-#define WEBGLTEXTURE_PRIVATE_IID \
-    {0x4c19f189, 0x1f86, 0x4e61, {0x96, 0x21, 0x0a, 0x11, 0xda, 0x28, 0x10, 0xdd}}
-class WebGLTexture :
-    public nsIWebGLTexture,
-    public WebGLZeroingObject<8>, // almost never has more than 8 owners
-    public WebGLContextBoundObject
+class WebGLTexture
+    : public nsIWebGLTexture
+    , public WebGLRefCountedObject<WebGLTexture>
+    , public WebGLContextBoundObject
 {
 public:
-    NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLTEXTURE_PRIVATE_IID)
+    WebGLTexture(WebGLContext *context)
+        : WebGLContextBoundObject(context)
+        , mHasEverBeenBound(false)
+        , mTarget(0)
+        , mMinFilter(LOCAL_GL_NEAREST_MIPMAP_LINEAR)
+        , mMagFilter(LOCAL_GL_LINEAR)
+        , mWrapS(LOCAL_GL_REPEAT)
+        , mWrapT(LOCAL_GL_REPEAT)
+        , mFacesCount(0)
+        , mMaxLevelWithCustomImages(0)
+        , mHaveGeneratedMipmap(false)
+        , mFakeBlackStatus(DoNotNeedFakeBlack)
+    {
+        mContext->MakeContextCurrent();
+        mContext->gl->fGenTextures(1, &mGLName);
+        mMonotonicHandle = mContext->mTextures.AppendElement(this);
+    }
 
-    WebGLTexture(WebGLContext *context, WebGLuint name) :
-        WebGLContextBoundObject(context),
-        mDeleted(false), mHasEverBeenBound(false), mName(name),
-        mTarget(0),
-        mMinFilter(LOCAL_GL_NEAREST_MIPMAP_LINEAR),
-        mMagFilter(LOCAL_GL_LINEAR),
-        mWrapS(LOCAL_GL_REPEAT),
-        mWrapT(LOCAL_GL_REPEAT),
-        mFacesCount(0),
-        mMaxLevelWithCustomImages(0),
-        mHaveGeneratedMipmap(false),
-        mFakeBlackStatus(DoNotNeedFakeBlack)
-    {
+    ~WebGLTexture() {
+        DeleteOnce();
     }
 
     void Delete() {
-        if (mDeleted)
-            return;
-        ZeroOwners();
-        mDeleted = true;
+        mImageInfos.Clear();
+        mContext->MakeContextCurrent();
+        mContext->gl->fDeleteTextures(1, &mGLName);
+        mContext->mTextures.RemoveElement(mMonotonicHandle);
     }
 
-    bool Deleted() { return mDeleted; }
     bool HasEverBeenBound() { return mHasEverBeenBound; }
     void SetHasEverBeenBound(bool x) { mHasEverBeenBound = x; }
-    WebGLuint GLName() { return mName; }
+    WebGLuint GLName() { return mGLName; }
+    GLenum Target() const { return mTarget; }
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLTEXTURE
 
 protected:
+
     friend class WebGLContext;
     friend class WebGLFramebuffer;
 
-    bool mDeleted;
     bool mHasEverBeenBound;
-    WebGLuint mName;
+    WebGLuint mGLName;
 
     // we store information about the various images that are part of
     // this texture (cubemap faces, mipmap levels)
 
 public:
 
     struct ImageInfo {
         ImageInfo() : mWidth(0), mHeight(0), mFormat(0), mType(0), mIsDefined(false) {}
@@ -1094,16 +1231,18 @@ public:
                ImageInfoAt(level, face).mIsDefined;
     }
 
     static size_t FaceForTarget(WebGLenum target) {
         return target == LOCAL_GL_TEXTURE_2D ? 0 : target - LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X;
     }
 
     PRInt64 MemoryUsage() const {
+        if (IsDeleted())
+            return 0;
         PRInt64 result = 0;
         for(size_t face = 0; face < mFacesCount; face++) {
             if (mHaveGeneratedMipmap) {
                 // Each mipmap level is 1/4 the size of the previous level
                 // 1 + x + x^2 + ... = 1/(1-x)
                 // for x = 1/4, we get 1/(1-1/4) = 4/3
                 result += ImageInfoAt(0, face).MemoryUsage() * 4 / 3;
             } else {
@@ -1120,16 +1259,18 @@ protected:
     WebGLenum mMinFilter, mMagFilter, mWrapS, mWrapT;
 
     size_t mFacesCount, mMaxLevelWithCustomImages;
     nsTArray<ImageInfo> mImageInfos;
 
     bool mHaveGeneratedMipmap;
     FakeBlackStatus mFakeBlackStatus;
 
+    WebGLMonotonicHandle mMonotonicHandle;
+
     void EnsureMaxLevelWithCustomImagesAtLeast(size_t aMaxLevelWithCustomImages) {
         mMaxLevelWithCustomImages = NS_MAX(mMaxLevelWithCustomImages, aMaxLevelWithCustomImages);
         mImageInfos.EnsureLengthAtLeast((mMaxLevelWithCustomImages + 1) * mFacesCount);
     }
 
     bool CheckFloatTextureFilterParams() const {
         // Without OES_texture_float_linear, only NEAREST and NEAREST_MIMPAMP_NEAREST are supported
         return (mMagFilter == LOCAL_GL_NEAREST) &&
@@ -1182,17 +1323,17 @@ public:
             mContext->ErrorInvalidOperation("bindTexture: this texture has already been bound to a different target");
             // very important to return here before modifying texture state! This was the place when I lost a whole day figuring
             // very strange 'invalid write' crashes.
             return;
         }
 
         mTarget = aTarget;
 
-        mContext->gl->fBindTexture(mTarget, mName);
+        mContext->gl->fBindTexture(mTarget, mGLName);
 
         if (firstTimeThisTextureIsBound) {
             mFacesCount = (mTarget == LOCAL_GL_TEXTURE_2D) ? 1 : 6;
             EnsureMaxLevelWithCustomImagesAtLeast(0);
             SetDontKnowIfNeedFakeBlack();
 
             // thanks to the WebKit people for finding this out: GL_TEXTURE_WRAP_R is not
             // present in GLES 2, but is present in GL and it seems as if for cube maps
@@ -1420,71 +1561,47 @@ public:
             if (mFakeBlackStatus == DontKnowIfNeedFakeBlack)
                 mFakeBlackStatus = DoNotNeedFakeBlack;
         }
 
         return mFakeBlackStatus == DoNeedFakeBlack;
     }
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(WebGLTexture, WEBGLTEXTURE_PRIVATE_IID)
-
-#define WEBGLSHADER_PRIVATE_IID \
-    {0x48cce975, 0xd459, 0x4689, {0x83, 0x82, 0x37, 0x82, 0x6e, 0xac, 0xe0, 0xa7}}
-class WebGLShader :
-    public nsIWebGLShader,
-    public WebGLZeroingObject<8>, // almost never has more than 8 owners
-    public WebGLContextBoundObject
+class WebGLShader
+    : public nsIWebGLShader
+    , public WebGLRefCountedObject<WebGLShader>
+    , public WebGLContextBoundObject
 {
 public:
-    NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLSHADER_PRIVATE_IID)
-
-    WebGLShader(WebGLContext *context, WebGLuint name, WebGLenum stype) :
-        WebGLContextBoundObject(context),
-        mName(name), mDeleted(false), mType(stype),
-        mNeedsTranslation(true), mAttachCount(0),
-        mDeletePending(false)
-    { }
-
-    void DetachedFromProgram() {
-        DecrementAttachCount();
-        if (mDeletePending && AttachCount() <= 0) {
-            DeleteWhenNotAttached();
-        }
+    WebGLShader(WebGLContext *context, WebGLenum stype)
+        : WebGLContextBoundObject(context)
+        , mType(stype)
+        , mNeedsTranslation(true)
+    {
+        mContext->MakeContextCurrent();
+        mGLName = mContext->gl->fCreateShader(mType);
+        mMonotonicHandle = mContext->mShaders.AppendElement(this);
     }
 
-    void DeleteWhenNotAttached() {
-        if (mDeleted)
-            return;
-
-        if (AttachCount() > 0) {
-            mDeletePending = true;
-            return;
-        }
-
-        Delete();
+    ~WebGLShader() {
+        DeleteOnce();
     }
 
     void Delete() {
-        if (mDeleted)
-            return;
-
-        ZeroOwners();
-        mDeleted = true;
-        mDeletePending = false;
+        mSource.Truncate();
+        mTranslationLog.Truncate();
+        mContext->MakeContextCurrent();
+        mContext->gl->fDeleteShader(mGLName);
+        mContext->mShaders.RemoveElement(mMonotonicHandle);
     }
 
-    bool Deleted() { return mDeleted; }
-    WebGLuint GLName() { return mName; }
+    WebGLuint GLName() { return mGLName; }
     WebGLenum ShaderType() { return mType; }
 
-    PRInt32 AttachCount() { return mAttachCount; }
-    void IncrementAttachCount() { mAttachCount++; }
-    void DecrementAttachCount() { mAttachCount--; }
-
     void SetSource(const nsAString& src) {
         // XXX do some quick gzip here maybe -- getting this will be very rare
         mSource.Assign(src);
     }
 
     const nsString& Source() const { return mSource; }
 
     void SetNeedsTranslation() { mNeedsTranslation = true; }
@@ -1498,120 +1615,93 @@ public:
     void SetTranslationFailure(const nsCString& msg) {
         mTranslationLog.Assign(msg); 
     }
 
     const nsCString& TranslationLog() const { return mTranslationLog; }
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLSHADER
+
 protected:
-    WebGLuint mName;
-    bool mDeleted;
+
+    WebGLuint mGLName;
     WebGLenum mType;
     nsString mSource;
     nsCString mTranslationLog;
     bool mNeedsTranslation;
-    PRInt32 mAttachCount;
-    bool mDeletePending;
+    WebGLMonotonicHandle mMonotonicHandle;
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(WebGLShader, WEBGLSHADER_PRIVATE_IID)
-
-#define WEBGLPROGRAM_PRIVATE_IID \
-    {0xb3084a5b, 0xa5b4, 0x4ee0, {0xa0, 0xf0, 0xfb, 0xdd, 0x64, 0xaf, 0x8e, 0x82}}
-class WebGLProgram :
-    public nsIWebGLProgram,
-    public WebGLZeroingObject<8>, // can actually have many more owners (WebGLUniformLocations),
-                                  // but that shouldn't be performance-critical as references to the uniformlocations are stored
-                                  // in mMapUniformLocations, limiting the churning
-    public WebGLContextBoundObject
+class WebGLProgram
+    : public nsIWebGLProgram
+    , public WebGLRefCountedObject<WebGLProgram>
+    , public WebGLContextBoundObject
 {
 public:
-    NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLPROGRAM_PRIVATE_IID)
-
-    WebGLProgram(WebGLContext *context, WebGLuint name) :
-        WebGLContextBoundObject(context),
-        mName(name), mDeleted(false), mDeletePending(false),
-        mLinkStatus(false), mGeneration(0),
-        mUniformMaxNameLength(0), mAttribMaxNameLength(0),
-        mUniformCount(0), mAttribCount(0)
+    WebGLProgram(WebGLContext *context)
+        : WebGLContextBoundObject(context)
+        , mLinkStatus(false)
+        , mGeneration(0)
+        , mUniformMaxNameLength(0)
+        , mAttribMaxNameLength(0)
+        , mUniformCount(0)
+        , mAttribCount(0)
     {
-        mMapUniformLocations.Init();
+        mContext->MakeContextCurrent();
+        mGLName = mContext->gl->fCreateProgram();
+        mMonotonicHandle = mContext->mPrograms.AppendElement(this);
     }
 
-    void DeleteWhenNotCurrent() {
-        if (mDeleted)
-            return;
-
-        if (mContext->mCurrentProgram == this) {
-            mDeletePending = true;
-            return;
-        }
-
-        Delete();
+    ~WebGLProgram() {
+        DeleteOnce();
     }
 
     void Delete() {
-        if (mDeleted)
-            return;
-
         DetachShaders();
-        ZeroOwners();
-        mDeleted = true;
-        mDeletePending = false;
+        mContext->MakeContextCurrent();
+        mContext->gl->fDeleteProgram(mGLName);
+        mContext->mPrograms.RemoveElement(mMonotonicHandle);
     }
 
     void DetachShaders() {
-        for (PRUint32 i = 0; i < mAttachedShaders.Length(); ++i) {
-            WebGLShader* shader = mAttachedShaders[i];
-            if (shader)
-                shader->DetachedFromProgram();
-        }
         mAttachedShaders.Clear();
     }
 
-    void NoLongerCurrent() {
-        if (mDeletePending) {
-            DetachShaders();
-            DeleteWhenNotCurrent();
-        }
-    }
-
-    bool Deleted() { return mDeleted; }
-    void SetDeletePending() { mDeletePending = true; }
-    void ClearDeletePending() { mDeletePending = false; }
-    bool HasDeletePending() { return mDeletePending; }
-
-    WebGLuint GLName() { return mName; }
-    const nsTArray<nsRefPtr<WebGLShader> >& AttachedShaders() const { return mAttachedShaders; }
+    WebGLuint GLName() { return mGLName; }
+    const nsTArray<WebGLRefPtr<WebGLShader> >& AttachedShaders() const { return mAttachedShaders; }
     bool LinkStatus() { return mLinkStatus; }
     PRUint32 Generation() const { return mGeneration.value(); }
     void SetLinkStatus(bool val) { mLinkStatus = val; }
 
     bool ContainsShader(WebGLShader *shader) {
         return mAttachedShaders.Contains(shader);
     }
 
     // return true if the shader wasn't already attached
     bool AttachShader(WebGLShader *shader) {
         if (ContainsShader(shader))
             return false;
         mAttachedShaders.AppendElement(shader);
-        shader->IncrementAttachCount();
+
+        mContext->MakeContextCurrent();
+        mContext->gl->fAttachShader(GLName(), shader->GLName());
+
         return true;
     }
 
     // return true if the shader was found and removed
     bool DetachShader(WebGLShader *shader) {
-        if (mAttachedShaders.RemoveElement(shader)) {
-            shader->DetachedFromProgram();
-            return true;
-        }
-        return false;
+        if (!mAttachedShaders.RemoveElement(shader))
+            return false;
+
+        mContext->MakeContextCurrent();
+        mContext->gl->fDetachShader(GLName(), shader->GLName());
+
+        return true;
     }
 
     bool HasAttachedShaderOfType(GLenum shaderType) {
         for (PRUint32 i = 0; i < mAttachedShaders.Length(); ++i) {
             if (mAttachedShaders[i] && mAttachedShaders[i]->ShaderType() == shaderType) {
                 return true;
             }
         }
@@ -1624,84 +1714,83 @@ public:
             HasAttachedShaderOfType(LOCAL_GL_FRAGMENT_SHADER);
     }
 
     bool NextGeneration()
     {
         if (!(mGeneration+1).valid())
             return false; // must exit without changing mGeneration
         ++mGeneration;
-        mMapUniformLocations.Clear();
         return true;
     }
-    
-
-    already_AddRefed<WebGLUniformLocation> GetUniformLocationObject(GLint glLocation);
 
     /* Called only after LinkProgram */
     bool UpdateInfo(gl::GLContext *gl);
 
     /* Getters for cached program info */
     WebGLint UniformMaxNameLength() const { return mUniformMaxNameLength; }
     WebGLint AttribMaxNameLength() const { return mAttribMaxNameLength; }
     WebGLint UniformCount() const { return mUniformCount; }
     WebGLint AttribCount() const { return mAttribCount; }
     bool IsAttribInUse(unsigned i) const { return mAttribsInUse[i]; }
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLPROGRAM
+
 protected:
-    WebGLuint mName;
-    bool mDeleted;
-    bool mDeletePending;
+
+    WebGLuint mGLName;
     bool mLinkStatus;
     // attached shaders of the program object
-    nsTArray<nsRefPtr<WebGLShader> > mAttachedShaders;
+    nsTArray<WebGLRefPtr<WebGLShader> > mAttachedShaders;
     CheckedUint32 mGeneration;
 
     // post-link data
-    nsRefPtrHashtable<nsUint32HashKey, WebGLUniformLocation> mMapUniformLocations;
+
     GLint mUniformMaxNameLength;
     GLint mAttribMaxNameLength;
     GLint mUniformCount;
     GLint mAttribCount;
     std::vector<bool> mAttribsInUse;
+    WebGLMonotonicHandle mMonotonicHandle;
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(WebGLProgram, WEBGLPROGRAM_PRIVATE_IID)
-
-#define WEBGLRENDERBUFFER_PRIVATE_IID \
-    {0x3cbc2067, 0x5831, 0x4e3f, {0xac, 0x52, 0x7e, 0xf4, 0x5c, 0x04, 0xff, 0xae}}
-class WebGLRenderbuffer :
-    public nsIWebGLRenderbuffer,
-    public WebGLZeroingObject<8>, // almost never has more than 8 owners
-    public WebGLRectangleObject,
-    public WebGLContextBoundObject
+class WebGLRenderbuffer
+    : public nsIWebGLRenderbuffer
+    , public WebGLRefCountedObject<WebGLRenderbuffer>
+    , public WebGLRectangleObject
+    , public WebGLContextBoundObject
 {
 public:
-    NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLRENDERBUFFER_PRIVATE_IID)
+    WebGLRenderbuffer(WebGLContext *context)
+        : WebGLContextBoundObject(context)
+        , mInternalFormat(0)
+        , mInternalFormatForGL(0)
+        , mHasEverBeenBound(false)
+        , mInitialized(false)
+    {
 
-    WebGLRenderbuffer(WebGLContext *context, WebGLuint name, WebGLuint secondBufferName = 0) :
-        WebGLContextBoundObject(context),
-        mName(name),
-        mInternalFormat(0),
-        mInternalFormatForGL(0),
-        mDeleted(false), mHasEverBeenBound(false), mInitialized(false)
-    { }
+        mContext->MakeContextCurrent();
+        mContext->gl->fGenRenderbuffers(1, &mGLName);
+        mMonotonicHandle = mContext->mRenderbuffers.AppendElement(this);
+    }
+
+    ~WebGLRenderbuffer() {
+        DeleteOnce();
+    }
 
     void Delete() {
-        if (mDeleted)
-            return;
-        ZeroOwners();
-        mDeleted = true;
+        mContext->MakeContextCurrent();
+        mContext->gl->fDeleteRenderbuffers(1, &mGLName);
+        mContext->mRenderbuffers.RemoveElement(mMonotonicHandle);
     }
-    bool Deleted() const { return mDeleted; }
+
     bool HasEverBeenBound() { return mHasEverBeenBound; }
     void SetHasEverBeenBound(bool x) { mHasEverBeenBound = x; }
-    WebGLuint GLName() const { return mName; }
+    WebGLuint GLName() const { return mGLName; }
 
     bool Initialized() const { return mInitialized; }
     void SetInitialized(bool aInitialized) { mInitialized = aInitialized; }
 
     WebGLenum InternalFormat() const { return mInternalFormat; }
     void SetInternalFormat(WebGLenum aInternalFormat) { mInternalFormat = aInternalFormat; }
     
     WebGLenum InternalFormatForGL() const { return mInternalFormatForGL; }
@@ -1729,53 +1818,57 @@ public:
         NS_ABORT();
         return 0;
     }
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLRENDERBUFFER
 
 protected:
-    WebGLuint mName;
+
+    WebGLuint mGLName;
     WebGLenum mInternalFormat;
     WebGLenum mInternalFormatForGL;
-
-    bool mDeleted;
+    WebGLMonotonicHandle mMonotonicHandle;
     bool mHasEverBeenBound;
     bool mInitialized;
 
     friend class WebGLFramebuffer;
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(WebGLRenderbuffer, WEBGLRENDERBUFFER_PRIVATE_IID)
-
 class WebGLFramebufferAttachment
     : public WebGLRectangleObject
 {
     // deleting a texture or renderbuffer immediately detaches it
-    WebGLObjectRefPtr<WebGLTexture> mTexturePtr;
-    WebGLObjectRefPtr<WebGLRenderbuffer> mRenderbufferPtr;
+    WebGLRefPtr<WebGLTexture> mTexturePtr;
+    WebGLRefPtr<WebGLRenderbuffer> mRenderbufferPtr;
     WebGLenum mAttachmentPoint;
     WebGLint mTextureLevel;
     WebGLenum mTextureCubeMapFace;
 
 public:
     WebGLFramebufferAttachment(WebGLenum aAttachmentPoint)
         : mAttachmentPoint(aAttachmentPoint)
     {}
 
-    bool IsNull() const {
-        return !mTexturePtr && !mRenderbufferPtr;
+    bool IsDefined() const {
+        return Texture() || Renderbuffer();
+    }
+
+    bool IsDeleteRequested() const {
+        return Texture() ? Texture()->IsDeleteRequested()
+             : Renderbuffer() ? Renderbuffer()->IsDeleteRequested()
+             : false;
     }
 
     bool HasAlpha() const {
         WebGLenum format = 0;
-        if (mTexturePtr)
+        if (Texture() && Texture()->HasImageInfoAt(0,0))
             format = mTexturePtr->ImageInfoAt(0,0).mFormat;
-        if (mRenderbufferPtr)
+        else if (Renderbuffer())
             format = mRenderbufferPtr->InternalFormat();
         return format == LOCAL_GL_RGBA ||
                format == LOCAL_GL_LUMINANCE_ALPHA ||
                format == LOCAL_GL_ALPHA ||
                format == LOCAL_GL_RGBA4 ||
                format == LOCAL_GL_RGB5_A1;
     }
 
@@ -1832,48 +1925,60 @@ public:
         }
 
         return false; // no attachment at all, so no incompatibility
     }
 
     bool HasUninitializedRenderbuffer() const {
         return mRenderbufferPtr && !mRenderbufferPtr->Initialized();
     }
+
+    void Reset() {
+        mTexturePtr = nsnull;
+        mRenderbufferPtr = nsnull;
+    }
 };
 
-#define WEBGLFRAMEBUFFER_PRIVATE_IID \
-    {0x0052a16f, 0x4bc9, 0x4a55, {0x9d, 0xa3, 0x54, 0x95, 0xaa, 0x4e, 0x80, 0xb9}}
-class WebGLFramebuffer :
-    public nsIWebGLFramebuffer,
-    public WebGLZeroingObject<8>, // almost never has more than 8 owners
-    public WebGLContextBoundObject
+class WebGLFramebuffer
+    : public nsIWebGLFramebuffer
+    , public WebGLRefCountedObject<WebGLFramebuffer>
+    , public WebGLContextBoundObject
 {
 public:
-    NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLFRAMEBUFFER_PRIVATE_IID)
+    WebGLFramebuffer(WebGLContext *context)
+        : WebGLContextBoundObject(context)
+        , mHasEverBeenBound(false)
+        , mColorAttachment(LOCAL_GL_COLOR_ATTACHMENT0)
+        , mDepthAttachment(LOCAL_GL_DEPTH_ATTACHMENT)
+        , mStencilAttachment(LOCAL_GL_STENCIL_ATTACHMENT)
+        , mDepthStencilAttachment(LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
+    {
+        mContext->MakeContextCurrent();
+        mContext->gl->fGenFramebuffers(1, &mGLName);
+        mMonotonicHandle = mContext->mFramebuffers.AppendElement(this);
+    }
 
-    WebGLFramebuffer(WebGLContext *context, WebGLuint name) :
-        WebGLContextBoundObject(context),
-        mName(name), mDeleted(false), mHasEverBeenBound(false),
-        mColorAttachment(LOCAL_GL_COLOR_ATTACHMENT0),
-        mDepthAttachment(LOCAL_GL_DEPTH_ATTACHMENT),
-        mStencilAttachment(LOCAL_GL_STENCIL_ATTACHMENT),
-        mDepthStencilAttachment(LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
-    { }
+    ~WebGLFramebuffer() {
+        DeleteOnce();
+    }
 
     void Delete() {
-        if (mDeleted)
-            return;
-        ZeroOwners();
-        mDeleted = true;
+        mColorAttachment.Reset();
+        mDepthAttachment.Reset();
+        mStencilAttachment.Reset();
+        mDepthStencilAttachment.Reset();
+        mContext->MakeContextCurrent();
+        mContext->gl->fDeleteFramebuffers(1, &mGLName);
+        mContext->mFramebuffers.RemoveElement(mMonotonicHandle);
     }
-    bool Deleted() { return mDeleted; }
+
     bool HasEverBeenBound() { return mHasEverBeenBound; }
     void SetHasEverBeenBound(bool x) { mHasEverBeenBound = x; }
-    WebGLuint GLName() { return mName; }
-    
+    WebGLuint GLName() { return mGLName; }
+
     WebGLsizei width() { return mColorAttachment.width(); }
     WebGLsizei height() { return mColorAttachment.height(); }
 
     nsresult FramebufferRenderbuffer(WebGLenum target,
                                      WebGLenum attachment,
                                      WebGLenum rbtarget,
                                      nsIWebGLRenderbuffer *rbobj)
     {
@@ -2002,33 +2107,33 @@ public:
         if (mColorAttachment.IsIncompatibleWithAttachmentPoint() ||
             mDepthAttachment.IsIncompatibleWithAttachmentPoint() ||
             mStencilAttachment.IsIncompatibleWithAttachmentPoint() ||
             mDepthStencilAttachment.IsIncompatibleWithAttachmentPoint())
         {
             // some attachment is incompatible with its attachment point
             return true;
         }
-        
-        if (int(mDepthAttachment.IsNull()) +
-            int(mStencilAttachment.IsNull()) +
-            int(mDepthStencilAttachment.IsNull()) <= 1)
+
+        if (int(mDepthAttachment.IsDefined()) +
+            int(mStencilAttachment.IsDefined()) +
+            int(mDepthStencilAttachment.IsDefined()) >= 2)
         {
             // has at least two among Depth, Stencil, DepthStencil
             return true;
         }
-        
-        if (!mDepthAttachment.IsNull() && !mDepthAttachment.HasSameDimensionsAs(mColorAttachment))
+
+        if (mDepthAttachment.IsDefined() && !mDepthAttachment.HasSameDimensionsAs(mColorAttachment))
             return true;
-        if (!mStencilAttachment.IsNull() && !mStencilAttachment.HasSameDimensionsAs(mColorAttachment))
+        if (mStencilAttachment.IsDefined() && !mStencilAttachment.HasSameDimensionsAs(mColorAttachment))
             return true;
-        if (!mDepthStencilAttachment.IsNull() && !mDepthStencilAttachment.HasSameDimensionsAs(mColorAttachment))
+        if (mDepthStencilAttachment.IsDefined() && !mDepthStencilAttachment.HasSameDimensionsAs(mColorAttachment))
             return true;
-        
-        else return false;
+
+        return false;
     }
 
     const WebGLFramebufferAttachment& ColorAttachment() const {
         return mColorAttachment;
     }
 
     const WebGLFramebufferAttachment& DepthAttachment() const {
         return mDepthAttachment;
@@ -2049,16 +2154,38 @@ public:
             return mDepthAttachment;
         if (attachment == LOCAL_GL_STENCIL_ATTACHMENT)
             return mStencilAttachment;
 
         NS_ASSERTION(attachment == LOCAL_GL_COLOR_ATTACHMENT0, "bad attachment!");
         return mColorAttachment;
     }
 
+    void DetachTexture(const WebGLTexture *tex) {
+        if (mColorAttachment.Texture() == tex)
+            FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D, nsnull, 0);
+        if (mDepthAttachment.Texture() == tex)
+            FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, LOCAL_GL_TEXTURE_2D, nsnull, 0);
+        if (mStencilAttachment.Texture() == tex)
+            FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT, LOCAL_GL_TEXTURE_2D, nsnull, 0);
+        if (mDepthStencilAttachment.Texture() == tex)
+            FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT, LOCAL_GL_TEXTURE_2D, nsnull, 0);
+    }
+
+    void DetachRenderbuffer(const WebGLRenderbuffer *rb) {
+        if (mColorAttachment.Renderbuffer() == rb)
+            FramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_RENDERBUFFER, nsnull);
+        if (mDepthAttachment.Renderbuffer() == rb)
+            FramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, LOCAL_GL_RENDERBUFFER, nsnull);
+        if (mStencilAttachment.Renderbuffer() == rb)
+            FramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT, LOCAL_GL_RENDERBUFFER, nsnull);
+        if (mDepthStencilAttachment.Renderbuffer() == rb)
+            FramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT, LOCAL_GL_RENDERBUFFER, nsnull);
+    }
+
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLFRAMEBUFFER
 
 protected:
 
     // protected because WebGLContext should only call InitializeRenderbuffers
     void InitializeRenderbuffers()
     {
@@ -2095,120 +2222,102 @@ protected:
 
         if (mStencilAttachment.HasUninitializedRenderbuffer())
             mStencilAttachment.Renderbuffer()->SetInitialized(true);
 
         if (mDepthStencilAttachment.HasUninitializedRenderbuffer())
             mDepthStencilAttachment.Renderbuffer()->SetInitialized(true);
     }
 
-    WebGLuint mName;
-    bool mDeleted;
+    WebGLuint mGLName;
     bool mHasEverBeenBound;
 
     // we only store pointers to attached renderbuffers, not to attached textures, because
     // we will only need to initialize renderbuffers. Textures are already initialized.
     WebGLFramebufferAttachment mColorAttachment,
                                mDepthAttachment,
                                mStencilAttachment,
                                mDepthStencilAttachment;
+
+    WebGLMonotonicHandle mMonotonicHandle;
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(WebGLFramebuffer, WEBGLFRAMEBUFFER_PRIVATE_IID)
-
-#define WEBGLUNIFORMLOCATION_PRIVATE_IID \
-    {0x01a8a614, 0xb109, 0x42f1, {0xb4, 0x40, 0x8d, 0x8b, 0x87, 0x0b, 0x43, 0xa7}}
-class WebGLUniformLocation :
-    public nsIWebGLUniformLocation,
-    public WebGLZeroingObject<2>, // never saw a WebGLUniformLocation have more than 2 owners, and since these
-                                  // are small objects and there are many of them, it's worth saving some memory
-                                  // by using a small value such as 2 here.
-    public WebGLContextBoundObject
+class WebGLUniformLocation
+    : public nsIWebGLUniformLocation
+    , public WebGLContextBoundObject
+    , public WebGLRefCountedObject<WebGLUniformLocation>
 {
 public:
-    NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLUNIFORMLOCATION_PRIVATE_IID)
+    WebGLUniformLocation(WebGLContext *context, WebGLProgram *program, GLint location)
+        : WebGLContextBoundObject(context)
+        , mProgram(program)
+        , mProgramGeneration(program->Generation())
+        , mLocation(location)
+    {
+        mMonotonicHandle = mContext->mUniformLocations.AppendElement(this);
+    }
 
-    WebGLUniformLocation(WebGLContext *context, WebGLProgram *program, GLint location) :
-        WebGLContextBoundObject(context), mProgram(program), mProgramGeneration(program->Generation()),
-        mLocation(location) { }
+    ~WebGLUniformLocation() {
+        DeleteOnce();
+    }
+
+    void Delete() {
+        mProgram = nsnull;
+        mContext->mUniformLocations.RemoveElement(mMonotonicHandle);
+    }
 
     WebGLProgram *Program() const { return mProgram; }
     GLint Location() const { return mLocation; }
     PRUint32 ProgramGeneration() const { return mProgramGeneration; }
 
-    // needed for our generic helpers to check nsIxxx parameters, see GetConcreteObject.
-    bool Deleted() { return false; }
-
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLUNIFORMLOCATION
 protected:
-    WebGLObjectRefPtr<WebGLProgram> mProgram;
+    // nsRefPtr, not WebGLRefPtr, so that we don't prevent the program from being explicitly deleted.
+    // we just want to avoid having a dangling pointer.
+    nsRefPtr<WebGLProgram> mProgram;
+
     PRUint32 mProgramGeneration;
     GLint mLocation;
+    WebGLMonotonicHandle mMonotonicHandle;
+    friend class WebGLProgram;
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(WebGLUniformLocation, WEBGLUNIFORMLOCATION_PRIVATE_IID)
-
-#define WEBGLACTIVEINFO_PRIVATE_IID \
-    {0x90def5ec, 0xc672, 0x4ac3, {0xb8, 0x97, 0x04, 0xa2, 0x6d, 0xda, 0x66, 0xd7}}
-class WebGLActiveInfo :
-    public nsIWebGLActiveInfo
+class WebGLActiveInfo
+    : public nsIWebGLActiveInfo
 {
 public:
-    NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLACTIVEINFO_PRIVATE_IID)
-
     WebGLActiveInfo(WebGLint size, WebGLenum type, const char *nameptr, PRUint32 namelength) :
-        mDeleted(false),
         mSize(size),
         mType(type)
     {
         mName.AssignASCII(nameptr, namelength);
     }
 
-    void Delete() {
-        if (mDeleted)
-            return;
-        mDeleted = true;
-    }
-
-    bool Deleted() { return mDeleted; }
-
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLACTIVEINFO
 protected:
-    bool mDeleted;
     WebGLint mSize;
     WebGLenum mType;
     nsString mName;
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(WebGLActiveInfo, WEBGLACTIVEINFO_PRIVATE_IID)
-
-#define WEBGLEXTENSION_PRIVATE_IID \
-    {0x457dd0b2, 0x9f77, 0x4c23, {0x95, 0x70, 0x9d, 0x62, 0x65, 0xc1, 0xa4, 0x81}}
-class WebGLExtension :
-    public nsIWebGLExtension,
-    public WebGLContextBoundObject,
-    public WebGLZeroingObject<2> // WebGLExtensions probably won't have many owers and
-                                 // can be very small objects. Also, we have a static array of those. So, saving some memory
-                                 // by using a small value such as 2 here.
+class WebGLExtension
+    : public nsIWebGLExtension
+    , public WebGLContextBoundObject
 {
 public:
     WebGLExtension(WebGLContext *baseContext)
         : WebGLContextBoundObject(baseContext)
     {}
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLEXTENSION
-
-    NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLEXTENSION_PRIVATE_IID)
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(WebGLExtension, WEBGLACTIVEINFO_PRIVATE_IID)
-
 /**
  ** Template implementations
  **/
 
 /* Helper function taking a BaseInterfaceType pointer, casting it to
  * ConcreteObjectType and performing some checks along the way.
  *
  * By default, null (respectively: deleted) aInterface pointers are
@@ -2242,38 +2351,30 @@ WebGLContext::GetConcreteObject(const ch
                 ErrorInvalidValue("%s: null object passed as argument", info);
             return false;
         }
     }
 
     if (isNull)
         *isNull = false;
 
-#ifdef DEBUG
-    {
-        // once bug 694114 is implemented, we want to replace this by a static assertion, without #ifdef DEBUG
-        nsresult rv = NS_OK;
-        nsCOMPtr<ConcreteObjectType> tmp(do_QueryInterface(aInterface, &rv));
-        NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv),
-                          "QueryInterface failed. WebGL objects are builtinclass, so this should never happen. "
-                          "Please file a bug at bugzilla.mozilla.org -> Core -> Canvas:WebGL and link to the present page.");
-    }
-#endif
-    
-    *aConcreteObject = static_cast<ConcreteObjectType*>(aInterface);
+    // the key to why this static_cast is all we need to do (as opposed to the QueryInterface check we used to do)
+    // is that since bug 638328, WebGL interfaces are marked 'builtinclass' in the IDL
+    ConcreteObjectType *concrete = static_cast<ConcreteObjectType*>(aInterface);
+    *aConcreteObject = concrete;
 
-    if (!(*aConcreteObject)->IsCompatibleWithContext(this)) {
+    if (!concrete->IsCompatibleWithContext(this)) {
         // the object doesn't belong to this WebGLContext
         if (generateErrors)
             ErrorInvalidOperation("%s: object from different WebGL context (or older generation of this one) "
                                   "passed as argument", info);
         return false;
     }
 
-    if ((*aConcreteObject)->Deleted()) {
+    if (concrete->IsDeleted()) {
         if (NS_LIKELY(isDeleted)) {
             // non-null isDeleted means that the caller will accept a deleted arg
             *isDeleted = true;
             return true;
         } else {
             if (generateErrors)
                 ErrorInvalidValue("%s: deleted object passed as argument", info);
             return false;
@@ -2368,159 +2469,100 @@ class WebGLMemoryReporter
         ContextsArrayType & contexts = Contexts();
         contexts.RemoveElement(c);
         if (contexts.IsEmpty()) {
             delete sUniqueInstance;
             sUniqueInstance = nsnull;
         }
     }
 
-    static PLDHashOperator TextureMemoryUsageFunction(const PRUint32&, WebGLTexture *aValue, void *aData)
-    {
-        PRInt64 *result = (PRInt64*) aData;
-        *result += aValue->MemoryUsage();
-        return PL_DHASH_NEXT;
-    }
-
     static PRInt64 GetTextureMemoryUsed() {
         const ContextsArrayType & contexts = Contexts();
         PRInt64 result = 0;
-        for(size_t i = 0; i < contexts.Length(); ++i) {
-            PRInt64 textureMemoryUsageForThisContext = 0;
-            contexts[i]->mMapTextures.EnumerateRead(TextureMemoryUsageFunction, &textureMemoryUsageForThisContext);
-            result += textureMemoryUsageForThisContext;
-        }
+        for(size_t i = 0; i < contexts.Length(); ++i)
+            for (size_t t = 0; t < contexts[i]->mTextures.Length(); ++t)
+              result += contexts[i]->mTextures[t]->MemoryUsage();
         return result;
     }
-    
+
     static PRInt64 GetTextureCount() {
         const ContextsArrayType & contexts = Contexts();
         PRInt64 result = 0;
-        for(size_t i = 0; i < contexts.Length(); ++i) {
-            result += contexts[i]->mMapTextures.Count();
-        }
+        for(size_t i = 0; i < contexts.Length(); ++i)
+            result += contexts[i]->mTextures.Length();
         return result;
     }
-    
-    static PLDHashOperator BufferMemoryUsageFunction(const PRUint32&, WebGLBuffer *aValue, void *aData)
-    {
-        PRInt64 *result = (PRInt64*) aData;
-        *result += aValue->ByteLength();
-        return PL_DHASH_NEXT;
-    }
 
     static PRInt64 GetBufferMemoryUsed() {
         const ContextsArrayType & contexts = Contexts();
         PRInt64 result = 0;
-        for(size_t i = 0; i < contexts.Length(); ++i) {
-            PRInt64 bufferMemoryUsageForThisContext = 0;
-            contexts[i]->mMapBuffers.EnumerateRead(BufferMemoryUsageFunction, &bufferMemoryUsageForThisContext);
-            result += bufferMemoryUsageForThisContext;
-        }
+        for(size_t i = 0; i < contexts.Length(); ++i)
+            for (size_t b = 0; b < contexts[i]->mBuffers.Length(); ++b)
+                result += contexts[i]->mBuffers[b]->ByteLength();
         return result;
     }
-    
-    static PLDHashOperator BufferCacheMemoryUsageFunction(const PRUint32&, WebGLBuffer *aValue, void *aData)
-    {
-        PRInt64 *result = (PRInt64*) aData;
-        // element array buffers are cached in the WebGL implementation. Other buffers aren't.
-        if (aValue->Target() == LOCAL_GL_ELEMENT_ARRAY_BUFFER)
-          *result += aValue->ByteLength();
-        return PL_DHASH_NEXT;
-    }
 
     static PRInt64 GetBufferCacheMemoryUsed() {
         const ContextsArrayType & contexts = Contexts();
         PRInt64 result = 0;
-        for(size_t i = 0; i < contexts.Length(); ++i) {
-            PRInt64 bufferCacheMemoryUsageForThisContext = 0;
-            contexts[i]->mMapBuffers.EnumerateRead(BufferCacheMemoryUsageFunction, &bufferCacheMemoryUsageForThisContext);
-            result += bufferCacheMemoryUsageForThisContext;
-        }
+        for(size_t i = 0; i < contexts.Length(); ++i)
+            for (size_t b = 0; b < contexts[i]->mBuffers.Length(); ++b)
+                if (contexts[i]->mBuffers[b]->Target() == LOCAL_GL_ELEMENT_ARRAY_BUFFER)
+                    result += contexts[i]->mBuffers[b]->ByteLength();
         return result;
     }
 
     static PRInt64 GetBufferCount() {
         const ContextsArrayType & contexts = Contexts();
         PRInt64 result = 0;
-        for(size_t i = 0; i < contexts.Length(); ++i) {
-            result += contexts[i]->mMapBuffers.Count();
-        }
+        for(size_t i = 0; i < contexts.Length(); ++i)
+            result += contexts[i]->mBuffers.Length();
         return result;
     }
-    
-    static PLDHashOperator RenderbufferMemoryUsageFunction(const PRUint32&, WebGLRenderbuffer *aValue, void *aData)
-    {
-        PRInt64 *result = (PRInt64*) aData;
-        *result += aValue->MemoryUsage();
-        return PL_DHASH_NEXT;
-    }
 
     static PRInt64 GetRenderbufferMemoryUsed() {
         const ContextsArrayType & contexts = Contexts();
         PRInt64 result = 0;
-        for(size_t i = 0; i < contexts.Length(); ++i) {
-            PRInt64 bufferMemoryUsageForThisContext = 0;
-            contexts[i]->mMapRenderbuffers.EnumerateRead(RenderbufferMemoryUsageFunction, &bufferMemoryUsageForThisContext);
-            result += bufferMemoryUsageForThisContext;
-        }
+        for(size_t i = 0; i < contexts.Length(); ++i)
+            for (size_t r = 0; r < contexts[i]->mRenderbuffers.Length(); ++r)
+              result += contexts[i]->mRenderbuffers[r]->MemoryUsage();
         return result;
     }
-    
+
     static PRInt64 GetRenderbufferCount() {
         const ContextsArrayType & contexts = Contexts();
         PRInt64 result = 0;
-        for(size_t i = 0; i < contexts.Length(); ++i) {
-            result += contexts[i]->mMapRenderbuffers.Count();
-        }
+        for(size_t i = 0; i < contexts.Length(); ++i)
+            result += contexts[i]->mRenderbuffers.Length();
         return result;
     }
 
-    static PLDHashOperator ShaderSourceSizeFunction(const PRUint32&, WebGLShader *aValue, void *aData)
-    {
-        PRInt64 *result = (PRInt64*) aData;
-        *result += aValue->Source().Length();
-        return PL_DHASH_NEXT;
-    }
-
-    static PLDHashOperator ShaderTranslationLogSizeFunction(const PRUint32&, WebGLShader *aValue, void *aData)
-    {
-        PRInt64 *result = (PRInt64*) aData;
-        *result += aValue->TranslationLog().Length();
-        return PL_DHASH_NEXT;
-    }
-
     static PRInt64 GetShaderSourcesSize() {
         const ContextsArrayType & contexts = Contexts();
         PRInt64 result = 0;
-        for(size_t i = 0; i < contexts.Length(); ++i) {
-            PRInt64 shaderSourcesSizeForThisContext = 0;
-            contexts[i]->mMapShaders.EnumerateRead(ShaderSourceSizeFunction, &shaderSourcesSizeForThisContext);
-            result += shaderSourcesSizeForThisContext;
-        }
+        for(size_t i = 0; i < contexts.Length(); ++i)
+            for (size_t s = 0; s < contexts[i]->mShaders.Length(); ++s)
+                result += contexts[i]->mShaders[s]->Source().Length();
         return result;
     }
-    
+
     static PRInt64 GetShaderTranslationLogsSize() {
         const ContextsArrayType & contexts = Contexts();
         PRInt64 result = 0;
-        for(size_t i = 0; i < contexts.Length(); ++i) {
-            PRInt64 shaderTranslationLogsSizeForThisContext = 0;
-            contexts[i]->mMapShaders.EnumerateRead(ShaderTranslationLogSizeFunction, &shaderTranslationLogsSizeForThisContext);
-            result += shaderTranslationLogsSizeForThisContext;
-        }
+        for(size_t i = 0; i < contexts.Length(); ++i)
+            for (size_t s = 0; s < contexts[i]->mShaders.Length(); ++s)
+                result += contexts[i]->mShaders[s]->TranslationLog().Length();
         return result;
     }
-    
+
     static PRInt64 GetShaderCount() {
         const ContextsArrayType & contexts = Contexts();
         PRInt64 result = 0;
-        for(size_t i = 0; i < contexts.Length(); ++i) {
-            result += contexts[i]->mMapShaders.Count();
-        }
+        for(size_t i = 0; i < contexts.Length(); ++i)
+            result += contexts[i]->mShaders.Length();
         return result;
     }
 
     static PRInt64 GetContextCount() {
         return Contexts().Length();
     }
 };
 
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -116,46 +116,30 @@ NS_IMETHODIMP WebGLContext::name(t1 a1, 
 }
 
 #define GL_SAME_METHOD_6(glname, name, t1, t2, t3, t4, t5, t6)          \
 NS_IMETHODIMP WebGLContext::name(t1 a1, t2 a2, t3 a3, t4 a4, t5 a5, t6 a6) { \
     if (mContextLost) { return NS_OK; }                                 \
     MakeContextCurrent(); gl->f##glname(a1,a2,a3,a4,a5,a6); return NS_OK; \
 }
 
-already_AddRefed<WebGLUniformLocation>
-WebGLProgram::GetUniformLocationObject(GLint glLocation)
-{
-    WebGLUniformLocation *existingLocationObject;
-    if (mMapUniformLocations.Get(glLocation, &existingLocationObject)) {
-        return existingLocationObject;
-    }
-
-    if (glLocation < 0) {
-        return nsnull;
-    }
-
-    nsRefPtr<WebGLUniformLocation> loc = new WebGLUniformLocation(mContext, this, glLocation);
-    mMapUniformLocations.Put(glLocation, loc);
-    return loc.forget();
-}
 
 //
 //  WebGL API
 //
 
 
 /* void GlActiveTexture (in GLenum texture); */
 NS_IMETHODIMP
 WebGLContext::ActiveTexture(WebGLenum texture)
 {
     if (mContextLost)
         return NS_OK;
 
-    if (texture < LOCAL_GL_TEXTURE0 || texture >= LOCAL_GL_TEXTURE0+mBound2DTextures.Length())
+    if (texture < LOCAL_GL_TEXTURE0 || texture >= LOCAL_GL_TEXTURE0 + mBound2DTextures.Length())
         return ErrorInvalidEnum("ActiveTexture: texture unit %d out of range (0..%d)",
                                 texture, mBound2DTextures.Length()-1);
 
     MakeContextCurrent();
     mActiveTexture = texture - LOCAL_GL_TEXTURE0;
     gl->fActiveTexture(texture);
     return NS_OK;
 }
@@ -181,20 +165,16 @@ WebGLContext::AttachShader(nsIWebGLProgr
     // attached.  This renders the next test somewhat moot, but we'll
     // leave it for when we support more than one shader of each type.
     if (program->HasAttachedShaderOfType(shader->ShaderType()))
         return ErrorInvalidOperation("AttachShader: only one of each type of shader may be attached to a program");
 
     if (!program->AttachShader(shader))
         return ErrorInvalidOperation("AttachShader: shader is already attached");
 
-    MakeContextCurrent();
-
-    gl->fAttachShader(progname, shadername);
-
     return NS_OK;
 }
 
 
 NS_IMETHODIMP
 WebGLContext::BindAttribLocation(nsIWebGLProgram *pobj, WebGLuint location, const nsAString& name)
 {
     if (mContextLost)
@@ -1082,23 +1062,18 @@ WebGLContext::CopyTexSubImage2D(WebGLenu
 NS_IMETHODIMP
 WebGLContext::CreateProgram(nsIWebGLProgram **retval)
 {
     if (mContextLost)
         return NS_OK;
 
     *retval = nsnull;
 
-    MakeContextCurrent();
-
-    WebGLuint name = gl->fCreateProgram();
-
-    WebGLProgram *prog = new WebGLProgram(this, name);
+    WebGLProgram *prog = new WebGLProgram(this);
     NS_ADDREF(*retval = prog);
-    mMapPrograms.Put(name, prog);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::CreateShader(WebGLenum type, nsIWebGLShader **retval)
 {
     if (mContextLost)
@@ -1107,23 +1082,18 @@ WebGLContext::CreateShader(WebGLenum typ
     *retval = nsnull;
 
     if (type != LOCAL_GL_VERTEX_SHADER &&
         type != LOCAL_GL_FRAGMENT_SHADER)
     {
         return ErrorInvalidEnumInfo("createShader: type", type);
     }
 
-    MakeContextCurrent();
-
-    WebGLuint name = gl->fCreateShader(type);
-
-    WebGLShader *shader = new WebGLShader(this, name, type);
+    WebGLShader *shader = new WebGLShader(this, type);
     NS_ADDREF(*retval = shader);
-    mMapShaders.Put(name, shader);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::CullFace(WebGLenum face)
 {
     if (mContextLost)
@@ -1147,111 +1117,109 @@ WebGLContext::DeleteBuffer(nsIWebGLBuffe
     WebGLBuffer *buf;
     bool isNull, isDeleted;
     if (!GetConcreteObjectAndGLName("deleteBuffer", bobj, &buf, &bufname, &isNull, &isDeleted))
         return NS_OK;
 
     if (isNull || isDeleted)
         return NS_OK;
 
-    MakeContextCurrent();
-
-    gl->fDeleteBuffers(1, &bufname);
-    buf->Delete();
-    mMapBuffers.Remove(bufname);
+    if (mBoundArrayBuffer == buf)
+        BindBuffer(LOCAL_GL_ARRAY_BUFFER, nsnull);
+    if (mBoundElementArrayBuffer == buf)
+        BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, nsnull);
+
+    for (int i = 0; i < mGLMaxVertexAttribs; i++) {
+        if (mAttribBuffers[i].buf == buf)
+            mAttribBuffers[i].buf = nsnull;
+    }
+
+    buf->RequestDelete();
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::DeleteFramebuffer(nsIWebGLFramebuffer *fbobj)
 {
     if (mContextLost)
         return NS_OK;
 
+    WebGLFramebuffer *fbuf;
     WebGLuint fbufname;
-    WebGLFramebuffer *fbuf;
     bool isNull, isDeleted;
     if (!GetConcreteObjectAndGLName("deleteFramebuffer", fbobj, &fbuf, &fbufname, &isNull, &isDeleted))
         return NS_OK;
 
     if (isNull || isDeleted)
         return NS_OK;
 
-    MakeContextCurrent();
-
-    gl->fDeleteFramebuffers(1, &fbufname);
-    fbuf->Delete();
-    mMapFramebuffers.Remove(fbufname);
-
-    if (mBoundFramebuffer && mBoundFramebuffer->GLName() == fbufname)
-        mBoundFramebuffer = NULL;
+    fbuf->RequestDelete();
+
+    if (mBoundFramebuffer == fbuf)
+        BindFramebuffer(LOCAL_GL_FRAMEBUFFER, nsnull);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::DeleteRenderbuffer(nsIWebGLRenderbuffer *rbobj)
 {
     if (mContextLost)
         return NS_OK;
 
+    WebGLRenderbuffer *rbuf;
     WebGLuint rbufname;
-    WebGLRenderbuffer *rbuf;
     bool isNull, isDeleted;
     if (!GetConcreteObjectAndGLName("deleteRenderbuffer", rbobj, &rbuf, &rbufname, &isNull, &isDeleted))
         return NS_OK;
 
     if (isNull || isDeleted)
         return NS_OK;
 
-    MakeContextCurrent();
-
-    // XXX we need to track renderbuffer attachments; from glDeleteRenderbuffers man page:
-
-    /*
-            If a renderbuffer object that is currently bound is deleted, the binding reverts
-            to 0 (the absence of any renderbuffer object). Additionally, special care
-            must be taken when deleting a renderbuffer object if the image of the renderbuffer
-            is attached to a framebuffer object. In this case, if the deleted renderbuffer object is
-            attached to the currently bound framebuffer object, it is 
-            automatically detached.  However, attachments to any other framebuffer objects are the
-            responsibility of the application.
-    */
-
-    gl->fDeleteRenderbuffers(1, &rbufname);
-    rbuf->Delete();
-    mMapRenderbuffers.Remove(rbufname);
-
-    if (mBoundRenderbuffer && mBoundRenderbuffer->GLName() == rbufname)
-        mBoundRenderbuffer = NULL;
+    if (mBoundFramebuffer)
+        mBoundFramebuffer->DetachRenderbuffer(rbuf);
+
+    if (mBoundRenderbuffer == rbuf)
+        BindRenderbuffer(LOCAL_GL_RENDERBUFFER, nsnull);
+
+    rbuf->RequestDelete();
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::DeleteTexture(nsIWebGLTexture *tobj)
 {
     if (mContextLost)
         return NS_OK;
 
+    WebGLTexture *tex;
     WebGLuint texname;
-    WebGLTexture *tex;
     bool isNull, isDeleted;
     if (!GetConcreteObjectAndGLName("deleteTexture", tobj, &tex, &texname, &isNull, &isDeleted))
         return NS_OK;
 
     if (isNull || isDeleted)
         return NS_OK;
 
-    MakeContextCurrent();
-
-    gl->fDeleteTextures(1, &texname);
-    tex->Delete();
-    mMapTextures.Remove(texname);
+    if (mBoundFramebuffer)
+        mBoundFramebuffer->DetachTexture(tex);
+
+    for (int i = 0; i < mGLMaxTextureUnits; i++) {
+        if ((tex->Target() == LOCAL_GL_TEXTURE_2D && mBound2DTextures[i] == tex) ||
+            (tex->Target() == LOCAL_GL_TEXTURE_CUBE_MAP && mBoundCubeMapTextures[i] == tex))
+        {
+            ActiveTexture(LOCAL_GL_TEXTURE0 + i);
+            BindTexture(tex->Target(), nsnull);
+        }
+    }
+    ActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture);
+
+    tex->RequestDelete();
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::DeleteProgram(nsIWebGLProgram *pobj)
 {
     if (mContextLost)
@@ -1261,22 +1229,17 @@ WebGLContext::DeleteProgram(nsIWebGLProg
     WebGLProgram *prog;
     bool isNull, isDeleted;
     if (!GetConcreteObjectAndGLName("deleteProgram", pobj, &prog, &progname, &isNull, &isDeleted))
         return NS_OK;
 
     if (isNull || isDeleted)
         return NS_OK;
 
-    MakeContextCurrent();
-
-    gl->fDeleteProgram(progname);
-
-    prog->DeleteWhenNotCurrent();
-    mMapPrograms.Remove(progname);
+    prog->RequestDelete();
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::DeleteShader(nsIWebGLShader *sobj)
 {
     if (mContextLost)
@@ -1286,21 +1249,17 @@ WebGLContext::DeleteShader(nsIWebGLShade
     WebGLShader *shader;
     bool isNull, isDeleted;
     if (!GetConcreteObjectAndGLName("deleteShader", sobj, &shader, &shadername, &isNull, &isDeleted))
         return NS_OK;
 
     if (isNull || isDeleted)
         return NS_OK;
 
-    MakeContextCurrent();
-
-    gl->fDeleteShader(shadername);
-    shader->DeleteWhenNotAttached();
-    mMapShaders.Remove(shadername);
+    shader->RequestDelete();
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::DetachShader(nsIWebGLProgram *pobj, nsIWebGLShader *shobj)
 {
     if (mContextLost)
@@ -1314,22 +1273,16 @@ WebGLContext::DetachShader(nsIWebGLProgr
         !GetConcreteObjectAndGLName("detachShader: shader", shobj, &shader, &shadername, nsnull, &shaderDeleted))
         return NS_OK;
 
     // shaderDeleted is ignored -- it's valid to attempt to detach a
     // deleted shader, since it's still a shader
     if (!program->DetachShader(shader))
         return ErrorInvalidOperation("DetachShader: shader is not attached");
 
-    MakeContextCurrent();
-
-    gl->fDetachShader(progname, shadername);
-
-    shader->DetachedFromProgram();
-
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::DepthFunc(WebGLenum func)
 {
     if (mContextLost)
         return NS_OK;
@@ -2550,44 +2503,34 @@ WebGLContext::GetRenderbufferParameter(W
 NS_IMETHODIMP
 WebGLContext::CreateBuffer(nsIWebGLBuffer **retval)
 {
     if (mContextLost)
         return NS_OK;
 
     *retval = nsnull;
 
-    MakeContextCurrent();
-
-    WebGLuint name;
-    gl->fGenBuffers(1, &name);
-
-    WebGLBuffer *globj = new WebGLBuffer(this, name);
+    WebGLBuffer *globj = new WebGLBuffer(this);
     NS_ADDREF(*retval = globj);
-    mMapBuffers.Put(name, globj);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::CreateTexture(nsIWebGLTexture **retval)
 {
     if (mContextLost)
         return NS_OK;
 
     *retval = nsnull;
 
     MakeContextCurrent();
 
-    WebGLuint name;
-    gl->fGenTextures(1, &name);
-
-    WebGLTexture *globj = new WebGLTexture(this, name);
+    WebGLTexture *globj = new WebGLTexture(this);
     NS_ADDREF(*retval = globj);
-    mMapTextures.Put(name, globj);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::GetError(WebGLenum *_retval)
 {
     if (!mContextLost) {
@@ -2606,17 +2549,18 @@ WebGLContext::GetProgramParameter(nsIWeb
 {
     if (mContextLost)
         return NS_OK;
 
     *retval = nsnull;
 
     WebGLuint progname;
     bool isDeleted;
-    if (!GetGLName<WebGLProgram>("getProgramParameter: program", pobj, &progname, nsnull, &isDeleted))
+    WebGLProgram *prog;
+    if (!GetConcreteObjectAndGLName("getProgramParameter: program", pobj, &prog, &progname, nsnull, &isDeleted))
         return NS_OK;
 
     nsCOMPtr<nsIWritableVariant> wrval = do_CreateInstance("@mozilla.org/variant;1");
     NS_ENSURE_TRUE(wrval, NS_ERROR_FAILURE);
 
     MakeContextCurrent();
 
     switch (pname) {
@@ -2629,16 +2573,18 @@ WebGLContext::GetProgramParameter(nsIWeb
         case LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH:
         {
             GLint i = 0;
             gl->fGetProgramiv(progname, pname, &i);
             wrval->SetAsInt32(i);
         }
             break;
         case LOCAL_GL_DELETE_STATUS:
+            wrval->SetAsBool(prog->IsDeleteRequested());
+            break;
         case LOCAL_GL_LINK_STATUS:
         {
             GLint i = 0;
             gl->fGetProgramiv(progname, pname, &i);
             wrval->SetAsBool(bool(i));
         }
             break;
         case LOCAL_GL_VALIDATE_STATUS:
@@ -2987,19 +2933,20 @@ WebGLContext::GetUniformLocation(nsIWebG
 
     if (!ValidateGLSLVariableName(name, "getUniformLocation"))
         return NS_OK; 
 
     MakeContextCurrent();
 
     GLint intlocation = gl->fGetUniformLocation(progname, NS_LossyConvertUTF16toASCII(name).get());
 
-    nsRefPtr<nsIWebGLUniformLocation> loc = prog->GetUniformLocationObject(intlocation);
-    *retval = loc.forget().get();
-
+    WebGLUniformLocation *loc = nsnull;
+    if (intlocation >= 0)
+        NS_ADDREF(loc = new WebGLUniformLocation(this, prog, intlocation));
+    *retval = loc;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::GetVertexAttrib(WebGLuint index, WebGLenum pname, nsIVariant **retval)
 {
     if (mContextLost)
         return NS_OK;
@@ -3118,133 +3065,105 @@ WebGLContext::IsBuffer(nsIWebGLBuffer *b
     {
         *retval = false;
         return NS_OK;
     }
 
     bool isDeleted;
     WebGLuint buffername;
     WebGLBuffer *buffer;
-    bool ok = GetConcreteObjectAndGLName("isBuffer", bobj, &buffer, &buffername, nsnull, &isDeleted) && 
-                !isDeleted &&
-                buffer->HasEverBeenBound();
-    if (ok) {
-        MakeContextCurrent();
-        ok = gl->fIsBuffer(buffername);
-    }
-
-    *retval = ok;
+    *retval = GetConcreteObjectAndGLName("isBuffer", bobj, &buffer, &buffername, nsnull, &isDeleted) && 
+              !isDeleted &&
+              buffer->HasEverBeenBound();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::IsFramebuffer(nsIWebGLFramebuffer *fbobj, WebGLboolean *retval)
 {
     if (mContextLost)
     {
         *retval = false;
         return NS_OK;
     }
 
     bool isDeleted;
     WebGLuint fbname;
     WebGLFramebuffer *fb;
-    bool ok = GetConcreteObjectAndGLName("isFramebuffer", fbobj, &fb, &fbname, nsnull, &isDeleted) &&
-                !isDeleted &&
-                fb->HasEverBeenBound();
-    if (ok) {
-        MakeContextCurrent();
-        ok = gl->fIsFramebuffer(fbname);
-    }
-
-    *retval = ok;
+    *retval = GetConcreteObjectAndGLName("isFramebuffer", fbobj, &fb, &fbname, nsnull, &isDeleted) &&
+              !isDeleted &&
+              fb->HasEverBeenBound();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::IsProgram(nsIWebGLProgram *pobj, WebGLboolean *retval)
 {
     if (mContextLost)
     {
         *retval = false;
         return NS_OK;
     }
 
     bool isDeleted;
     WebGLProgram *prog = nsnull;
-    bool ok = GetConcreteObject("isProgram", pobj, &prog, nsnull, &isDeleted, false) &&
-                !isDeleted;
-
-    *retval = ok;
+    *retval = GetConcreteObject("isProgram", pobj, &prog, nsnull, &isDeleted, false) &&
+              !isDeleted;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::IsRenderbuffer(nsIWebGLRenderbuffer *rbobj, WebGLboolean *retval)
 {
     if (mContextLost)
     {
         *retval = false;
         return NS_OK;
     }
 
     bool isDeleted;
     WebGLuint rbname;
     WebGLRenderbuffer *rb;
-    bool ok = GetConcreteObjectAndGLName("isRenderBuffer", rbobj, &rb, &rbname, nsnull, &isDeleted) &&
-                !isDeleted &&
-                rb->HasEverBeenBound();
-    if (ok) {
-        MakeContextCurrent();
-        ok = gl->fIsRenderbuffer(rbname);
-    }
-
-    *retval = ok;
+    *retval = GetConcreteObjectAndGLName("isRenderBuffer", rbobj, &rb, &rbname, nsnull, &isDeleted) &&
+              !isDeleted &&
+              rb->HasEverBeenBound();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::IsShader(nsIWebGLShader *sobj, WebGLboolean *retval)
 {
     if (mContextLost)
     {
         *retval = false;
         return NS_OK;
     }
 
     bool isDeleted;
     WebGLShader *shader = nsnull;
-    bool ok = GetConcreteObject("isShader", sobj, &shader, nsnull, &isDeleted, false) &&
-                !isDeleted;
-
-    *retval = ok;
+    *retval = GetConcreteObject("isShader", sobj, &shader, nsnull, &isDeleted, false) &&
+              !isDeleted;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::IsTexture(nsIWebGLTexture *tobj, WebGLboolean *retval)
 {
     if (mContextLost)
     {
         *retval = false;
         return NS_OK;
     }
 
     bool isDeleted;
     WebGLuint texname;
     WebGLTexture *tex;
-    bool ok = GetConcreteObjectAndGLName("isTexture", tobj, &tex, &texname, nsnull, &isDeleted) &&
-                !isDeleted &&
-                tex->HasEverBeenBound();
-    if (ok) {
-        MakeContextCurrent();
-        ok = gl->fIsTexture(texname);
-    }
-
-    *retval = ok;
+    *retval = GetConcreteObjectAndGLName("isTexture", tobj, &tex, &texname, nsnull, &isDeleted) &&
+              !isDeleted &&
+              tex->HasEverBeenBound();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::IsEnabled(WebGLenum cap, WebGLboolean *retval)
 {
     if (mContextLost)
     {
@@ -4331,22 +4250,18 @@ WebGLContext::UseProgram(nsIWebGLProgram
 
     MakeContextCurrent();
 
     if (prog && !prog->LinkStatus())
         return ErrorInvalidOperation("UseProgram: program was not linked successfully");
 
     gl->fUseProgram(progname);
 
-    WebGLProgram* previous = mCurrentProgram;
     mCurrentProgram = prog;
 
-    if (previous)
-        previous->NoLongerCurrent();
-
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::ValidateProgram(nsIWebGLProgram *pobj)
 {
     if (mContextLost)
         return NS_OK;
@@ -4371,44 +4286,32 @@ WebGLContext::ValidateProgram(nsIWebGLPr
 NS_IMETHODIMP
 WebGLContext::CreateFramebuffer(nsIWebGLFramebuffer **retval)
 {
     if (mContextLost)
         return NS_OK;
 
     *retval = 0;
 
-    MakeContextCurrent();
-
-    GLuint name;
-    gl->fGenFramebuffers(1, &name);
-
-    WebGLFramebuffer *globj = new WebGLFramebuffer(this, name);
+    WebGLFramebuffer *globj = new WebGLFramebuffer(this);
     NS_ADDREF(*retval = globj);
-    mMapFramebuffers.Put(name, globj);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::CreateRenderbuffer(nsIWebGLRenderbuffer **retval)
 {
     if (mContextLost)
         return NS_OK;
 
     *retval = 0;
 
-    MakeContextCurrent();
-
-    GLuint name;
-    gl->fGenRenderbuffers(1, &name);
-
-    WebGLRenderbuffer *globj = new WebGLRenderbuffer(this, name);
+    WebGLRenderbuffer *globj = new WebGLRenderbuffer(this);
     NS_ADDREF(*retval = globj);
-    mMapRenderbuffers.Put(name, globj);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::Viewport(WebGLint x, WebGLint y, WebGLsizei width, WebGLsizei height)
 {
     if (mContextLost)
@@ -4561,16 +4464,18 @@ WebGLContext::GetShaderParameter(nsIWebG
         }
             break;
         case LOCAL_GL_SHADER_SOURCE_LENGTH:
         {
             wrval->SetAsInt32(PRInt32(shader->Source().Length()) + 1);
         }
             break;
         case LOCAL_GL_DELETE_STATUS:
+            wrval->SetAsBool(shader->IsDeleteRequested());
+            break;
         case LOCAL_GL_COMPILE_STATUS:
         {
             GLint i = 0;
             gl->fGetShaderiv(shadername, pname, &i);
             wrval->SetAsBool(bool(i));
         }
             break;
         default:
--- a/content/canvas/src/WebGLContextValidate.cpp
+++ b/content/canvas/src/WebGLContextValidate.cpp
@@ -52,39 +52,39 @@
 using namespace mozilla;
 
 /*
  * Pull all the data out of the program that will be used by validate later on
  */
 bool
 WebGLProgram::UpdateInfo(gl::GLContext *gl)
 {
-    gl->fGetProgramiv(mName, LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &mAttribMaxNameLength);
-    gl->fGetProgramiv(mName, LOCAL_GL_ACTIVE_UNIFORM_MAX_LENGTH, &mUniformMaxNameLength);
-    gl->fGetProgramiv(mName, LOCAL_GL_ACTIVE_UNIFORMS, &mUniformCount);
-    gl->fGetProgramiv(mName, LOCAL_GL_ACTIVE_ATTRIBUTES, &mAttribCount);
+    gl->fGetProgramiv(mGLName, LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &mAttribMaxNameLength);
+    gl->fGetProgramiv(mGLName, LOCAL_GL_ACTIVE_UNIFORM_MAX_LENGTH, &mUniformMaxNameLength);
+    gl->fGetProgramiv(mGLName, LOCAL_GL_ACTIVE_UNIFORMS, &mUniformCount);
+    gl->fGetProgramiv(mGLName, LOCAL_GL_ACTIVE_ATTRIBUTES, &mAttribCount);
 
     GLint numVertexAttribs;
     if (mContext->MinCapabilityMode())  {
         numVertexAttribs = MINVALUE_GL_MAX_VERTEX_ATTRIBS;
     } else {
         gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_ATTRIBS, &numVertexAttribs);
     }
     mAttribsInUse.clear();
     mAttribsInUse.resize(numVertexAttribs);
 
     nsAutoArrayPtr<char> nameBuf(new char[mAttribMaxNameLength]);
 
     for (int i = 0; i < mAttribCount; ++i) {
         GLint attrnamelen;
         GLint attrsize;
         GLenum attrtype;
-        gl->fGetActiveAttrib(mName, i, mAttribMaxNameLength, &attrnamelen, &attrsize, &attrtype, nameBuf);
+        gl->fGetActiveAttrib(mGLName, i, mAttribMaxNameLength, &attrnamelen, &attrsize, &attrtype, nameBuf);
         if (attrnamelen > 0) {
-            GLint loc = gl->fGetAttribLocation(mName, nameBuf);
+            GLint loc = gl->fGetAttribLocation(mGLName, nameBuf);
             mAttribsInUse[loc] = true;
         }
     }
 
     return true;
 }
 
 /*
@@ -510,34 +510,26 @@ WebGLContext::InitAndValidateGL()
     mMinCapability = Preferences::GetBool("webgl.min_capability_mode", false);
     mDisableExtensions = Preferences::GetBool("webgl.disable-extensions", false);
 
     mActiveTexture = 0;
     mWebGLError = LOCAL_GL_NO_ERROR;
 
     mAttribBuffers.Clear();
 
-    mUniformTextures.Clear();
     mBound2DTextures.Clear();
     mBoundCubeMapTextures.Clear();
 
     mBoundArrayBuffer = nsnull;
     mBoundElementArrayBuffer = nsnull;
     mCurrentProgram = nsnull;
 
     mBoundFramebuffer = nsnull;
     mBoundRenderbuffer = nsnull;
 
-    mMapTextures.Clear();
-    mMapBuffers.Clear();
-    mMapPrograms.Clear();
-    mMapShaders.Clear();
-    mMapFramebuffers.Clear();
-    mMapRenderbuffers.Clear();
-
     MakeContextCurrent();
 
     // on desktop OpenGL, we always keep vertex attrib 0 array enabled
     if (!gl->IsGLES2()) {
         gl->fEnableVertexAttribArray(0);
     }
 
     if (MinCapabilityMode()) {
--- a/content/canvas/src/WebGLExtensions.h
+++ b/content/canvas/src/WebGLExtensions.h
@@ -39,47 +39,35 @@
 #ifndef WEBGLEXTENSIONS_H_
 #define WEBGLEXTENSIONS_H_
 
 namespace mozilla {
 
 class WebGLExtensionLoseContext;
 class WebGLExtensionStandardDerivatives;
 
-#define WEBGLEXTENSIONLOSECONTEXT_PRIVATE_IID \
-    {0xb0afc2eb, 0x0895, 0x4509, {0x98, 0xde, 0x5c, 0x38, 0x3d, 0x16, 0x06, 0x94}}
 class WebGLExtensionLoseContext :
     public nsIWebGLExtensionLoseContext,
     public WebGLExtension
 {
 public:
     WebGLExtensionLoseContext(WebGLContext*);
     virtual ~WebGLExtensionLoseContext();
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLEXTENSIONLOSECONTEXT
-
-    NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLEXTENSIONLOSECONTEXT_PRIVATE_IID)
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(WebGLExtensionLoseContext, WEBGLACTIVEINFO_PRIVATE_IID)
-
-#define WEBGLEXTENSIONSTANDARDDERIVATIVES_PRIVATE_IID \
-    {0x3de3dfd9, 0x864a, 0x4e4c, {0x98, 0x9b, 0x29, 0x77, 0xea, 0xa8, 0x0b, 0x7b}}
 class WebGLExtensionStandardDerivatives :
     public nsIWebGLExtensionStandardDerivatives,
     public WebGLExtension
 {
 public:
     WebGLExtensionStandardDerivatives(WebGLContext* context);
     virtual ~WebGLExtensionStandardDerivatives();
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLEXTENSION
-
-    NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLEXTENSIONSTANDARDDERIVATIVES_PRIVATE_IID)
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(WebGLExtensionStandardDerivatives, WEBGLACTIVEINFO_PRIVATE_IID)
-
 }
 
 #endif // WEBGLEXTENSIONS_H_
--- a/content/canvas/test/webgl/README.mozilla
+++ b/content/canvas/test/webgl/README.mozilla
@@ -1,9 +1,9 @@
-This is a local copy of the WebGL conformance suite, SVN revision 15981
+This is a local copy of the WebGL conformance suite, SVN revision 16237
 
 The canonical location for this testsuite is:
 
   https://cvs.khronos.org/svn/repos/registry/trunk/public/webgl/sdk/tests
 
 All files and directories in this directory, with the exceptions listed below, come from
 upstream and should not be modified without corresponding upstream fixes and/or a
 patch file in this directory. The exceptions (the Mozilla-specific files) are:
--- a/content/canvas/test/webgl/conformance/context/context-lost-restored.html
+++ b/content/canvas/test/webgl/conformance/context/context-lost-restored.html
@@ -6,42 +6,43 @@
 <script src="../../resources/js-test-pre.js"></script>
 <script src="../resources/webgl-test.js"></script>
 <script src="../resources/webgl-test-utils.js"></script>
 <script>
 var wtu = WebGLTestUtils;
 var canvas;
 var gl;
 var shouldGenerateGLError;
-var extension_name = "WEBKIT_lose_context";
+var extension_name = "WEBGL_EXT_lose_context";
 var extension;
 var bufferObjects;
 var program;
 var texture;
 var texColor = [255, 10, 20, 255];
 var allowRestore;
+var contextLostEventFired;
+var contextRestoredEventFired;
 
 function init()
 {
     if (window.initNonKhronosFramework) {
         window.initNonKhronosFramework(true);
     }
 
     description("Tests behavior under a restored context.");
 
     shouldGenerateGLError = wtu.shouldGenerateGLError;
-    runTests();
+    testLosingContext();
 }
 
-function runTests()
+function runTest1()
 {
-    testLosingContext();
     testLosingAndRestoringContext();
 
-    finish();
+    finishTest();
 }
 
 function setupTest()
 {
     canvas = document.createElement("canvas");
     canvas.width = 1;
     canvas.height = 1;
     gl = wtu.create3DContext(canvas);
@@ -51,45 +52,85 @@ function setupTest()
         return false;
     }
     return true;
 }
 
 function testLosingContext()
 {
     if (!setupTest())
-        return;
+        finishTest();
 
     debug("Test losing a context and inability to restore it.");
 
-    canvas.addEventListener("webglcontextlost", testLostContext);
+    canvas.addEventListener("webglcontextlost", function() {
+       testLostContext();
+       // restore the context after this event has exited.
+       setTimeout(function() {
+         // we didn't call prevent default so we should not be able to restore the context
+         shouldGenerateGLError(gl, gl.INVALID_OPERATION, "extension.restoreContext()");
+         testLosingAndRetoreingContext();
+       }, 1);
+    });
     canvas.addEventListener("webglcontextrestored", testShouldNotRestoreContext);
     allowRestore = false;
+    contextLostEventFired = false;
+    contextRestoredEventFired = false;
 
     testOriginalContext();
     extension.loseContext();
-    shouldGenerateGLError(gl, gl.INVALID_OPERATION, "extension.restoreContext()");
-    debug("");
+    // The context should be lost immediately.
+    shouldBeTrue("gl.isContextLost()");
+    shouldBe("gl.getError()", "gl.CONTEXT_LOST_WEBGL");
+    shouldBe("gl.getError()", "gl.NO_ERROR");
+    // gl methods should be no-ops
+    shouldGenerateGLError(gl, gl.NO_ERROR, gl.blendFunc(gl.TEXTURE_2D, gl.TEXTURE_CUBE_MAP));
+    // but the event should not have been fired.
+    shouldBeFalse("contextLostEventFired");
 }
 
 function testLosingAndRestoringContext()
 {
     if (!setupTest())
-        return;
+        finishTest();
 
+    debug("");
     debug("Test losing and restoring a context.");
 
-    canvas.addEventListener("webglcontextlost", testLostContext);
-    canvas.addEventListener("webglcontextrestored", testRestoredContext);
+    canvas.addEventListener("webglcontextlost", function() {
+      testLostContext();
+      // restore the context after this event has exited.
+      setTimeout(function() {
+        shouldGenerateGLError(gl, gl.NO_ERROR, "extension.restoreContext()");
+        // The context should still be lost. It will not get restored until the 
+        // webglrestorecontext event is fired.
+        shouldBeTrue("gl.isContextLost()");
+        shouldBe("gl.getError()", "gl.NO_ERROR");
+        // gl methods should still be no-ops
+        shouldGenerateGLError(gl, gl.NO_ERROR, gl.blendFunc(gl.TEXTURE_2D, gl.TEXTURE_CUBE_MAP));
+      }, 1);
+    });
+    canvas.addEventListener("webglcontextrestored", function() {
+      testRestoredContext();
+      finishTest();
+    });
     allowRestore = true;
+    contextLostEventFired = false;
+    contextRestoredEventFired = false;
 
     testOriginalContext();
     extension.loseContext();
-    shouldGenerateGLError(gl, gl.NO_ERROR, "extension.restoreContext()");
-    debug("");
+    // The context should be lost immediately.
+    shouldBeTrue("gl.isContextLost()");
+    shouldBe("gl.getError()", "gl.CONTEXT_LOST_WEBGL");
+    shouldBe("gl.getError()", "gl.NO_ERROR");
+    // gl methods should be no-ops
+    shouldGenerateGLError(gl, gl.NO_ERROR, gl.blendFunc(gl.TEXTURE_2D, gl.TEXTURE_CUBE_MAP));
+    // but the event should not have been fired.
+    shouldBeFalse("contextLostEventFired");
 }
 
 function testRendering()
 {
     gl.clearColor(0, 0, 0, 255);
     gl.colorMask(1, 1, 1, 0);
     gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
@@ -113,22 +154,23 @@ function testOriginalContext()
     shouldBe("gl.getError()", "gl.NO_ERROR");
     testRendering();
     debug("");
 }
 
 function testLostContext(e)
 {
     debug("Test lost context");
+    shouldBeFalse("contextLostEventFired");
+    contextLostEventFired = true;
     shouldBeTrue("gl.isContextLost()");
-    shouldBe("gl.getError()", "gl.CONTEXT_LOST_WEBGL");
     shouldBe("gl.getError()", "gl.NO_ERROR");
     debug("");
-    if (allowRestore)
-        e.preventDefault();
+    if (allowRestore) 
+      e.preventDefault();
 }
 
 function testShouldNotRestoreContext(e)
 {
     testFailed("Should not restore the context unless preventDefault is called on the context lost event");
     debug("");
 }
 
@@ -142,39 +184,31 @@ function testResources(expected)
 
     for (var i = 0; i < tests.length; ++i)
         shouldGenerateGLError(gl, expected, tests[i]);
 }
 
 function testRestoredContext()
 {
     debug("Test restored context");
+    shouldBeFalse("contextRestoredEventFired");
+    contextRestoredEventFired = true;
     shouldBeFalse("gl.isContextLost()");
     shouldBe("gl.getError()", "gl.NO_ERROR");
 
     // Validate that using old resources fails.
     testResources(gl.INVALID_OPERATION);
 
     testRendering();
 
     // Validate new resources created in testRendering().
     testResources(gl.NO_ERROR);
     debug("");
 }
 
-function finish() {
-    successfullyParsed = true;
-    var epilogue = document.createElement("script");
-    epilogue.onload = function() {
-        if (window.nonKhronosFrameworkNotifyDone)
-            window.nonKhronosFrameworkNotifyDone();
-    };
-    epilogue.src = "../../resources/js-test-post.js";
-    document.body.appendChild(epilogue);
-}
 
 </script>
 </head>
 <body onload="init()">
 <div id="description"></div>
 <div id="console"></div>
 </body>
 </html>
--- a/content/canvas/test/webgl/conformance/context/context-lost.html
+++ b/content/canvas/test/webgl/conformance/context/context-lost.html
@@ -6,17 +6,17 @@
 <script src="../../resources/js-test-pre.js"></script>
 <script src="../resources/webgl-test.js"></script>
 <script src="../resources/webgl-test-utils.js"></script>
 <script>
 var wtu;
 var canvas;
 var gl;
 var shouldGenerateGLError;
-var extension_name = "WEBKIT_lose_context";
+var extension_name = "WEBGL_EXT_lose_context";
 var extension;
 
 var buffer;
 var framebuffer;
 var program;
 var renderbuffer;
 var shader;
 var texture;
@@ -46,32 +46,32 @@ function init()
 
     // call testValidContext() before checking for the extension, because this is where we check
     // for the isContextLost() method, which we want to do regardless of the extension's presence.
     testValidContext();
 
     extension = gl.getExtension(extension_name);
     if (!extension) {
         debug(extension_name + " extension not found.");
-        finish();
+        finishTest();
         return;
     }
 
     canvas.addEventListener("webglcontextlost", testLostContext, false);
 
     loseContext();
 }
 
 function loseContext()
 {
     debug("");
     debug("Lose context");
 
-    // Note: this will cause the context to be lost, and the
-    // webglcontextlost event listener to be called, immediately.
+    // Note: this will cause the context to be lost, but the
+    // webglcontextlost event listener to be queued.
     shouldGenerateGLError(gl, gl.NO_ERROR, "extension.loseContext()");
     debug("");
 }
 
 function testValidContext()
 {
     debug("Test valid context");
 
@@ -297,29 +297,19 @@ function testLostContext()
     shouldBeFalse("gl.isRenderbuffer(renderbuffer)");
     shouldBeFalse("gl.isShader(shader)");
     shouldBeFalse("gl.isTexture(texture)");
 
     shouldBe("gl.getError()", "gl.NO_ERROR");
 
     debug("");
 
-    finish();
+    finishTest();
 }
 
-function finish() {
-    successfullyParsed = true;
-    var epilogue = document.createElement("script");
-    epilogue.onload = function() {
-        if (window.nonKhronosFrameworkNotifyDone)
-            window.nonKhronosFrameworkNotifyDone();
-    };
-    epilogue.src = "../../resources/js-test-post.js";
-    document.body.appendChild(epilogue);
-}
 </script>
 </head>
 <body onload="init()">
 <div id="description"></div>
 <div id="console"></div>
 <canvas id="canvas">
 </body>
 </html>
--- a/content/canvas/test/webgl/conformance/extensions/00_test_list.txt
+++ b/content/canvas/test/webgl/conformance/extensions/00_test_list.txt
@@ -1,6 +1,8 @@
 oes-standard-derivatives.html
 oes-texture-float.html
 oes-vertex-array-object.html
 webgl-debug-renderer-info.html
 webgl-debug-shaders.html
+# commented out until 1.0.1 cut
+# webgl-experimental-compressed-textures.html
 
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/compressed-textures/4x4.rgb.dxt1.js
@@ -0,0 +1,3 @@
+var img_4x4_rgb_dxt1 = [
+0xe0,0x07,0x00,0xf8,0x11,0x10,0x15,0x00,
+];
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/compressed-textures/4x4.rgba.dxt1.js
@@ -0,0 +1,3 @@
+var img_4x4_rgba_dxt1 = [
+0xe0,0x07,0x00,0xf8,0x13,0x10,0x15,0x00,
+];
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/compressed-textures/4x4.rgba.dxt3.js
@@ -0,0 +1,3 @@
+var img_4x4_rgba_dxt3 = [
+0xf6,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0xf8,0xe0,0x07,0x44,0x45,0x40,0x55,
+];
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/compressed-textures/4x4.rgba.dxt5.js
@@ -0,0 +1,3 @@
+var img_4x4_rgba_dxt5 = [
+0xf6,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0xf8,0xe0,0x07,0x44,0x45,0x40,0x55,
+];
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/compressed-textures/4x4.rgba.raw.js
@@ -0,0 +1,3 @@
+var img_4x4_rgba_raw = [
+0xff,0x00,0x00,0x69,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,
+];
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/compressed-textures/8x8.rgb.dxt1.js
@@ -0,0 +1,3 @@
+var img_8x8_rgb_dxt1 = [
+0xe0,0x07,0x00,0xf8,0x11,0x10,0x15,0x00,0x1f,0x00,0xe0,0xff,0x11,0x10,0x15,0x00,0xe0,0x07,0x1f,0xf8,0x44,0x45,0x40,0x55,0x1f,0x00,0xff,0x07,0x44,0x45,0x40,0x55,
+];
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/compressed-textures/8x8.rgba.dxt1.js
@@ -0,0 +1,3 @@
+var img_8x8_rgba_dxt1 = [
+0xe0,0x07,0x00,0xf8,0x13,0x13,0x15,0x00,0x1f,0x00,0xe0,0xff,0x11,0x10,0x15,0x00,0xe0,0x07,0x1f,0xf8,0x44,0x45,0x43,0x57,0x1f,0x00,0xff,0x07,0x44,0x45,0x40,0x55,
+];
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/compressed-textures/8x8.rgba.dxt3.js
@@ -0,0 +1,3 @@
+var img_8x8_rgba_dxt3 = [
+0xf6,0xff,0xf6,0xff,0xff,0xff,0xff,0xff,0x00,0xf8,0xe0,0x07,0x44,0x45,0x40,0x55,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xe0,0xff,0x1f,0x00,0x44,0x45,0x40,0x55,0xff,0xff,0xff,0xff,0xf6,0xff,0xf6,0xff,0x1f,0xf8,0xe0,0x07,0x11,0x10,0x15,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x1f,0x00,0x11,0x10,0x15,0x00,
+];
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/compressed-textures/8x8.rgba.dxt5.js
@@ -0,0 +1,3 @@
+var img_8x8_rgba_dxt5 = [
+0xff,0x69,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0xf8,0xe0,0x07,0x44,0x45,0x40,0x55,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0x1f,0x00,0x44,0x45,0x40,0x55,0xff,0x69,0x00,0x00,0x00,0x01,0x10,0x00,0x1f,0xf8,0xe0,0x07,0x11,0x10,0x15,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x07,0x1f,0x00,0x11,0x10,0x15,0x00,
+];
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/compressed-textures/8x8.rgba.pvrtc4bpp.js
@@ -0,0 +1,3 @@
+var img_8x8_rgba_pvrtc4bpp = [
+0x33,0x30,0x3f,0x00,0xe0,0x83,0x00,0xfc,0xcc,0xcf,0xc0,0xff,0xe0,0x83,0x1f,0xfc,0xcc,0xcf,0xc0,0xff,0xe0,0xff,0x1f,0x80,0x33,0x30,0x3f,0x00,0xfe,0x83,0x1f,0x80,
+];
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/compressed-textures/8x8.rgba.raw.js
@@ -0,0 +1,3 @@
+var img_8x8_rgba_raw = [
+0xff,0x00,0x00,0x69,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0x00,0x69,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0x00,0x69,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0xff,0x00,0xff,0x69,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,
+];
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/webgl-experimental-compressed-textures.html
@@ -0,0 +1,520 @@
+<!--
+Copyright (c) 2011 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+ -->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL WEBGL_EXPERIMENTAL_compressed_textures Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<style>
+img {
+ border: 1px solid black;
+ margin-right: 1em;
+}
+.testimages {
+}
+
+.testimages br {
+  clear: both;
+}
+
+.testimages > div {
+  float: left;
+  margin: 1em;
+}
+</style>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="compressed-textures/4x4.rgb.dxt1.js"></script>
+<script src="compressed-textures/4x4.rgba.dxt1.js"></script>
+<script src="compressed-textures/4x4.rgba.dxt5.js"></script>
+<script src="compressed-textures/8x8.rgb.dxt1.js"></script>
+<script src="compressed-textures/8x8.rgba.dxt1.js"></script>
+<script src="compressed-textures/8x8.rgba.dxt5.js"></script>
+<script src="compressed-textures/8x8.rgba.pvrtc4bpp.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" width="8" height="8" style="width: 8px; height: 8px;"></canvas>
+<div id="console"></div>
+<!-- Shaders for testing standard derivatives -->
+<script>
+description("This test verifies the functionality of the OES_vertex_array_object extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, {antialias: false});
+var program = wtu.setupTexturedQuad(gl);
+var ext = null;
+var vao = null;
+var validFormats = {
+  COMPRESSED_RGB_S3TC_DXT1_EXT        : 0x83F0,
+  COMPRESSED_RGBA_S3TC_DXT1_EXT       : 0x83F1,
+  COMPRESSED_RGBA_S3TC_DXT5_EXT       : 0x83F3,
+  ETC1_RGB8_OES                       : 0x8D64,
+  COMPRESSED_RGB_PVRTC_4BPPV1_IMG     : 0x8C00,
+  COMPRESSED_RGBA_PVRTC_4BPPV1_IMG    : 0x8C02
+};
+var name;
+var supportedFormats;
+
+if (!gl) {
+  testFailed("WebGL context does not exist");
+} else {
+  testPassed("WebGL context exists");
+
+  // Run tests with extension disabled
+  runTestDisabled();
+
+  // Query the extension and store globally so shouldBe can access it
+  ext = gl.getExtension("WEBGL_EXPERIMENTAL_compressed_textures");
+  if (!ext) {
+    testPassed("No WEBGL_EXPERIMENTAL_compressed_textures support -- this is legal");
+    runSupportedTest(false);
+  } else {
+    testPassed("Successfully enabled WEBGL_EXPERIMENTAL_compressed_textures extension");
+
+    runSupportedTest(true);
+    runTestExtension();
+  }
+}
+
+function runSupportedTest(extensionEnabled) {
+  var supported = gl.getSupportedExtensions();
+  if (supported.indexOf("WEBGL_EXPERIMENTAL_compressed_textures") >= 0) {
+    if (extensionEnabled) {
+      testPassed("WEBGL_EXPERIMENTAL_compressed_textures listed as supported and getExtension succeeded");
+    } else {
+      testFailed("WEBGL_EXPERIMENTAL_compressed_textures listed as supported but getExtension failed");
+    }
+  } else {
+    if (extensionEnabled) {
+      testFailed("WEBGL_EXPERIMENTAL_compressed_textures not listed as supported but getExtension succeeded");
+    } else {
+      testPassed("WEBGL_EXPERIMENTAL_compressed_textures not listed as supported and getExtension failed -- this is legal");
+    }
+  }
+}
+
+
+function runTestDisabled() {
+  debug("Testing binding enum with extension disabled");
+
+  // Default value is null
+  if (gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS) === null) {
+    testPassed("Default value of COMPRESSED_TEXTURE_FORMATS is null");
+  } else {
+    testFailed("Default value of COMPRESSED_TEXTURE_FORMATS is not null");
+  }
+}
+
+function checkIsValidFormat(format) {
+  for (var name in validFormats) {
+    if (format == validFormats[name]) {
+      testPassed("supported format " + formatToString(format) + " is valid");
+      return;
+    }
+  }
+  testFailed("supported format " + formatToString(format) + " is not valid");
+}
+
+function formatToString(format) {
+  for (var p in ext) {
+    if (ext[p] == format) {
+      return p;
+    }
+  }
+  return "0x" + format.toString(16);
+}
+
+function runTestExtension() {
+  debug("Testing WEBGL_EXPERIMENTAL_compressed_textures");
+
+  // check that all format enums exist.
+  for (name in validFormats) {
+    var expected = "0x" + validFormats[name].toString(16);
+    var actual = "ext['" + name + "']";
+    shouldBe(actual, expected);
+  }
+
+  supportedFormats = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS);
+  // Even if no formats are supported this should return an array.
+  shouldBeTrue("supportedFormats.length !== undefined");
+
+  // check that each format is an allowed format
+  for (var ii = 0; ii < supportedFormats.length; ++ii) {
+    checkIsValidFormat(supportedFormats[ii]);
+  }
+
+  // Test each format
+  for (var ii = 0; ii < supportedFormats.length; ++ii) {
+    switch (supportedFormats[ii]) {
+    case ext.COMPRESSED_RGB_S3TC_DXT1_EXT:
+      testDXT1_RGB();
+      break;
+    case ext.COMPRESSED_RGBA_S3TC_DXT1_EXT:
+      testDXT1_RGBA();
+      break;
+    case ext.COMPRESSED_RGBA_S3TC_DXT5_EXT:
+      testDXT5_RGBA();
+      break;
+    case ext.ETC1_RGB8_OES:
+      testETC1_RGB8();
+      break;
+    case ext.COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
+      testPVRTC_RGB_4BPPV1();
+      break;
+    case ext.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
+      testPVRTC_RGBA_4BPPV1();
+      break;
+    }
+  }
+}
+
+function testDXT1_RGB() {
+  var tests = [
+    { width: 4,
+      height: 4,
+      channels: 3,
+      data: img_4x4_rgb_dxt1,
+      format: ext.COMPRESSED_RGB_S3TC_DXT1_EXT
+    },
+    { width: 8,
+      height: 8,
+      channels: 3,
+      data: img_8x8_rgb_dxt1,
+      format: ext.COMPRESSED_RGB_S3TC_DXT1_EXT
+    }
+  ];
+  testDXTTextures(tests);
+}
+
+function testDXT1_RGBA() {
+  var tests = [
+    { width: 4,
+      height: 4,
+      channels: 4,
+      data: img_4x4_rgba_dxt1,
+      format: ext.COMPRESSED_RGBA_S3TC_DXT1_EXT
+    },
+    { width: 8,
+      height: 8,
+      channels: 4,
+      data: img_8x8_rgba_dxt1,
+      format: ext.COMPRESSED_RGBA_S3TC_DXT1_EXT
+    }
+  ];
+  testDXTTextures(tests);
+}
+
+function testDXT5_RGBA() {
+  var tests = [
+    { width: 4,
+      height: 4,
+      channels: 4,
+      data: img_4x4_rgba_dxt5,
+      format: ext.COMPRESSED_RGBA_S3TC_DXT5_EXT
+    },
+    { width: 8,
+      height: 8,
+      channels: 4,
+      data: img_8x8_rgba_dxt5,
+      format: ext.COMPRESSED_RGBA_S3TC_DXT5_EXT
+    }
+  ];
+  testDXTTextures(tests);
+}
+
+function testETC1_RGB8() {
+  testFailed("ETC1 test not yet implemented");
+}
+
+function testPVRTC_RGB_4BPPV1() {
+  var tests = [
+    { width: 8,
+      height: 8,
+      channels: 4,
+      data: img_8x8_rgba_pvrtc4bpp,
+      format: ext.COMPRESSED_RGB_PVRTC_4BPPV1_IMG
+    }
+  ];
+  testPVRTCTextures(tests);
+}
+
+function testPVRTC_RGB_4BPPV1() {
+  var tests = [
+    { width: 8,
+      height: 8,
+      channels: 4,
+      data: img_8x8_rgba_pvrtc4bpp,
+      format: ext.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
+    }
+  ];
+  testPVRTCTextures(tests);
+}
+
+function testDXTTextures(tests) {
+  debug("<hr/>");
+  for (var ii = 0; ii < tests.length; ++ii) {
+    testDXTTexture(tests[ii]);
+  }
+}
+
+function uncompressDXTBlock(
+    destBuffer, destX, destY, destWidth, src, srcOffset, format) {
+  function make565(src, offset) {
+    return src[offset + 0] + src[offset + 1] * 256;
+  }
+  function make8888From565(c) {
+    return [
+        Math.floor(((c >> 11) & 0x1F) * 255 / 31),
+        Math.floor(((c >>  5) & 0x3F) * 255 / 63),
+        Math.floor(((c >>  0) & 0x1F) * 255 / 31),
+        255
+      ];
+  }
+  function mix(mult, c0, c1, div) {
+    var r = [];
+    for (var ii = 0; ii < c0.length; ++ii) {
+      r[ii] = Math.floor((c0[ii] * mult + c1[ii]) / div);
+    }
+    return r;
+  }
+  var colorOffset =
+      srcOffset + ((format == ext.COMPRESSED_RGBA_S3TC_DXT5_EXT) ? 8 : 0);
+  var color0 = make565(src, colorOffset + 0);
+  var color1 = make565(src, colorOffset + 2);
+  var c0gtc1 = color0 > color1 || format == ext.COMPRESSED_RGBA_S3TC_DXT5_EXT;
+  var rgba0 = make8888From565(color0);
+  var rgba1 = make8888From565(color1);
+  var colors = [
+      rgba0,
+      rgba1,
+      c0gtc1 ? mix(2, rgba0, rgba1, 3) : mix(1, rgba0, rgba1, 2),
+      c0gtc1 ? mix(2, rgba1, rgba0, 3) : [0, 0, 0, 255]
+    ];
+
+  // yea I know there is a lot of math in this inner loop.
+  // so sue me.
+  for (var yy = 0; yy < 4; ++yy) {
+    var pixels = src[colorOffset + 4 + yy];
+    for (var xx = 0; xx < 4; ++xx) {
+      var dstOff = ((destY + yy) * destWidth + destX + xx) * 4;
+      var code = (pixels >> (xx * 2)) & 0x3;
+      var srcColor = colors[code];
+      var alpha;
+      switch (format) {
+      case ext.COMPRESSED_RGB_S3TC_DXT1_EXT:
+        alpha = 255;
+        break;
+      case ext.COMPRESSED_RGBA_S3TC_DXT1_EXT:
+        alpha = (code == 3 && !c0gtc1) ? 0 : 255;
+        break;
+      case ext.COMPRESSED_RGBA_S3TC_DXT5_EXT:
+        {
+          var alpha0 = src[srcOffset + 0];
+          var alpha1 = src[srcOffset + 1];
+          var alphaOff = Math.floor(yy / 2) * 3 + 2;
+          var alphaBits =
+             src[srcOffset + alphaOff + 0] +
+             src[srcOffset + alphaOff + 1] * 256 +
+             src[srcOffset + alphaOff + 2] * 65536;
+          var alphaShift = (yy % 2) * 12 + xx * 3;
+          var alphaCode = (alphaBits >> alphaShift) & 0x7;
+          if (alpha0 > alpha1) {
+            switch (alphaCode) {
+            case 0:
+              alpha = alpha0;
+              break;
+            case 1:
+              alpha = alpha1;
+              break;
+            default:
+              alpha = ((8 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 7;
+              break;
+            }
+          } else {
+            switch (alphaCode) {
+            case 0:
+              alpha = alpha0;
+              break;
+            case 1:
+              alpha = alpha1;
+              break;
+            case 6:
+              alpha = 0;
+              break;
+            case 7:
+              alpha = 255;
+              break;
+            default:
+              alpha = ((6 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 5;
+              break;
+            }
+          }
+        }
+        break;
+      default:
+        throw "bad format";
+      }
+      destBuffer[dstOff + 0] = srcColor[0];
+      destBuffer[dstOff + 1] = srcColor[1];
+      destBuffer[dstOff + 2] = srcColor[2];
+      destBuffer[dstOff + 3] = alpha;
+    }
+  }
+}
+
+function uncompressDXT(width, height, data, format) {
+  if (width % 4 || height % 4) throw "bad width or height";
+
+  var dest = new Uint8Array(width * height * 4);
+  var blocksAcross = width / 4;
+  var blocksDown = height / 4;
+  var blockSize = (format == ext.COMPRESSED_RGBA_S3TC_DXT5_EXT ? 16 : 8);
+  for (var yy = 0; yy < blocksDown; ++yy) {
+    for (var xx = 0; xx < blocksAcross; ++xx) {
+      uncompressDXTBlock(
+        dest, xx * 4, yy * 4, width, data,
+        (yy * blocksAcross + xx) * blockSize, format);
+    }
+  }
+  return dest;
+}
+
+function testDXTTexture(test) {
+  var uncompressedData = uncompressDXT(
+      test.width, test.height, test.data, test.format);
+
+  var inner = 4; //test.width == 4 ? 4 : 1
+  for (var ii = 0; ii < inner; ++ii) {
+    var width = test.width - ii;
+    var height = test.height - ii;
+    canvas.width = width;
+    canvas.height = height;
+    gl.viewport(0, 0, width, height);
+    debug("testing " + formatToString(test.format) + " " +
+          width + "x" + height);
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+    ext.compressedTexImage2D(
+        gl.TEXTURE_2D, 0, test.format, width, height, 0,
+        new Uint8Array(test.data));
+    glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
+    wtu.drawQuad(gl);
+    compareRect(
+        width, height, test.channels, test.width, test.height, uncompressedData,
+        test.data, test.format);
+  }
+
+  gl.compressedTexImage2D(
+      gl.TEXTURE_2D, 0, test.format, width + 1, height, 0,
+      new Uint8Array(test.data));
+  glErrorShouldBe(
+      gl, gl.INVALID_OPERATION, "data size does not match dimensions");
+
+
+  // TODO: test compressedTexSubImage2D
+  // TODO: test invalid width, height, xoffset, yoffset
+}
+
+function insertImg(element, caption, img) {
+  var div = document.createElement("div");
+  div.appendChild(img);
+  var label = document.createElement("div");
+  label.appendChild(document.createTextNode(caption));
+  div.appendChild(label);
+  element.appendChild(div);
+}
+
+function makeImage(imageWidth, imageHeight, dataWidth, data, alpha) {
+  var scale = 8;
+  var c = document.createElement("canvas");
+  c.width = imageWidth * scale;
+  c.height = imageHeight * scale;
+  var ctx = c.getContext("2d");
+  for (var yy = 0; yy < imageWidth; ++yy) {
+    for (var xx = 0; xx < imageHeight; ++xx) {
+      var offset = (yy * dataWidth + xx) * 4;
+      ctx.fillStyle = "rgba(" +
+          data[offset + 0] + "," +
+          data[offset + 1] + "," +
+          data[offset + 2] + "," +
+          (alpha ? data[offset + 3] / 255 : 1) + ")";
+      ctx.fillRect(xx * scale, yy * scale, scale, scale);
+    }
+  }
+  var img = document.createElement("img");
+  img.src = c.toDataURL();
+  return img;
+}
+function compareRect(
+    actualWidth, actualHeight, actualChannels,
+    dataWidth, dataHeight, expectedData,
+    testData, testFormat) {
+  var actual = new Uint8Array(actualWidth * actualHeight * 4);
+  gl.readPixels(
+      0, 0, actualWidth, actualHeight, gl.RGBA, gl.UNSIGNED_BYTE, actual);
+
+  var div = document.createElement("div");
+  div.className = "testimages";
+  insertImg(div, "expected", makeImage(
+      actualWidth, actualHeight, dataWidth, expectedData,
+      actualChannels == 4));
+  insertImg(div, "actual", makeImage(
+      actualWidth, actualHeight, actualWidth, actual,
+      actualChannels == 4));
+  div.appendChild(document.createElement('br'));
+  document.getElementById("console").appendChild(div);
+
+  var failed = false;
+  for (var yy = 0; yy < actualHeight; ++yy) {
+    for (var xx = 0; xx < actualWidth; ++xx) {
+      var actualOffset = (yy * actualWidth + xx) * 4;
+      var expectedOffset = (yy * dataWidth + xx) * 4;
+      var expected = [
+          expectedData[expectedOffset + 0],
+          expectedData[expectedOffset + 1],
+          expectedData[expectedOffset + 2],
+          (actualChannels == 3 ? 255 : expectedData[expectedOffset + 3])
+      ];
+      for (var jj = 0; jj < 4; ++jj) {
+        if (actual[actualOffset + jj] != expected[jj]) {
+          failed = true;
+          var was = actual[actualOffset + 0].toString();
+          for (j = 1; j < 4; ++j) {
+            was += "," + actual[actualOffset + j];
+          }
+          testFailed('at (' + xx + ', ' + yy +
+                     ') expected: ' + expected + ' was ' + was);
+        }
+      }
+    }
+  }
+  if (!failed) {
+    testPassed("texture rendered correctly");
+  }
+}
+
+function testPVRTCTextures() {
+  testFailed("PVRTC test not yet implemented");
+}
+
+debug("");
+successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
--- a/content/canvas/test/webgl/conformance/glsl/functions/glsl-function-dot.html
+++ b/content/canvas/test/webgl/conformance/glsl/functions/glsl-function-dot.html
@@ -20,16 +20,17 @@ found in the LICENSE file.
 <div id="console"></div>
 <script>
 
 GLSLGenerator.runFeatureTest({
   feature: "dot",
   args: "$(type) p1, $(type) p2",
   baseArgs: "value$(field)",
   testFunc: "$(func)($(type),$(type))",
+  fragmentTolerance: 1,
   emuFuncs: [
     { type: "float",
       code: [
         "float $(func)_emu($(args)) {",
         "   return p1 * p2;",
         "}"].join("\n")
     },
     { type: "vec2",
--- a/content/canvas/test/webgl/conformance/glsl/functions/glsl-function-length.html
+++ b/content/canvas/test/webgl/conformance/glsl/functions/glsl-function-length.html
@@ -20,16 +20,17 @@ found in the LICENSE file.
 <div id="console"></div>
 <script>
 
 GLSLGenerator.runFeatureTest({
   feature: "length",
   args: "$(type) value",
   baseArgs: "value$(field)",
   testFunc: "$(func)($(type))",
+  fragmentTolerance: 1,
   emuFuncs: [
     { type: "float",
       code: [
         "float $(func)_emu($(args)) {",
         "   return abs($(baseArgs));",
         "}"].join("\n")
     },
     { type: "vec2",
--- a/content/canvas/test/webgl/conformance/glsl/functions/glsl-function-mod-float.html
+++ b/content/canvas/test/webgl/conformance/glsl/functions/glsl-function-mod-float.html
@@ -19,16 +19,17 @@ found in the LICENSE file.
 <div id="description"></div>
 <div id="console"></div>
 <script>
 GLSLGenerator.runFeatureTest({
   feature: "mod",
   args: "$(type) value, float divisor",
   baseArgs: "value$(field), divisor",
   testFunc: "$(func)($(arg0), float)",
+  fragmentTolerance: 1,
   emuFunc: ["float $(func)_base(float value, float divisor) {",
             "  return value - divisor * floor(value / divisor);",
             "}"].join("\n"),
   gridRes: 8,
   tests: [
     ["$(output) = vec4(",
      "    $(func)($(input).x * 6.0 - 3.0, 1.5) / 1.5,",
      "    $(func)($(input).y * 6.0 - 3.0, 1.5) / 1.5,",
--- a/content/canvas/test/webgl/conformance/misc/object-deletion-behaviour.html
+++ b/content/canvas/test/webgl/conformance/misc/object-deletion-behaviour.html
@@ -27,16 +27,17 @@ debug("");
 debug("shader and program deletion");
 
 var vertexShader = wtu.loadStandardVertexShader(gl);
 assertMsg(vertexShader, "vertex shader loaded");
 var fragmentShader = wtu.loadStandardFragmentShader(gl);
 assertMsg(fragmentShader, "fragment shader loaded");
 
 var program = gl.createProgram();
+shouldBeTrue("program != null");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.attachShader(program, vertexShader)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.attachShader(program, fragmentShader)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.linkProgram(program)");
 shouldBeTrue("gl.getProgramParameter(program, gl.LINK_STATUS)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.useProgram(program)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteShader(vertexShader)");
 shouldBeTrue("gl.isShader(vertexShader)");
 shouldBeTrue("gl.getShaderParameter(vertexShader, gl.DELETE_STATUS)");
@@ -51,53 +52,76 @@ shouldBeTrue("gl.getProgramParameter(pro
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.useProgram(null)");
 shouldBeFalse("gl.isProgram(program)");
 shouldBeFalse("gl.isShader(fragmentShader)");
 
 debug("");
 debug("texture deletion");
 
 var fbo = gl.createFramebuffer(), fbo2 = gl.createFramebuffer(), fbo3 = gl.createFramebuffer();
+shouldBeTrue("fbo != null");
+shouldBeTrue("fbo2 != null");
+shouldBeTrue("fbo3 != null");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
 
 var tex = gl.createTexture();
+shouldBeTrue("tex != null");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, tex)");
 shouldBe("gl.getParameter(gl.TEXTURE_BINDING_2D)", "tex");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)");
 shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", "tex");
+shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", "gl.TEXTURE");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteTexture(tex)");
 // Deleting a texture bound to the currently-bound fbo is the same as
 // detaching the textue from fbo first, then delete the texture.
 shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", "gl.NONE");
 shouldGenerateGLError(gl, gl.INVALID_ENUM, "gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)");
 shouldBeFalse("gl.isTexture(tex)");
 shouldBeNull("gl.getParameter(gl.TEXTURE_BINDING_2D)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, tex)");
 shouldBeNull("gl.getParameter(gl.TEXTURE_BINDING_2D)");
 
 var texCubeMap = gl.createTexture();
+shouldBeTrue("texCubeMap != null");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_CUBE_MAP, texCubeMap)");
 shouldBe("gl.getParameter(gl.TEXTURE_BINDING_CUBE_MAP)", "texCubeMap");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteTexture(texCubeMap)");
 shouldBeFalse("gl.isTexture(texCubeMap)");
 shouldBeNull("gl.getParameter(gl.TEXTURE_BINDING_CUBE_MAP)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_CUBE_MAP, texCubeMap)");
 shouldBeNull("gl.getParameter(gl.TEXTURE_BINDING_CUBE_MAP)");
 
 var t = gl.createTexture();
+shouldBeTrue("t != null");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, t)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteTexture(t)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, t)");
 shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)");
 
+var t2 = gl.createTexture();
+shouldBeTrue("t2 != null");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.activeTexture(gl.TEXTURE0)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, t2)");
+shouldBe("gl.getParameter(gl.TEXTURE_BINDING_2D)", "t2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.activeTexture(gl.TEXTURE1)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, t2)");
+shouldBe("gl.getParameter(gl.TEXTURE_BINDING_2D)", "t2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteTexture(t2)");
+shouldBeNull("gl.getParameter(gl.TEXTURE_BINDING_2D)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.activeTexture(gl.TEXTURE0)");
+shouldBeNull("gl.getParameter(gl.TEXTURE_BINDING_2D)");
+
 debug("");
 debug("renderbuffer deletion");
 
 var rbo = gl.createRenderbuffer(), rbo2 = gl.createRenderbuffer(), rbo3 = gl.createRenderbuffer();
+shouldBeTrue("rbo != null");
+shouldBeTrue("rbo2 != null");
+shouldBeTrue("rbo3 != null");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo)");
 shouldBe("gl.getParameter(gl.RENDERBUFFER_BINDING)", "rbo");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo)");
 shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", "rbo");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteRenderbuffer(rbo)");
 // Deleting a renderbuffer bound to the currently-bound fbo is the same as
 // detaching the renderbuffer from fbo first, then delete the renderbuffer.
 shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", "gl.NONE");
@@ -107,56 +131,295 @@ shouldBeNull("gl.getParameter(gl.RENDERB
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo)");
 shouldBeNull("gl.getParameter(gl.RENDERBUFFER_BINDING)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo2)");
 shouldBe("gl.getParameter(gl.RENDERBUFFER_BINDING)", "rbo2");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteRenderbuffer(rbo3)");
 shouldBe("gl.getParameter(gl.RENDERBUFFER_BINDING)", "rbo2");
 
 debug("");
+debug("using deleted renderbuffer");
+rbo = gl.createRenderbuffer();
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo)");
+if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+  // make backbuffer red
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(1,0,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // make fbo green
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,1,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // Bind backbuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  // delete renderbuffer. It should still be attached to fbo though.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteRenderbuffer(rbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [255,0,0,255], "backbuffer should be red")');
+  // Use fbo that has deleted rbo.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [0,255,0,255], "fbo should be green")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,0,1,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [0,0,255,255], "fbo should be blue")');
+  // Bind backbuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [255,0,0,255], "backbuffer should be red")');
+}
+
+debug("");
+debug("renderbuffer attached twice to same framebuffer");
+rbo = gl.createRenderbuffer();
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo)");
+if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+  var rbo2 = gl.createRenderbuffer();
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo2)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16)");
+  // attach rbo2 at two attachment points incompatible with it
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rbo2)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, rbo2)");
+  shouldBeTrue("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) == rbo2");
+  shouldBeTrue("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) == rbo2");
+  // fbo can't be complete as rbo2 is attached at incompatible attachment points
+  shouldBeFalse("gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE");
+  // now we delete rbo2, which detaches it from the two attachment points where it currently is attached
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteRenderbuffer(rbo2)");
+  shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", "gl.NONE");
+  shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", "gl.NONE");
+  // we should now be in the same state as before with only rbo attached, so fbo should be complete again
+  shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+  shouldBeTrue("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) == rbo");
+}
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteRenderbuffer(rbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+
+
+
+debug("");
+debug("using deleted texture");
+tex = gl.createTexture();
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, tex)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null)");
+if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+  // make fbo green
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,1,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // Bind backbuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  // delete texture. It should still be attached to fbo though.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteTexture(tex)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 1, 1, [255,0,0,255], "backbuffer should be red")');
+  // Use fbo that has deleted texture.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0,255,0,255], "fbo should be green")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,0,1,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0,0,255,255], "fbo should be blue")');
+  // Bind backbuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [255,0,0,255], "backbuffer should be red")');
+}
+
+debug("");
+debug("using deleted renderbuffer");
+rbo = gl.createRenderbuffer();
+shouldBeTrue("rbo != null");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo)");
+if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo)");
+  shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+  // make backbuffer red
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(1,0,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // make fbo green
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,1,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // delete renderbuffer. It should still be attached to fbo2 though.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteRenderbuffer(rbo)");
+  // fbo has no attachments
+  shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+  // Use fbo2 that has deleted rbo.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [0,255,0,255], "fbo should be green")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,0,1,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [0,0,255,255], "fbo should be blue")');
+  shouldBeTrue("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) == rbo");
+
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.INVALID_ENUM, "gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)");
+  shouldGenerateGLError(gl, gl.NONE, "gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)");
+  shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+  // Bind backbuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [255,0,0,255], "backbuffer should be red")');
+}
+
+debug("");
+debug("using deleted texture");
+tex = gl.createTexture();
+shouldBeTrue("tex != null");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindTexture(gl.TEXTURE_2D, tex)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null)");
+if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)");
+  // make fbo green
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,1,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // delete texture. It should still be attached to fbo2 though.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteTexture(tex)");
+  // fbo has no attachments
+  shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+  // Use fbo that has deleted texture.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2)");
+  shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0,255,0,255], "fbo should be green")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,0,1,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0,0,255,255], "fbo should be blue")');
+  shouldBeTrue("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) == tex");
+
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.INVALID_ENUM, "gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)");
+  shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+  // Bind backbuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [255,0,0,255], "backbuffer should be red")');
+}
+
+debug("");
 debug("buffer deletion");
 
 var buffer = gl.createBuffer();
+shouldBeTrue("buffer != null");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, buffer)");
 shouldBe("gl.getParameter(gl.ARRAY_BUFFER_BINDING)", "buffer");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteBuffer(buffer)");
 shouldBeFalse("gl.isBuffer(buffer)");
 shouldBeNull("gl.getParameter(gl.ARRAY_BUFFER_BINDING)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, buffer)");
 shouldBeNull("gl.getParameter(gl.ARRAY_BUFFER_BINDING)");
 
+var buffer2 = gl.createBuffer();
+shouldBeTrue("buffer2 != null");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, buffer2)");
+shouldBe("gl.getParameter(gl.ARRAY_BUFFER_BINDING)", "buffer2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, null)");
+shouldBeNull("gl.getParameter(gl.ARRAY_BUFFER_BINDING)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteBuffer(buffer2)");
+shouldBeFalse("gl.isBuffer(buffer2)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, buffer2)");
+shouldBeNull("gl.getParameter(gl.ARRAY_BUFFER_BINDING)");
+
 var bufferElement = gl.createBuffer();
+shouldBeTrue("bufferElement != null");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferElement)");
 shouldBe("gl.getParameter(gl.ELEMENT_ARRAY_BUFFER_BINDING)", "bufferElement");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteBuffer(bufferElement)");
 shouldBeFalse("gl.isBuffer(bufferElement)");
 shouldBeNull("gl.getParameter(gl.ELEMENT_ARRAY_BUFFER_BINDING)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferElement)");
 shouldBeNull("gl.getParameter(gl.ELEMENT_ARRAY_BUFFER_BINDING)");
 
 var b = gl.createBuffer();
+shouldBeTrue("b != null");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, b)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bufferData(gl.ARRAY_BUFFER, 1, gl.STATIC_DRAW)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteBuffer(b)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, b)");
 shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.bufferData(gl.ARRAY_BUFFER, 1, gl.STATIC_DRAW)");
 
+var b1 = gl.createBuffer();
+shouldBeTrue("b1 != null");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, b1);");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.enableVertexAttribArray(1);");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);");
+var b2 = gl.createBuffer();
+shouldBeTrue("b2 != null");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindBuffer(gl.ARRAY_BUFFER, b2);");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.enableVertexAttribArray(2);");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.enableVertexAttribArray(3);");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.vertexAttribPointer(3, 4, gl.FLOAT, false, 0, 0);");
+shouldBe("gl.getVertexAttrib(1, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)", "b1");
+shouldBe("gl.getVertexAttrib(2, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)", "b2");
+shouldBe("gl.getVertexAttrib(3, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)", "b2");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteBuffer(b2);");
+shouldBe("gl.getVertexAttrib(1, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)", "b1");
+shouldBeNull("gl.getVertexAttrib(2, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)");
+shouldBeNull("gl.getVertexAttrib(3, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteBuffer(b1);");
+shouldBeNull("gl.getVertexAttrib(1, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)");
+
 debug("");
 debug("framebuffer deletion");
 
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
 shouldBe("gl.getParameter(gl.FRAMEBUFFER_BINDING)", "fbo");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteFramebuffer(fbo)");
 shouldBeFalse("gl.isFramebuffer(fbo)");
 shouldBeNull("gl.getParameter(gl.FRAMEBUFFER_BINDING)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
 shouldBeNull("gl.getParameter(gl.FRAMEBUFFER_BINDING)");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2)");
 shouldBe("gl.getParameter(gl.FRAMEBUFFER_BINDING)", "fbo2");
 shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteFramebuffer(fbo3)");
 shouldBe("gl.getParameter(gl.FRAMEBUFFER_BINDING)", "fbo2");
 
+fbo = gl.createFramebuffer();
+rbo = gl.createRenderbuffer();
+shouldBeTrue("fbo != null");
+shouldBeTrue("rbo != null");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindRenderbuffer(gl.RENDERBUFFER, rbo)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16)");
+shouldGenerateGLError(gl, gl.NO_ERROR, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo)");
+if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+  // set backbuffer to red
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(1,0,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // set framebuffer to green
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,1,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  // check framebuffer
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 16, 16, [0,255,0,255], "fbo should be green")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 16, 16, 1, 1, [0,0,0,0], "outside fbo should be black")');
+  // delete framebuffer. because this was the bound fbo the backbuffer should be active now
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteFramebuffer(fbo)");
+  // check backbuffer
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 300, 150, [255,0,0,255], "backbuffer should be red")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 300, 0, 300, 300, [0,0,0,0], "outside backbuffer should be black")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 150, 300, 300, [0,0,0,0], "outside backbuffer should be black")');
+  // check drawing to backbuffer
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clearColor(0,1,0,1)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.clear(gl.COLOR_BUFFER_BIT)");
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 300, 150, [0,255,0,255], "fbo should be green")');
+  shouldGenerateGLError(gl, gl.NO_ERROR, "gl.bindFramebuffer(gl.FRAMEBUFFER, null)");
+  // check again because many buggy implementations will have bound to the true backbuffer on deleteFramebuffer.
+  shouldGenerateGLError(gl, gl.NO_ERROR, 'wtu.checkCanvasRect(gl, 0, 0, 300, 150, [0,255,0,255], "fbo should be green")');
+}
+
 successfullyParsed = true;
 </script>
 
 <script src="../../resources/js-test-post.js"></script>
 </body>
 </html>
--- a/content/canvas/test/webgl/conformance/more/functions/uniformf.html
+++ b/content/canvas/test/webgl/conformance/more/functions/uniformf.html
@@ -54,15 +54,15 @@ void main()
 <script id="foobar-frag" type="x-shader/x-fragment">
 precision mediump float;
 
 uniform vec4 foo;
 
 varying vec4 texCoord0;
 void main()
 {
-    gl_FragColor = vec4(foo.r/256.0, foo.g/256.0, foo.b/256.0, foo.a*texCoord0.z/256.0);
+    gl_FragColor = vec4(foo.r/255.0, foo.g/255.0, foo.b/255.0, foo.a*texCoord0.z/255.0);
 }
 </script>
 <style>canvas{ position:absolute; }</style>
 </head><body>
 <canvas id="gl" width="16" height="16"></canvas>
 </body></html>
--- a/content/canvas/test/webgl/conformance/more/functions/uniformfArrayLen1.html
+++ b/content/canvas/test/webgl/conformance/more/functions/uniformfArrayLen1.html
@@ -77,18 +77,18 @@ void main()
 precision mediump float;
 
 uniform vec4 uniV4[1];
 
 varying vec4 texCoord0;
 void main()
 {
     gl_FragColor = vec4(
-        uniV4[0].r/256.0,
-        uniV4[0].g/256.0,
-        uniV4[0].b/256.0,
-        uniV4[0].a*texCoord0.z/256.0);
+        uniV4[0].r/255.0,
+        uniV4[0].g/255.0,
+        uniV4[0].b/255.0,
+        uniV4[0].a*texCoord0.z/255.0);
 }
 </script>
 <style>canvas{ position:absolute; }</style>
 </head><body>
 <canvas id="gl" width="16" height="16"></canvas>
 </body></html>
--- a/content/canvas/test/webgl/conformance/more/functions/uniformfBadArgs.html
+++ b/content/canvas/test/webgl/conformance/more/functions/uniformfBadArgs.html
@@ -82,18 +82,18 @@ void main()
 precision mediump float;
 
 uniform vec4 uniV4;
 
 varying vec4 texCoord0;
 void main()
 {
     gl_FragColor = vec4(
-        uniV4.r/256.0,
-        uniV4.g/256.0,
-        uniV4.b/256.0,
-        uniV4.a*texCoord0.z/256.0);
+        uniV4.r/255.0,
+        uniV4.g/255.0,
+        uniV4.b/255.0,
+        uniV4.a*texCoord0.z/255.0);
 }
 </script>
 <style>canvas{ position:absolute; }</style>
 </head><body>
 <canvas id="gl" width="16" height="16"></canvas>
 </body></html>
--- a/content/canvas/test/webgl/conformance/renderbuffers/framebuffer-object-attachment.html
+++ b/content/canvas/test/webgl/conformance/renderbuffers/framebuffer-object-attachment.html
@@ -22,68 +22,75 @@ var gl;
 var fbo;
 var depthBuffer;
 var stencilBuffer;
 var depthStencilBuffer;
 var colorBuffer;
 var width;
 var height;
 
-function testAttachment(attachment, buffer, isConflicted)
+const ALLOW_COMPLETE              = 0x01;
+const ALLOW_UNSUPPORTED           = 0x02;
+const ALLOW_INCOMPLETE_ATTACHMENT = 0x04;
+
+function CheckFramebufferForAllowedStatuses(allowedStatuses)
+{
+    // If the framebuffer is in an error state for multiple reasons,
+    // we can't guarantee which one will be reported.
+    var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+    var statusAllowed = ((allowedStatuses & ALLOW_COMPLETE) && (status == gl.FRAMEBUFFER_COMPLETE)) ||
+                        ((allowedStatuses & ALLOW_UNSUPPORTED) && (status == gl.FRAMEBUFFER_UNSUPPORTED)) ||
+                        ((allowedStatuses & ALLOW_INCOMPLETE_ATTACHMENT) && (status == gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT));
+    var msg = "gl.checkFramebufferStatus(gl.FRAMEBUFFER) returned " + status;
+    if (statusAllowed)
+        testPassed(msg);
+    else
+        testFailed(msg);
+}
+
+function testAttachment(attachment, buffer, allowedStatuses)
 {
     shouldBeNonNull("fbo = gl.createFramebuffer()");
     gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
     gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer);
     gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, buffer);
     glErrorShouldBe(gl, gl.NO_ERROR);
-    // If the framebuffer is in an error state for multiple reasons,
-    // we can't guarantee which one will be reported.
-    if ((width == 0 || height == 0) && !isConflicted) {
-        // Zero-sized renderbuffers are supposed to result in an incomplete attachment.
-        // However, certain combination may violate implementation specific restrictions.
-        shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT || gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_UNSUPPORTED");
-    } else if (isConflicted) {
-        shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_UNSUPPORTED");
+    CheckFramebufferForAllowedStatuses(allowedStatuses);
+    if ((allowedStatuses & ALLOW_COMPLETE) == 0) {
         gl.clear(gl.COLOR_BUFFER_BIT);
         glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION);
         gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(width * height * 4));
         glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION);
     }
+    gl.deleteFramebuffer(fbo);
 }
 
-function testAttachments(attachment0, buffer0, attachment1, buffer1, isConflicted)
+function testAttachments(attachment0, buffer0, attachment1, buffer1, allowedStatuses)
 {
     shouldBeNonNull("fbo = gl.createFramebuffer()");
     gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
     gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer);
     gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment0, gl.RENDERBUFFER, buffer0);
     glErrorShouldBe(gl, gl.NO_ERROR);
     gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment1, gl.RENDERBUFFER, buffer1);
     glErrorShouldBe(gl, gl.NO_ERROR);
-    // If the framebuffer is in an error state for multiple reasons,
-    // we can't guarantee which one will be reported.
-    if ((width == 0 || height == 0) && !isConflicted) {
-        // Zero-sized renderbuffers are supposed to result in an incomplete attachment.
-        // However, certain combination may violate implementation specific restrictions.
-        shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT || gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_UNSUPPORTED");
-    } else if (isConflicted) {
-        shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_UNSUPPORTED");
-    }
+    CheckFramebufferForAllowedStatuses(allowedStatuses);
+    gl.deleteFramebuffer(fbo);
 }
 
-function testColorRenderbuffer(internalformat)
+function testColorRenderbuffer(internalformat, allowedStatuses)
 {
     shouldBeNonNull("colorBuffer = gl.createRenderbuffer()");
     gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
     gl.renderbufferStorage(gl.RENDERBUFFER, internalformat, width, height);
     glErrorShouldBe(gl, gl.NO_ERROR);
-    testAttachment(gl.COLOR_ATTACHMENT0, colorBuffer, false);
+    testAttachment(gl.COLOR_ATTACHMENT0, colorBuffer, allowedStatuses);
 }
 
-function testDepthStencilRenderbuffer()
+function testDepthStencilRenderbuffer(allowedStatuses)
 {
     shouldBeNonNull("depthStencilBuffer = gl.createRenderbuffer()");
     gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
     gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height);
     glErrorShouldBe(gl, gl.NO_ERROR);
 
     // OpenGL itself doesn't seem to guarantee that e.g. a 2 x 0
     // renderbuffer will report 2 for its width when queried.
@@ -98,17 +105,17 @@ function testDepthStencilRenderbuffer()
     shouldBe("gl.getRenderbufferParameter(gl.RENDERBUFFER, gl.RENDERBUFFER_ALPHA_SIZE)", "0");
     // Avoid verifying these for zero-sized renderbuffers for the time
     // being since it appears that even OpenGL doesn't guarantee them.
     if (width > 0 && height > 0) {
         shouldBeTrue("gl.getRenderbufferParameter(gl.RENDERBUFFER, gl.RENDERBUFFER_DEPTH_SIZE) > 0");
         shouldBeTrue("gl.getRenderbufferParameter(gl.RENDERBUFFER, gl.RENDERBUFFER_STENCIL_SIZE) > 0");
     }
     glErrorShouldBe(gl, gl.NO_ERROR);
-    testAttachment(gl.DEPTH_STENCIL_ATTACHMENT, depthStencilBuffer, false);
+    testAttachment(gl.DEPTH_STENCIL_ATTACHMENT, depthStencilBuffer, allowedStatuses);
 }
 
 description("Test framebuffer object attachment behaviors");
 
 for (width = 0; width <= 2; width += 2)
 {
     for (height = 0; height <= 2; height += 2)
     {
@@ -129,59 +136,69 @@ for (width = 0; width <= 2; width += 2)
         gl.bindRenderbuffer(gl.RENDERBUFFER, stencilBuffer);
         gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, width, height);
         glErrorShouldBe(gl, gl.NO_ERROR);
         shouldBeNonNull("depthStencilBuffer = gl.createRenderbuffer()");
         gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
         gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height);
         glErrorShouldBe(gl, gl.NO_ERROR);
 
+        var allowedStatusForGoodCase
+            = (width == 0 || height == 0) ? ALLOW_INCOMPLETE_ATTACHMENT : ALLOW_COMPLETE;
+
+        // some cases involving stencil seem to be implementation-dependent
+        var allowedStatusForImplDependentCase = allowedStatusForGoodCase | ALLOW_UNSUPPORTED;
+
         debug("Attach depth using DEPTH_ATTACHMENT");
-        testAttachment(gl.DEPTH_ATTACHMENT, depthBuffer, false);
+        testAttachment(gl.DEPTH_ATTACHMENT, depthBuffer, allowedStatusForGoodCase);
         debug("Attach depth using STENCIL_ATTACHMENT");
-        testAttachment(gl.STENCIL_ATTACHMENT, depthBuffer, true);
+        testAttachment(gl.STENCIL_ATTACHMENT, depthBuffer, ALLOW_INCOMPLETE_ATTACHMENT);
         debug("Attach depth using DEPTH_STENCIL_ATTACHMENT");
-        testAttachment(gl.DEPTH_STENCIL_ATTACHMENT, depthBuffer, true);
+        testAttachment(gl.DEPTH_STENCIL_ATTACHMENT, depthBuffer, ALLOW_INCOMPLETE_ATTACHMENT);
         debug("Attach stencil using STENCIL_ATTACHMENT");
-        testAttachment(gl.STENCIL_ATTACHMENT, stencilBuffer, false);
+        testAttachment(gl.STENCIL_ATTACHMENT, stencilBuffer, allowedStatusForImplDependentCase);
         debug("Attach stencil using DEPTH_ATTACHMENT");
-        testAttachment(gl.DEPTH_ATTACHMENT, stencilBuffer, true);
+        testAttachment(gl.DEPTH_ATTACHMENT, stencilBuffer, ALLOW_INCOMPLETE_ATTACHMENT);
         debug("Attach stencil using DEPTH_STENCIL_ATTACHMENT");
-        testAttachment(gl.DEPTH_STENCIL_ATTACHMENT, stencilBuffer, true);
+        testAttachment(gl.DEPTH_STENCIL_ATTACHMENT, stencilBuffer, ALLOW_INCOMPLETE_ATTACHMENT);
         debug("Attach depthStencil using DEPTH_STENCIL_ATTACHMENT");
-        testAttachment(gl.DEPTH_STENCIL_ATTACHMENT, depthStencilBuffer, false);
+        testAttachment(gl.DEPTH_STENCIL_ATTACHMENT, depthStencilBuffer, allowedStatusForGoodCase);
         debug("Attach depthStencil using DEPTH_ATTACHMENT");
-        testAttachment(gl.DEPTH_ATTACHMENT, depthStencilBuffer, true);
+        testAttachment(gl.DEPTH_ATTACHMENT, depthStencilBuffer, ALLOW_INCOMPLETE_ATTACHMENT);
         debug("Attach depthStencil using STENCIL_ATTACHMENT");
-        testAttachment(gl.STENCIL_ATTACHMENT, depthStencilBuffer, true);
+        testAttachment(gl.STENCIL_ATTACHMENT, depthStencilBuffer, ALLOW_INCOMPLETE_ATTACHMENT);
+
+        var allowedStatusForConflictedAttachment
+            = (width == 0 || height == 0) ? ALLOW_UNSUPPORTED | ALLOW_INCOMPLETE_ATTACHMENT
+                                          : ALLOW_UNSUPPORTED;
 
         debug("Attach depth, then stencil, causing conflict");
-        testAttachments(gl.DEPTH_ATTACHMENT, depthBuffer, gl.STENCIL_ATTACHMENT, stencilBuffer, true);
+        testAttachments(gl.DEPTH_ATTACHMENT, depthBuffer, gl.STENCIL_ATTACHMENT, stencilBuffer, allowedStatusForConflictedAttachment);
         debug("Attach stencil, then depth, causing conflict");
-        testAttachments(gl.STENCIL_ATTACHMENT, stencilBuffer, gl.DEPTH_ATTACHMENT, depthBuffer, true);
+        testAttachments(gl.STENCIL_ATTACHMENT, stencilBuffer, gl.DEPTH_ATTACHMENT, depthBuffer, allowedStatusForConflictedAttachment);
         debug("Attach depth, then depthStencil, causing conflict");
-        testAttachments(gl.DEPTH_ATTACHMENT, depthBuffer, gl.DEPTH_STENCIL_ATTACHMENT, depthStencilBuffer, true);
+        testAttachments(gl.DEPTH_ATTACHMENT, depthBuffer, gl.DEPTH_STENCIL_ATTACHMENT, depthStencilBuffer, allowedStatusForConflictedAttachment);
         debug("Attach depthStencil, then depth, causing conflict");
-        testAttachments(gl.DEPTH_STENCIL_ATTACHMENT, depthStencilBuffer, gl.DEPTH_ATTACHMENT, depthBuffer, true);
+        testAttachments(gl.DEPTH_STENCIL_ATTACHMENT, depthStencilBuffer, gl.DEPTH_ATTACHMENT, depthBuffer, allowedStatusForConflictedAttachment);
         debug("Attach stencil, then depthStencil, causing conflict");
-        testAttachments(gl.DEPTH_ATTACHMENT, depthBuffer, gl.DEPTH_STENCIL_ATTACHMENT, depthStencilBuffer, true);
+        testAttachments(gl.DEPTH_ATTACHMENT, depthBuffer, gl.DEPTH_STENCIL_ATTACHMENT, depthStencilBuffer, allowedStatusForConflictedAttachment);
         debug("Attach depthStencil, then stencil, causing conflict");
-        testAttachments(gl.DEPTH_STENCIL_ATTACHMENT, depthStencilBuffer, gl.STENCIL_ATTACHMENT, stencilBuffer, true);
+        testAttachments(gl.DEPTH_STENCIL_ATTACHMENT, depthStencilBuffer, gl.STENCIL_ATTACHMENT, stencilBuffer, allowedStatusForConflictedAttachment);
 
         debug("Attach color renderbuffer with internalformat == RGBA4");
-        testColorRenderbuffer(gl.RGBA4);
+        testColorRenderbuffer(gl.RGBA4, allowedStatusForGoodCase);
 
         debug("Attach color renderbuffer with internalformat == RGB5_A1");
-        testColorRenderbuffer(gl.RGB5_A1);
+        testColorRenderbuffer(gl.RGB5_A1, allowedStatusForGoodCase);
 
         debug("Attach color renderbuffer with internalformat == RGB565");
-        testColorRenderbuffer(gl.RGB565);
+        testColorRenderbuffer(gl.RGB565, allowedStatusForGoodCase);
 
         debug("Create and attach depthStencil renderbuffer");
-        testDepthStencilRenderbuffer();
+        testDepthStencilRenderbuffer(allowedStatusForGoodCase);
     }
 }
 
 // Determine if we can attach both color and depth or color and depth_stencil
 var depthFormat;
 var depthAttachment;
 
 function checkValidColorDepthCombination() {
@@ -261,33 +278,45 @@ function testUsingIncompleteFramebuffer(
 
     // We pick this combination because it works on desktop OpenGL but should not work on OpenGL ES 2.0
     gl.renderbufferStorage(gl.RENDERBUFFER, depthFormat, 32, 16);
     CheckFramebuffer(gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS);
     debug("");
     debug("Drawing or reading from an incomplete framebuffer should generate INVALID_FRAMEBUFFER_OPERATION");
     testRenderingAndReading();
 
-    shouldBeNonNull("fbo = gl.createFramebuffer()");
-    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+    shouldBeNonNull("fbo2 = gl.createFramebuffer()");
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2);
     CheckFramebuffer(gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
     debug("");
     debug("Drawing or reading from an incomplete framebuffer should generate INVALID_FRAMEBUFFER_OPERATION");
     testRenderingAndReading();
 
+    shouldBeNonNull("colorBuffer = gl.createRenderbuffer()");
+    gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer);
+    gl.framebufferRenderbuffer(
+        gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer);
+    gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 0, 0);
+    debug("");
+    debug("Drawing or reading from an incomplete framebuffer should generate INVALID_FRAMEBUFFER_OPERATION");
+    testRenderingAndReading();
+
     function testRenderingAndReading() {
         glErrorShouldBe(gl, gl.NO_ERROR);
         wtu.drawQuad(gl);
         glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "drawArrays with incomplete framebuffer");
         gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
         glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "readPixels from incomplete framebuffer");
+        // copyTexImage and copyTexSubImage can be either INVALID_FRAMEBUFFER_OPERATION because
+        // the framebuffer is invalid OR INVALID_OPERATION because in the case of no attachments
+        // the framebuffer is not of a compatible type.
         gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, 1, 1);
-        glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "copyTexImage2D from incomplete framebuffer");
+        glErrorShouldBe(gl, [gl.INVALID_FRAMEBUFFER_OPERATION, gl.INVALID_OPERATION], "copyTexImage2D from incomplete framebuffer");
         gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, 1, 1, 0);
-        glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "copyTexSubImage2D from incomplete framebuffer");
+        glErrorShouldBe(gl, [gl.INVALID_FRAMEBUFFER_OPERATION, gl.INVALID_OPERATION], "copyTexSubImage2D from incomplete framebuffer");
         gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
         glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "clear with incomplete framebuffer");
     }
 }
 
 function testFramebufferIncompleteAttachment() {
     shouldBeNonNull("fbo = gl.createFramebuffer()");
     gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
--- a/content/canvas/test/webgl/conformance/rendering/00_test_list.txt
+++ b/content/canvas/test/webgl/conformance/rendering/00_test_list.txt
@@ -1,9 +1,9 @@
 draw-arrays-out-of-bounds.html
 draw-elements-out-of-bounds.html
 gl-clear.html
 gl-drawelements.html
 gl-scissor-test.html
 more-than-65536-indices.html
 point-size.html
 triangle.html
-
+line-loop-tri-fan.html
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/rendering/line-loop-tri-fan.html
@@ -0,0 +1,238 @@
+<!--
+Copyright (C) 2011 Opera Software ASA.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY OPERA SOFTWARE ASA. ''AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <link rel="stylesheet" href="../../resources/js-test-style.css"/>
+        <script src="../../resources/js-test-pre.js"></script>
+        <script src="../resources/webgl-test.js"></script>
+        <script id="vshader" type="x-shader/x-vertex">
+            attribute vec2 pos;
+
+            void main()
+            {
+                gl_Position = vec4(pos, 0, 1);
+            }
+        </script>
+
+        <script id="fshader" type="x-shader/x-fragment">
+            precision mediump float;
+
+            void main()
+            {
+                gl_FragColor = vec4(0, 1, 0, 1);
+            }
+        </script>
+
+        <script>
+            // Check a single 32-bit RGBA pixel.
+            function checkPixel(buf, index, correct) {
+                for (var i = 0; i < 4; ++i) {
+                    if (buf[index + i] != correct[i]) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            // Check the line loop by reading the pixels and making sure just the edge
+            // pixels are green and the rest are black.
+            function checkLineLoop(gl, w) {
+                var buf = new Uint8Array(w * w * 4);
+                gl.readPixels(0, 0, w, w, gl.RGBA, gl.UNSIGNED_BYTE, buf);
+                var green = [0,255,0,255];
+                var black = [0,0,0,255];
+                var isCorrect = true;
+                for (var j = 0; j < w * w * 4; j += 4) {
+                    var correct = black;
+                    if (j < w * 4 || j > w * (w - 1) * 4 || j % (w * 4) == 0 || j % (w * 4) == (w - 1) * 4) {
+                        correct = green;
+                    }
+                    if (!checkPixel(buf, j, correct)) {
+                        isCorrect = false;
+                        break;
+                    }
+                }
+                if (isCorrect) {
+                    testPassed("Line loop was drawn correctly.");
+                } else {
+                    testFailed("Line loop was drawn incorrectly.");
+                }
+            }
+
+            // Check the tri fan by reading the pixels and making sure they are all green.
+            function checkTriFan(gl, w) {
+                buf = new Uint8Array(w * w * 4);
+                gl.readPixels(0, 0, w, w, gl.RGBA, gl.UNSIGNED_BYTE, buf);
+                var filled = true;
+                for (var j = 0; j < w * w * 4; j += 4) {
+                    if (!checkPixel(buf, j, [0,255,0,255])) {
+                        filled = false;
+                        break;
+                    }
+                }
+                if (filled) {
+                    testPassed("Triangle fan was drawn correctly.");
+                } else {
+                    testFailed("Triangle fan was drawn incorrectly.");
+                }                
+            }
+
+            function runTest()
+            {
+                var gl = initWebGL('testbed', 'vshader', 'fshader', ['pos'], [0, 0, 0, 1], 1, { antialias: false });
+                if (!gl) {
+                    testFailed('initWebGL(..) failed');
+                    return;
+                }
+                var w = document.getElementById('testbed').width;
+
+                gl.enableVertexAttribArray(0);
+
+                //---------- LINE_LOOP----------
+                var d = 1/w;
+                var vertices = new Float32Array([-1+d, -1+d, 1-d, -1+d, 1-d, 1-d, -1+d, 1-d]);
+                var vertBuf = gl.createBuffer();
+                gl.bindBuffer(gl.ARRAY_BUFFER, vertBuf);
+                gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
+                gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+                var indBuf = gl.createBuffer();
+                var indices = new Uint16Array([0, 1, 2, 3]);
+                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indBuf);
+                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
+
+                debug('Draw a square using a line loop and verify that it draws all four sides and nothing else.');
+                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+                gl.drawArrays(gl.LINE_LOOP, 0, vertices.length / 2);
+                checkLineLoop(gl, w);
+
+                debug('Draw a square using an indexed line loop and verify that it draws all four sides and nothing else.');
+                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+                gl.drawElements(gl.LINE_LOOP, indices.length, gl.UNSIGNED_SHORT, 0);
+                checkLineLoop(gl, w);
+
+                vertices = new Float32Array([0, 0, 0, 0, 0, 0, -1+d, -1+d, 1-d, -1+d, 1-d, 1-d, -1+d, 1-d]);
+                vertBuf = gl.createBuffer();
+                gl.bindBuffer(gl.ARRAY_BUFFER, vertBuf);
+                gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
+                gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+                indBuf = gl.createBuffer();
+                indices = new Uint16Array([0, 1, 2, 3, 4, 5, 6]);
+                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indBuf);
+                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
+
+                debug('Draw a square using a line loop with a vertex buffer offset and verify that it draws all four sides and nothing else.');
+                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+                gl.drawArrays(gl.LINE_LOOP, 3, vertices.length / 2 - 3);
+                checkLineLoop(gl, w);
+
+                debug('Draw a square using an indexed line loop with an index buffer offset and verify that it draws all four sides and nothing else.');
+                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+                gl.drawElements(gl.LINE_LOOP, indices.length - 3, gl.UNSIGNED_SHORT, 3 * 2);
+                checkLineLoop(gl, w);
+
+                //---------- LINE_LOOP UBYTE ----------
+                var degenVerts = new Array(252 * 2);
+                for (var j = 0; j < 252 * 2; ++j) {
+                    degenVerts[j] = -1+d;
+                }
+                degenVerts = degenVerts.concat([-1+d, -1+d, 1-d, -1+d, 1-d, 1-d, -1+d, 1-d]);
+                vertices = new Float32Array(degenVerts);
+                vertBuf = gl.createBuffer();
+                gl.bindBuffer(gl.ARRAY_BUFFER, vertBuf);
+                gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
+                gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+                indBuf = gl.createBuffer();
+                var degenInd = new Array(252);
+                for (var j = 0; j < 252; ++j) {
+                    degenInd[j] = j;
+                }
+                degenInd = degenInd.concat([252, 253, 254, 255]);
+                indices = new Uint8Array(degenInd);
+                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indBuf);
+                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
+
+                debug('Draw a square using an ubyte indexed line loop with 256 indices and verify that it draws all four sides and nothing else.');
+                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+                gl.drawElements(gl.LINE_LOOP, indices.length, gl.UNSIGNED_BYTE, 0);
+                checkLineLoop(gl, w);
+
+
+                //---------- TRIANGLE_FAN ----------
+                vertices = new Float32Array([0, 0, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1]);
+                vertBuf = gl.createBuffer();
+                gl.bindBuffer(gl.ARRAY_BUFFER, vertBuf);
+                gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
+                gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+                indices = new Uint16Array([0,1,2,3,4,5]);
+                indBuf = gl.createBuffer();
+                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indBuf);
+                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
+
+                debug('Draw a filled square using a triangle fan and verify that it fills the entire canvas.');
+                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+                gl.drawArrays(gl.TRIANGLE_FAN, 0, vertices.length / 2);
+                checkTriFan(gl, w);
+
+                debug('Draw a filled square using an indexed triangle fan and verify that it fills the entire canvas.');
+                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+                gl.drawElements(gl.TRIANGLE_FAN, indices.length, gl.UNSIGNED_SHORT, 0);
+                checkTriFan(gl, w);
+
+                vertices = new Float32Array([1, 1, 1, 1, 1, 1, 0, 0, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1]);
+                vertBuf = gl.createBuffer();
+                gl.bindBuffer(gl.ARRAY_BUFFER, vertBuf);
+                gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
+                gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+                indices = new Uint16Array([0,1,2,3,4,5,6,7,8]);
+                indBuf = gl.createBuffer();
+                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indBuf);
+                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
+
+                debug('Draw a filled square using a triangle fan with a vertex buffer offset and verify that it fills the entire canvas.');
+                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+                gl.drawArrays(gl.TRIANGLE_FAN, 3, vertices.length / 2 - 3);
+                checkTriFan(gl, w);
+
+                debug('Draw a filled square using an indexed triangle fan with an index buffer offset and verify that it fills the entire canvas.');
+                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+                gl.drawElements(gl.TRIANGLE_FAN, indices.length - 3, gl.UNSIGNED_SHORT, 3 * 2);
+                checkTriFan(gl, w);
+            }
+        </script>
+    </head>
+    <body>
+        <canvas id="testbed" width="10px" height="10px" style="width:50px; height:50px"></canvas>
+        <div id="description"></div>
+        <div id="console"></div>
+        <script>
+            description('Verify that LINE_LOOP and TRIANGLE_FAN works correctly.');
+            runTest();
+            successfullyParsed = true;
+        </script>
+        <script src="../../resources/js-test-post.js"></script>
+    </body>
+</html>
--- a/content/canvas/test/webgl/conformance/resources/glsl-generator.js
+++ b/content/canvas/test/webgl/conformance/resources/glsl-generator.js
@@ -218,17 +218,20 @@ var makeImage = function(canvas) {
 
 var runFeatureTest = function(params) {
   if (window.initNonKhronosFramework) {
     window.initNonKhronosFramework(false);
   }
 
   var wtu = WebGLTestUtils;
   var gridRes = params.gridRes;
-  var tolerance = params.tolerance || 0;
+  var vertexTolerance = params.tolerance || 0;
+  var fragmentTolerance = vertexTolerance;
+  if ('fragmentTolerance' in params)
+    fragmentTolerance = params.fragmentTolerance || 0;
 
   description("Testing GLSL feature: " + params.feature);
 
   var width = 32;
   var height = 32;
 
   var console = document.getElementById("console");
   var canvas = document.createElement('canvas');
@@ -247,23 +250,25 @@ var runFeatureTest = function(params) {
   var ctx = canvas2d.getContext("2d");
   var imgData = ctx.getImageData(0, 0, width, height);
 
   var shaderInfos = [
     { type: "vertex",
       input: "color",
       output: "vColor",
       vertexShaderTemplate: vertexShaderTemplate,
-      fragmentShaderTemplate: baseFragmentShader
+      fragmentShaderTemplate: baseFragmentShader,
+      tolerance: vertexTolerance
     },
     { type: "fragment",
       input: "vColor",
       output: "gl_FragColor",
       vertexShaderTemplate: baseVertexShader,
-      fragmentShaderTemplate: fragmentShaderTemplate
+      fragmentShaderTemplate: fragmentShaderTemplate,
+      tolerance: fragmentTolerance
     }
   ];
   for (var ss = 0; ss < shaderInfos.length; ++ss) {
     var shaderInfo = shaderInfos[ss];
     var tests = params.tests;
     var testTypes = params.emuFuncs || (params.bvecTest ? bvecTypes : types);
     // Test vertex shaders
     for (var ii = 0; ii < tests.length; ++ii) {
@@ -323,17 +328,17 @@ var runFeatureTest = function(params) {
         var testData = draw(
             canvas, testVertexShaderSource, referenceFragmentShaderSource);
       } else {
         var testData = draw(
             canvas, referenceVertexShaderSource, testFragmentShaderSource);
       }
       var testImg = makeImage(canvas);
 
-      reportResults(refData, refImg, testData, testImg);
+      reportResults(refData, refImg, testData, testImg, shaderInfo.tolerance);
     }
   }
 
   finishTest();
 
   function addShaderSource(label, source) {
     var div = document.createElement("div");
     var s = document.createElement("pre");
@@ -359,17 +364,17 @@ var runFeatureTest = function(params) {
         s.style.display = (s.style.display == 'none') ? 'block' : 'none';
         return false;
       }, false);
     div.appendChild(l);
     div.appendChild(s);
     console.appendChild(div);
   }
 
-  function reportResults(refData, refImage, testData, testImage) {
+  function reportResults(refData, refImage, testData, testImage, tolerance) {
     var same = true;
     for (var yy = 0; yy < height; ++yy) {
       for (var xx = 0; xx < width; ++xx) {
         var offset = (yy * width + xx) * 4;
         var imgOffset = ((height - yy - 1) * width + xx) * 4;
         imgData.data[imgOffset + 0] = 0;
         imgData.data[imgOffset + 1] = 0;
         imgData.data[imgOffset + 2] = 0;
@@ -482,16 +487,28 @@ return {
    *    The resolution of the mesh to generate. The default is a
    *    1x1 grid but many vertex shaders need a higher resolution
    *    otherwise the only values passed in are the 4 corners
    *    which often have the same value.
    *
    * tests:
    *    The code for each test. It is assumed the tests are for
    *    float, vec2, vec3, vec4 in that order.
+   *
+   * tolerance: (optional)
+   *    Allow some tolerance in the comparisons. The tolerance is applied to 
+   *    both vertex and fragment shaders. The default tolerance is 0, meaning 
+   *    the values have to be identical.
+   *
+   * fragmentTolerance: (optional)
+   *    Specify a tolerance which only applies to fragment shaders. The 
+   *    fragment-only tolerance will override the shared tolerance for 
+   *    fragment shaders if both are specified. Fragment shaders usually
+   *    use mediump float precision so they sometimes require higher tolerance
+   *    than vertex shaders which use highp.
    */
   runFeatureTest: runFeatureTest,
 
   none: false
 };
 
 }());
 
--- a/content/canvas/test/webgl/conformance/resources/webgl-test.js
+++ b/content/canvas/test/webgl/conformance/resources/webgl-test.js
@@ -125,28 +125,43 @@ function shouldGenerateGLError(ctx, glEr
       testPassed(evalStr + " generated expected GL error: " + getGLErrorAsString(ctx, err) + ".");
     }
   }
 }
 
 /**
  * Tests that the first error GL returns is the specified error.
  * @param {!WebGLContext} gl The WebGLContext to use.
- * @param {number} glError The expected gl error.
+ * @param {number|!Array.<number>} glError The expected gl
+ *        error. Multiple errors can be passed in using an
+ *        array.
  * @param {string} opt_msg Optional additional message.
  */
-function glErrorShouldBe(gl, glError, opt_msg) {
+function glErrorShouldBe(gl, glErrors, opt_msg) {
+  if (!glErrors.length) {
+    glErrors = [glErrors];
+  }
   opt_msg = opt_msg || "";
   var err = gl.getError();
-  if (err != glError) {
-    testFailed("getError expected: " + getGLErrorAsString(gl, glError) +
-               ". Was " + getGLErrorAsString(gl, err) + " : " + opt_msg);
+  var ndx = glErrors.indexOf(err);
+  if (ndx < 0) {
+    if (glErrors.length == 1) {
+      testFailed("getError expected: " + getGLErrorAsString(gl, glErrors[0]) +
+                 ". Was " + getGLErrorAsString(gl, err) + " : " + opt_msg);
+    } else {
+      var errs = [];
+      for (var ii = 0; ii < glErrors.length; ++ii) {
+        errs.push(getGLErrorAsString(gl, glErrors[ii]));
+      }
+      testFailed("getError expected one of: [" + errs.join(", ") +
+                 "]. Was " + getGLErrorAsString(gl, err) + " : " + opt_msg);
+    }
   } else {
     testPassed("getError was expected value: " +
-                getGLErrorAsString(gl, glError) + " : " + opt_msg);
+                getGLErrorAsString(gl, err) + " : " + opt_msg);
   }
 };
 
 //
 // createProgram
 //
 // Create and return a program object, attaching each of the given shaders.
 //
@@ -356,28 +371,28 @@ var getBasePathForResources = function()
     }
   }
   throw 'oops';
 };
 
 
 function loadStandardVertexShader(context) {
     return loadShader(
-        context, 
-        getBasePathForResources() + "vertexShader.vert", 
-        context.VERTEX_SHADER, 
-        true); 
+        context,
+        getBasePathForResources() + "vertexShader.vert",
+        context.VERTEX_SHADER,
+        true);
 }
 
 function loadStandardFragmentShader(context) {
     return loadShader(
-        context, 
-        getBasePathForResources() + "fragmentShader.frag", 
-        context.FRAGMENT_SHADER, 
-        true); 
+        context,
+        getBasePathForResources() + "fragmentShader.frag",
+        context.FRAGMENT_SHADER,
+        true);
 }
 
 //
 // makeBox
 //
 // Create a box with vertices, normals and texCoords. Create VBOs for each as well as the index array.
 // Return an object with the following properties:
 //
--- a/content/canvas/test/webgl/conformance/state/gl-get-calls.html
+++ b/content/canvas/test/webgl/conformance/state/gl-get-calls.html
@@ -56,18 +56,18 @@ else {
     if ("getError" in context)
       testPassed("context contains getError");
     else
       testFailed("context does not contains getError");
 
     debug("");
     debug("Check default values");
     shouldBe('context.getParameter(context.ACTIVE_TEXTURE)', 'context.TEXTURE0');
-    shouldBe('(context.getParameter(context.ALIASED_LINE_WIDTH_RANGE)[0] == 1) || (context.getParameter(context.ALIASED_LINE_WIDTH_RANGE)[1] == 1)', 'true');
-    shouldBe('(context.getParameter(context.ALIASED_POINT_SIZE_RANGE)[0] == 1) || (context.getParameter(context.ALIASED_POINT_SIZE_RANGE)[1] == 1)', 'true');
+	shouldBe('(context.getParameter(context.ALIASED_LINE_WIDTH_RANGE)[0] <= 1) && (context.getParameter(context.ALIASED_LINE_WIDTH_RANGE)[0] > 0) && (context.getParameter(context.ALIASED_LINE_WIDTH_RANGE)[1] >= 1)', 'true');
+	shouldBe('(context.getParameter(context.ALIASED_POINT_SIZE_RANGE)[0] <= 1) && (context.getParameter(context.ALIASED_POINT_SIZE_RANGE)[0] > 0) && (context.getParameter(context.ALIASED_POINT_SIZE_RANGE)[1] >= 1)', 'true');
     shouldBe('context.getParameter(context.ARRAY_BUFFER_BINDING)', 'null');
     shouldBe('context.getParameter(context.BLEND)', 'false');
     shouldBe('context.getParameter(context.BLEND_COLOR)', '[0, 0, 0, 0]');
     shouldBe('context.getParameter(context.BLEND_DST_ALPHA)', '0');
     shouldBe('context.getParameter(context.BLEND_DST_RGB)', '0');
     shouldBe('context.getParameter(context.BLEND_EQUATION_ALPHA)', 'context.FUNC_ADD');
     shouldBe('context.getParameter(context.BLEND_EQUATION_RGB)', 'context.FUNC_ADD');
     shouldBe('context.getParameter(context.BLEND_SRC_ALPHA)', '1');
--- a/content/canvas/test/webgl/conformance/textures/gl-teximage.html
+++ b/content/canvas/test/webgl/conformance/textures/gl-teximage.html
@@ -17,16 +17,17 @@ Use of this source code is governed by a
 <canvas id="example" width="256" height="16" style="width: 256px; height: 48px;"></canvas>
 <div id="description"></div>
 <div id="console"></div>
 <script>
 description("Test texImage2D conversions.");
 var wtu = WebGLTestUtils;
 var canvas = document.getElementById("example");
 var gl = wtu.create3DContext(canvas);
+gl.disable(gl.DITHER);
 var program = wtu.setupTexturedQuad(gl);
 
 glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors from setup.");
 
 var imgURLs = [
   '../resources/gray-ramp-256-with-128-alpha.png',
   '../resources/gray-ramp-256.png',
   '../resources/gray-ramp-default-gamma.png',
--- a/content/canvas/test/webgl/conformance/textures/tex-image-with-format-and-type.html
+++ b/content/canvas/test/webgl/conformance/textures/tex-image-with-format-and-type.html
@@ -41,16 +41,17 @@ function init()
     if (window.initNonKhronosFramework) {
         window.initNonKhronosFramework(true);
     }
 
     description('Verify texImage2D and texSubImage2D code paths taking both HTML and user-specified data with all format/type combinations');
 
     var canvas = document.getElementById("example");
     gl = wtu.create3DContext(canvas);
+    gl.disable(gl.DITHER);
     var program = wtu.setupTexturedQuad(gl);
 
     gl.clearColor(0,0,0,1);
     gl.clearDepth(1);
     gl.disable(gl.BLEND);
 
     textureLoc = gl.getUniformLocation(program, "tex");
 
--- a/content/canvas/test/webgl/conformance/textures/tex-sub-image-2d.html
+++ b/content/canvas/test/webgl/conformance/textures/tex-sub-image-2d.html
@@ -30,16 +30,17 @@ void main()
 <div id="description"></div>
 <div id="console"></div>
 <script>
 description('Tests texSubImage2D upload path from Uint8Array');
 
 var wtu = WebGLTestUtils;
 var canvas = document.getElementById("example");
 var gl = wtu.create3DContext(canvas);
+gl.disable(gl.DITHER);
 var program = wtu.setupProgram(
     gl,
     [wtu.setupSimpleTextureVertexShader(gl),
      wtu.loadShaderFromScript(gl, "fshader")],
     ['vPosition', 'texCoord0']);
 wtu.setupUnitQuad(gl);
 var textureWidth = 256;
 var textureHeight = 1;
--- a/content/canvas/test/webgl/conformance/textures/texture-size-cube-maps.html
+++ b/content/canvas/test/webgl/conformance/textures/texture-size-cube-maps.html
@@ -206,22 +206,27 @@ function checkRect(x, y, width, height, 
        color.color,
        "" + x + ", " + y + ", " + width + ", " + height +
        " should be " + color.name);
 }
 
 function fillLevel(target, level, size, color) {
   var numPixels = size * size;
   var pixels = new Uint8Array(numPixels * 4);
-  for (var jj = 0; jj < numPixels; ++jj) {
+  var pixelRow = new Uint8Array(size * 4);
+  for (var jj = 0; jj < size; ++jj) {
     var off = jj * 4;
-    pixels[off + 0] = color[0];
-    pixels[off + 1] = color[1];
-    pixels[off + 2] = color[2];
-    pixels[off + 3] = color[3];
+    pixelRow[off + 0] = color[0];
+    pixelRow[off + 1] = color[1];
+    pixelRow[off + 2] = color[2];
+    pixelRow[off + 3] = color[3];
+  }
+  for (var jj = 0; jj < size; ++jj) {
+    var off = jj * size * 4;
+    pixels.set(pixelRow, off);
   }
   gl.texImage2D(
       target, level, gl.RGBA, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE,
       pixels);
 }
 
 function printMat(mat) {
   debug("" + mat[0] + ", " + mat[1] + ", " + mat[2] + ", " + mat[3] + ", ");
--- a/content/canvas/test/webgl/conformance/textures/texture-size.html
+++ b/content/canvas/test/webgl/conformance/textures/texture-size.html
@@ -9,17 +9,17 @@ found in the LICENSE file.
 <meta charset="utf-8">
 <title>WebGL texture size conformance test.</title>
 <link rel="stylesheet" href="../../resources/js-test-style.css"/>
 <script src="../../resources/js-test-pre.js"></script>
 <script src="../resources/webgl-test.js"> </script>
 <script src="../resources/webgl-test-utils.js"></script>
 </head>
 <body>
-<canvas id="example" width="256" height="256" style="width: 40px; height: 40px;"></canvas>
+<canvas id="example" width="32" height="32" style="width: 40px; height: 40px;"></canvas>
 <div id="description"></div>
 <div id="console"></div>
 <script id="vshader" type="x-shader/x-vertex">
 attribute vec4 vPosition;
 attribute vec3 texCoord0;
 varying vec3 texCoord;
 void main()
 {
@@ -144,24 +144,39 @@ function checkTexture(width, height, cub
   wtu.checkCanvas(gl, color.rgba,
       type + " of size " + width + "x" + height + " with mips should draw with " + color.name);
   gl.deleteTexture(tex);
   return true;
 }
 
 function fillLevel(level, width, height, color, opt_cubemap) {
   var numPixels = width * height;
-  var pixels = new Uint8Array(numPixels * 4);
-  for (var jj = 0; jj < numPixels; ++jj) {
+  var pixels = null;
+  var largeDim = Math.max(width, height);
+  var smallDim = Math.min(width, height);
+
+  var pixelRow = new Uint8Array(largeDim * 4);
+  for (var jj = 0; jj < largeDim; ++jj) {
     var off = jj * 4;
-    pixels[off + 0] = color[0];
-    pixels[off + 1] = color[1];
-    pixels[off + 2] = color[2];
-    pixels[off + 3] = color[3];
+    pixelRow[off + 0] = color[0];
+    pixelRow[off + 1] = color[1];
+    pixelRow[off + 2] = color[2];
+    pixelRow[off + 3] = color[3];
   }
+
+  if (largeDim == numPixels) {
+    pixels = pixelRow;
+  } else {
+    var pixels = new Uint8Array(numPixels * 4);
+    for (var jj = 0; jj < smallDim; ++jj) {
+      var off = jj * largeDim * 4;
+      pixels.set(pixelRow, off);
+    }
+  }
+ 
   var targets = opt_cubemap ? [
     gl.TEXTURE_CUBE_MAP_POSITIVE_X,
     gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
     gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
     gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
     gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
     gl.TEXTURE_CUBE_MAP_NEGATIVE_Z] :
     [gl.TEXTURE_2D];
--- a/content/canvas/test/webgl/conformance/typedarrays/array-unit-tests.html
+++ b/content/canvas/test/webgl/conformance/typedarrays/array-unit-tests.html
@@ -514,28 +514,24 @@ function testConstructionWithOutOfRangeV
         var array = new type(buffer, 4, 0x3FFFFFFF);
     }, "Construction of " + name + " with out-of-range number of elements");
     shouldThrowIndexSizeErr(function() {
         var buffer = new ArrayBuffer(4);
         var array = new type(buffer, 8);
     }, "Construction of " + name + " with out-of-range offset");
 }
 
-function testConstructionWithNegativeOutOfRangeValues(type, name) {
-    try {
-        var buffer = new ArrayBuffer(-1);
-        testFailed("Construction of ArrayBuffer with negative size should throw exception");
-    } catch (e) {
-        testPassed("Construction of ArrayBuffer with negative size threw exception");
-    }
-    try {
-        var array = new type(-1);
-        testFailed("Construction of " + name + " with negative size should throw exception");
-    } catch (e) {
-        testPassed("Construction of " + name + " with negative size threw exception");
+function testConstructionWithNegativeOutOfRangeValues(type, name, elementSizeInBytes) {
+    if (elementSizeInBytes > 1) {
+        try {
+            var array = new type(-1);
+            testFailed("Construction of " + name + " with negative size should throw exception");
+        } catch (e) {
+            testPassed("Construction of " + name + " with negative size threw exception");
+        }
     }
     shouldThrowIndexSizeErr(function() {
         var buffer = new ArrayBuffer(4);
         var array = new type(buffer, 4, -2147483648);
     }, "Construction of " + name + " with negative out-of-range values");
 }
 
 function testConstructionWithUnalignedOffset(type, name, elementSizeInBytes) {
@@ -890,17 +886,17 @@ function runTests() {
                               testCase.testValues,
                               testCase.expectedValues);
     testConstructionBoundaryConditions(type,
                                        name,
                                        testCase.testValues,
                                        testCase.expectedValues);
     testConstructionWithNullBuffer(type, name);
     testConstructionWithOutOfRangeValues(type, name);
-    testConstructionWithNegativeOutOfRangeValues(type, name);
+    testConstructionWithNegativeOutOfRangeValues(type, name, testCase.elementSizeInBytes);
     testConstructionWithUnalignedOffset(type, name, testCase.elementSizeInBytes);
     testConstructionWithUnalignedLength(type, name, testCase.elementSizeInBytes);
     testConstructionOfHugeArray(type, name, testCase.elementSizeInBytes);
     testConstructionWithBothArrayBufferAndLength(type, name, testCase.elementSizeInBytes);
     testConstructionWithSubPortionOfArrayBuffer(type, name, testCase.elementSizeInBytes);
     testSubarrayWithOutOfRangeValues(type, name, testCase.elementSizeInBytes);
     testSubarrayWithDefaultValues(type, name, testCase.elementSizeInBytes);
     testSettingFromArrayWithOutOfRangeOffset(type, name);
--- a/content/canvas/test/webgl/conformance/uniforms/uniform-location.html
+++ b/content/canvas/test/webgl/conformance/uniforms/uniform-location.html
@@ -90,14 +90,23 @@ shouldGenerateGLError(contextA, contextA
 shouldGenerateGLError(contextA, contextA.INVALID_OPERATION, "contextA.getUniform(programS, locationSx)");
 
 // Retrieve the locations again, and they should be good.
 locationSx = contextA.getUniformLocation(programS, "u_struct.x");
 locationArray0 = contextA.getUniformLocation(programS, "u_array[0]");
 shouldGenerateGLError(contextA, contextA.NO_ERROR, "contextA.uniform1i(locationSx, 3)");
 shouldBe("contextA.getUniform(programS, locationSx)", "3");
 
+// getUniformLocation should return a different object everytime, should not cache and return the same object
+debug("Testing that getUniformLocation returns a different object everytime");
+locationSx  = contextA.getUniformLocation(programS, "u_struct.x");
+locationSx2 = contextA.getUniformLocation(programS, "u_struct.x");
+shouldBeFalse("locationSx === locationSx2");
+locationSx.foo  = {};
+locationSx2.foo = {};
+shouldBeFalse("locationSx.foo === locationSx2.foo");
+
 successfullyParsed = true;
 </script>
 
 <script src="../../resources/js-test-post.js"></script>
 </body>
 </html>
deleted file mode 100644
--- a/content/canvas/test/webgl/disable-quickCheckAPI.patch
+++ /dev/null
@@ -1,22 +0,0 @@
-# HG changeset patch
-# Parent 587913950733649a7cbd184a195b08cbb86b2fed
-diff --git a/content/canvas/test/webgl/conformance/more/00_test_list.txt b/content/canvas/test/webgl/conformance/more/00_test_list.txt
---- a/content/canvas/test/webgl/conformance/more/00_test_list.txt
-+++ b/content/canvas/test/webgl/conformance/more/00_test_list.txt
-@@ -1,12 +1,15 @@
- conformance/constants.html
- conformance/getContext.html
- conformance/methods.html
--conformance/quickCheckAPI.html
-+#this test causes whichever comes after to intermittently time out.
-+#forcing a GC run doesn't solve this issue. Could be something about using a random amount of memory that
-+#can be too high, causing e.g. swapping.
-+#conformance/quickCheckAPI.html
- conformance/webGLArrays.html
- functions/bindBuffer.html
- functions/bindBufferBadArgs.html
- functions/bindFramebufferLeaveNonZero.html
- functions/bufferData.html
- functions/bufferDataBadArgs.html
- functions/bufferSubData.html
- functions/bufferSubDataBadArgs.html
--- a/content/canvas/test/webgl/dont-load-image-from-internet.patch
+++ b/content/canvas/test/webgl/dont-load-image-from-internet.patch
@@ -1,11 +1,10 @@
 # HG changeset patch
-# Parent 95e5d92098ad52788348e609e261305d4b05b551
-
+# Parent 9c1a90f789e3d43455cb18a9a911627c80c0d9ac
 diff --git a/content/canvas/test/webgl/conformance/more/functions/readPixelsBadArgs.html b/content/canvas/test/webgl/conformance/more/functions/readPixelsBadArgs.html
 --- a/content/canvas/test/webgl/conformance/more/functions/readPixelsBadArgs.html
 +++ b/content/canvas/test/webgl/conformance/more/functions/readPixelsBadArgs.html
 @@ -110,10 +110,10 @@ Tests.testReadPixelsSOPCanvas = function
  
  Tests.endUnit = function(gl) {
  }
  
--- a/content/canvas/test/webgl/failing_tests_linux.txt
+++ b/content/canvas/test/webgl/failing_tests_linux.txt
@@ -1,13 +1,15 @@
+conformance/context/context-lost-restored.html
 conformance/context/premultiplyalpha-test.html
 conformance/glsl/misc/glsl-long-variable-names.html
 conformance/glsl/misc/shader-with-256-character-identifier.frag.html
 conformance/glsl/misc/shader-with-long-line.html
 conformance/misc/uninitialized-test.html
 conformance/programs/gl-get-active-attribute.html
 conformance/textures/texture-mips.html
 conformance/uniforms/gl-uniform-bool.html
 conformance/more/conformance/quickCheckAPI-S_V.html
 conformance/more/functions/uniformfArrayLen1.html
 conformance/glsl/misc/attrib-location-length-limits.html
 conformance/glsl/misc/uniform-location-length-limits.html
 conformance/renderbuffers/framebuffer-object-attachment.html
+conformance/misc/object-deletion-behaviour.html
--- a/content/canvas/test/webgl/failing_tests_mac.txt
+++ b/content/canvas/test/webgl/failing_tests_mac.txt
@@ -1,11 +1,13 @@
+conformance/context/context-lost-restored.html
 conformance/context/premultiplyalpha-test.html
 conformance/glsl/misc/glsl-function-nodes.html
 conformance/glsl/misc/glsl-long-variable-names.html
 conformance/glsl/misc/shader-with-256-character-identifier.frag.html
 conformance/glsl/misc/shader-with-long-line.html
 conformance/more/conformance/quickCheckAPI-S_V.html
 conformance/more/functions/uniformfBadArgs.html
 conformance/more/functions/uniformiBadArgs.html
 conformance/glsl/misc/attrib-location-length-limits.html
 conformance/glsl/misc/uniform-location-length-limits.html
 conformance/renderbuffers/framebuffer-object-attachment.html
+conformance/misc/object-deletion-behaviour.html
--- a/content/canvas/test/webgl/failing_tests_windows.txt
+++ b/content/canvas/test/webgl/failing_tests_windows.txt
@@ -1,13 +1,14 @@
+conformance/context/context-lost-restored.html
 conformance/context/premultiplyalpha-test.html
 conformance/glsl/functions/glsl-function-atan.html
 conformance/glsl/functions/glsl-function-atan-xy.html
-conformance/glsl/functions/glsl-function-mod-gentype.html
 conformance/glsl/misc/glsl-long-variable-names.html
 conformance/glsl/misc/shader-with-256-character-identifier.frag.html
 conformance/glsl/misc/shader-with-long-line.html
 conformance/more/conformance/quickCheckAPI-S_V.html
 conformance/more/functions/uniformfArrayLen1.html
 conformance/glsl/misc/attrib-location-length-limits.html
 conformance/glsl/misc/struct-nesting-under-maximum.html
 conformance/glsl/misc/uniform-location-length-limits.html
 conformance/renderbuffers/framebuffer-object-attachment.html
+conformance/misc/object-deletion-behaviour.html
--- a/content/canvas/test/webgl/log-more-info-about-test-failures.patch
+++ b/content/canvas/test/webgl/log-more-info-about-test-failures.patch
@@ -1,21 +1,14 @@
 # HG changeset patch
-# Parent 2dbd71999fe8a8da476ab6cc97716f7b7c294fb8
-# User Doug Sherk <dsherk@mozilla.com>
-Bug 693703: added additional logging information for mochitests, incl. image reference differences r=bjacob
-
-Added some code to print to dump output of WebGL mochitest failures. Also added
-special code to handle incorrect reference images. It will now provide the user
-with a way to compare the reference and actual drawings.
-
+# Parent 4ed0bad4933ba69927ee5f75cf67093d3e99566a
 diff --git a/content/canvas/test/webgl/conformance/glsl/misc/glsl-function-nodes.html b/content/canvas/test/webgl/conformance/glsl/misc/glsl-function-nodes.html
 --- a/content/canvas/test/webgl/conformance/glsl/misc/glsl-function-nodes.html
 +++ b/content/canvas/test/webgl/conformance/glsl/misc/glsl-function-nodes.html
-@@ -115,17 +115,18 @@ function init()
+@@ -117,17 +117,18 @@ function init()
      var bufFunction = new Uint8Array(width * height * 4);
      var bufMacro = new Uint8Array(width * height * 4);
  
      if (drawAndRead("canvasFunction", "vshaderFunction", bufFunction) == false ||
          drawAndRead("canvasMacro", "vshaderMacro", bufMacro) == false) {
          testFailed("Setup failed");
      } else {
          if (compareRendering(bufFunction, bufMacro, 4) == false)
@@ -143,17 +136,17 @@ diff --git a/content/canvas/test/webgl/r
 +    if (typeof test.getImageData == 'function') {
 +        testData = test.canvas.toDataURL();
 +    } else {
 +        testData = arrayToURLData(test, width, height);
 +    }
 +    
 +    testFailed(msg);
 +
-+    var data = 'REFTEST TEST-UNEXPECTED-FAIL | ' + msg + ' | image comparison (==)\n' +
++    var data = 'REFTEST TEST-DEBUG-INFO | ' + msg + ' | image comparison (==)\n' +
 +               'REFTEST   IMAGE 1 (TEST): ' + testData + '\n' +
 +               'REFTEST   IMAGE 2 (REFERENCE): ' + refData;
 +    dump('FAIL: ' + data + '\n');
 +    dump('To view the differences between these image renderings, go to the following link: https://hg.mozilla.org/mozilla-central/raw-file/tip/layout/tools/reftest/reftest-analyzer.xhtml#log=' +
 +    encodeURIComponent(encodeURIComponent(data)) + '\n');
 +}
 +
 +function arrayToURLData(buf, width, height)
--- a/content/canvas/test/webgl/remove-uniqueObjectTest.patch
+++ b/content/canvas/test/webgl/remove-uniqueObjectTest.patch
@@ -1,11 +1,10 @@
 # HG changeset patch
-# Parent 041265c6ac29c43856a8df4d4581cb6a04b95a68
-
+# Parent bf9d7872738cdb7cf425e6dd060ae62882487e1a
 diff --git a/content/canvas/test/webgl/conformance/extensions/oes-standard-derivatives.html b/content/canvas/test/webgl/conformance/extensions/oes-standard-derivatives.html
 --- a/content/canvas/test/webgl/conformance/extensions/oes-standard-derivatives.html
 +++ b/content/canvas/test/webgl/conformance/extensions/oes-standard-derivatives.html
 @@ -107,17 +107,18 @@ if (!gl) {
      } else {
          testPassed("Successfully enabled OES_standard_derivatives extension");
  
          runSupportedTest(true);
--- a/content/canvas/test/webgl/undo-r15330-async-test-list-loading.patch
+++ b/content/canvas/test/webgl/undo-r15330-async-test-list-loading.patch
@@ -1,10 +1,11 @@
 # HG changeset patch
-# Parent 0e44920c4e5e697f86e0a42b909c5a032d70a1a4
+# Parent 41137626edf2a358f2be1b7ed3f83211230ab4f5
+
 diff --git a/content/canvas/test/webgl/resources/webgl-test-harness.js b/content/canvas/test/webgl/resources/webgl-test-harness.js
 --- a/content/canvas/test/webgl/resources/webgl-test-harness.js
 +++ b/content/canvas/test/webgl/resources/webgl-test-harness.js
 @@ -90,190 +90,105 @@ var log = function(msg) {
    if (window.console && window.console.log) {
      window.console.log(msg);
    }
  };
--- a/dom/Makefile.in
+++ b/dom/Makefile.in
@@ -78,16 +78,20 @@ DIRS += \
   plugins/base \
   plugins/ipc \
   indexedDB \
   system \
   ipc \
   workers \
   $(NULL)
 
+ifdef MOZ_B2G_RIL #{
+DIRS += telephony
+endif #}
+
 ifdef ENABLE_TESTS
 DIRS += tests
 ifneq (,$(filter gtk2 cocoa windows android qt os2,$(MOZ_WIDGET_TOOLKIT)))
 DIRS += plugins/test
 endif
 endif
 
 include $(topsrcdir)/config/rules.mk
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -1636,16 +1636,17 @@ jsid nsDOMClassInfo::sNodePrincipal_id  
 jsid nsDOMClassInfo::sDocumentURIObject_id=JSID_VOID;
 jsid nsDOMClassInfo::sJava_id            = JSID_VOID;
 jsid nsDOMClassInfo::sPackages_id        = JSID_VOID;
 jsid nsDOMClassInfo::sWrappedJSObject_id = JSID_VOID;
 jsid nsDOMClassInfo::sURL_id             = JSID_VOID;
 jsid nsDOMClassInfo::sKeyPath_id         = JSID_VOID;
 jsid nsDOMClassInfo::sAutoIncrement_id   = JSID_VOID;
 jsid nsDOMClassInfo::sUnique_id          = JSID_VOID;
+jsid nsDOMClassInfo::sMultiEntry_id      = JSID_VOID;
 jsid nsDOMClassInfo::sOnload_id          = JSID_VOID;
 jsid nsDOMClassInfo::sOnerror_id         = JSID_VOID;
 
 static const JSClass *sObjectClass = nsnull;
 
 /**
  * Set our JSClass pointer for the Object class
  */
@@ -1899,16 +1900,17 @@ nsDOMClassInfo::DefineStaticJSVals(JSCon
   SET_JSID_TO_STRING(sDocumentURIObject_id,cx,"documentURIObject");
   SET_JSID_TO_STRING(sJava_id,            cx, "java");
   SET_JSID_TO_STRING(sPackages_id,        cx, "Packages");
   SET_JSID_TO_STRING(sWrappedJSObject_id, cx, "wrappedJSObject");
   SET_JSID_TO_STRING(sURL_id,             cx, "URL");
   SET_JSID_TO_STRING(sKeyPath_id,         cx, "keyPath");
   SET_JSID_TO_STRING(sAutoIncrement_id,   cx, "autoIncrement");
   SET_JSID_TO_STRING(sUnique_id,          cx, "unique");
+  SET_JSID_TO_STRING(sMultiEntry_id,      cx, "multiEntry");
   SET_JSID_TO_STRING(sOnload_id,          cx, "onload");
   SET_JSID_TO_STRING(sOnerror_id,         cx, "onerror");
 
   return NS_OK;
 }
 
 static nsresult
 CreateExceptionFromResult(JSContext *cx, nsresult aResult)
@@ -4897,16 +4899,17 @@ nsDOMClassInfo::ShutDown()
   sNodePrincipal_id   = JSID_VOID;
   sDocumentURIObject_id=JSID_VOID;
   sJava_id            = JSID_VOID;
   sPackages_id        = JSID_VOID;
   sWrappedJSObject_id = JSID_VOID;
   sKeyPath_id         = JSID_VOID;
   sAutoIncrement_id   = JSID_VOID;
   sUnique_id          = JSID_VOID;
+  sMultiEntry_id      = JSID_VOID;
   sOnload_id          = JSID_VOID;
   sOnerror_id         = JSID_VOID;
 
   NS_IF_RELEASE(sXPConnect);
   NS_IF_RELEASE(sSecMan);
   sIsInitialized = false;
 }
 
--- a/dom/base/nsDOMClassInfo.h
+++ b/dom/base/nsDOMClassInfo.h
@@ -290,16 +290,17 @@ public:
   static jsid sDocumentURIObject_id;
   static jsid sJava_id;
   static jsid sPackages_id;
   static jsid sWrappedJSObject_id;
   static jsid sURL_id;
   static jsid sKeyPath_id;
   static jsid sAutoIncrement_id;
   static jsid sUnique_id;
+  static jsid sMultiEntry_id;
   static jsid sOnload_id;
   static jsid sOnerror_id;
 
 protected:
   static JSPropertyOp sXPCNativeWrapperGetPropertyOp;
   static JSPropertyOp sXrayWrapperPropertyHolderGetPropertyOp;
 };
 
--- a/dom/dom-config.mk
+++ b/dom/dom-config.mk
@@ -2,16 +2,17 @@ DOM_SRCDIRS = \
   dom/base \
   dom/battery \
   dom/sms/src \
   dom/src/events \
   dom/src/storage \
   dom/src/offline \
   dom/src/geolocation \
   dom/src/notification \
+  dom/telephony \
   dom/workers \
   content/xbl/src \
   content/xul/document/src \
   content/events/src \
   content/base/src \
   content/html/content/src \
   content/html/document/src \
   content/svg/content/src \
--- a/dom/indexedDB/DatabaseInfo.cpp
+++ b/dom/indexedDB/DatabaseInfo.cpp
@@ -92,27 +92,29 @@ DatabaseInfo::~DatabaseInfo()
   }
 }
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 
 IndexInfo::IndexInfo()
 : id(LL_MININT),
   unique(false),
-  autoIncrement(false)
+  autoIncrement(false),
+  multiEntry(false)
 {
   MOZ_COUNT_CTOR(IndexInfo);
 }
 
 IndexInfo::IndexInfo(const IndexInfo& aOther)
 : id(aOther.id),
   name(aOther.name),
   keyPath(aOther.keyPath),
   unique(aOther.unique),
-  autoIncrement(aOther.autoIncrement)
+  autoIncrement(aOther.autoIncrement),
+  multiEntry(aOther.multiEntry)
 {
   MOZ_COUNT_CTOR(IndexInfo);
 }
 
 IndexInfo::~IndexInfo()
 {
   MOZ_COUNT_DTOR(IndexInfo);
 }
--- a/dom/indexedDB/DatabaseInfo.h
+++ b/dom/indexedDB/DatabaseInfo.h
@@ -116,16 +116,17 @@ struct IndexInfo
   : id(LL_MININT), unique(false), autoIncrement(false) { }
 #endif
 
   PRInt64 id;
   nsString name;
   nsString keyPath;
   bool unique;
   bool autoIncrement;
+  bool multiEntry;
 };
 
 struct ObjectStoreInfo
 {
 #ifdef NS_BUILD_REFCNT_LOGGING
   ObjectStoreInfo();
   ObjectStoreInfo(ObjectStoreInfo& aOther);
   ~ObjectStoreInfo();
@@ -144,15 +145,16 @@ struct ObjectStoreInfo
 
 struct IndexUpdateInfo
 {
 #ifdef NS_BUILD_REFCNT_LOGGING
   IndexUpdateInfo();
   ~IndexUpdateInfo();
 #endif
 
-  IndexInfo info;
+  PRInt64 indexId;
+  bool indexUnique;
   Key value;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_databaseinfo_h__
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -382,21 +382,18 @@ IDBDatabase::CreateObjectStore(const nsA
 
   if (!transaction ||
       transaction->Mode() != nsIIDBTransaction::VERSION_CHANGE) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   DatabaseInfo* databaseInfo = Info();
 
-  if (databaseInfo->ContainsStoreName(aName)) {
-    return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
-  }
-
   nsString keyPath;
+  keyPath.SetIsVoid(true);
   bool autoIncrement = false;
 
   if (!JSVAL_IS_VOID(aOptions) && !JSVAL_IS_NULL(aOptions)) {
     if (JSVAL_IS_PRIMITIVE(aOptions)) {
       // XXX This isn't the right error
       return NS_ERROR_DOM_TYPE_ERR;
     }
 
@@ -432,18 +429,27 @@ IDBDatabase::CreateObjectStore(const nsA
     JSBool boolVal;
     if (!JS_ValueToBoolean(aCx, val, &boolVal)) {
       NS_WARNING("JS_ValueToBoolean failed!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
     autoIncrement = !!boolVal;
   }
 
-  if (!IDBObjectStore::IsValidKeyPath(aCx, keyPath)) {
-    return NS_ERROR_DOM_SYNTAX_ERR;
+  if (databaseInfo->ContainsStoreName(aName)) {
+    return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
+  }
+
+  if (!keyPath.IsVoid()) {
+    if (keyPath.IsEmpty() && autoIncrement) {
+      return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+    }
+    if (!IDBObjectStore::IsValidKeyPath(aCx, keyPath)) {
+      return NS_ERROR_DOM_SYNTAX_ERR;
+    }
   }
 
   nsAutoPtr<ObjectStoreInfo> newInfo(new ObjectStoreInfo());
 
   newInfo->name = aName;
   newInfo->id = databaseInfo->nextObjectStoreId++;
   newInfo->keyPath = keyPath;
   newInfo->autoIncrement = autoIncrement;
@@ -493,16 +499,19 @@ IDBDatabase::DeleteObjectStore(const nsA
   }
 
   nsRefPtr<DeleteObjectStoreHelper> helper =
     new DeleteObjectStoreHelper(transaction, objectStoreInfo->id);
   nsresult rv = helper->DispatchToTransactionPool();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   info->RemoveObjectStore(aName);
+
+  transaction->ReleaseCachedObjectStore(aName);
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBDatabase::Transaction(const jsval& aStoreNames,
                          PRUint16 aMode,
                          JSContext* aCx,
                          PRUint8 aOptionalArgCount,
@@ -718,36 +727,38 @@ IDBDatabase::PostHandleEvent(nsEventChai
   return NS_OK;
 }
 
 nsresult
 CreateObjectStoreHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   nsCOMPtr<mozIStorageStatement> stmt =
     mTransaction->GetCachedStatement(NS_LITERAL_CSTRING(
-    "INSERT INTO object_store (id, name, key_path, auto_increment) "
-    "VALUES (:id, :name, :key_path, :auto_increment)"
+    "INSERT INTO object_store (id, auto_increment, name, key_path) "
+    "VALUES (:id, :auto_increment, :name, :key_path)"
   ));
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   mozStorageStatementScoper scoper(stmt);
 
   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"),
                                        mObjectStore->Id());
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("auto_increment"),
+                             mObjectStore->IsAutoIncrement() ? 1 : 0);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
   rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mObjectStore->Name());
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
-                              mObjectStore->KeyPath());
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("auto_increment"),
-                             mObjectStore->IsAutoIncrement() ? 1 : 0);
+  rv = mObjectStore->HasKeyPath() ?
+    stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
+                           mObjectStore->KeyPath()) :
+    stmt->BindNullByName(NS_LITERAL_CSTRING("key_path"));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   rv = stmt->Execute();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   return NS_OK;
 }
 
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -242,33 +242,43 @@ IDBFactory::LoadDatabaseInformation(mozI
 
     ObjectStoreInfo* info = element->get();
 
     rv = stmt->GetString(0, info->name);
     NS_ENSURE_SUCCESS(rv, rv);
 
     info->id = stmt->AsInt64(1);
 
-    rv = stmt->GetString(2, info->keyPath);
+    PRInt32 columnType;
+    nsresult rv = stmt->GetTypeOfIndex(2, &columnType);
     NS_ENSURE_SUCCESS(rv, rv);
+    if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) {
+      info->keyPath.SetIsVoid(true);
+    }
+    else {
+      NS_ASSERTION(columnType == mozIStorageStatement::VALUE_TYPE_TEXT,
+                   "Should be a string");
+      rv = stmt->GetString(2, info->keyPath);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
 
     info->autoIncrement = !!stmt->AsInt32(3);
     info->databaseId = aDatabaseId;
 
     ObjectStoreInfoMap* mapEntry = infoMap.AppendElement();
     NS_ENSURE_TRUE(mapEntry, NS_ERROR_OUT_OF_MEMORY);
 
     mapEntry->id = info->id;
     mapEntry->info = info;
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Load index information
   rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT object_store_id, id, name, key_path, unique_index, "
+    "SELECT object_store_id, id, name, key_path, unique_index, multientry, "
            "object_store_autoincrement "
     "FROM object_store_index"
   ), getter_AddRefs(stmt));
   NS_ENSURE_SUCCESS(rv, rv);
 
   while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
     PRInt64 objectStoreId = stmt->AsInt64(0);
 
@@ -292,17 +302,18 @@ IDBFactory::LoadDatabaseInformation(mozI
 
     rv = stmt->GetString(2, indexInfo->name);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = stmt->GetString(3, indexInfo->keyPath);
     NS_ENSURE_SUCCESS(rv, rv);
 
     indexInfo->unique = !!stmt->AsInt32(4);
-    indexInfo->autoIncrement = !!stmt->AsInt32(5);
+    indexInfo->multiEntry = !!stmt->AsInt32(5);
+    indexInfo->autoIncrement = !!stmt->AsInt32(6);
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Load version information.
   rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT version "
     "FROM database"
   ), getter_AddRefs(stmt));
--- a/dom/indexedDB/IDBIndex.cpp
+++ b/dom/indexedDB/IDBIndex.cpp
@@ -313,16 +313,17 @@ IDBIndex::Create(IDBObjectStore* aObject
   index->mScriptContext = database->ScriptContext();
   index->mOwner = database->Owner();
 
   index->mObjectStore = aObjectStore;
   index->mId = aIndexInfo->id;
   index->mName = aIndexInfo->name;
   index->mKeyPath = aIndexInfo->keyPath;
   index->mUnique = aIndexInfo->unique;
+  index->mMultiEntry = aIndexInfo->multiEntry;
   index->mAutoIncrement = aIndexInfo->autoIncrement;
 
   return index.forget();
 }
 
 IDBIndex::IDBIndex()
 : mId(LL_MININT),
   mUnique(false),
@@ -392,16 +393,25 @@ IDBIndex::GetUnique(bool* aUnique)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   *aUnique = mUnique;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+IDBIndex::GetMultiEntry(bool* aMultiEntry)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  *aMultiEntry = mMultiEntry;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 IDBIndex::GetObjectStore(nsIIDBObjectStore** aObjectStore)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   nsCOMPtr<nsIIDBObjectStore> objectStore(mObjectStore);
   objectStore.forget(aObjectStore);
   return NS_OK;
 }
--- a/dom/indexedDB/IDBIndex.h
+++ b/dom/indexedDB/IDBIndex.h
@@ -82,16 +82,21 @@ public:
     return mName;
   }
 
   bool IsUnique() const
   {
     return mUnique;
   }
 
+  bool IsMultiEntry() const
+  {
+    return mMultiEntry;
+  }
+
   bool IsAutoIncrement() const
   {
     return mAutoIncrement;
   }
 
   const nsString& KeyPath() const
   {
     return mKeyPath;
@@ -105,14 +110,15 @@ private:
 
   nsCOMPtr<nsIScriptContext> mScriptContext;
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
   PRInt64 mId;
   nsString mName;
   nsString mKeyPath;
   bool mUnique;
+  bool mMultiEntry;
   bool mAutoIncrement;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_idbindex_h__
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -92,19 +92,16 @@ public:
 
   void ReleaseMainThreadObjects()
   {
     mObjectStore = nsnull;
     IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
     AsyncConnectionHelper::ReleaseMainThreadObjects();
   }
 
-  nsresult UpdateIndexes(mozIStorageConnection* aConnection,
-                         PRInt64 aObjectDataId);
-
 private:
   // In-params.
   nsRefPtr<IDBObjectStore> mObjectStore;
 
   // These may change in the autoincrement case.
   JSAutoStructuredCloneBuffer mCloneBuffer;
   Key mKey;
   const bool mOverwrite;
@@ -417,51 +414,63 @@ IgnoreWhitespace(PRUnichar c)
 {
   return false;
 }
 
 typedef nsCharSeparatedTokenizerTemplate<IgnoreWhitespace> KeyPathTokenizer;
 
 inline
 nsresult
-GetKeyFromValue(JSContext* aCx,
-                jsval aVal,
-                const nsAString& aKeyPath,
-                Key& aKey)
+GetJSValFromKeyPath(JSContext* aCx,
+                    jsval aVal,
+                    const nsAString& aKeyPath,
+                    jsval& aKey)
 {
   NS_ASSERTION(aCx, "Null pointer!");
   // aVal can be primitive iff the key path is empty.
-  NS_ASSERTION(!JSVAL_IS_PRIMITIVE(aVal) || aKeyPath.IsEmpty(),
-               "Why are we here!?");
   NS_ASSERTION(IDBObjectStore::IsValidKeyPath(aCx, aKeyPath),
                "This will explode!");
 
   KeyPathTokenizer tokenizer(aKeyPath, '.');
 
   jsval intermediate = aVal;
   while (tokenizer.hasMoreTokens()) {
-    nsString token(tokenizer.nextToken());
-
-    if (!token.Length()) {
-      return NS_ERROR_DOM_SYNTAX_ERR;
-    }
-
-    const jschar* keyPathChars = token.get();
+    const nsDependentSubstring& token = tokenizer.nextToken();
+
+    NS_ASSERTION(!token.IsEmpty(), "Should be a valid keypath");
+
+    const jschar* keyPathChars = token.BeginReading();
     const size_t keyPathLen = token.Length();
 
     if (JSVAL_IS_PRIMITIVE(intermediate)) {
-      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+      intermediate = JSVAL_VOID;
+      break;
     }
 
     JSBool ok = JS_GetUCProperty(aCx, JSVAL_TO_OBJECT(intermediate),
                                  keyPathChars, keyPathLen, &intermediate);
     NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
   }
-
-  if (NS_FAILED(aKey.SetFromJSVal(aCx, intermediate))) {
+  
+  aKey = intermediate;
+  return NS_OK;
+}
+
+inline
+nsresult
+GetKeyFromValue(JSContext* aCx,
+                jsval aVal,
+                const nsAString& aKeyPath,
+                Key& aKey)
+{
+  jsval key;
+  nsresult rv = GetJSValFromKeyPath(aCx, aVal, aKeyPath, key);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (NS_FAILED(aKey.SetFromJSVal(aCx, key))) {
     aKey.Unset();
   }
 
   return NS_OK;
 }
 
 inline
 already_AddRefed<IDBRequest>
@@ -569,116 +578,89 @@ IDBObjectStore::IsValidKeyPath(JSContext
     return false;
   }
 
   return true;
 }
 
 // static
 nsresult
-IDBObjectStore::GetKeyPathValueFromStructuredData(const PRUint8* aData,
-                                                  PRUint32 aDataLength,
-                                                  const nsAString& aKeyPath,
-                                                  JSContext* aCx,
-                                                  Key& aValue)
+IDBObjectStore::AppendIndexUpdateInfo(PRInt64 aIndexID,
+                                      const nsAString& aKeyPath,
+                                      bool aUnique,
+                                      bool aMultiEntry,
+                                      JSContext* aCx,
+                                      jsval aObject,
+                                      nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
 {
-  NS_ASSERTION(aData, "Null pointer!");
-  NS_ASSERTION(aDataLength, "Empty data!");
-  NS_ASSERTION(aCx, "Null pointer!");
-
-  JSAutoRequest ar(aCx);
-
-  jsval clone;
-  if (!JS_ReadStructuredClone(aCx, reinterpret_cast<const uint64*>(aData),
-                              aDataLength, JS_STRUCTURED_CLONE_VERSION,
-                              &clone, NULL, NULL)) {
-    return NS_ERROR_DOM_DATA_CLONE_ERR;
-  }
-
-  if (JSVAL_IS_PRIMITIVE(clone) && !aKeyPath.IsEmpty()) {
-    // This isn't an object, so just leave the key unset.
-    aValue.Unset();
-    return NS_OK;
-  }
-
-  nsresult rv = GetKeyFromValue(aCx, clone, aKeyPath, aValue);
+  jsval key;
+  nsresult rv = GetJSValFromKeyPath(aCx, aObject, aKeyPath, key);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return NS_OK;
-}
-
-/* static */
-nsresult
-IDBObjectStore::GetIndexUpdateInfo(ObjectStoreInfo* aObjectStoreInfo,
-                                   JSContext* aCx,
-                                   jsval aObject,
-                                   nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
-{
-  JSObject* cloneObj = nsnull;
-
-  PRUint32 count = aObjectStoreInfo->indexes.Length();
-  if (count) {
-    if (!aUpdateInfoArray.SetCapacity(count)) {
-      NS_ERROR("Out of memory!");
-      return NS_ERROR_OUT_OF_MEMORY;
+  if (aMultiEntry && !JSVAL_IS_PRIMITIVE(key) &&
+      JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(key))) {
+    JSObject* array = JSVAL_TO_OBJECT(key);
+    jsuint arrayLength;
+    if (!JS_GetArrayLength(aCx, array, &arrayLength)) {
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
-    for (PRUint32 indexesIndex = 0; indexesIndex < count; indexesIndex++) {
-      const IndexInfo& indexInfo = aObjectStoreInfo->indexes[indexesIndex];
-
-      if (JSVAL_IS_PRIMITIVE(aObject) && !indexInfo.keyPath.IsEmpty()) {
-        continue;
+    for (jsuint arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
+      jsval arrayItem;
+      if (!JS_GetElement(aCx, array, arrayIndex, &arrayItem)) {
+        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
       }
 
       Key value;
-      nsresult rv = GetKeyFromValue(aCx, aObject, indexInfo.keyPath, value);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      if (value.IsUnset()) {
+      if (NS_FAILED(value.SetFromJSVal(aCx, arrayItem)) ||
+          value.IsUnset()) {
         // Not a value we can do anything with, ignore it.
         continue;
       }
 
       IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement();
-      updateInfo->info = indexInfo;
+      updateInfo->indexId = aIndexID;
+      updateInfo->indexUnique = aUnique;
       updateInfo->value = value;
     }
   }
   else {
-    aUpdateInfoArray.Clear();
+    Key value;
+    if (NS_FAILED(value.SetFromJSVal(aCx, key)) ||
+        value.IsUnset()) {
+      // Not a value we can do anything with, ignore it.
+      return NS_OK;
+    }
+
+    IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement();
+    updateInfo->indexId = aIndexID;
+    updateInfo->indexUnique = aUnique;
+    updateInfo->value = value;
   }
 
   return NS_OK;
 }
 
-/* static */
+// static
 nsresult
 IDBObjectStore::UpdateIndexes(IDBTransaction* aTransaction,
                               PRInt64 aObjectStoreId,
                               const Key& aObjectStoreKey,
                               bool aAutoIncrement,
                               bool aOverwrite,
                               PRInt64 aObjectDataId,
                               const nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
 {
-#ifdef DEBUG
-  if (aAutoIncrement) {
-    NS_ASSERTION(aObjectDataId != LL_MININT, "Bad objectData id!");
-  }
-  else {
-    NS_ASSERTION(aObjectDataId == LL_MININT, "Bad objectData id!");
-  }
-#endif
-
-  PRUint32 indexCount = aUpdateInfoArray.Length();
+  NS_ASSERTION(!aAutoIncrement || aObjectDataId != LL_MININT,
+               "Bad objectData id!");
 
   nsCOMPtr<mozIStorageStatement> stmt;
   nsresult rv;
 
-  if (!aAutoIncrement) {
+  if (aObjectDataId == LL_MININT) {
     stmt = aTransaction->GetCachedStatement(
       "SELECT id "
       "FROM object_data "
       "WHERE object_store_id = :osid "
       "AND key_value = :key_value"
     );
     NS_ENSURE_TRUE(stmt, NS_ERROR_FAILURE);
 
@@ -730,43 +712,59 @@ IDBObjectStore::UpdateIndexes(IDBTransac
 
     rv = stmt->BindInt64ByName(objectDataId, aObjectDataId);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = stmt->Execute();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  for (PRUint32 indexIndex = 0; indexIndex < indexCount; indexIndex++) {
-    const IndexUpdateInfo& updateInfo = aUpdateInfoArray[indexIndex];
-
-    NS_ASSERTION(updateInfo.info.autoIncrement == aAutoIncrement, "Huh?!");
+  PRUint32 infoCount = aUpdateInfoArray.Length();
+  for (PRUint32 i = 0; i < infoCount; i++) {
+    const IndexUpdateInfo& updateInfo = aUpdateInfoArray[i];
 
     // Insert new values.
     stmt = aTransaction->IndexDataInsertStatement(aAutoIncrement,
-                                                  updateInfo.info.unique);
+                                                  updateInfo.indexUnique);
     NS_ENSURE_TRUE(stmt, NS_ERROR_FAILURE);
 
     mozStorageStatementScoper scoper4(stmt);
 
-    rv = stmt->BindInt64ByName(indexId, updateInfo.info.id);
+    rv = stmt->BindInt64ByName(indexId, updateInfo.indexId);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = stmt->BindInt64ByName(objectDataId, aObjectDataId);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    if (!updateInfo.info.autoIncrement) {
+    if (!aAutoIncrement) {
       rv = aObjectStoreKey.BindToStatement(stmt, objectDataKey);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     rv = updateInfo.value.BindToStatement(stmt, value);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = stmt->Execute();
+    if (rv == NS_ERROR_STORAGE_CONSTRAINT && updateInfo.indexUnique) {
+      // If we're inserting multiple entries for the same unique index, then
+      // we might have failed to insert due to colliding with another entry for
+      // the same index in which case we should ignore it.
+      
+      for (PRInt32 j = (PRInt32)i - 1;
+           j >= 0 && aUpdateInfoArray[j].indexId == updateInfo.indexId;
+           --j) {
+        if (updateInfo.value == aUpdateInfoArray[j].value) {
+          // We found a key with the same value for the same index. So we
+          // must have had a collision with a value we just inserted.
+          rv = NS_OK;
+          break;
+        }
+      }
+    }
+
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
@@ -901,72 +899,156 @@ IDBObjectStore::GetAddInfo(JSContext* aC
                            Key& aKey,
                            nsTArray<IndexUpdateInfo>& aUpdateInfoArray,
                            PRUint64* aOffsetToKeyProp)
 {
   nsresult rv;
 
   // Return DATA_ERR if a key was passed in and this objectStore uses inline
   // keys.
-  if (!JSVAL_IS_VOID(aKeyVal) && !mKeyPath.IsEmpty()) {
+  if (!JSVAL_IS_VOID(aKeyVal) && HasKeyPath()) {
     return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
   }
 
   JSAutoRequest ar(aCx);
 
-  if (mKeyPath.IsEmpty()) {
+  if (!HasKeyPath()) {
     // Out-of-line keys must be passed in.
     rv = aKey.SetFromJSVal(aCx, aKeyVal);
     NS_ENSURE_SUCCESS(rv, rv);
   }
-  else {
+  else if (!mAutoIncrement) {
     // Inline keys live on the object. Make sure that the value passed in is an
     // object.
-    if (JSVAL_IS_PRIMITIVE(aValue)) {
-      return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
-    }
-
     rv = GetKeyFromValue(aCx, aValue, mKeyPath, aKey);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Return DATA_ERR if no key was specified this isn't an autoIncrement
   // objectStore.
   if (aKey.IsUnset() && !mAutoIncrement) {
     return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
   }
 
   // Figure out indexes and the index values to update here.
   ObjectStoreInfo* info;
   if (!mTransaction->Database()->Info()->GetObjectStore(mName, &info)) {
     NS_ERROR("This should never fail!");
   }
 
-  rv = GetIndexUpdateInfo(info, aCx, aValue, aUpdateInfoArray);
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-  const jschar* keyPathChars =
-    reinterpret_cast<const jschar*>(mKeyPath.get());
-  const size_t keyPathLen = mKeyPath.Length();
-  JSBool ok = JS_FALSE;
-
-  if (!mKeyPath.IsEmpty() && aKey.IsUnset()) {
-    NS_ASSERTION(mAutoIncrement, "Should have bailed earlier!");
-
-    JSObject* obj = JS_NewObject(aCx, &gDummyPropClass, nsnull, nsnull);
-    NS_ENSURE_TRUE(obj, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-    jsval key = OBJECT_TO_JSVAL(obj);
- 
-    ok = JS_DefineUCProperty(aCx, JSVAL_TO_OBJECT(aValue), keyPathChars,
-                             keyPathLen, key, nsnull, nsnull,
-                             JSPROP_ENUMERATE);
-    NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-    // From this point on we have to try to remove the property.
+  PRUint32 count = info->indexes.Length();
+  aUpdateInfoArray.SetCapacity(count); // Pretty good estimate
+  for (PRUint32 indexesIndex = 0; indexesIndex < count; indexesIndex++) {
+    const IndexInfo& indexInfo = info->indexes[indexesIndex];
+
+    rv = AppendIndexUpdateInfo(indexInfo.id, indexInfo.keyPath,
+                               indexInfo.unique, indexInfo.multiEntry,
+                               aCx, aValue, aUpdateInfoArray);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  nsString targetObjectPropName;
+  JSObject* targetObject = nsnull;
+
+  rv = NS_OK;
+  if (mAutoIncrement && HasKeyPath()) {
+    NS_ASSERTION(aKey.IsUnset(), "Shouldn't have gotten the key yet!");
+
+    if (JSVAL_IS_PRIMITIVE(aValue)) {
+      return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
+    }
+
+    KeyPathTokenizer tokenizer(mKeyPath, '.');
+    NS_ASSERTION(tokenizer.hasMoreTokens(),
+                 "Shouldn't have empty keypath and autoincrement");
+
+    JSObject* obj = JSVAL_TO_OBJECT(aValue);
+    while (tokenizer.hasMoreTokens()) {
+      const nsDependentSubstring& token = tokenizer.nextToken();
+  
+      NS_ASSERTION(!token.IsEmpty(), "Should be a valid keypath");
+  
+      const jschar* keyPathChars = token.BeginReading();
+      const size_t keyPathLen = token.Length();
+  
+      JSBool hasProp;
+      if (!targetObject) {
+        // We're still walking the chain of existing objects
+
+        JSBool ok = JS_HasUCProperty(aCx, obj, keyPathChars, keyPathLen,
+                                     &hasProp);
+        NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+        if (hasProp) {
+          // Get if the property exists...
+          jsval intermediate;
+          JSBool ok = JS_GetUCProperty(aCx, obj, keyPathChars, keyPathLen,
+                                       &intermediate);
+          NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+          if (tokenizer.hasMoreTokens()) {
+            // ...and walk to it if there are more steps...
+            if (JSVAL_IS_PRIMITIVE(intermediate)) {
+              return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
+            }
+            obj = JSVAL_TO_OBJECT(intermediate);
+          }
+          else {
+            // ...otherwise use it as key
+            aKey.SetFromJSVal(aCx, intermediate);
+            if (aKey.IsUnset()) {
+              return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
+            }
+          }
+        }
+        else {
+          // If the property doesn't exist, fall into below path of starting
+          // to define properties
+          targetObject = obj;
+          targetObjectPropName = token;
+        }
+      }
+
+      if (targetObject) {
+        // We have started inserting new objects or are about to just insert
+        // the first one.
+        if (tokenizer.hasMoreTokens()) {
+          // If we're not at the end, we need to add a dummy object to the chain.
+          JSObject* dummy = JS_NewObject(aCx, nsnull, nsnull, nsnull);
+          if (!dummy) {
+            rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+            break;
+          }
+  
+          if (!JS_DefineUCProperty(aCx, obj, token.BeginReading(),
+                                   token.Length(),
+                                   OBJECT_TO_JSVAL(dummy), nsnull, nsnull,
+                                   JSPROP_ENUMERATE)) {
+            rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+            break;
+          }
+  
+          obj = dummy;
+        }
+        else {
+          JSObject* dummy = JS_NewObject(aCx, &gDummyPropClass, nsnull, nsnull);
+          if (!dummy) {
+            rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+            break;
+          }
+  
+          if (!JS_DefineUCProperty(aCx, obj, token.BeginReading(),
+                                   token.Length(), OBJECT_TO_JSVAL(dummy),
+                                   nsnull, nsnull, JSPROP_ENUMERATE)) {
+            rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+            break;
+          }
+        }
+      }
+    }
   }
 
   JSStructuredCloneCallbacks callbacks = {
     nsnull,
     StructuredCloneWriteDummyProp,
     nsnull
   };
   *aOffsetToKeyProp = 0;
@@ -974,23 +1056,25 @@ IDBObjectStore::GetAddInfo(JSContext* aC
   // We guard on rv being a success because we need to run the property
   // deletion code below even if we should not be serializing the value
   if (NS_SUCCEEDED(rv) && 
       !IDBObjectStore::SerializeValue(aCx, aCloneBuffer, aValue, &callbacks,
                                       aOffsetToKeyProp)) {
     rv = NS_ERROR_DOM_DATA_CLONE_ERR;
   }
 
-  if (ok) {
+  if (targetObject) {
     // If this fails, we lose, and the web page sees a magical property
     // appear on the object :-(
     jsval succeeded;
-    ok = JS_DeleteUCProperty2(aCx, JSVAL_TO_OBJECT(aValue), keyPathChars,
-                              keyPathLen, &succeeded);
-    NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+    if (!JS_DeleteUCProperty2(aCx, targetObject,
+                              targetObjectPropName.get(),
+                              targetObjectPropName.Length(), &succeeded)) {
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
     NS_ASSERTION(JSVAL_IS_BOOLEAN(succeeded), "Wtf?");
     NS_ENSURE_TRUE(JSVAL_TO_BOOLEAN(succeeded),
                    NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
   }
 
   return rv;
 }
 
@@ -1367,16 +1451,17 @@ IDBObjectStore::CreateIndex(const nsAStr
 
   if (found) {
     return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
   }
 
   NS_ASSERTION(mTransaction->IsOpen(), "Impossible!");
 
   bool unique = false;
+  bool multiEntry = false;
 
   // Get optional arguments.
   if (!JSVAL_IS_VOID(aOptions) && !JSVAL_IS_NULL(aOptions)) {
     if (JSVAL_IS_PRIMITIVE(aOptions)) {
       // XXX Update spec for a real code here
       return NS_ERROR_DOM_TYPE_ERR;
     }
 
@@ -1390,30 +1475,42 @@ IDBObjectStore::CreateIndex(const nsAStr
     }
 
     JSBool boolVal;
     if (!JS_ValueToBoolean(aCx, val, &boolVal)) {
       NS_WARNING("JS_ValueToBoolean failed!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
     unique = !!boolVal;
+
+    if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sMultiEntry_id, &val)) {
+      NS_WARNING("JS_GetPropertyById failed!");
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+
+    if (!JS_ValueToBoolean(aCx, val, &boolVal)) {
+      NS_WARNING("JS_ValueToBoolean failed!");
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+    multiEntry = !!boolVal;
   }
 
   DatabaseInfo* databaseInfo = mTransaction->Database()->Info();
 
   IndexInfo* indexInfo = info->indexes.AppendElement();
   if (!indexInfo) {
     NS_WARNING("Out of memory!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   indexInfo->id = databaseInfo->nextIndexId++;
   indexInfo->name = aName;
   indexInfo->keyPath = aKeyPath;
   indexInfo->unique = unique;
+  indexInfo->multiEntry = multiEntry;
   indexInfo->autoIncrement = mAutoIncrement;
 
   // Don't leave this in the list if we fail below!
   AutoRemoveIndex autoRemove(mTransaction->Database(), mName, aName);
 
 #ifdef DEBUG
   for (PRUint32 index = 0; index < mCreatedIndexes.Length(); index++) {
     if (mCreatedIndexes[index]->Name() == aName) {
@@ -1525,16 +1622,24 @@ IDBObjectStore::DeleteIndex(const nsAStr
 
   nsRefPtr<DeleteIndexHelper> helper =
     new DeleteIndexHelper(mTransaction, aName, this);
 
   nsresult rv = helper->DispatchToTransactionPool();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   info->indexes.RemoveElementAt(index);
+
+  for (PRUint32 i = 0; i < mCreatedIndexes.Length(); i++) {
+    if (mCreatedIndexes[i]->Name() == aName) {
+      mCreatedIndexes.RemoveElementAt(i);
+      break;
+    }
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBObjectStore::Count(const jsval& aKey,
                       JSContext* aCx,
                       PRUint8 aOptionalArgCount,
                       nsIIDBRequest** _retval)
@@ -2155,18 +2260,19 @@ CreateIndexHelper::DestroyTLSEntry(void*
 
 nsresult
 CreateIndexHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   // Insert the data into the database.
   nsCOMPtr<mozIStorageStatement> stmt =
     mTransaction->GetCachedStatement(
     "INSERT INTO object_store_index (id, name, key_path, unique_index, "
-      "object_store_id, object_store_autoincrement) "
-    "VALUES (:id, :name, :key_path, :unique, :osid, :os_auto_increment)"
+      "multientry, object_store_id, object_store_autoincrement) "
+    "VALUES (:id, :name, :key_path, :unique, :multientry, :osid, "
+      ":os_auto_increment)"
   );
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   mozStorageStatementScoper scoper(stmt);
 
   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"),
                                       mIndex->Id());
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
@@ -2177,16 +2283,20 @@ CreateIndexHelper::DoDatabaseWork(mozISt
   rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
                               mIndex->KeyPath());
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("unique"),
                              mIndex->IsUnique() ? 1 : 0);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("multientry"),
+                             mIndex->IsMultiEntry() ? 1 : 0);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"),
                              mIndex->ObjectStore()->Id());
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("os_auto_increment"),
                              mIndex->IsAutoIncrement() ? 1 : 0);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
@@ -2233,79 +2343,79 @@ CreateIndexHelper::InsertDataFromObjectS
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   mozStorageStatementScoper scoper(stmt);
 
   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"),
                                       mIndex->ObjectStore()->Id());
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
+  NS_ENSURE_TRUE(sTLSIndex != BAD_TLS_INDEX, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
   bool hasResult;
-  while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
-    nsCOMPtr<mozIStorageStatement> insertStmt =
-      mTransaction->IndexDataInsertStatement(mIndex->IsAutoIncrement(),
-                                             mIndex->IsUnique());
-    NS_ENSURE_TRUE(insertStmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-    mozStorageStatementScoper scoper2(insertStmt);
-
-    rv = insertStmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
-                                     mIndex->Id());
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-    rv = insertStmt->BindInt64ByName(NS_LITERAL_CSTRING("object_data_id"),
-                                     stmt->AsInt64(0));
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-    if (!mIndex->IsAutoIncrement()) {
-      NS_NAMED_LITERAL_CSTRING(objectDataKey, "object_data_key");
-
-      Key key;
-      rv = key.SetFromStatement(stmt, 2);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      rv =
-        key.BindToStatement(insertStmt, NS_LITERAL_CSTRING("object_data_key"));
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-
+  rv = stmt->ExecuteStep(&hasResult);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  if (!hasResult) {
+    // Bail early if we have no data to avoid creating the below runtime
+    return NS_OK;
+  }
+
+  ThreadLocalJSRuntime* tlsEntry =
+    reinterpret_cast<ThreadLocalJSRuntime*>(PR_GetThreadPrivate(sTLSIndex));
+
+  if (!tlsEntry) {
+    tlsEntry = ThreadLocalJSRuntime::Create();
+    NS_ENSURE_TRUE(tlsEntry, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    PR_SetThreadPrivate(sTLSIndex, tlsEntry);
+  }
+
+  JSContext* cx = tlsEntry->Context();
+  JSAutoRequest ar(cx);
+
+  do {
     const PRUint8* data;
     PRUint32 dataLength;
     rv = stmt->GetSharedBlob(1, &dataLength, &data);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-    NS_ENSURE_TRUE(sTLSIndex != BAD_TLS_INDEX, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-    ThreadLocalJSRuntime* tlsEntry =
-      reinterpret_cast<ThreadLocalJSRuntime*>(PR_GetThreadPrivate(sTLSIndex));
-
-    if (!tlsEntry) {
-      tlsEntry = ThreadLocalJSRuntime::Create();
-      NS_ENSURE_TRUE(tlsEntry, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-      PR_SetThreadPrivate(sTLSIndex, tlsEntry);
+    jsval clone;
+    if (!JS_ReadStructuredClone(cx, reinterpret_cast<const uint64*>(data),
+                                dataLength, JS_STRUCTURED_CLONE_VERSION,
+                                &clone, NULL, NULL)) {
+      return NS_ERROR_DOM_DATA_CLONE_ERR;
     }
 
+    nsTArray<IndexUpdateInfo> updateInfo;
+    rv = IDBObjectStore::AppendIndexUpdateInfo(mIndex->Id(),
+                                               mIndex->KeyPath(),
+                                               mIndex->IsUnique(),
+                                               mIndex->IsMultiEntry(),
+                                               tlsEntry->Context(),
+                                               clone, updateInfo);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PRInt64 objectDataID = stmt->AsInt64(0);
+
     Key key;
-    rv = IDBObjectStore::GetKeyPathValueFromStructuredData(data, dataLength,
-                                                           mIndex->KeyPath(),
-                                                           tlsEntry->Context(),
-                                                           key);
+    if (!mIndex->IsAutoIncrement()) {
+      rv = key.SetFromStatement(stmt, 2);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    else {
+      key.SetFromInteger(objectDataID);
+    }
+
+    rv = IDBObjectStore::UpdateIndexes(mTransaction, mIndex->Id(),
+                                       key, mIndex->IsAutoIncrement(),
+                                       false, objectDataID, updateInfo);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    if (key.IsUnset()) {
-      continue;
-    }
-
-    rv = key.BindToStatement(insertStmt, NS_LITERAL_CSTRING("value"));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = insertStmt->Execute();
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-  }
+  } while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasResult)) && hasResult);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   return NS_OK;
 }
 
 nsresult
 DeleteIndexHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!");
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -71,27 +71,23 @@ public:
   static already_AddRefed<IDBObjectStore>
   Create(IDBTransaction* aTransaction,
          const ObjectStoreInfo* aInfo);
 
   static bool
   IsValidKeyPath(JSContext* aCx, const nsAString& aKeyPath);
 
   static nsresult
-  GetKeyPathValueFromStructuredData(const PRUint8* aData,
-                                    PRUint32 aDataLength,
-                                    const nsAString& aKeyPath,
-                                    JSContext* aCx,
-                                    Key& aValue);
-
-  static nsresult
-  GetIndexUpdateInfo(ObjectStoreInfo* aObjectStoreInfo,
-                     JSContext* aCx,
-                     jsval aObject,
-                     nsTArray<IndexUpdateInfo>& aUpdateInfoArray);
+  AppendIndexUpdateInfo(PRInt64 aIndexID,
+                        const nsAString& aKeyPath,
+                        bool aUnique,
+                        bool aMultiEntry,
+                        JSContext* aCx,
+                        jsval aObject,
+                        nsTArray<IndexUpdateInfo>& aUpdateInfoArray);
 
   static nsresult
   UpdateIndexes(IDBTransaction* aTransaction,
                 PRInt64 aObjectStoreId,
                 const Key& aObjectStoreKey,
                 bool aAutoIncrement,
                 bool aOverwrite,
                 PRInt64 aObjectDataId,
@@ -140,16 +136,21 @@ public:
     return mId;
   }
 
   const nsString& KeyPath() const
   {
     return mKeyPath;
   }
 
+  const bool HasKeyPath() const
+  {
+    return !mKeyPath.IsVoid();
+  }
+
   IDBTransaction* Transaction()
   {
     return mTransaction;
   }
 
   nsresult ModifyValueForNewKey(JSAutoStructuredCloneBuffer& aBuffer,
                                 Key& aKey,
                                 PRUint64 aOffsetToKeyProp);
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -209,16 +209,27 @@ IDBTransaction::OnRequestFinished()
     NS_ASSERTION(mAborted || mReadyState == nsIIDBTransaction::LOADING,
                  "Bad state!");
     mReadyState = IDBTransaction::COMMITTING;
     CommitOrRollback();
   }
 }
 
 void
+IDBTransaction::ReleaseCachedObjectStore(const nsAString& aName)
+{
+  for (PRUint32 i = 0; i < mCreatedObjectStores.Length(); i++) {
+    if (mCreatedObjectStores[i]->Name() == aName) {
+      mCreatedObjectStores.RemoveElementAt(i);
+      break;
+    }
+  }
+}
+
+void
 IDBTransaction::SetTransactionListener(IDBTransactionListener* aListener)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!mListener, "Shouldn't already have a listener!");
   mListener = aListener;
 }
 
 nsresult
@@ -401,30 +412,30 @@ IDBTransaction::IndexDataInsertStatement
     if (aUnique) {
       return GetCachedStatement(
         "INSERT INTO ai_unique_index_data "
           "(index_id, ai_object_data_id, value) "
         "VALUES (:index_id, :object_data_id, :value)"
       );
     }
     return GetCachedStatement(
-      "INSERT INTO ai_index_data "
+      "INSERT OR IGNORE INTO ai_index_data "
         "(index_id, ai_object_data_id, value) "
       "VALUES (:index_id, :object_data_id, :value)"
     );
   }
   if (aUnique) {
     return GetCachedStatement(
       "INSERT INTO unique_index_data "
         "(index_id, object_data_id, object_data_key, value) "
       "VALUES (:index_id, :object_data_id, :object_data_key, :value)"
     );
   }
   return GetCachedStatement(
-    "INSERT INTO index_data ("
+    "INSERT OR IGNORE INTO index_data ("
       "index_id, object_data_id, object_data_key, value) "
     "VALUES (:index_id, :object_data_id, :object_data_key, :value)"
   );
 }
 
 already_AddRefed<mozIStorageStatement>
 IDBTransaction::IndexDataDeleteStatement(bool aAutoIncrement,
                                          bool aUnique)
--- a/dom/indexedDB/IDBTransaction.h
+++ b/dom/indexedDB/IDBTransaction.h
@@ -98,16 +98,18 @@ public:
          bool aDispatchDelayed);
 
   // nsIDOMEventTarget
   virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor);
 
   void OnNewRequest();
   void OnRequestFinished();
 
+  void ReleaseCachedObjectStore(const nsAString& aName);
+
   void SetTransactionListener(IDBTransactionListener* aListener);
 
   bool StartSavepoint();
   nsresult ReleaseSavepoint();
   void RollbackSavepoint();
 
   // Only meant to be called on mStorageThread!
   nsresult GetOrCreateConnection(mozIStorageConnection** aConnection);
--- a/dom/indexedDB/IndexedDatabase.h
+++ b/dom/indexedDB/IndexedDatabase.h
@@ -46,17 +46,17 @@
 #include "jsapi.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsDOMError.h"
 #include "nsStringGlue.h"
 #include "nsTArray.h"
 
-#define DB_SCHEMA_VERSION 6
+#define DB_SCHEMA_VERSION 8
 
 #define BEGIN_INDEXEDDB_NAMESPACE \
   namespace mozilla { namespace dom { namespace indexedDB {
 
 #define END_INDEXEDDB_NAMESPACE \
   } /* namespace indexedDB */ } /* namepsace dom */ } /* namespace mozilla */
 
 #define USING_INDEXEDDB_NAMESPACE \
--- a/dom/indexedDB/OpenDatabaseHelper.cpp
+++ b/dom/indexedDB/OpenDatabaseHelper.cpp
@@ -117,19 +117,19 @@ CreateTables(mozIStorageConnection* aDBC
     ");"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Table `object_store`
   rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE TABLE object_store ("
       "id INTEGER PRIMARY KEY, "
+      "auto_increment INTEGER NOT NULL DEFAULT 0, "
       "name TEXT NOT NULL, "
-      "key_path TEXT NOT NULL, "
-      "auto_increment INTEGER NOT NULL DEFAULT 0, "
+      "key_path TEXT, "
       "UNIQUE (name)"
     ");"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Table `object_data`
   rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE TABLE object_data ("
@@ -160,16 +160,17 @@ CreateTables(mozIStorageConnection* aDBC
   // Table `index`
   rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE TABLE object_store_index ("
       "id INTEGER, "
       "object_store_id INTEGER NOT NULL, "
       "name TEXT NOT NULL, "
       "key_path TEXT NOT NULL, "
       "unique_index INTEGER NOT NULL, "
+      "multientry INTEGER NOT NULL DEFAULT 0, "
       "object_store_autoincrement INTERGER NOT NULL, "
       "PRIMARY KEY (id), "
       "UNIQUE (object_store_id, name), "
       "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
         "CASCADE"
     ");"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -749,16 +750,159 @@ UpgradeSchemaFrom5To6(mozIStorageConnect
 
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
+UpgradeSchemaFrom6To7(mozIStorageConnection* aConnection)
+{
+  mozStorageTransaction transaction(aConnection, false,
+                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+  // Turn off foreign key constraints before we do anything here.
+  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "PRAGMA foreign_keys = OFF;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TEMPORARY TABLE temp_upgrade ("
+      "id, "
+      "name, "
+      "key_path, "
+      "auto_increment, "
+    ");"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO temp_upgrade "
+      "SELECT id, name, key_path, auto_increment "
+      "FROM object_store;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TABLE object_store;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TABLE object_store ("
+      "id INTEGER PRIMARY KEY, "
+      "auto_increment INTEGER NOT NULL DEFAULT 0, "
+      "name TEXT NOT NULL, "
+      "key_path TEXT, "
+      "UNIQUE (name)"
+    ");"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO object_store "
+      "SELECT id, auto_increment, name, nullif(key_path, '') "
+      "FROM temp_upgrade;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TABLE temp_upgrade;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->SetSchemaVersion(7);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = transaction.Commit();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+
+nsresult
+UpgradeSchemaFrom7To8(mozIStorageConnection* aConnection)
+{
+  mozStorageTransaction transaction(aConnection, false,
+                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+  // Turn off foreign key constraints before we do anything here.
+  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "PRAGMA foreign_keys = OFF;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TEMPORARY TABLE temp_upgrade ("
+      "id, "
+      "object_store_id, "
+      "name, "
+      "key_path, "
+      "unique_index, "
+      "object_store_autoincrement, "
+    ");"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO temp_upgrade "
+      "SELECT id, object_store_id, name, key_path, "
+      "unique_index, object_store_autoincrement, "
+      "FROM object_store_index;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TABLE object_store_index;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TABLE object_store_index ("
+      "id INTEGER, "
+      "object_store_id INTEGER NOT NULL, "
+      "name TEXT NOT NULL, "
+      "key_path TEXT NOT NULL, "
+      "unique_index INTEGER NOT NULL, "
+      "multientry INTEGER NOT NULL, "
+      "object_store_autoincrement INTERGER NOT NULL, "
+      "PRIMARY KEY (id), "
+      "UNIQUE (object_store_id, name), "
+      "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+        "CASCADE"
+    ");"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO object_store_index "
+      "SELECT id, object_store_id, name, key_path, "
+      "unique_index, 0, object_store_autoincrement, "
+      "FROM temp_upgrade;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TABLE temp_upgrade;"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aConnection->SetSchemaVersion(8);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = transaction.Commit();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
 CreateDatabaseConnection(const nsAString& aName,
                          nsIFile* aDBFile,
                          mozIStorageConnection** aConnection)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   NS_NAMED_LITERAL_CSTRING(quotaVFSName, "quota");
 
@@ -799,31 +943,33 @@ CreateDatabaseConnection(const nsAString
     NS_ENSURE_SUCCESS(rv, rv);
 
     NS_ASSERTION(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)) &&
                  schemaVersion == DB_SCHEMA_VERSION,
                  "CreateTables set a bad schema version!");
   }
   else if (schemaVersion != DB_SCHEMA_VERSION) {
     // This logic needs to change next time we change the schema!
-    PR_STATIC_ASSERT(DB_SCHEMA_VERSION == 6);
+    PR_STATIC_ASSERT(DB_SCHEMA_VERSION == 8);
 
 #define UPGRADE_SCHEMA_CASE(_from, _to)                                        \
   if (schemaVersion == _from) {                                                \
     rv = UpgradeSchemaFrom##_from##To##_to (connection);                       \
     NS_ENSURE_SUCCESS(rv, rv);                                                 \
                                                                                \
     rv = connection->GetSchemaVersion(&schemaVersion);                         \
     NS_ENSURE_SUCCESS(rv, rv);                                                 \
                                                                                \
     NS_ASSERTION(schemaVersion == _to, "Bad upgrade function!");               \
   }
 
     UPGRADE_SCHEMA_CASE(4, 5)
     UPGRADE_SCHEMA_CASE(5, 6)
+    UPGRADE_SCHEMA_CASE(6, 7)
+    UPGRADE_SCHEMA_CASE(7, 8)
 
 #undef UPGRADE_SCHEMA_CASE
 
     if (schemaVersion != DB_SCHEMA_VERSION) {
       NS_WARNING("Unable to open IndexedDB database, schema doesn't match");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
   }
--- a/dom/indexedDB/OpenDatabaseHelper.h
+++ b/dom/indexedDB/OpenDatabaseHelper.h
@@ -55,18 +55,18 @@ class OpenDatabaseHelper : public Helper
 public:
   OpenDatabaseHelper(IDBOpenDBRequest* aRequest,
                      const nsAString& aName,
                      const nsACString& aASCIIOrigin,
                      PRUint64 aRequestedVersion,
                      bool aForDeletion)
     : HelperBase(aRequest), mOpenDBRequest(aRequest), mName(aName),
       mASCIIOrigin(aASCIIOrigin), mRequestedVersion(aRequestedVersion),
-      mForDeletion(aForDeletion), mCurrentVersion(0),
-      mDataVersion(DB_SCHEMA_VERSION), mDatabaseId(0), mLastObjectStoreId(0),
+      mForDeletion(aForDeletion), mDatabaseId(nsnull), mCurrentVersion(0),
+      mDataVersion(DB_SCHEMA_VERSION), mLastObjectStoreId(0),
       mLastIndexId(0), mState(eCreated), mResultCode(NS_OK)
   {
     NS_ASSERTION(!aForDeletion || !aRequestedVersion,
                  "Can't be for deletion and request a version!");
   }
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIRUNNABLE
--- a/dom/indexedDB/nsIIDBIndex.idl
+++ b/dom/indexedDB/nsIIDBIndex.idl
@@ -42,27 +42,29 @@
 interface nsIIDBObjectStore;
 interface nsIIDBRequest;
 
 /**
  * IDBIndex interface.  See
  * http://dev.w3.org/2006/webapi/WebSimpleDB/#idl-def-IDBIndex for more
  * information.
  */
-[scriptable, builtinclass, uuid(1da60889-3db4-4f66-9fd7-b78c1e7969b7)]
+[scriptable, builtinclass, uuid(fcb9a158-833e-4aa9-ab19-ab90cbb50afc)]
 interface nsIIDBIndex : nsISupports
 {
   readonly attribute DOMString name;
 
   readonly attribute DOMString storeName;
 
   readonly attribute DOMString keyPath;
 
   readonly attribute boolean unique;
 
+  readonly attribute boolean multiEntry;
+
   readonly attribute nsIIDBObjectStore objectStore;
 
   [implicit_jscontext]
   nsIIDBRequest
   get(in jsval key);
 
   [implicit_jscontext]
   nsIIDBRequest
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -79,16 +79,17 @@ TEST_FILES = \
   test_index_getAll.html \
   test_index_getAllObjects.html \
   test_index_object_cursors.html \
   test_index_update_delete.html \
   test_indexes.html \
   test_indexes_bad_values.html \
   test_key_requirements.html \
   test_leaving_page.html \
+  test_multientry.html \
   test_objectCursors.html \
   test_objectStore_inline_autoincrement_key_added_on_put.html \
   test_objectStore_remove_values.html \
   test_object_identity.html \
   test_odd_result_order.html \
   test_open_empty_db.html \
   test_open_objectStore.html \
   test_optionalArguments.html \
--- a/dom/indexedDB/test/test_complex_keyPaths.html
+++ b/dom/indexedDB/test/test_complex_keyPaths.html
@@ -13,161 +13,205 @@
     function testSteps()
     {
       const nsIIDBObjectStore = Components.interfaces.nsIIDBObjectStore;
       const nsIIDBTransaction = Components.interfaces.nsIIDBTransaction;
 
       // Test object stores
 
       const name = window.location.pathname;
-      const objectStoreInfo = [
-        { name: "a", options: { keyPath: "id"} },
-        { name: "b", options: { keyPath: "foo.id"} },
-        { name: "c", options: { keyPath: ""} },
-        { name: "d", options: { keyPath: "foo..id"}, exception: true },
-        { name: "e", options: { keyPath: "foo."}, exception: true },
-        { name: "f", options: { keyPath: "foo.bar" } },
-        { name: "g", options: { keyPath: "fo o" }, exception: true},
-        { name: "h", options: { keyPath: "foo " }, exception: true},
-        { name: "i", options: { keyPath: "foo[bar]" }, exception: true },
-        { name: "j", options: { keyPath: "$('id').stuff" }, exception: true },
-        { name: "k", options: { keyPath: "foo.2.bar" }, exception: true }
+      const keyPaths = [
+        { keyPath: "id",      value: { id: 5 },                      key: 5 },
+        { keyPath: "id",      value: { id: "14", iid: 12 },          key: "14" },
+        { keyPath: "id",      value: { iid: "14", id: 12 },          key: 12 },
+        { keyPath: "id",      value: {} },
+        { keyPath: "id",      value: { id: {} } },
+        { keyPath: "id",      value: { id: /x/ } },
+        { keyPath: "id",      value: 2 },
+        { keyPath: "id",      value: undefined },
+        { keyPath: "foo.id",  value: { foo: { id: 7 } },             key: 7 },
+        { keyPath: "foo.id",  value: { id: 7, foo: { id: "asdf" } }, key: "asdf" },
+        { keyPath: "foo.id",  value: { foo: { id: undefined } } },
+        { keyPath: "foo.id",  value: { foo: 47 } },
+        { keyPath: "foo.id",  value: {} },
+        { keyPath: "",        value: "foopy",                        key: "foopy" },
+        { keyPath: "",        value: 2,                              key: 2 },
+        { keyPath: "",        value: undefined },
+        { keyPath: "",        value: { id: 12 } },
+        { keyPath: "",        value: /x/ },
+        { keyPath: "foo.bar", value: { baz: 1, foo: { baz2: 2, bar: "xo" } },     key: "xo" },
+        { keyPath: "foo.bar.baz", value: { foo: { bar: { bazz: 16, baz: 17 } } }, key: 17 },
+        { keyPath: "foo..id", exception: true },
+        { keyPath: "foo.",    exception: true },
+        { keyPath: "fo o",    exception: true },
+        { keyPath: "foo ",    exception: true },
+        { keyPath: "foo[bar]",exception: true },
+        { keyPath: "$('id').stuff", exception: true },
+        { keyPath: "foo.2.bar", exception: true },
+        { keyPath: "foo. .bar", exception: true },
+        { keyPath: ".bar",    exception: true },
       ];
 
-      const indexInfo = [
-        { name: "1", keyPath: "id" },
-        { name: "2", keyPath: "foo..id", exception: true },
-        { name: "3", keyPath: "foo.", exception: true },
-        { name: "4", keyPath: "foo.baz" },
-        { name: "5", keyPath: "fo o", exception: true },
-        { name: "6", keyPath: "foo ", exception: true },
-        { name: "7", keyPath: "foo[bar]", exception: true },
-        { name: "8", keyPath: "$('id').stuff", exception: true },
-        { name: "9", keyPath: "foo.2.bar", exception: true },
-        { name: "10", keyPath: "foo.bork" },
-      ];
-
-      let request = mozIndexedDB.open(name, 1);
-      request.onerror = errorHandler;
-      request.onupgradeneeded = grabEventAndContinueHandler;
-      request.onsuccess = unexpectedSuccessHandler;
+      let openRequest = mozIndexedDB.open(name, 1);
+      openRequest.onerror = errorHandler;
+      openRequest.onupgradeneeded = grabEventAndContinueHandler;
+      openRequest.onsuccess = unexpectedSuccessHandler;
       let event = yield;
       let db = event.target.result;
 
-      for (let i = 0; i < objectStoreInfo.length; i++) {
-        let info = objectStoreInfo[i];
-        try {
-          let objectStore = info.hasOwnProperty("options") ?
-                            db.createObjectStore(info.name, info.options) :
-                            db.createObjectStore(info.name);
-          ok(!info.hasOwnProperty("exception"), "expected exception behavior observed");
-        } catch (e) {
-          ok(info.hasOwnProperty("exception"), "expected exception behavior observed");
-          ok(e instanceof DOMException, "Got a DOM Exception");
-          is(e.code, DOMException.SYNTAX_ERR, "expect a syntax error");
+      let stores = {};
+
+      // Test creating object stores and inserting data
+      for (let i = 0; i < keyPaths.length; i++) {
+        let info = keyPaths[i];
+        let test = " for objectStore test " + JSON.stringify(info);
+        if (!stores[info.keyPath]) {
+          try {
+            let objectStore = db.createObjectStore(info.keyPath, { keyPath: info.keyPath });
+            ok(!("exception" in info), "shouldn't throw" + test);
+            is(objectStore.keyPath, info.keyPath, "correct keyPath property" + test);
+            stores[info.keyPath] = objectStore;
+          } catch (e) {
+            ok("exception" in info, "should throw" + test);
+            ok(e instanceof DOMException, "Got a DOM Exception" + test);
+            is(e.code, DOMException.SYNTAX_ERR, "expect a syntax error" + test);
+            continue;
+          }
         }
-      }
 
-      request.onsuccess = grabEventAndContinueHandler;
-      yield;
-
-      let trans = db.transaction(["f"], IDBTransaction.READ_WRITE);
-      let objectStore = trans.objectStore("f");
+        let store = stores[info.keyPath];
 
-      objectStore.put({foo: {baz: -1, bar: 72, bork: true}});
-      objectStore.put({foo: {baz: 2, bar: 1, bork: false}});
+        try {
+          request = store.add(info.value);
+          ok("key" in info, "successfully created request to insert value" + test);
+        } catch (e) {
+          ok(!("key" in info), "threw when attempted to insert" + test);
+          ok(e instanceof IDBDatabaseException, "Got a IDBDatabaseException" + test);
+          is(e.code, IDBDatabaseException.DATA_ERR, "expect a DATA_ERR error" + test);
+          continue;
+        }
 
-      try {
-        objectStore.put({foo: {}});
-        ok(false, "Should have thrown!");
-      } catch (e) {
-        ok(true, "Putting an object without the key should throw");
+        request.onerror = errorHandler;
+        request.onsuccess = grabEventAndContinueHandler;
+
+        let e = yield;
+        is(e.type, "success", "inserted successfully" + test);
+        is(e.target, request, "expected target" + test);
+        is(request.result, info.key, "found correct key" + test);
+
+        store.clear().onsuccess = grabEventAndContinueHandler;
+        yield;
       }
 
-      trans.onerror = errorHandler;
-      trans.oncomplete = grabEventAndContinueHandler;
-
-      yield;
-
-      let trans = db.transaction(["f"], IDBTransaction.READ);
-      let objectStore = trans.objectStore("f");
-      let request = objectStore.openCursor();
-
-      request.onerror = errorHandler;
-      request.onsuccess = grabEventAndContinueHandler;
-
-      let event = yield;
-
-      let cursor = event.target.result;
-      is(cursor.value.foo.baz, 2, "got things in the right order");
-
-      cursor.continue();
+      // Attempt to create indexes and insert data
+      let store = db.createObjectStore("indexStore");
+      let indexes = {};
+      for (let i = 0; i < keyPaths.length; i++) {
+        let test = " for index test " + JSON.stringify(info);
+        let info = keyPaths[i];
+        if (!indexes[info.keyPath]) {
+          try {
+            let index = store.createIndex(info.keyPath, info.keyPath);
+            ok(!("exception" in info), "shouldn't throw" + test);
+            is(index.keyPath, info.keyPath, "index has correct keyPath property" + test);
+            indexes[info.keyPath] = index;
+          } catch (e) {
+            ok("exception" in info, "should throw" + test);
+            ok(e instanceof DOMException, "Got a DOM Exception" + test);
+            is(e.code, DOMException.SYNTAX_ERR, "expect a syntax error" + test);
+            continue;
+          }
+        }
+        
+        let index = indexes[info.keyPath];
 
-      let event = yield
-
-      let cursor = event.target.result;
-      is(cursor.value.foo.baz, -1, "got things in the right order");
-
-      db.close();
-
-      // Test indexes
+        request = store.add(info.value, 1);
+        if ("key" in info) {
+          index.getKey(info.key).onsuccess = grabEventAndContinueHandler;
+          e = yield;
+          is(e.target.result, 1, "found value when reading" + test);
+        }
+        else {
+          index.count().onsuccess = grabEventAndContinueHandler;
+          e = yield;
+          is(e.target.result, 0, "should be empty" + test);
+        }
 
-      let request = mozIndexedDB.open(name, 2);
-      request.onerror = errorHandler;
-      request.onupgradeneeded = grabEventAndContinueHandler;
-
-      let event = yield;
-      let db = event.target.result;
-
-      let trans = event.target.transaction;
-      let objectStore = trans.objectStore("f");
+        store.clear().onsuccess = grabEventAndContinueHandler;
+        yield;
+      }
 
-      let indexes = [];
-      for (let i = 0; i < indexInfo.length; i++) {
-        let info = indexInfo[i];
-        try {
-          indexes[i] = info.hasOwnProperty("options") ?
-                       objectStore.createIndex(info.name, info.keyPath, info.options) :
-                       objectStore.createIndex(info.name, info.keyPath);
-          ok(!info.hasOwnProperty("exception"), "expected exception behavior observed");
-        } catch (e) {
-          ok(info.hasOwnProperty("exception"), "expected exception behavior observed");
-          ok(e instanceof DOMException, "Got a DOM Exception");
-          is(e.code, DOMException.SYNTAX_ERR, "expect a syntax error");
-        }
-      }
-      request.onsuccess = grabEventAndContinueHandler;
+      // Autoincrement and complex key paths
+      let aitests = [{ v: {},                           k: 1, res: { foo: { id: 1 }} },
+                     { v: { value: "x" },               k: 2, res: { value: "x", foo: { id: 2 }} },
+                     { v: { value: "x", foo: {} },      k: 3, res: { value: "x", foo: { id: 3 }} },
+                     { v: { v: "x", foo: { x: "y" } },  k: 4, res: { v: "x", foo: { x: "y", id: 4 }} },
+                     { v: { value: 2, foo: { id: 10 }}, k: 10 },
+                     { v: { value: 2 },                 k: 11, res: { value: 2, foo: { id: 11 }} },
+                     { v: true,                         },
+                     { v: { value: 2, foo: 12 },        },
+                     { v: { foo: { id: true }},         },
+                     { v: { foo: { x: 5, id: {} }},     },
+                     { v: undefined,                    },
+                     { v: { foo: undefined },           },
+                     { v: { foo: { id: undefined }},    },
+                     { v: null,                         },
+                     { v: { foo: null },                },
+                     { v: { foo: { id: null }},         },
+                     ];
 
-      yield;
-
-      let trans = db.transaction(["f"], IDBTransaction.READ);
-      let objectStore = trans.objectStore("f");
+      store = db.createObjectStore("gen", { keyPath: "foo.id", autoIncrement: true });
+      for (let i = 0; i < aitests.length; ++i) {
+        let info = aitests[i];
+        let test = " for autoIncrement test " + JSON.stringify(info);
 
-      let request = objectStore.index("4").openCursor();
-      request.onsuccess = grabEventAndContinueHandler;
-
-      let event = yield;
+        let preValue = JSON.stringify(info.v);
+        if ("k" in info) {
+          store.add(info.v).onsuccess = grabEventAndContinueHandler;
+          is(JSON.stringify(info.v), preValue, "put didn't modify value" + test);
+        }
+        else {
+          try {
+            store.add(info.v);
+            ok(false, "should throw" + test);
+          }
+          catch(e) {
+            ok(true, "did throw" + test);
+            ok(e instanceof IDBDatabaseException, "Got a IDBDatabaseException" + test);
+            is(e.code, IDBDatabaseException.DATA_ERR, "expect a DATA_ERR" + test);
+  
+            is(JSON.stringify(info.v), preValue, "failing put didn't modify value" + test);
+  
+            continue;
+          }
+        }
 
-      let cursor = event.target.result;
+        let e = yield;
+        is(e.target.result, info.k, "got correct return key" + test);
+
+        store.get(info.k).onsuccess = grabEventAndContinueHandler;
+        e = yield;
+        is(JSON.stringify(e.target.result), JSON.stringify(info.res || info.v),
+           "expected value stored" + test);
+      }
 
-      is(cursor.value.foo.bar, 72, "got things in the right order");
+      // Can't handle autoincrement and empty keypath
+      try {
+        store = db.createObjectStore("storefail", { keyPath: "", autoIncrement: true });
+        ok(false, "Should have thrown when creating empty-keypath autoincrement store");
+      }
+      catch(e) {
+        ok(true, "Did throw when creating empty-keypath autoincrement store");
+        ok(e instanceof DOMException, "Got a DOMException when creating empty-keypath autoincrement store");
+        is(e.code, DOMException.INVALID_ACCESS_ERR, "expect a INVALID_ACCESS_ERR when creating empty-keypath autoincrement store");
+      }
 
-      cursor.continue();
+      openRequest.onsuccess = grabEventAndContinueHandler;
       yield;
 
-      is(cursor.value.foo.bar, 1, "got things in the right order");
-
-      let request = objectStore.index("10").openCursor();
-      request.onerror = errorHandler;
-      request.onsuccess = grabEventAndContinueHandler;
-
-      let event = yield;
-
-      is(event.target.result, null, "should have no results");
-
       finishTest();
       yield;
     }
   </script>
   <script type="text/javascript;version=1.7" src="helpers.js"></script>
 </head>
 
 <body onload="runTest();"></body>
--- a/dom/indexedDB/test/test_create_objectStore.html
+++ b/dom/indexedDB/test/test_create_objectStore.html
@@ -81,17 +81,17 @@
             found = true;
             break;
           }
         }
         is(found, true, "objectStoreNames contains name");
 
         is(objectStore.name, name, "Bad name");
         is(objectStore.keyPath, info.options && info.options.keyPath ?
-                                info.options.keyPath : "",
+                                info.options.keyPath : null,
            "Bad keyPath");
         if(objectStore.indexNames.length, 0, "Bad indexNames");
 
         ok(event.target.transaction, "event has a transaction");
         ok(event.target.transaction.db === db, "transaction has the right db");
         is(event.target.transaction.readyState, nsIIDBTransaction.LOADING,
            "transaction has the correct readyState");
         is(event.target.transaction.mode, nsIIDBTransaction.VERSION_CHANGE,
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_multientry.html
@@ -0,0 +1,230 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+    function testSteps()
+    {
+      // Test object stores
+
+      let openRequest = mozIndexedDB.open(window.location.pathname, 1);
+      openRequest.onerror = errorHandler;
+      openRequest.onupgradeneeded = grabEventAndContinueHandler;
+      openRequest.onsuccess = unexpectedSuccessHandler;
+      let event = yield;
+      let db = event.target.result;
+      db.onerror = errorHandler;
+      let tests =
+        [{ add:     { x: 1, id: 1 },
+           indexes:[{ v: 1, k: 1 }] },
+         { add:     { x: [2, 3], id: 2 },
+           indexes:[{ v: 1, k: 1 },
+                    { v: 2, k: 2 },
+                    { v: 3, k: 2 }] },
+         { put:     { x: [2, 4], id: 1 },
+           indexes:[{ v: 2, k: 1 },
+                    { v: 2, k: 2 },
+                    { v: 3, k: 2 },
+                    { v: 4, k: 1 }] },
+         { add:     { x: [5, 6, 5, -2, 3], id: 3 },
+           indexes:[{ v:-2, k: 3 },
+                    { v: 2, k: 1 },
+                    { v: 2, k: 2 },
+                    { v: 3, k: 2 },
+                    { v: 3, k: 3 },
+                    { v: 4, k: 1 },
+                    { v: 5, k: 3 },
+                    { v: 6, k: 3 }] },
+         { delete:  IDBKeyRange.bound(1, 3),
+           indexes:[] },
+         { put:     { x: ["food", {}, false, undefined, /x/, [73, false]], id: 2 },
+           indexes:[{ v: "food", k: 2 }] },
+         { add:     { x: [{}, /x/, -12, "food", null, [false], undefined], id: 3 },
+           indexes:[{ v: -12, k: 3 },
+                    { v: "food", k: 2 },
+                    { v: "food", k: 3 }] },
+         { put:     { x: [], id: 2 },
+           indexes:[{ v: -12, k: 3 },
+                    { v: "food", k: 3 }] },
+         { put:     { x: { y: 3 }, id: 3 },
+           indexes:[] },
+         { add:     { x: false, id: 7 },
+           indexes:[] },
+         { delete:  IDBKeyRange.lowerBound(0),
+           indexes:[] },
+        ];
+
+      let store = db.createObjectStore("mystore", { keyPath: "id" });
+      let index = store.createIndex("myindex", "x", { multiEntry: true });
+      is(index.multiEntry, true, "index created with multiEntry");
+
+      let i;
+      for (i = 0; i < tests.length; ++i) {
+        let test = tests[i];
+        let testName = " for " + JSON.stringify(test);
+        let req;
+        if (test.add) {
+          req = store.add(test.add);
+        }
+        else if (test.put) {
+          req = store.put(test.put);
+        }
+        else if (test.delete) {
+          req = store.delete(test.delete);
+        }
+        else {
+          ok(false, "borked test");
+        }
+        req.onsuccess = grabEventAndContinueHandler;
+        let e = yield;
+        
+        req = index.openKeyCursor();
+        req.onsuccess = grabEventAndContinueHandler;
+        for (let j = 0; j < test.indexes.length; ++j) {
+          e = yield;
+          is(req.result.key, test.indexes[j].v, "found expected index key at index " + j + testName);
+          is(req.result.primaryKey, test.indexes[j].k, "found expected index primary key at index " + j + testName);
+          req.result.continue();
+        }
+        e = yield;
+        is(req.result, undefined, "exhausted indexes");
+
+        let tempIndex = store.createIndex("temp index", "x", { multiEntry: true });
+        req = tempIndex.openKeyCursor();
+        req.onsuccess = grabEventAndContinueHandler;
+        for (let j = 0; j < test.indexes.length; ++j) {
+          e = yield;
+          is(req.result.key, test.indexes[j].v, "found expected temp index key at index " + j + testName);
+          is(req.result.primaryKey, test.indexes[j].k, "found expected temp index primary key at index " + j + testName);
+          req.result.continue();
+        }
+        e = yield;
+        is(req.result, undefined, "exhausted temp index");
+        store.deleteIndex("temp index");
+      }
+
+      // Unique indexes
+      tests =
+        [{ add:     { x: 1, id: 1 },
+           indexes:[{ v: 1, k: 1 }] },
+         { add:     { x: [2, 3], id: 2 },
+           indexes:[{ v: 1, k: 1 },
+                    { v: 2, k: 2 },
+                    { v: 3, k: 2 }] },
+         { put:     { x: [2, 4], id: 3 },
+           fail:    true },
+         { put:     { x: [1, 4], id: 1 },
+           indexes:[{ v: 1, k: 1 },
+                    { v: 2, k: 2 },
+                    { v: 3, k: 2 },
+                    { v: 4, k: 1 }] },
+         { add:     { x: [5, 0, 5, 5, 5], id: 3 },
+           indexes:[{ v: 0, k: 3 },
+                    { v: 1, k: 1 },
+                    { v: 2, k: 2 },
+                    { v: 3, k: 2 },
+                    { v: 4, k: 1 },
+                    { v: 5, k: 3 }] },
+         { delete:  IDBKeyRange.bound(1, 2),
+           indexes:[{ v: 0, k: 3 },
+                    { v: 5, k: 3 }] },
+         { add:     { x: [0, 6], id: 8 },
+           fail:    true },
+         { add:     { x: 5, id: 8 },
+           fail:    true },
+         { put:     { x: 0, id: 8 },
+           fail:    true },
+        ];
+
+      store.deleteIndex("myindex");
+      index = store.createIndex("myindex", "x", { multiEntry: true, unique: true });
+      is(index.multiEntry, true, "index created with multiEntry");
+
+      let i;
+      let indexes;
+      for (i = 0; i < tests.length; ++i) {
+        let test = tests[i];
+        let testName = " for " + JSON.stringify(test);
+        let req;
+        if (test.add) {
+          req = store.add(test.add);
+        }
+        else if (test.put) {
+          req = store.put(test.put);
+        }
+        else if (test.delete) {
+          req = store.delete(test.delete);
+        }
+        else {
+          ok(false, "borked test");
+        }
+        
+        if (!test.fail) {
+          req.onsuccess = grabEventAndContinueHandler;
+          let e = yield;
+          indexes = test.indexes;
+        }
+        else {
+          req.onsuccess = unexpectedSuccessHandler;
+          req.onerror = grabEventAndContinueHandler;
+          ok(true, "waiting for error");
+          let e = yield;
+          ok(true, "got error: " + e.type);
+          e.preventDefault();
+          e.stopPropagation();
+        }
+
+        let e;
+        req = index.openKeyCursor();
+        req.onsuccess = grabEventAndContinueHandler;
+        for (let j = 0; j < indexes.length; ++j) {
+          e = yield;
+          is(req.result.key, indexes[j].v, "found expected index key at index " + j + testName);
+          is(req.result.primaryKey, indexes[j].k, "found expected index primary key at index " + j + testName);
+          req.result.continue();
+        }
+        e = yield;
+        is(req.result, undefined, "exhausted indexes");
+
+        let tempIndex = store.createIndex("temp index", "x", { multiEntry: true, unique: true });
+        req = tempIndex.openKeyCursor();
+        req.onsuccess = grabEventAndContinueHandler;
+        for (let j = 0; j < indexes.length; ++j) {
+          e = yield;
+          is(req.result.key, indexes[j].v, "found expected temp index key at index " + j + testName);
+          is(req.result.primaryKey, indexes[j].k, "found expected temp index primary key at index " + j + testName);
+          req.result.continue();
+        }
+        e = yield;
+        is(req.result, undefined, "exhausted temp index");
+        store.deleteIndex("temp index");
+      }
+
+
+      openRequest.onsuccess = grabEventAndContinueHandler;
+      yield;
+
+      let trans = db.transaction(["mystore"], IDBTransaction.READ_WRITE);
+      store = trans.objectStore("mystore");
+      index = store.index("myindex");
+      is(index.multiEntry, true, "index still is multiEntry");
+      trans.oncomplete = grabEventAndContinueHandler;
+      yield;
+
+      finishTest();
+      yield;
+    }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/indexedDB/test/test_remove_index.html
+++ b/dom/indexedDB/test/test_remove_index.html
@@ -27,24 +27,40 @@
       is(db.objectStoreNames.length, 0, "Correct objectStoreNames list");
 
       let objectStore = db.createObjectStore("test store", { keyPath: "foo" });
       is(db.objectStoreNames.length, 1, "Correct objectStoreNames list");
       is(db.objectStoreNames.item(0), objectStore.name, "Correct name");
 
       is(objectStore.indexNames.length, 0, "Correct indexNames list");
 
-      objectStore.createIndex(indexName, "foo");
+      let index = objectStore.createIndex(indexName, "foo");
 
       is(objectStore.indexNames.length, 1, "Correct indexNames list");
       is(objectStore.indexNames.item(0), indexName, "Correct name");
+      is(objectStore.index(indexName), index, "Correct instance");
 
       objectStore.deleteIndex(indexName);
 
       is(objectStore.indexNames.length, 0, "Correct indexNames list");
+      try {
+        objectStore.index(indexName);
+        ok(false, "should have thrown");
+      }
+      catch(ex) {
+        ok(ex instanceof IDBDatabaseException, "Got a IDBDatabaseException");
+        is(ex.code, IDBDatabaseException.NOT_FOUND_ERR, "expect a NOT_FOUND_ERR");
+      }
+
+      let index2 = objectStore.createIndex(indexName, "foo");
+      isnot(index, index2, "New instance should be created");
+
+      is(objectStore.indexNames.length, 1, "Correct recreacted indexNames list");
+      is(objectStore.indexNames.item(0), indexName, "Correct recreacted name");
+      is(objectStore.index(indexName), index2, "Correct instance");
 
       finishTest();
       yield;
     }
   </script>
   <script type="text/javascript;version=1.7" src="helpers.js"></script>
 
 </head>
--- a/dom/indexedDB/test/test_remove_objectStore.html
+++ b/dom/indexedDB/test/test_remove_objectStore.html
@@ -50,23 +50,36 @@
       db.close();
 
       let request = mozIndexedDB.open(name, 2, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
+      let trans = event.target.transaction;
 
-      db.deleteObjectStore(objectStore.name);
+      let oldObjectStore = trans.objectStore(objectStoreName);
+      isnot(oldObjectStore, null, "Correct object store prior to deleting");
+      db.deleteObjectStore(objectStoreName);
       is(db.objectStoreNames.length, 0, "Correct objectStores list");
+      try {
+        trans.objectStore(objectStoreName);
+        ok(false, "should have thrown");
+      }
+      catch(ex) {
+        ok(ex instanceof IDBDatabaseException, "Got a IDBDatabaseException");
+        is(ex.code, IDBDatabaseException.NOT_FOUND_ERR, "expect a NOT_FOUND_ERR");
+      }
 
       objectStore = db.createObjectStore(objectStoreName, { keyPath: "foo" });
       is(db.objectStoreNames.length, 1, "Correct objectStoreNames list");
       is(db.objectStoreNames.item(0), objectStoreName, "Correct name");
+      is(trans.objectStore(objectStoreName), objectStore, "Correct new objectStore");
+      isnot(oldObjectStore, objectStore, "Old objectStore is not new objectStore");
 
       request = objectStore.openCursor();
       request.onerror = errorHandler;
       request.onsuccess = function(event) {
         is(event.target.result, undefined, "ObjectStore shouldn't have any items");
         testGenerator.send(event);
       }
       event = yield;
--- a/dom/indexedDB/test/test_transaction_abort.html
+++ b/dom/indexedDB/test/test_transaction_abort.html
@@ -52,17 +52,17 @@
       is(transaction.mode, VERSION_CHANGE, "Correct mode");
       is(transaction.objectStoreNames.length, 1, "Correct names length");
       is(transaction.objectStoreNames.item(0), "foo", "Correct name");
       is(transaction.objectStore("foo"), objectStore, "Can get stores");
       is(transaction.oncomplete, null, "No complete listener");
       is(transaction.onabort, null, "No abort listener");
 
       is(objectStore.name, "foo", "Correct name");
-      is(objectStore.keyPath, "", "Correct keyPath");
+      is(objectStore.keyPath, null, "Correct keyPath");
 
       is(objectStore.indexNames.length, 1, "Correct indexNames length");
       is(objectStore.indexNames[0], "fooindex", "Correct indexNames name");
       is(objectStore.index("fooindex"), index, "Can get index");
 
       // Wait until it's complete!
       transaction.oncomplete = grabEventAndContinueHandler;
       event = yield;
@@ -78,17 +78,17 @@
         is(transaction.objectStore("foo").name, "foo", "Can't get stores");
         ok(false, "Should have thrown");
       }
       catch (e) {
         ok(true, "Out of scope transaction can't make stores");
       }
 
       is(objectStore.name, "foo", "Correct name");
-      is(objectStore.keyPath, "", "Correct keyPath");
+      is(objectStore.keyPath, null, "Correct keyPath");
 
       is(objectStore.indexNames.length, 1, "Correct indexNames length");
       is(objectStore.indexNames[0], "fooindex", "Correct indexNames name");
 
       try {
         objectStore.add({});
         ok(false, "Should have thrown");
       }
new file mode 100644
--- /dev/null
+++ b/dom/telephony/Makefile.in
@@ -0,0 +1,72 @@
+# ***** 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 Telephony.
+#
+# The Initial Developer of the Original Code is
+#   The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+#
+# 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 *****
+
+DEPTH            = ../..
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+LIBRARY_NAME     = domtelephony_s
+XPIDL_MODULE     = dom_telephony
+LIBXUL_LIBRARY   = 1
+FORCE_STATIC_LIB = 1
+
+include $(topsrcdir)/dom/dom-config.mk
+
+CPPSRCS = \
+  Radio.cpp \
+  $(NULL)
+
+LOCAL_INCLUDES = \
+  -I$(topsrcdir)/dom/base \
+  -I$(topsrcdir)/content/events/src \
+  $(NULL)
+
+XPIDLSRCS = \
+  mozIDOMTelephony.idl \
+  $(NULL)
+
+EXTRA_COMPONENTS = \
+  Telephony.manifest \
+  Telephony.js \
+  $(NULL)
+
+DIRS += worker-component
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/telephony/Radio.cpp
@@ -0,0 +1,343 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* ***** 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 Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * 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 "Radio.h"
+#include "nsITelephonyWorker.h"
+#include "nsContentUtils.h"
+#include "nsIXPConnect.h"
+#include "nsIJSContextStack.h"
+#include "nsIObserverService.h"
+#include "mozilla/dom/workers/Workers.h"
+#include "jstypedarray.h"
+
+#include "nsThreadUtils.h"
+
+#if defined(MOZ_WIDGET_GONK)
+#include <android/log.h>
+#define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gonk", args)
+#else
+#define LOG(args...)  printf(args);
+#endif
+
+USING_WORKERS_NAMESPACE
+using namespace mozilla::ipc;
+
+static NS_DEFINE_CID(kTelephonyWorkerCID, NS_TELEPHONYWORKER_CID);
+
+// Topic we listen to for shutdown.
+#define PROFILE_BEFORE_CHANGE_TOPIC "profile-before-change"
+
+USING_TELEPHONY_NAMESPACE
+
+namespace {
+
+// Doesn't carry a reference, we're owned by services.
+Radio* gInstance = nsnull;
+
+class ConnectWorkerToRIL : public WorkerTask {
+public:
+  virtual bool RunTask(JSContext *aCx);
+};
+
+JSBool
+PostToRIL(JSContext *cx, uintN argc, jsval *vp)
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Expecting to be on the worker thread");
+
+  if (argc != 1) {
+    JS_ReportError(cx, "Expecting a single argument with the RIL message");
+    return false;
+  }
+
+  jsval v = JS_ARGV(cx, vp)[0];
+
+  nsAutoPtr<RilMessage> rm(new RilMessage());
+  JSAutoByteString abs;
+  void *data;
+  size_t size;
+  if (JSVAL_IS_STRING(v)) {
+    JSString *str = JSVAL_TO_STRING(v);
+    if (!abs.encode(cx, str)) {
+      return false;
+    }
+
+    size = JS_GetStringLength(str);
+    data = abs.ptr();
+  } else if (!JSVAL_IS_PRIMITIVE(v)) {
+    JSObject *obj = JSVAL_TO_OBJECT(v);
+    if (!js_IsTypedArray(obj)) {
+      JS_ReportError(cx, "Object passed in wasn't a typed array");
+      return false;
+    }
+
+    JSUint32 type = JS_GetTypedArrayType(obj);
+    if (type != js::TypedArray::TYPE_INT8 &&
+        type != js::TypedArray::TYPE_UINT8 &&
+        type != js::TypedArray::TYPE_UINT8_CLAMPED) {
+      JS_ReportError(cx, "Typed array data is not octets");
+      return false;
+    }
+
+    size = JS_GetTypedArrayByteLength(obj);
+    data = JS_GetTypedArrayData(obj);
+  } else {
+    JS_ReportError(cx,
+                   "Incorrect argument. Expecting a string or a typed array");
+    return false;
+  }
+
+  if (size > RilMessage::DATA_SIZE) {
+    JS_ReportError(cx, "Passed-in data is too large");
+    return false;
+  }
+
+  rm->mSize = size;
+  memcpy(rm->mData, data, size);
+
+  RilMessage *tosend = rm.forget();
+  JS_ALWAYS_TRUE(SendRilMessage(&tosend));
+  return true;
+}
+
+bool
+ConnectWorkerToRIL::RunTask(JSContext *aCx)
+{
+  // Set up the postRILMessage on the function for worker -> RIL thread
+  // communication.
+  NS_ASSERTION(!NS_IsMainThread(), "Expecting to be on the worker thread");
+  NS_ASSERTION(!JS_IsRunning(aCx), "Are we being called somehow?");
+  JSObject *workerGlobal = JS_GetGlobalObject(aCx);
+
+  return JS_DefineFunction(aCx, workerGlobal, "postRILMessage", PostToRIL, 1, 0);
+}
+
+class RILReceiver : public RilConsumer
+{
+  class DispatchRILEvent : public WorkerTask {
+  public:
+    DispatchRILEvent(RilMessage *aMessage)
+      : mMessage(aMessage)
+    { }
+
+    virtual bool RunTask(JSContext *aCx);
+
+  private:
+    nsAutoPtr<RilMessage> mMessage;
+  };
+
+public:
+  RILReceiver(WorkerCrossThreadDispatcher *aDispatcher)
+    : mDispatcher(aDispatcher)
+  { }
+
+  virtual void MessageReceived(RilMessage *aMessage) {
+    nsRefPtr<DispatchRILEvent> dre(new DispatchRILEvent(aMessage));
+    mDispatcher->PostTask(dre);
+  }
+
+private:
+  nsRefPtr<WorkerCrossThreadDispatcher> mDispatcher;
+};
+
+bool
+RILReceiver::DispatchRILEvent::RunTask(JSContext *aCx)
+{
+  JSObject *obj = JS_GetGlobalObject(aCx);
+
+  JSObject *array =
+    js_CreateTypedArray(aCx, js::TypedArray::TYPE_UINT8, mMessage->mSize);
+  if (!array) {
+    return false;
+  }
+
+  memcpy(JS_GetTypedArrayData(array), mMessage->mData, mMessage->mSize);
+  jsval argv[] = { OBJECT_TO_JSVAL(array) };
+  return JS_CallFunctionName(aCx, obj, "onRILMessage", NS_ARRAY_LENGTH(argv),
+                             argv, argv);
+}
+
+} // anonymous namespace
+
+Radio::Radio()
+  : mShutdown(false)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(!gInstance, "There should only be one instance!");
+}
+
+Radio::~Radio()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(!gInstance || gInstance == this,
+               "There should only be one instance!");
+  gInstance = nsnull;
+}
+
+nsresult
+Radio::Init()
+{
+  NS_ASSERTION(NS_IsMainThread(), "We can only initialize on the main thread");
+
+  nsCOMPtr<nsIObserverService> obs =
+    do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
+  if (!obs) {
+    NS_WARNING("Failed to get observer service!");
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = obs->AddObserver(this, PROFILE_BEFORE_CHANGE_TOPIC, false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // The telephony worker component is a hack that gives us a global object for
+  // our own functions and makes creating the worker possible.
+  nsCOMPtr<nsITelephonyWorker> worker(do_CreateInstance(kTelephonyWorkerCID));
+  if (!worker) {
+    return NS_ERROR_FAILURE;
+  }
+
+  jsval workerval;
+  rv = worker->GetWorker(&workerval);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ASSERTION(!JSVAL_IS_PRIMITIVE(workerval), "bad worker value");
+
+  JSContext *cx;
+  rv = nsContentUtils::ThreadJSContextStack()->GetSafeJSContext(&cx);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCxPusher pusher;
+  if (!cx || !pusher.Push(cx, false)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  JSObject *workerobj = JSVAL_TO_OBJECT(workerval);
+
+  JSAutoRequest ar(cx);
+  JSAutoEnterCompartment ac;
+  if (!ac.enter(cx, workerobj)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  WorkerCrossThreadDispatcher *wctd = GetWorkerCrossThreadDispatcher(cx, workerval);
+  if (!wctd) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<ConnectWorkerToRIL> connection = new ConnectWorkerToRIL();
+  if (!wctd->PostTask(connection)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // Now that we're set up, connect ourselves to the RIL thread.
+  mozilla::RefPtr<RILReceiver> receiver = new RILReceiver(wctd);
+  StartRil(receiver);
+
+  mRadioInterface = do_QueryInterface(worker);
+  NS_ENSURE_TRUE(mRadioInterface, NS_ERROR_FAILURE);
+
+  return NS_OK;
+}
+
+void
+Radio::Shutdown()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  StopRil();
+  mRadioInterface = nsnull;
+
+  mShutdown = true;
+}
+
+// static
+already_AddRefed<Radio>
+Radio::FactoryCreate()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  nsRefPtr<Radio> instance(gInstance);
+
+  if (!instance) {
+    instance = new Radio();
+    if (NS_FAILED(instance->Init())) {
+      return nsnull;
+    }
+
+    gInstance = instance;
+  }
+
+  return instance.forget();
+}
+
+// static
+already_AddRefed<nsIRadioInterface>
+Radio::GetRadioInterface()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  if (gInstance) {
+    nsCOMPtr<nsIRadioInterface> retval = gInstance->mRadioInterface;
+    return retval.forget();
+  }
+
+  return nsnull;
+}
+
+
+NS_IMPL_ISUPPORTS1(Radio, nsIObserver)
+
+NS_IMETHODIMP
+Radio::Observe(nsISupports* aSubject, const char* aTopic,
+               const PRUnichar* aData)
+{
+  if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_TOPIC)) {
+    Shutdown();
+
+    nsCOMPtr<nsIObserverService> obs =
+      do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
+    if (obs) {
+      if (NS_FAILED(obs->RemoveObserver(this, aTopic))) {
+        NS_WARNING("Failed to remove observer!");
+      }
+    }
+    else {
+      NS_WARNING("Failed to get observer service!");
+    }
+  }
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/telephony/Radio.h
@@ -0,0 +1,101 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** 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 Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * 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 mozilla_dom_telephony_radio_h__
+#define mozilla_dom_telephony_radio_h__
+
+#include "jsapi.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsIObserver.h"
+#include "mozilla/ipc/Ril.h"
+#include "nsIRadioInterface.h"
+
+#define TELEPHONYRADIO_CONTRACTID "@mozilla.org/telephony/radio;1"
+#define TELEPHONYRADIOINTERFACE_CONTRACTID "@mozilla.org/telephony/radio-interface;1"
+
+#define BEGIN_TELEPHONY_NAMESPACE \
+  namespace mozilla { namespace dom { namespace telephony {
+#define END_TELEPHONY_NAMESPACE \
+  } /* namespace telephony */ } /* namespace dom */ } /* namespace mozilla */
+#define USING_TELEPHONY_NAMESPACE \
+  using namespace mozilla::dom::telephony;
+
+// {a5c3a6de-84c4-4b15-8611-8aeb8d97f8ba}
+#define TELEPHONYRADIO_CID \
+  {0xa5c3a6de, 0x84c4, 0x4b15, {0x86, 0x11, 0x8a, 0xeb, 0x8d, 0x97, 0xf8, 0xba}}
+
+// {a688f191-8ffc-47f3-8740-94a312cf59cb}}
+#define TELEPHONYRADIOINTERFACE_CID \
+  {0xd66e7ece, 0x41b1, 0x4608, {0x82, 0x80, 0x72, 0x50, 0xa6, 0x44, 0xe6, 0x6f}}
+
+
+class nsIXPConnectJSObjectHolder;
+
+BEGIN_TELEPHONY_NAMESPACE
+
+class Radio : public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  nsresult Init();
+  void Shutdown();
+
+  static already_AddRefed<Radio>
+  FactoryCreate();
+
+  static already_AddRefed<nsIRadioInterface>
+  GetRadioInterface();
+
+protected:
+  Radio();
+  ~Radio();
+
+  nsCOMPtr<nsIRadioInterface> mRadioInterface;
+  bool mShutdown;
+};
+
+END_TELEPHONY_NAMESPACE
+
+#endif // mozilla_dom_telephony_radio_h__
new file mode 100644
--- /dev/null
+++ b/dom/telephony/Telephony.js
@@ -0,0 +1,313 @@
+/* ***** 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 Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Philipp von Weitershausen <philipp@weitershausen.de>
+ *
+ * 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 ***** */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const TELEPHONY_CID = Components.ID("{37e248d2-02ff-469b-bb31-eef5a4a4bee3}");
+const TELEPHONY_CONTRACTID = "@mozilla.org/telephony;1";
+
+const TELEPHONY_CALL_CID = Components.ID("{6b9b3daf-e5ea-460b-89a5-641ee20dd577}");
+const TELEPHONY_CALL_CONTRACTID = "@mozilla.org/telephony-call;1";
+
+
+/**
+ * Define an event listener slot on an object, e.g.
+ * 
+ *   obj.onerror = function () {...}
+ * 
+ * will register the function as an event handler for the "error" event
+ * if the "error" slot was defined on 'obj' or its prototype.
+ */
+function defineEventListenerSlot(object, event_type) {
+  let property_name = "on" + event_type;
+  let hidden_name = "_on" + event_type;
+  let bound_name = "_bound_on" + event_type;
+  object.__defineGetter__(property_name, function getter() {
+    return this[hidden_name];
+  });
+  object.__defineSetter__(property_name, function setter(handler) {
+    let old_handler = this[bound_name];
+    if (old_handler) {
+      this.removeEventListener(event_type, old_handler);
+    }
+    // Bind the handler to the object so that its 'this' is correct.
+    let bound_handler = handler.bind(this);
+    this.addEventListener(event_type, bound_handler);
+    this[hidden_name] = handler;
+    this[bound_name] = bound_handler;
+  });
+}
+
+
+/**
+ * Base object for event targets.
+ */
+function EventTarget() {}
+EventTarget.prototype = {
+
+  addEventListener: function addEventListener(type, handler) {
+    //TODO verify that handler is an nsIDOMEventListener (or function)
+    if (!this._listeners) {
+      this._listeners = {};
+    }
+    if (!this._listeners[type]) {
+      this._listeners[type] = [];
+    }
+    if (this._listeners[type].indexOf(handler) != -1) {
+      // The handler is already registered. Ignore.
+      return;
+    }
+    this._listeners[type].push(handler);
+  },
+
+  removeEventListener: function removeEventListener(type, handler) {
+     let list, index;
+     if (this._listeners &&
+         (list = this._listeners[type]) &&
+         (index = list.indexOf(handler) != -1)) {
+       list.splice(index, 1);
+       return;
+     }
+  },
+
+  dispatchEvent: function dispatchEvent(event) {
+    //TODO this does not deal with bubbling, defaultPrevented, canceling, etc.
+    //TODO disallow re-dispatch of the same event if it's already being
+    // dispatched (recursion).
+    let handlerList = this._listeners[event.type];
+    if (!handlerList) {
+      return;
+    }
+    event.target = this;
+
+    // We need to worry about event handler mutations during the event firing.
+    // The correct behaviour is to *not* call any listeners that are added
+    // during the firing and to *not* call any listeners that are removed
+    // during the firing. To address this, we make a copy of the listener list
+    // before dispatching and then double-check that each handler is still
+    // registered before firing it.
+    let handlers = handlerList.slice();
+    handlers.forEach(function (handler) {
+      if (handerList.indexOf(handler) == -1) {
+        return;
+      }
+      switch (typeof handler) {
+        case "function":
+          handler(event);
+          break;
+        case "object":
+          handler.handleEvent(event);
+          break;
+      }
+    });
+  }
+};
+
+const DOM_RADIOSTATE_UNAVAILABLE = "unavailable";
+const DOM_RADIOSTATE_OFF         = "off";
+const DOM_RADIOSTATE_READY       = "ready";
+   
+const DOM_CARDSTATE_UNAVAILABLE    = "unavailable";
+const DOM_CARDSTATE_ABSENT         = "absent";
+const DOM_CARDSTATE_PIN_REQUIRED   = "pin_required";
+const DOM_CARDSTATE_PUK_REQUIRED   = "puk_required";
+const DOM_CARDSTATE_NETWORK_LOCKED = "network_locked";
+const DOM_CARDSTATE_NOT_READY      = "not_ready";
+const DOM_CARDSTATE_READY          = "ready";
+
+/**
+ * Callback object that Telephony registers with nsIRadioInterface.
+ * Telephony can't use itself because that might overload event handler
+ * attributes ('onfoobar').
+ */
+function TelephonyRadioCallback(telephony) {
+  this.telephony = telephony;
+}
+TelephonyRadioCallback.prototype = {
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIRadioCallback]),
+
+  // nsIRadioCallback
+
+  onsignalstrengthchange: function onsignalstrengthchange(signalStrength) {
+    this.telephony.signalStrength = signalStrength;
+    this.telephony._dispatchEventByType("signalstrengthchange");
+  },
+
+  onoperatorchange: function onoperatorchange(operator) {
+    this.telephony.operator = operator;
+    this.telephony._dispatchEventByType("operatorchange");
+  },
+
+  onradiostatechange: function onradiostatechange(radioState) {
+    this.telephony.radioState = radioState;
+    this.telephony._dispatchEventByType("radiostatechange");
+  },
+
+  oncardstatechange: function oncardstatechange(cardState) {
+    this.telephony.cardState = cardState;
+    this.telephony._dispatchEventByType("cardstatechange");
+  },
+
+  oncallstatechange: function oncallstatechange(callState) {
+    this.telephony._processCallState(callState);
+  },
+
+};
+
+/**
+ * The navigator.mozTelephony object.
+ */
+function Telephony() {}
+Telephony.prototype = {
+
+  __proto__: EventTarget.prototype,
+
+  classID: TELEPHONY_CID,
+  classInfo: XPCOMUtils.generateCI({classID: TELEPHONY_CID,
+                                    contractID: TELEPHONY_CONTRACTID,
+                                    interfaces: [Ci.mozIDOMTelephony,
+                                                 Ci.nsIDOMEventTarget],
+                                    flags: Ci.nsIClassInfo.DOM_OBJECT,
+                                    classDescription: "Telephony"}),
+  QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMTelephony,
+                                         Ci.nsIDOMEventTarget,
+                                         Ci.nsIDOMGlobalPropertyInitializer]),
+
+  // nsIDOMGlobalPropertyInitializer
+
+  init: function init(window) {
+    this.window = window;
+    this.radioInterface = Cc["@mozilla.org/telephony/radio-interface;1"]
+                            .createInstance(Ci.nsIRadioInterface);
+    this.radioCallback = new TelephonyRadioCallback(this);
+    window.addEventListener("unload", function onunload(event) {
+      this.radioInterface.unregisterCallback(this.radioCallback);
+      this.radioCallback = null;
+      this.window = null;
+    }.bind(this));
+    this.radioInterface.registerCallback(this.radioCallback);
+    this.liveCalls = [];
+
+    let initialState = this.radioInterface.initialState;
+    this.operator        = initialState.operator;
+    this.radioState      = initialState.radioState;
+    this.cardState       = initialState.cardState;
+    this.signalStrength  = initialState.signalStrength;
+    this._processCallState(initialState.callState);
+  },
+
+  _dispatchEventByType: function _dispatchEventByType(type) {
+    let event = this.window.document.createEvent("Event");
+    event.initEvent(type, false, false);
+    //event.isTrusted = true;
+    this.dispatchEvent(event);
+  },
+
+  _processCallState: function _processCallState(callState) {
+    //TODO
+  },
+
+  // mozIDOMTelephony
+
+  liveCalls: null,
+
+  dial: function dial(number) {
+    this.radioInterface.dial(number);
+    return new TelephonyCall(number, DOM_CALL_READYSTATE_DIALING);
+  },
+
+  // Additional stuff that's useful.
+
+  signalStrength: null,
+  operator: null,
+  radioState: DOM_RADIOSTATE_UNAVAILABLE,
+  cardState: DOM_CARDSTATE_UNAVAILABLE,
+
+};
+defineEventListenerSlot(Telephony.prototype, "radiostatechange");
+defineEventListenerSlot(Telephony.prototype, "cardstatechange");
+defineEventListenerSlot(Telephony.prototype, "signalstrengthchange");
+defineEventListenerSlot(Telephony.prototype, "operatorchange");
+defineEventListenerSlot(Telephony.prototype, "incoming");
+
+
+const DOM_CALL_READYSTATE_DIALING   = "dialing";
+const DOM_CALL_READYSTATE_DOM_CALLING   = "calling";
+const DOM_CALL_READYSTATE_INCOMING  = "incoming";
+const DOM_CALL_READYSTATE_CONNECTED = "connected";
+const DOM_CALL_READYSTATE_CLOSED    = "closed";
+const DOM_CALL_READYSTATE_BUSY      = "busy";
+
+function TelephonyCall(number, initialState) {
+  this.number = number;
+  this.readyState = initialState;
+}
+TelephonyCall.prototype = {
+
+  __proto__: EventTarget.prototype,
+
+  classID: TELEPHONY_CALL_CID,
+  classInfo: XPCOMUtils.generateCI({classID: TELEPHONY_CALL_CID,
+                                    contractID: TELEPHONY_CALL_CONTRACTID,
+                                    interfaces: [Ci.mozIDOMTelephonyCall,
+                                                 Ci.nsIDOMEventTarget],
+                                    flags: Ci.nsIClassInfo.DOM_OBJECT,
+                                    classDescription: "TelephonyCall"}),
+  QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMTelephonyCall,
+                                         Ci.nsIDOMEventTarget]),
+
+  number: null,
+  readyState: null,
+
+  answer: function answer() {
+    //TODO
+  },
+
+  disconnect: function disconnect() {
+    //TODO
+  },
+
+};
+defineEventListenerSlot(TelephonyCall.prototype, "connect");
+defineEventListenerSlot(TelephonyCall.prototype, "disconnect");
+defineEventListenerSlot(TelephonyCall.prototype, "busy");
+
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([Telephony]);
new file mode 100644
--- /dev/null
+++ b/dom/telephony/Telephony.manifest
@@ -0,0 +1,4 @@
+# Telephony.js
+component {37e248d2-02ff-469b-bb31-eef5a4a4bee3} Telephony.js
+contract @mozilla.org/telephony;1 {37e248d2-02ff-469b-bb31-eef5a4a4bee3}
+category JavaScript-navigator-property mozTelephony @mozilla.org/telephony;1
new file mode 100644
--- /dev/null
+++ b/dom/telephony/mozIDOMTelephony.idl
@@ -0,0 +1,76 @@
+/* ***** 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 Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Philipp von Weitershausen <philipp@weitershausen.de>
+ *
+ * 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 "nsIDOMEventTarget.idl"
+interface nsIDOMEventListener;
+interface mozIDOMTelephonyCall;
+
+[scriptable, uuid(24371c72-1631-477d-b26e-f14db369bc22)]
+interface mozIDOMTelephony : nsIDOMEventTarget {
+
+  readonly attribute jsval liveCalls;
+  mozIDOMTelephonyCall dial(in DOMString number);
+  attribute nsIDOMEventListener onincoming;
+
+  //XXX philikon's additions
+  attribute nsIDOMEventListener onoperatorchange;
+  attribute nsIDOMEventListener onradiostatechange;
+  attribute nsIDOMEventListener oncardstatechange;
+  attribute nsIDOMEventListener onsignalstrengthchange;
+  readonly attribute jsval signalStrength;
+  readonly attribute jsval operator;
+  readonly attribute jsval radioState;
+  readonly attribute jsval cardState;
+};
+
+[scriptable, uuid(3d0060db-72ef-4b87-aceb-a16ed4c5253e)]
+interface mozIDOMTelephonyCall : nsIDOMEventTarget {
+
+  readonly attribute DOMString number;
+  readonly attribute DOMString readyState;
+
+  void answer();
+  void disconnect();
+
+  attribute nsIDOMEventListener onreadystatechange;
+  attribute nsIDOMEventListener onringing;
+  attribute nsIDOMEventListener onbusy;
+  attribute nsIDOMEventListener onconnecting;
+  attribute nsIDOMEventListener onconnected;
+  attribute nsI