bug 738869 - implement OES_vertex_array_object webgl extension - r=bjacob
authorGuillaume Abadie <gabadie@mozilla.com> and James King <james@agentultra.com>
Thu, 27 Jun 2013 17:07:21 -0400
changeset 136678 88b65229c78c4b2483377fe16a4e47efc5263368
parent 136677 5d38d2a6e4c103cd9e7b042c131d65b7fd963dac
child 136679 bb3d8befb0e9b09df1772525e65ab272d1f36f1e
push id30234
push userbjacob@mozilla.com
push dateThu, 27 Jun 2013 21:08:45 +0000
treeherdermozilla-inbound@88b65229c78c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbjacob
bugs738869
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 738869 - implement OES_vertex_array_object webgl extension - r=bjacob
content/canvas/src/WebGLContext.cpp
content/canvas/src/WebGLContext.h
content/canvas/src/WebGLContextGL.cpp
content/canvas/src/WebGLContextValidate.cpp
content/canvas/src/WebGLExtensionVertexArray.cpp
content/canvas/src/WebGLExtensions.h
content/canvas/src/WebGLVertexArray.cpp
content/canvas/src/WebGLVertexArray.h
content/canvas/src/moz.build
content/canvas/test/webgl/conformance/extensions/oes-vertex-array-object.html
dom/bindings/Bindings.conf
dom/webidl/WebGLRenderingContext.webidl
gfx/gl/GLContext.cpp
gfx/gl/GLContext.h
gfx/gl/GLDefs.h
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -6,16 +6,17 @@
 #include "WebGLContext.h"
 #include "WebGLObjectModel.h"
 #include "WebGLExtensions.h"
 #include "WebGLContextUtils.h"
 #include "WebGLBuffer.h"
 #include "WebGLVertexAttribData.h"
 #include "WebGLMemoryMultiReporterWrapper.h"
 #include "WebGLFramebuffer.h"
+#include "WebGLVertexArray.h"
 
 #include "AccessCheck.h"
 #include "nsIConsoleService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIClassInfoImpl.h"
 #include "nsContentUtils.h"
 #include "nsIXPConnect.h"
 #include "nsError.h"
@@ -248,25 +249,26 @@ WebGLContext::DestroyResourcesAndContext
     if (!gl)
         return;
 
     gl->MakeCurrent();
 
     mBound2DTextures.Clear();
     mBoundCubeMapTextures.Clear();
     mBoundArrayBuffer = nullptr;
-    mBoundElementArrayBuffer = nullptr;
     mCurrentProgram = nullptr;
     mBoundFramebuffer = nullptr;
     mBoundRenderbuffer = nullptr;
-
-    mAttribBuffers.Clear();
+    mBoundVertexArray = nullptr;
+    mDefaultVertexArray = nullptr;
 
     while (!mTextures.isEmpty())
         mTextures.getLast()->DeleteOnce();
+    while (!mVertexArrays.isEmpty())
+        mVertexArrays.getLast()->DeleteOnce();
     while (!mBuffers.isEmpty())
         mBuffers.getLast()->DeleteOnce();
     while (!mRenderbuffers.isEmpty())
         mRenderbuffers.getLast()->DeleteOnce();
     while (!mFramebuffers.isEmpty())
         mFramebuffers.getLast()->DeleteOnce();
     while (!mShaders.isEmpty())
         mShaders.getLast()->DeleteOnce();
@@ -979,16 +981,18 @@ bool WebGLContext::IsExtensionSupported(
             // We always support this extension.
             return true;
         case OES_texture_float:
             return gl->IsExtensionSupported(gl->IsGLES2() ? GLContext::OES_texture_float
                                                           : GLContext::ARB_texture_float);
         case OES_texture_float_linear:
             return gl->IsExtensionSupported(gl->IsGLES2() ? GLContext::OES_texture_float_linear
                                                           : GLContext::ARB_texture_float);
+        case OES_vertex_array_object:
+            return WebGLExtensionVertexArray::IsSupported(this);
         case EXT_texture_filter_anisotropic:
             return gl->IsExtensionSupported(GLContext::EXT_texture_filter_anisotropic);
         case WEBGL_compressed_texture_s3tc:
             if (gl->IsExtensionSupported(GLContext::EXT_texture_compression_s3tc)) {
                 return true;
             }
             else if (gl->IsExtensionSupported(GLContext::EXT_texture_compression_dxt1) &&
                      gl->IsExtensionSupported(GLContext::ANGLE_texture_compression_dxt3) &&
@@ -1058,16 +1062,20 @@ WebGLContext::GetExtension(JSContext *cx
     else if (CompareWebGLExtensionName(name, "OES_texture_float"))
     {
         ext = OES_texture_float;
     }
     else if (CompareWebGLExtensionName(name, "OES_texture_float_linear"))
     {
         ext = OES_texture_float_linear;
     }
+    else if (CompareWebGLExtensionName(name, "OES_vertex_array_object"))
+    {
+        ext = OES_vertex_array_object;
+    }
     else if (CompareWebGLExtensionName(name, "OES_standard_derivatives"))
     {
         ext = OES_standard_derivatives;
     }
     else if (CompareWebGLExtensionName(name, "EXT_texture_filter_anisotropic"))
     {
         ext = EXT_texture_filter_anisotropic;
     }
@@ -1156,16 +1164,19 @@ WebGLContext::GetExtension(JSContext *cx
                 obj = new WebGLExtensionTextureFloat(this);
                 break;
             case OES_texture_float_linear:
                 obj = new WebGLExtensionTextureFloatLinear(this);
                 break;
             case WEBGL_draw_buffers:
                 obj = new WebGLExtensionDrawBuffers(this);
                 break;
+            case OES_vertex_array_object:
+                obj = new WebGLExtensionVertexArray(this);
+                break;
             default:
                 MOZ_ASSERT(false, "should not get there.");
         }
         mExtensions.EnsureLengthAtLeast(ext + 1);
         mExtensions[ext] = obj;
     }
 
     return WebGLObjectAsJSObject(cx, mExtensions[ext].get(), rv);
@@ -1559,36 +1570,37 @@ WebGLContext::GetSupportedExtensions(JSC
     if (IsExtensionSupported(cx, WEBGL_debug_renderer_info))
         arr.AppendElement(NS_LITERAL_STRING("WEBGL_debug_renderer_info"));
     if (IsExtensionSupported(cx, WEBGL_depth_texture))
         arr.AppendElement(NS_LITERAL_STRING("MOZ_WEBGL_depth_texture"));
     if (IsExtensionSupported(cx, WEBGL_depth_texture))
         arr.AppendElement(NS_LITERAL_STRING("WEBGL_depth_texture"));
     if (IsExtensionSupported(cx, WEBGL_draw_buffers))
         arr.AppendElement(NS_LITERAL_STRING("WEBGL_draw_buffers"));
+    if (IsExtensionSupported(cx, OES_vertex_array_object))
+        arr.AppendElement(NS_LITERAL_STRING("OES_vertex_array_object"));
 }
 
 //
 // XPCOM goop
 //
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebGLContext)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext)
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_10(WebGLContext,
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_9(WebGLContext,
   mCanvasElement,
   mExtensions,
   mBound2DTextures,
   mBoundCubeMapTextures,
   mBoundArrayBuffer,
-  mBoundElementArrayBuffer,
   mCurrentProgram,
   mBoundFramebuffer,
   mBoundRenderbuffer,
-  mAttribBuffers)
+  mBoundVertexArray)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebGLContext)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsIDOMWebGLRenderingContext)
   NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   // If the exact way we cast to nsISupports here ever changes, fix our
   // ToSupports() method.
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -71,16 +71,17 @@ class WebGLBuffer;
 class WebGLVertexAttribData;
 class WebGLShader;
 class WebGLProgram;
 class WebGLUniformLocation;
 class WebGLFramebuffer;
 class WebGLRenderbuffer;
 class WebGLShaderPrecisionFormat;
 class WebGLTexture;
+class WebGLVertexArray;
 
 namespace dom {
 struct WebGLContextAttributes;
 struct WebGLContextAttributesInitializer;
 }
 
 using WebGLTexelConversions::WebGLTexelFormat;
 
@@ -123,16 +124,17 @@ class WebGLContext :
     friend class WebGLMemoryPressureObserver;
     friend class WebGLMemoryMultiReporterWrapper;
     friend class WebGLExtensionLoseContext;
     friend class WebGLExtensionCompressedTextureS3TC;
     friend class WebGLExtensionCompressedTextureATC;
     friend class WebGLExtensionCompressedTexturePVRTC;
     friend class WebGLExtensionDepthTexture;
     friend class WebGLExtensionDrawBuffers;
+    friend class WebGLExtensionVertexArray;
 
     enum {
         UNPACK_FLIP_Y_WEBGL = 0x9240,
         UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241,
         CONTEXT_LOST_WEBGL = 0x9242,
         UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243,
         BROWSER_DEFAULT_WEBGL = 0x9244,
         UNMASKED_VENDOR_WEBGL = 0x9245,
@@ -316,16 +318,17 @@ public:
     void ActiveTexture(WebGLenum texture);
     void AttachShader(WebGLProgram* program, WebGLShader* shader);
     void BindAttribLocation(WebGLProgram* program, WebGLuint location,
                             const nsAString& name);
     void BindBuffer(WebGLenum target, WebGLBuffer* buf);
     void BindFramebuffer(WebGLenum target, WebGLFramebuffer* wfb);
     void BindRenderbuffer(WebGLenum target, WebGLRenderbuffer* wrb);
     void BindTexture(WebGLenum target, WebGLTexture *tex);
+    void BindVertexArray(WebGLVertexArray *vao);
     void BlendColor(WebGLclampf r, WebGLclampf g, WebGLclampf b, WebGLclampf a) {
         if (!IsContextStable())
             return;
         MakeContextCurrent();
         gl->fBlendColor(r, g, b, a);
     }
     void BlendEquation(WebGLenum mode);
     void BlendEquationSeparate(WebGLenum modeRGB, WebGLenum modeAlpha);
@@ -362,22 +365,24 @@ public:
                            WebGLint yoffset, WebGLint x, WebGLint y,
                            WebGLsizei width, WebGLsizei height);
     already_AddRefed<WebGLBuffer> CreateBuffer();
     already_AddRefed<WebGLFramebuffer> CreateFramebuffer();
     already_AddRefed<WebGLProgram> CreateProgram();
     already_AddRefed<WebGLRenderbuffer> CreateRenderbuffer();
     already_AddRefed<WebGLTexture> CreateTexture();
     already_AddRefed<WebGLShader> CreateShader(WebGLenum type);
+    already_AddRefed<WebGLVertexArray> CreateVertexArray();
     void CullFace(WebGLenum face);
     void DeleteBuffer(WebGLBuffer *buf);
     void DeleteFramebuffer(WebGLFramebuffer *fbuf);
     void DeleteProgram(WebGLProgram *prog);
     void DeleteRenderbuffer(WebGLRenderbuffer *rbuf);
     void DeleteShader(WebGLShader *shader);
+    void DeleteVertexArray(WebGLVertexArray *vao);
     void DeleteTexture(WebGLTexture *tex);
     void DepthFunc(WebGLenum func);
     void DepthMask(WebGLboolean b);
     void DepthRange(WebGLclampf zNear, WebGLclampf zFar);
     void DetachShader(WebGLProgram *program, WebGLShader *shader);
     void Disable(WebGLenum cap);
     void DisableVertexAttribArray(WebGLuint index);
     void DrawArrays(GLenum mode, WebGLint first, WebGLsizei count);
@@ -460,16 +465,17 @@ public:
     void Hint(WebGLenum target, WebGLenum mode);
     bool IsBuffer(WebGLBuffer *buffer);
     bool IsEnabled(WebGLenum cap);
     bool IsFramebuffer(WebGLFramebuffer *fb);
     bool IsProgram(WebGLProgram *prog);
     bool IsRenderbuffer(WebGLRenderbuffer *rb);
     bool IsShader(WebGLShader *shader);
     bool IsTexture(WebGLTexture *tex);
+    bool IsVertexArray(WebGLVertexArray *vao);
     void LineWidth(WebGLfloat width) {
         if (!IsContextStable())
             return;
         MakeContextCurrent();
         gl->fLineWidth(width);
     }
     void LinkProgram(WebGLProgram *program);
     void PixelStorei(WebGLenum pname, WebGLint param);
@@ -869,16 +875,17 @@ protected:
 
     // extensions
     enum WebGLExtensionID {
         EXT_texture_filter_anisotropic,
         OES_element_index_uint,
         OES_standard_derivatives,
         OES_texture_float,
         OES_texture_float_linear,
+        OES_vertex_array_object,
         WEBGL_compressed_texture_atc,
         WEBGL_compressed_texture_pvrtc,
         WEBGL_compressed_texture_s3tc,
         WEBGL_debug_renderer_info,
         WEBGL_depth_texture,
         WEBGL_lose_context,
         WEBGL_draw_buffers,
         WebGLExtensionID_unknown_extension
@@ -1026,38 +1033,38 @@ protected:
 
     void MaybeRestoreContext();
     bool IsContextStable() const {
         return mContextStatus == ContextStable;
     }
     void ForceLoseContext();
     void ForceRestoreContext();
 
-    // the buffers bound to the current program's attribs
-    nsTArray<WebGLVertexAttribData> mAttribBuffers;
-
     nsTArray<WebGLRefPtr<WebGLTexture> > mBound2DTextures;
     nsTArray<WebGLRefPtr<WebGLTexture> > mBoundCubeMapTextures;
 
     WebGLRefPtr<WebGLBuffer> mBoundArrayBuffer;
-    WebGLRefPtr<WebGLBuffer> mBoundElementArrayBuffer;
 
     WebGLRefPtr<WebGLProgram> mCurrentProgram;
 
     uint32_t mMaxFramebufferColorAttachments;
 
     WebGLRefPtr<WebGLFramebuffer> mBoundFramebuffer;
     WebGLRefPtr<WebGLRenderbuffer> mBoundRenderbuffer;
+    WebGLRefPtr<WebGLVertexArray> mBoundVertexArray;
 
     LinkedList<WebGLTexture> mTextures;
     LinkedList<WebGLBuffer> mBuffers;
     LinkedList<WebGLProgram> mPrograms;
     LinkedList<WebGLShader> mShaders;
     LinkedList<WebGLRenderbuffer> mRenderbuffers;
     LinkedList<WebGLFramebuffer> mFramebuffers;
+    LinkedList<WebGLVertexArray> mVertexArrays;
+
+    WebGLRefPtr<WebGLVertexArray> mDefaultVertexArray;
 
     // PixelStore parameters
     uint32_t mPixelStorePackAlignment, mPixelStoreUnpackAlignment, mPixelStoreColorspaceConversion;
     bool mPixelStoreFlipY, mPixelStorePremultiplyAlpha;
 
     FakeBlackStatus mFakeBlackStatus;
 
     WebGLuint mBlackTexture2D, mBlackTextureCubeMap;
@@ -1133,16 +1140,17 @@ public:
 
     friend class WebGLTexture;
     friend class WebGLFramebuffer;
     friend class WebGLRenderbuffer;
     friend class WebGLProgram;
     friend class WebGLBuffer;
     friend class WebGLShader;
     friend class WebGLUniformLocation;
+    friend class WebGLVertexArray;
 };
 
 // used by DOM bindings in conjunction with GetParentObject
 inline nsISupports*
 ToSupports(WebGLContext* context)
 {
   return static_cast<nsIDOMWebGLRenderingContext*>(context);
 }
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -10,16 +10,17 @@
 #include "WebGLShader.h"
 #include "WebGLProgram.h"
 #include "WebGLUniformLocation.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLShaderPrecisionFormat.h"
 #include "WebGLTexture.h"
 #include "WebGLExtensions.h"
+#include "WebGLVertexArray.h"
 
 #include "nsString.h"
 #include "nsDebug.h"
 
 #include "gfxImageSurface.h"
 #include "gfxContext.h"
 #include "gfxPlatform.h"
 
@@ -157,17 +158,17 @@ WebGLContext::BindBuffer(WebGLenum targe
         buf->SetHasEverBeenBound(true);
     }
 
     // we really want to do this AFTER all the validation is done, otherwise our bookkeeping could get confused.
     // see bug 656752
     if (target == LOCAL_GL_ARRAY_BUFFER) {
         mBoundArrayBuffer = buf;
     } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
-        mBoundElementArrayBuffer = buf;
+        mBoundVertexArray->mBoundElementArrayBuffer = buf;
     }
 
     MakeContextCurrent();
 
     gl->fBindBuffer(target, bufname);
 }
 
 void
@@ -222,16 +223,49 @@ WebGLContext::BindRenderbuffer(WebGLenum
 
     WebGLuint renderbuffername = wrb ? wrb->GLName() : 0;
     gl->fBindRenderbuffer(target, renderbuffername);
 
     mBoundRenderbuffer = wrb;
 }
 
 void
+WebGLContext::BindVertexArray(WebGLVertexArray *array)
+{
+    if (!IsContextStable())
+        return;
+
+    if (!ValidateObjectAllowDeletedOrNull("bindVertexArrayObject", array))
+        return;
+
+    if (array && array->IsDeleted()) {
+        /* http://www.khronos.org/registry/gles/extensions/OES/OES_vertex_array_object.txt
+         * BindVertexArrayOES fails and an INVALID_OPERATION error is
+         * generated if array is not a name returned from a previous call to
+         * GenVertexArraysOES, or if such a name has since been deleted with
+         * DeleteVertexArraysOES
+         */
+        ErrorInvalidOperation("bindVertexArray: can't bind a deleted array!");
+        return;
+    }
+
+    MakeContextCurrent();
+
+    if (array) {
+        gl->fBindVertexArray(array->GLName());
+        array->SetHasEverBeenBound(true);
+        mBoundVertexArray = array;
+    }
+    else {
+        gl->fBindVertexArray(0);
+        mBoundVertexArray = mDefaultVertexArray;
+    }
+}
+
+void
 WebGLContext::BindTexture(WebGLenum target, WebGLTexture *tex)
 {
     if (!IsContextStable())
         return;
 
     if (!ValidateObjectAllowDeletedOrNull("bindTexture", tex))
         return;
 
@@ -332,17 +366,17 @@ GLenum WebGLContext::CheckedBufferData(G
         GenerateWarning("Rejecting valid bufferData call with size %lu to avoid a Mac bug", size);
         return LOCAL_GL_INVALID_VALUE;
     }
 #endif
     WebGLBuffer *boundBuffer = nullptr;
     if (target == LOCAL_GL_ARRAY_BUFFER) {
         boundBuffer = mBoundArrayBuffer;
     } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
-        boundBuffer = mBoundElementArrayBuffer;
+        boundBuffer = mBoundVertexArray->mBoundElementArrayBuffer;
     }
     NS_ABORT_IF_FALSE(boundBuffer != nullptr, "no buffer bound for this target");
     
     bool sizeChanges = uint32_t(size) != boundBuffer->ByteLength();
     if (sizeChanges) {
         UpdateWebGLErrorAndClearGLError();
         gl->fBufferData(target, size, data, usage);
         GLenum error = LOCAL_GL_NO_ERROR;
@@ -361,17 +395,17 @@ WebGLContext::BufferData(WebGLenum targe
     if (!IsContextStable())
         return;
 
     WebGLBuffer *boundBuffer = nullptr;
 
     if (target == LOCAL_GL_ARRAY_BUFFER) {
         boundBuffer = mBoundArrayBuffer;
     } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
-        boundBuffer = mBoundElementArrayBuffer;
+        boundBuffer = mBoundVertexArray->mBoundElementArrayBuffer;
     } else {
         return ErrorInvalidEnumInfo("bufferData: target", target);
     }
 
     if (size < 0)
         return ErrorInvalidValue("bufferData: negative size");
 
     if (!ValidateBufferUsageEnum(usage, "bufferData: usage"))
@@ -406,17 +440,17 @@ WebGLContext::BufferData(WebGLenum targe
         return ErrorInvalidValue("bufferData: null object passed");
     }
 
     WebGLBuffer *boundBuffer = nullptr;
 
     if (target == LOCAL_GL_ARRAY_BUFFER) {
         boundBuffer = mBoundArrayBuffer;
     } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
-        boundBuffer = mBoundElementArrayBuffer;
+        boundBuffer = mBoundVertexArray->mBoundElementArrayBuffer;
     } else {
         return ErrorInvalidEnumInfo("bufferData: target", target);
     }
 
     if (!ValidateBufferUsageEnum(usage, "bufferData: usage"))
         return;
 
     if (!boundBuffer)
@@ -444,17 +478,17 @@ WebGLContext::BufferData(WebGLenum targe
     if (!IsContextStable())
         return;
 
     WebGLBuffer *boundBuffer = nullptr;
 
     if (target == LOCAL_GL_ARRAY_BUFFER) {
         boundBuffer = mBoundArrayBuffer;
     } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
-        boundBuffer = mBoundElementArrayBuffer;
+        boundBuffer = mBoundVertexArray->mBoundElementArrayBuffer;
     } else {
         return ErrorInvalidEnumInfo("bufferData: target", target);
     }
 
     if (!ValidateBufferUsageEnum(usage, "bufferData: usage"))
         return;
 
     if (!boundBuffer)
@@ -487,17 +521,17 @@ WebGLContext::BufferSubData(GLenum targe
         return;
     }
 
     WebGLBuffer *boundBuffer = nullptr;
 
     if (target == LOCAL_GL_ARRAY_BUFFER) {
         boundBuffer = mBoundArrayBuffer;
     } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
-        boundBuffer = mBoundElementArrayBuffer;
+        boundBuffer = mBoundVertexArray->mBoundElementArrayBuffer;
     } else {
         return ErrorInvalidEnumInfo("bufferSubData: target", target);
     }
 
     if (byteOffset < 0)
         return ErrorInvalidValue("bufferSubData: negative offset");
 
     if (!boundBuffer)
@@ -525,17 +559,17 @@ WebGLContext::BufferSubData(WebGLenum ta
     if (!IsContextStable())
         return;
 
     WebGLBuffer *boundBuffer = nullptr;
 
     if (target == LOCAL_GL_ARRAY_BUFFER) {
         boundBuffer = mBoundArrayBuffer;
     } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
-        boundBuffer = mBoundElementArrayBuffer;
+        boundBuffer = mBoundVertexArray->mBoundElementArrayBuffer;
     } else {
         return ErrorInvalidEnumInfo("bufferSubData: target", target);
     }
 
     if (byteOffset < 0)
         return ErrorInvalidValue("bufferSubData: negative offset");
 
     if (!boundBuffer)
@@ -1066,23 +1100,24 @@ WebGLContext::DeleteBuffer(WebGLBuffer *
         return;
 
     if (!buf || buf->IsDeleted())
         return;
 
     if (mBoundArrayBuffer == buf)
         BindBuffer(LOCAL_GL_ARRAY_BUFFER,
                    static_cast<WebGLBuffer*>(nullptr));
-    if (mBoundElementArrayBuffer == buf)
+
+    if (mBoundVertexArray->mBoundElementArrayBuffer == buf)
         BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER,
                    static_cast<WebGLBuffer*>(nullptr));
 
     for (int32_t i = 0; i < mGLMaxVertexAttribs; i++) {
-        if (mAttribBuffers[i].buf == buf)
-            mAttribBuffers[i].buf = nullptr;
+        if (mBoundVertexArray->mAttribBuffers[i].buf == buf)
+            mBoundVertexArray->mAttribBuffers[i].buf = nullptr;
     }
 
     buf->RequestDelete();
 }
 
 void
 WebGLContext::DeleteFramebuffer(WebGLFramebuffer* fbuf)
 {
@@ -1120,16 +1155,34 @@ WebGLContext::DeleteRenderbuffer(WebGLRe
     if (mBoundRenderbuffer == rbuf)
         BindRenderbuffer(LOCAL_GL_RENDERBUFFER,
                          static_cast<WebGLRenderbuffer*>(nullptr));
 
     rbuf->RequestDelete();
 }
 
 void
+WebGLContext::DeleteVertexArray(WebGLVertexArray *array)
+{
+    if (!IsContextStable())
+        return;
+
+    if (array == nullptr)
+        return;
+
+    if (array->IsDeleted())
+        return;
+
+    if (mBoundVertexArray == array)
+        BindVertexArray(static_cast<WebGLVertexArray*>(nullptr));
+
+    array->RequestDelete();
+}
+
+void
 WebGLContext::DeleteTexture(WebGLTexture *tex)
 {
     if (!IsContextStable())
         return;
 
     if (!ValidateObjectAllowDeletedOrNull("deleteTexture", tex))
         return;
 
@@ -1246,35 +1299,35 @@ WebGLContext::DisableVertexAttribArray(W
         return;
 
     MakeContextCurrent();
     InvalidateCachedMinInUseAttribArrayLength();
 
     if (index || gl->IsGLES2())
         gl->fDisableVertexAttribArray(index);
 
-    mAttribBuffers[index].enabled = false;
+    mBoundVertexArray->mAttribBuffers[index].enabled = false;
 }
 
 int
 WebGLContext::WhatDoesVertexAttrib0Need()
 {
   // here we may assume that mCurrentProgram != null
 
     // work around Mac OSX crash, see bug 631420
 #ifdef XP_MACOSX
     if (gl->WorkAroundDriverBugs() &&
-        mAttribBuffers[0].enabled &&
+        mBoundVertexArray->mAttribBuffers[0].enabled &&
         !mCurrentProgram->IsAttribInUse(0))
     {
         return VertexAttrib0Status::EmulatedUninitializedArray;
     }
 #endif
 
-    return (gl->IsGLES2() || mAttribBuffers[0].enabled) ? VertexAttrib0Status::Default
+    return (gl->IsGLES2() || mBoundVertexArray->mAttribBuffers[0].enabled) ? VertexAttrib0Status::Default
          : mCurrentProgram->IsAttribInUse(0)            ? VertexAttrib0Status::EmulatedInitializedArray
                                                         : VertexAttrib0Status::EmulatedUninitializedArray;
 }
 
 bool
 WebGLContext::DoFakeVertexAttrib0(WebGLuint vertexCount)
 {
     int whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
@@ -1364,23 +1417,23 @@ WebGLContext::DoFakeVertexAttrib0(WebGLu
 void
 WebGLContext::UndoFakeVertexAttrib0()
 {
     int whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
 
     if (whatDoesAttrib0Need == VertexAttrib0Status::Default)
         return;
 
-    gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mAttribBuffers[0].buf ? mAttribBuffers[0].buf->GLName() : 0);
+    gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mBoundVertexArray->mAttribBuffers[0].buf ? mBoundVertexArray->mAttribBuffers[0].buf->GLName() : 0);
     gl->fVertexAttribPointer(0,
-                             mAttribBuffers[0].size,
-                             mAttribBuffers[0].type,
-                             mAttribBuffers[0].normalized,
-                             mAttribBuffers[0].stride,
-                             reinterpret_cast<const GLvoid *>(mAttribBuffers[0].byteOffset));
+                             mBoundVertexArray->mAttribBuffers[0].size,
+                             mBoundVertexArray->mAttribBuffers[0].type,
+                             mBoundVertexArray->mAttribBuffers[0].normalized,
+                             mBoundVertexArray->mAttribBuffers[0].stride,
+                             reinterpret_cast<const GLvoid *>(mBoundVertexArray->mAttribBuffers[0].byteOffset));
 
     gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mBoundArrayBuffer ? mBoundArrayBuffer->GLName() : 0);
 }
 
 bool
 WebGLContext::NeedFakeBlack()
 {
     // handle this case first, it's the generic case
@@ -1587,36 +1640,36 @@ WebGLContext::DrawElements(WebGLenum mod
     if (!checked_byteCount.isValid())
         return ErrorInvalidValue("drawElements: overflow in byteCount");
 
     // If there is no current program, this is silently ignored.
     // Any checks below this depend on a program being available.
     if (!mCurrentProgram)
         return;
 
-    if (!mBoundElementArrayBuffer)
+    if (!mBoundVertexArray->mBoundElementArrayBuffer)
         return ErrorInvalidOperation("drawElements: must have element array buffer binding");
 
-    if (!mBoundElementArrayBuffer->ByteLength())
+    if (!mBoundVertexArray->mBoundElementArrayBuffer->ByteLength())
         return ErrorInvalidOperation("drawElements: bound element array buffer doesn't have any data");
 
     CheckedUint32 checked_neededByteCount = checked_byteCount + byteOffset;
 
     if (!checked_neededByteCount.isValid())
         return ErrorInvalidOperation("drawElements: overflow in byteOffset+byteCount");
 
-    if (checked_neededByteCount.value() > mBoundElementArrayBuffer->ByteLength())
+    if (checked_neededByteCount.value() > mBoundVertexArray->mBoundElementArrayBuffer->ByteLength())
         return ErrorInvalidOperation("drawElements: bound element array buffer is too small for given count and offset");
 
     uint32_t maxAllowedCount = 0;
     if (!ValidateBuffers(&maxAllowedCount, "drawElements"))
         return;
 
     if (!maxAllowedCount ||
-        !mBoundElementArrayBuffer->Validate(type, maxAllowedCount - 1, first, count))
+        !mBoundVertexArray->mBoundElementArrayBuffer->Validate(type, maxAllowedCount - 1, first, count))
     {
         return ErrorInvalidOperation(
             "DrawElements: bound vertex attribute buffers do not have sufficient "
             "size for given indices from the bound element array");
     }
 
     MakeContextCurrent();
 
@@ -1705,17 +1758,17 @@ WebGLContext::EnableVertexAttribArray(We
 
     if (!ValidateAttribIndex(index, "enableVertexAttribArray"))
         return;
 
     MakeContextCurrent();
     InvalidateCachedMinInUseAttribArrayLength();
 
     gl->fEnableVertexAttribArray(index);
-    mAttribBuffers[index].enabled = true;
+    mBoundVertexArray->mAttribBuffers[index].enabled = true;
 }
 
 void
 WebGLContext::FramebufferRenderbuffer(WebGLenum target, WebGLenum attachment, WebGLenum rbtarget, WebGLRenderbuffer *wrb)
 {
     if (!IsContextStable())
         return;
 
@@ -2025,16 +2078,31 @@ WebGLContext::GetParameter(JSContext* cx
             if (iv == GLint(LOCAL_GL_COLOR_ATTACHMENT0 + pname - LOCAL_GL_DRAW_BUFFER0)) {
                 return JS::Int32Value(LOCAL_GL_BACK);
             }
             
             return JS::Int32Value(LOCAL_GL_NONE);
         }
     }
 
+    if (IsExtensionEnabled(OES_vertex_array_object)) {
+        switch (pname) {
+
+             case LOCAL_GL_VERTEX_ARRAY_BINDING:
+             {
+                 if (mBoundVertexArray == mDefaultVertexArray){
+                     return WebGLObjectAsJSValue(cx, (WebGLVertexArray *) nullptr, rv);
+                 }
+
+                 return WebGLObjectAsJSValue(cx, mBoundVertexArray.get(), rv);
+             }
+
+        }
+    }
+
     switch (pname) {
         //
         // String params
         //
         case LOCAL_GL_VENDOR:
             return StringValue(cx, "Mozilla", rv);
         case LOCAL_GL_RENDERER:
             return StringValue(cx, "Mozilla", rv);
@@ -2286,17 +2354,17 @@ WebGLContext::GetParameter(JSContext* cx
 
         case LOCAL_GL_ARRAY_BUFFER_BINDING:
         {
             return WebGLObjectAsJSValue(cx, mBoundArrayBuffer.get(), rv);
         }
 
         case LOCAL_GL_ELEMENT_ARRAY_BUFFER_BINDING:
         {
-            return WebGLObjectAsJSValue(cx, mBoundElementArrayBuffer.get(), rv);
+            return WebGLObjectAsJSValue(cx, mBoundVertexArray->mBoundElementArrayBuffer.get(), rv);
         }
 
         case LOCAL_GL_RENDERBUFFER_BINDING:
         {
             return WebGLObjectAsJSValue(cx, mBoundRenderbuffer.get(), rv);
         }
 
         case LOCAL_GL_FRAMEBUFFER_BINDING:
@@ -2950,36 +3018,36 @@ WebGLContext::GetUniformLocation(WebGLPr
 
 JS::Value
 WebGLContext::GetVertexAttrib(JSContext* cx, WebGLuint index, WebGLenum pname,
                               ErrorResult& rv)
 {
     if (!IsContextStable())
         return JS::NullValue();
 
-    if (!ValidateAttribIndex(index, "getVertexAttrib"))
+    if (!mBoundVertexArray->EnsureAttribIndex(index, "getVertexAttrib"))
         return JS::NullValue();
 
     MakeContextCurrent();
 
     switch (pname) {
         case LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
         {
-            return WebGLObjectAsJSValue(cx, mAttribBuffers[index].buf.get(), rv);
+            return WebGLObjectAsJSValue(cx, mBoundVertexArray->mAttribBuffers[index].buf.get(), rv);
         }
 
         case LOCAL_GL_VERTEX_ATTRIB_ARRAY_STRIDE:
-            return JS::Int32Value(mAttribBuffers[index].stride);
+            return JS::Int32Value(mBoundVertexArray->mAttribBuffers[index].stride);
 
         case LOCAL_GL_VERTEX_ATTRIB_ARRAY_SIZE:
         {
             if (!ValidateAttribIndex(index, "enableVertexAttribArray"))
                 return JS::NullValue();
 
-            if (!mAttribBuffers[index].enabled)
+            if (!mBoundVertexArray->mAttribBuffers[index].enabled)
                 return JS::Int32Value(4);
 
             // Don't break; fall through.
         }
         case LOCAL_GL_VERTEX_ATTRIB_ARRAY_TYPE:
         {
             GLint i = 0;
             gl->fGetVertexAttribiv(index, pname, &i);
@@ -3004,24 +3072,22 @@ WebGLContext::GetVertexAttrib(JSContext*
             if (!obj) {
                 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
             }
             return JS::ObjectOrNullValue(obj);
         }
 
         case LOCAL_GL_VERTEX_ATTRIB_ARRAY_ENABLED:
         {
-            return JS::BooleanValue(mAttribBuffers[index].enabled);
+            return JS::BooleanValue(mBoundVertexArray->mAttribBuffers[index].enabled);
         }
 
         case LOCAL_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
         {
-            GLint i = 0;
-            gl->fGetVertexAttribiv(index, pname, &i);
-            return JS::BooleanValue(bool(i));
+            return JS::BooleanValue(mBoundVertexArray->mAttribBuffers[index].normalized);
         }
 
         default:
             ErrorInvalidEnumInfo("getVertexAttrib: parameter", pname);
     }
 
     return JS::NullValue();
 }
@@ -3035,17 +3101,17 @@ WebGLContext::GetVertexAttribOffset(WebG
     if (!ValidateAttribIndex(index, "getVertexAttribOffset"))
         return 0;
 
     if (pname != LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER) {
         ErrorInvalidEnum("getVertexAttribOffset: bad parameter");
         return 0;
     }
 
-    return mAttribBuffers[index].byteOffset;
+    return mBoundVertexArray->mAttribBuffers[index].byteOffset;
 }
 
 void
 WebGLContext::Hint(WebGLenum target, WebGLenum mode)
 {
     if (!IsContextStable())
         return;
 
@@ -3105,16 +3171,30 @@ WebGLContext::IsRenderbuffer(WebGLRender
         return false;
 
     return ValidateObjectAllowDeleted("isRenderBuffer", rb) &&
         !rb->IsDeleted() &&
         rb->HasEverBeenBound();
 }
 
 bool
+WebGLContext::IsVertexArray(WebGLVertexArray *array)
+{
+    if (!IsContextStable())
+        return false;
+
+    if (!array)
+        return false;
+
+    return ValidateObjectAllowDeleted("isVertexArray", array) &&
+           !array->IsDeleted() &&
+           array->HasEverBeenBound();
+}
+
+bool
 WebGLContext::IsShader(WebGLShader *shader)
 {
     if (!IsContextStable())
         return false;
 
     return ValidateObjectAllowDeleted("isShader", shader) &&
         !shader->IsDeleted();
 }
@@ -4325,16 +4405,32 @@ already_AddRefed<WebGLRenderbuffer>
 WebGLContext::CreateRenderbuffer()
 {
     if (!IsContextStable())
         return nullptr;
     nsRefPtr<WebGLRenderbuffer> globj = new WebGLRenderbuffer(this);
     return globj.forget();
 }
 
+already_AddRefed<WebGLVertexArray>
+WebGLContext::CreateVertexArray()
+{
+    if (!IsContextStable())
+        return nullptr;
+
+    nsRefPtr<WebGLVertexArray> globj = new WebGLVertexArray(this);
+
+    MakeContextCurrent();
+    gl->fGenVertexArrays(1, &globj->mGLName);
+
+    mVertexArrays.insertBack(globj);
+
+    return globj.forget();
+}
+
 void
 WebGLContext::Viewport(WebGLint x, WebGLint y, WebGLsizei width, WebGLsizei height)
 {
     if (!IsContextStable())
         return;
 
     if (width < 0 || height < 0)
         return ErrorInvalidValue("viewport: negative size");
@@ -4884,18 +4980,19 @@ WebGLContext::VertexAttribPointer(WebGLu
           break;
       default:
           return ErrorInvalidEnumInfo("vertexAttribPointer: type", type);
     }
 
     // requiredAlignment should always be a power of two.
     WebGLsizei requiredAlignmentMask = requiredAlignment - 1;
 
-    if (!ValidateAttribIndex(index, "vertexAttribPointer"))
-        return;
+    if ( !mBoundVertexArray->EnsureAttribIndex(index, "vertexAttribPointer") ) {
+        return;
+    }
 
     if (size < 1 || size > 4)
         return ErrorInvalidValue("vertexAttribPointer: invalid element size");
 
     if (stride < 0 || stride > 255) // see WebGL spec section 6.6 "Vertex Attribute Data Stride"
         return ErrorInvalidValue("vertexAttribPointer: negative or too large stride");
 
     if (byteOffset < 0)
@@ -4914,17 +5011,17 @@ WebGLContext::VertexAttribPointer(WebGLu
 
     InvalidateCachedMinInUseAttribArrayLength();
 
     /* XXX make work with bufferSubData & heterogeneous types 
     if (type != mBoundArrayBuffer->GLType())
         return ErrorInvalidOperation("vertexAttribPointer: type must match bound VBO type: %d != %d", type, mBoundArrayBuffer->GLType());
     */
 
-    WebGLVertexAttribData &vd = mAttribBuffers[index];
+    WebGLVertexAttribData &vd = mBoundVertexArray->mAttribBuffers[index];
 
     vd.buf = mBoundArrayBuffer;
     vd.stride = stride;
     vd.size = size;
     vd.byteOffset = byteOffset;
     vd.type = type;
     vd.normalized = normalized;
 
--- a/content/canvas/src/WebGLContextValidate.cpp
+++ b/content/canvas/src/WebGLContextValidate.cpp
@@ -7,16 +7,17 @@
 #include "WebGLBuffer.h"
 #include "WebGLVertexAttribData.h"
 #include "WebGLShader.h"
 #include "WebGLProgram.h"
 #include "WebGLUniformLocation.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLTexture.h"
+#include "WebGLVertexArray.h"
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 
 #include "jsfriendapi.h"
 
 #include "angle/ShaderLang.h"
@@ -107,19 +108,19 @@ WebGLContext::ValidateBuffers(uint32_t *
 #endif
 
     if (mMinInUseAttribArrayLengthCached) {
         *maxAllowedCount = mMinInUseAttribArrayLength;
         return true;
     }
 
     uint32_t maxAllowed = UINT32_MAX;
-    uint32_t attribs = mAttribBuffers.Length();
+    uint32_t attribs = mBoundVertexArray->mAttribBuffers.Length();
     for (uint32_t i = 0; i < attribs; ++i) {
-        const WebGLVertexAttribData& vd = mAttribBuffers[i];
+        const WebGLVertexAttribData& vd = mBoundVertexArray->mAttribBuffers[i];
 
         // If the attrib array isn't enabled, there's nothing to check;
         // it's a static value.
         if (!vd.enabled)
             continue;
 
         if (vd.buf == nullptr) {
             ErrorInvalidOperation("%s: no VBO bound to enabled vertex attrib index %d!", info, i);
@@ -845,28 +846,17 @@ WebGLContext::ValidateUniformSetter(cons
     if (!ValidateUniformLocation(name, location_object))
         return false;
     location = location_object->Location();
     return true;
 }
 
 bool WebGLContext::ValidateAttribIndex(WebGLuint index, const char *info)
 {
-    if (index >= mAttribBuffers.Length()) {
-        if (index == WebGLuint(-1)) {
-             ErrorInvalidValue("%s: index -1 is invalid. That probably comes from a getAttribLocation() call, "
-                               "where this return value -1 means that the passed name didn't correspond to an active attribute in "
-                               "the specified program.", info);
-        } else {
-             ErrorInvalidValue("%s: index %d is out of range", info, index);
-        }
-        return false;
-    } else {
-        return true;
-    }
+    return mBoundVertexArray->EnsureAttribIndex(index, info);
 }
 
 bool WebGLContext::ValidateStencilParamsForDrawCall()
 {
   const char *msg = "%s set different front and back stencil %s. Drawing in this configuration is not allowed.";
   if (mStencilRefFront != mStencilRefBack) {
       ErrorInvalidOperation(msg, "stencilFuncSeparate", "reference values");
       return false;
@@ -900,23 +890,20 @@ WebGLContext::InitAndValidateGL()
 
     if (MinCapabilityMode()) {
       mDisableFragHighP = true;
     }
 
     mActiveTexture = 0;
     mWebGLError = LOCAL_GL_NO_ERROR;
 
-    mAttribBuffers.Clear();
-
     mBound2DTextures.Clear();
     mBoundCubeMapTextures.Clear();
 
     mBoundArrayBuffer = nullptr;
-    mBoundElementArrayBuffer = nullptr;
     mCurrentProgram = nullptr;
 
     mBoundFramebuffer = nullptr;
     mBoundRenderbuffer = nullptr;
 
     MakeContextCurrent();
 
     // on desktop OpenGL, we always keep vertex attrib 0 array enabled
@@ -929,18 +916,16 @@ WebGLContext::InitAndValidateGL()
     } else {
         gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_ATTRIBS, &mGLMaxVertexAttribs);
     }
     if (mGLMaxVertexAttribs < 8) {
         GenerateWarning("GL_MAX_VERTEX_ATTRIBS: %d is < 8!", mGLMaxVertexAttribs);
         return false;
     }
 
-    mAttribBuffers.SetLength(mGLMaxVertexAttribs);
-
     // Note: GL_MAX_TEXTURE_UNITS is fixed at 4 for most desktop hardware,
     // even though the hardware supports much more.  The
     // GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS value is the accurate value.
     if (MinCapabilityMode()) {
         mGLMaxTextureUnits = MINVALUE_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS;
     } else {
         gl->fGetIntegerv(LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mGLMaxTextureUnits);
     }
@@ -1079,10 +1064,14 @@ WebGLContext::InitAndValidateGL()
     nsCOMPtr<nsIObserverService> observerService
         = mozilla::services::GetObserverService();
     if (observerService) {
         observerService->AddObserver(mMemoryPressureObserver,
                                      "memory-pressure",
                                      false);
     }
 
+    mDefaultVertexArray = new WebGLVertexArray(this);
+    mDefaultVertexArray->mAttribBuffers.SetLength(mGLMaxVertexAttribs);
+    mBoundVertexArray = mDefaultVertexArray;
+
     return true;
 }
new file mode 100644
--- /dev/null
+++ b/content/canvas/src/WebGLExtensionVertexArray.cpp
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebGLContext.h"
+#include "WebGLBuffer.h"
+#include "WebGLVertexArray.h"
+#include "WebGLExtensions.h"
+#include "mozilla/dom/WebGLRenderingContextBinding.h"
+
+using namespace mozilla;
+
+WebGLExtensionVertexArray::WebGLExtensionVertexArray(WebGLContext* context)
+  : WebGLExtensionBase(context)
+{
+    MOZ_ASSERT(IsSupported(context), "should not construct WebGLExtensionVertexArray :"
+                                     "OES_vertex_array_object unsuported.");
+}
+
+WebGLExtensionVertexArray::~WebGLExtensionVertexArray()
+{
+}
+
+already_AddRefed<WebGLVertexArray> WebGLExtensionVertexArray::CreateVertexArrayOES()
+{
+    return mContext->CreateVertexArray();
+}
+
+void WebGLExtensionVertexArray::DeleteVertexArrayOES(WebGLVertexArray* array)
+{
+    mContext->DeleteVertexArray(array);
+}
+
+bool WebGLExtensionVertexArray::IsVertexArrayOES(WebGLVertexArray* array)
+{
+    return mContext->IsVertexArray(array);
+}
+
+void WebGLExtensionVertexArray::BindVertexArrayOES(WebGLVertexArray* array)
+{
+    mContext->BindVertexArray(array);
+}
+
+bool WebGLExtensionVertexArray::IsSupported(const WebGLContext* context)
+{
+    gl::GLContext* gl = context->GL();
+
+    if (gl->IsGLES2()) {
+        return gl->IsExtensionSupported(gl::GLContext::OES_vertex_array_object);
+    }
+
+    return gl->IsExtensionSupported(gl::GLContext::ARB_vertex_array_object) ||
+           gl->IsExtensionSupported(gl::GLContext::APPLE_vertex_array_object);
+}
+
+IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionVertexArray)
--- a/content/canvas/src/WebGLExtensions.h
+++ b/content/canvas/src/WebGLExtensions.h
@@ -167,11 +167,28 @@ public:
      WEBGL_draw_buffers does not give a minal value for GL_MAX_DRAW_BUFFERS. But, we request
      for GL_MAX_DRAW_BUFFERS = 4 at least to be able to use all requested color attachements.
      See DrawBuffersWEBGL in WebGLExtensionDrawBuffers.cpp inner comments for more informations.
      */
 
     DECL_WEBGL_EXTENSION_GOOP
 };
 
+class WebGLExtensionVertexArray
+    : public WebGLExtensionBase
+{
+public:
+    WebGLExtensionVertexArray(WebGLContext*);
+    virtual ~WebGLExtensionVertexArray();
+
+    already_AddRefed<WebGLVertexArray> CreateVertexArrayOES();
+    void DeleteVertexArrayOES(WebGLVertexArray* array);
+    bool IsVertexArrayOES(WebGLVertexArray* array);
+    void BindVertexArrayOES(WebGLVertexArray* array);
+
+    static bool IsSupported(const WebGLContext* context);
+
+    DECL_WEBGL_EXTENSION_GOOP
+};
+
 } // namespace mozilla
 
 #endif // WEBGLEXTENSIONS_H_
new file mode 100644
--- /dev/null
+++ b/content/canvas/src/WebGLVertexArray.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebGLContext.h"
+#include "WebGLBuffer.h"
+#include "WebGLVertexArray.h"
+#include "mozilla/dom/WebGLRenderingContextBinding.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+
+JSObject*
+WebGLVertexArray::WrapObject(JSContext *cx, JS::Handle<JSObject*> scope) {
+    return dom::WebGLVertexArrayBinding::Wrap(cx, scope, this);
+}
+
+WebGLVertexArray::WebGLVertexArray(WebGLContext* context)
+    : WebGLContextBoundObject(context)
+    , mGLName(0)
+    , mHasEverBeenBound(false)
+{
+    SetIsDOMBinding();
+}
+
+void WebGLVertexArray::Delete() {
+    if (mGLName != 0) {
+        mBoundElementArrayBuffer = nullptr;
+
+        mContext->MakeContextCurrent();
+        mContext->gl->fDeleteVertexArrays(1, &mGLName);
+        LinkedListElement<WebGLVertexArray>::removeFrom(mContext->mVertexArrays);
+    }
+
+    mBoundElementArrayBuffer = nullptr;
+    mAttribBuffers.Clear();
+}
+
+bool WebGLVertexArray::EnsureAttribIndex(WebGLuint index, const char *info)
+{
+    if (index >= WebGLuint(mContext->mGLMaxVertexAttribs)) {
+        if (index == WebGLuint(-1)) {
+            mContext->ErrorInvalidValue("%s: index -1 is invalid. That probably comes from a getAttribLocation() call, "
+                                        "where this return value -1 means that the passed name didn't correspond to an active attribute in "
+                                        "the specified program.", info);
+        } else {
+            mContext->ErrorInvalidValue("%s: index %d is out of range", info, index);
+        }
+        return false;
+    }
+    else if (index >= mAttribBuffers.Length()) {
+        mAttribBuffers.SetLength(index + 1);
+    }
+
+    return true;
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_2(WebGLVertexArray,
+  mAttribBuffers,
+  mBoundElementArrayBuffer)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebGLVertexArray)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLVertexArray)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebGLVertexArray)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
new file mode 100644
--- /dev/null
+++ b/content/canvas/src/WebGLVertexArray.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WEBGLVERTEXARRAY_H_
+#define WEBGLVERTEXARRAY_H_
+
+#include "WebGLObjectModel.h"
+#include "WebGLVertexAttribData.h"
+
+#include "nsWrapperCache.h"
+
+#include "mozilla/LinkedList.h"
+
+namespace mozilla {
+
+class WebGLVertexArray MOZ_FINAL
+    : public nsISupports
+    , public WebGLRefCountedObject<WebGLVertexArray>
+    , public LinkedListElement<WebGLVertexArray>
+    , public WebGLContextBoundObject
+    , public nsWrapperCache
+{
+public:
+    WebGLVertexArray(WebGLContext *context);
+
+    ~WebGLVertexArray() {
+        DeleteOnce();
+    };
+
+    void Delete();
+
+    bool HasEverBeenBound() { return mHasEverBeenBound; }
+    void SetHasEverBeenBound(bool x) { mHasEverBeenBound = x; }
+    WebGLuint GLName() const { return mGLName; }
+
+    WebGLContext* GetParentObject() const {
+        return Context();
+    }
+
+    bool EnsureAttribIndex(WebGLuint index, const char *info);
+
+    virtual JSObject* WrapObject(JSContext *cx,
+                                 JS::Handle<JSObject*> scope) MOZ_OVERRIDE;
+
+    NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+    NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebGLVertexArray)
+
+    WebGLuint mGLName;
+    bool mHasEverBeenBound;
+
+    nsTArray<WebGLVertexAttribData> mAttribBuffers;
+    WebGLRefPtr<WebGLBuffer> mBoundElementArrayBuffer;
+
+    friend class WebGLContext;
+    friend class WebGLExtensionVertexArray;
+};
+
+} // namespace mozilla
+
+#endif
--- a/content/canvas/src/moz.build
+++ b/content/canvas/src/moz.build
@@ -42,22 +42,24 @@ if CONFIG['MOZ_WEBGL']:
         'WebGLExtensionDepthTexture.cpp',
         'WebGLExtensionDrawBuffers.cpp',
         'WebGLExtensionElementIndexUint.cpp',
         'WebGLExtensionLoseContext.cpp',
         'WebGLExtensionStandardDerivatives.cpp',
         'WebGLExtensionTextureFilterAnisotropic.cpp',
         'WebGLExtensionTextureFloat.cpp',
         'WebGLExtensionTextureFloatLinear.cpp',
+        'WebGLExtensionVertexArray.cpp',
         'WebGLFramebuffer.cpp',
         'WebGLObjectModel.cpp',
         'WebGLProgram.cpp',
         'WebGLRenderbuffer.cpp',
         'WebGLShader.cpp',
         'WebGLShaderPrecisionFormat.cpp',
         'WebGLTexelConversions.cpp',
         'WebGLTexture.cpp',
         'WebGLUniformLocation.cpp',
+        'WebGLVertexArray.cpp',
     ]
 else:
     CPP_SOURCES += [
         'WebGLContextNotSupported.cpp',
     ]
--- a/content/canvas/test/webgl/conformance/extensions/oes-vertex-array-object.html
+++ b/content/canvas/test/webgl/conformance/extensions/oes-vertex-array-object.html
@@ -143,17 +143,21 @@ function runObjectTest() {
     
     // Expect false if never bound
     shouldBeFalse("ext.isVertexArrayOES(vao)");
     ext.bindVertexArrayOES(vao);
     shouldBeTrue("ext.isVertexArrayOES(vao)");
     ext.bindVertexArrayOES(null);
     shouldBeTrue("ext.isVertexArrayOES(vao)");
     
-    shouldBeFalse("ext.isVertexArrayOES()");
+    /*
+     * Issue found in the conformance test. The public webgl mailing list has been notified about it.
+     * The tests have already been fixed upstream.
+     */
+    //shouldBeFalse("ext.isVertexArrayOES()");
     shouldBeFalse("ext.isVertexArrayOES(null)");
     
     ext.deleteVertexArrayOES(vao);
     vao = null;
 }
 
 function runAttributeTests() {
     debug("Testing attributes work across bindings");
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1191,16 +1191,21 @@ DOMInterfaces = {
    'headerFile': 'WebGLExtensions.h'
 },
 
 'WebGLExtensionDrawBuffers': {
    'nativeType': 'mozilla::WebGLExtensionDrawBuffers',
    'headerFile': 'WebGLExtensions.h'
 },
 
+'WebGLExtensionVertexArray': {
+   'nativeType': 'mozilla::WebGLExtensionVertexArray',
+   'headerFile': 'WebGLExtensions.h'
+},
+
 'WebGLFramebuffer': {
    'nativeType': 'mozilla::WebGLFramebuffer',
    'headerFile': 'WebGLFramebuffer.h'
 },
 
 'WebGLProgram': {
    'nativeType': 'mozilla::WebGLProgram',
    'headerFile': 'WebGLProgram.h'
@@ -1236,16 +1241,21 @@ DOMInterfaces = {
 },
 
 'WebGLUniformLocation': {
    'nativeType': 'mozilla::WebGLUniformLocation',
    'headerFile': 'WebGLUniformLocation.h',
    'wrapperCache': False
 },
 
+'WebGLVertexArray': {
+   'nativeType': 'mozilla::WebGLVertexArray',
+   'headerFile': 'WebGLVertexArray.h'
+},
+
 'WebSocket': {
     'headerFile': 'WebSocket.h',
     'implicitJSContext': [ 'constructor' ]
 },
 
 'WheelEvent': {
     'headerFile': 'DOMWheelEvent.h',
     'nativeType': 'mozilla::dom::DOMWheelEvent',
--- a/dom/webidl/WebGLRenderingContext.webidl
+++ b/dom/webidl/WebGLRenderingContext.webidl
@@ -59,16 +59,19 @@ interface WebGLShader {
 };
 
 interface WebGLTexture {
 };
 
 interface WebGLUniformLocation {
 };
 
+interface WebGLVertexArray {
+};
+
 interface WebGLActiveInfo {
     readonly attribute GLint size;
     readonly attribute GLenum type;
     readonly attribute DOMString name;
 };
 
 interface WebGLShaderPrecisionFormat {
     readonly attribute GLint rangeMin;
@@ -878,8 +881,18 @@ interface WebGLExtensionDrawBuffers {
 
     void drawBuffersWEBGL(sequence<GLenum> buffers);
 };
 
 [NoInterfaceObject]
 interface WebGLExtensionTextureFloatLinear
 {
 };
+
+[NoInterfaceObject]
+interface WebGLExtensionVertexArray {
+    const GLenum VERTEX_ARRAY_BINDING_OES = 0x85B5;
+
+    WebGLVertexArray? createVertexArrayOES();
+    void deleteVertexArrayOES(WebGLVertexArray? arrayObject);
+    [WebGLHandlesContextLoss] GLboolean isVertexArrayOES(WebGLVertexArray? arrayObject);
+    void bindVertexArrayOES(WebGLVertexArray? arrayObject);
+};
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -83,16 +83,17 @@ static const char *sExtensionNames[] = {
     "GL_ARB_sync",
     "GL_OES_EGL_image",
     "GL_OES_EGL_sync",
     "GL_OES_EGL_image_external",
     "GL_EXT_packed_depth_stencil",
     "GL_OES_element_index_uint",
     "GL_OES_vertex_array_object",
     "GL_ARB_vertex_array_object",
+    "GL_APPLE_vertex_array_object",
     "GL_ARB_draw_buffers",
     "GL_EXT_draw_buffers",
     nullptr
 };
 
 static int64_t sTextureMemoryUsage = 0;
 
 static int64_t
@@ -547,29 +548,55 @@ GLContext::InitWithPrefix(const char *pr
                 NS_ERROR("GL supports OES_EGL_image without supplying its functions.");
 
                 MarkExtensionUnsupported(OES_EGL_image);
                 mSymbols.fEGLImageTargetTexture2D = nullptr;
                 mSymbols.fEGLImageTargetRenderbufferStorage = nullptr;
             }
         }
 
-        if (IsExtensionSupported(OES_vertex_array_object)) {
+        if (IsExtensionSupported(ARB_vertex_array_object) ||
+            IsExtensionSupported(OES_vertex_array_object)) {
             SymLoadStruct vaoSymbols[] = {
                 { (PRFuncPtr*) &mSymbols.fIsVertexArray, { "IsVertexArray", "IsVertexArrayOES", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fGenVertexArrays, { "GenVertexArrays", "GenVertexArraysOES", nullptr } },
-                { (PRFuncPtr*) &mSymbols.fBindVertexArray, { "BindVertexArrays", "BindVertexArrayOES", nullptr } },
+                { (PRFuncPtr*) &mSymbols.fBindVertexArray, { "BindVertexArray", "BindVertexArrayOES", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fDeleteVertexArrays, { "DeleteVertexArrays", "DeleteVertexArraysOES", nullptr } },
                 { nullptr, { nullptr } },
             };
 
             if (!LoadSymbols(&vaoSymbols[0], trygl, prefix)) {
                 NS_ERROR("GL supports Vertex Array Object without supplying its functions.");
 
+                MarkExtensionUnsupported(ARB_vertex_array_object);
                 MarkExtensionUnsupported(OES_vertex_array_object);
+                MarkExtensionUnsupported(APPLE_vertex_array_object);
+                mSymbols.fIsVertexArray = nullptr;
+                mSymbols.fGenVertexArrays = nullptr;
+                mSymbols.fBindVertexArray = nullptr;
+                mSymbols.fDeleteVertexArrays = nullptr;
+            }
+        }
+        else if (IsExtensionSupported(APPLE_vertex_array_object)) {
+            /*
+             * separate call to LoadSymbols with APPLE_vertex_array_object to work around
+             * a driver bug : the IsVertexArray symbol (without suffix) can be present but unusable.
+             */
+            SymLoadStruct vaoSymbols[] = {
+                { (PRFuncPtr*) &mSymbols.fIsVertexArray, { "IsVertexArrayAPPLE", nullptr } },
+                { (PRFuncPtr*) &mSymbols.fGenVertexArrays, { "GenVertexArraysAPPLE", nullptr } },
+                { (PRFuncPtr*) &mSymbols.fBindVertexArray, { "BindVertexArrayAPPLE", nullptr } },
+                { (PRFuncPtr*) &mSymbols.fDeleteVertexArrays, { "DeleteVertexArraysAPPLE", nullptr } },
+                { nullptr, { nullptr } },
+            };
+
+            if (!LoadSymbols(&vaoSymbols[0], trygl, prefix)) {
+                NS_ERROR("GL supports Vertex Array Object without supplying its functions.");
+
+                MarkExtensionUnsupported(APPLE_vertex_array_object);
                 mSymbols.fIsVertexArray = nullptr;
                 mSymbols.fGenVertexArrays = nullptr;
                 mSymbols.fBindVertexArray = nullptr;
                 mSymbols.fDeleteVertexArrays = nullptr;
             }
         }
 
         // Load developer symbols, don't fail if we can't find them.
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -1021,16 +1021,17 @@ public:
         ARB_sync,
         OES_EGL_image,
         OES_EGL_sync,
         OES_EGL_image_external,
         EXT_packed_depth_stencil,
         OES_element_index_uint,
         OES_vertex_array_object,
         ARB_vertex_array_object,
+        APPLE_vertex_array_object,
         ARB_draw_buffers,
         EXT_draw_buffers,
         Extensions_Max
     };
 
     bool IsExtensionSupported(GLExtensions aKnownExtension) const {
         return mAvailableExtensions[aKnownExtension];
     }
--- a/gfx/gl/GLDefs.h
+++ b/gfx/gl/GLDefs.h
@@ -3024,16 +3024,19 @@ typedef uint64_t EGLTime;
 #define LOCAL_GL_SUN_vertex 1
 #define LOCAL_GL_WIN_phong_shading 1
 #define LOCAL_GL_PHONG_WIN 0x80EA
 #define LOCAL_GL_PHONG_HINT_WIN 0x80EB
 #define LOCAL_GL_WIN_specular_fog 1
 #define LOCAL_GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC
 #define LOCAL_GL_WIN_swap_hint 1
 
+// ARB_vertex_array_object
+#define LOCAL_GL_VERTEX_ARRAY_BINDING             0x85B5
+
 // ARB_sync
 #define LOCAL_GL_MAX_SERVER_WAIT_TIMEOUT          0x9111
 #define LOCAL_GL_OBJECT_TYPE                      0x9112
 #define LOCAL_GL_SYNC_CONDITION                   0x9113
 #define LOCAL_GL_SYNC_STATUS                      0x9114
 #define LOCAL_GL_SYNC_FLAGS                       0x9115
 #define LOCAL_GL_SYNC_FENCE                       0x9116
 #define LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE       0x9117