From d9481586433e998c7c05669d626c1c5c23c83e0d Mon Sep 17 00:00:00 2001 From: Michal Novotny Date: Tue, 27 Nov 2012 12:48:15 +0100 Subject: [PATCH 001/160] Bug 725993 - Remove usage of STORE_ON_DISK flag in XHR code, r=jonas --- content/base/public/nsDOMFile.h | 24 ++++------ content/base/src/nsXMLHttpRequest.cpp | 63 ++++++--------------------- content/base/src/nsXMLHttpRequest.h | 3 +- dom/devicestorage/nsDeviceStorage.cpp | 3 +- 4 files changed, 25 insertions(+), 68 deletions(-) diff --git a/content/base/public/nsDOMFile.h b/content/base/public/nsDOMFile.h index 2458371ddcd6..b6feedb290c9 100644 --- a/content/base/public/nsDOMFile.h +++ b/content/base/public/nsDOMFile.h @@ -246,24 +246,17 @@ public: NS_ASSERTION(mFile, "must have file"); } - // Create as a blob - nsDOMFileFile(nsIFile *aFile, const nsAString& aContentType, - nsISupports *aCacheToken) - : nsDOMFile(aContentType, UINT64_MAX), - mFile(aFile), mWholeFile(true), mStoredFile(false), - mCacheToken(aCacheToken) - { - NS_ASSERTION(mFile, "must have file"); - } - // Create as a file with custom name - nsDOMFileFile(nsIFile *aFile, const nsAString& aName) - : nsDOMFile(aName, EmptyString(), UINT64_MAX, UINT64_MAX), + nsDOMFileFile(nsIFile *aFile, const nsAString& aName, + const nsAString& aContentType) + : nsDOMFile(aName, aContentType, UINT64_MAX, UINT64_MAX), mFile(aFile), mWholeFile(true), mStoredFile(false) { NS_ASSERTION(mFile, "must have file"); - // Lazily get the content type and size - mContentType.SetIsVoid(true); + if (aContentType.IsEmpty()) { + // Lazily get the content type and size + mContentType.SetIsVoid(true); + } } // Create as a stored file @@ -324,7 +317,7 @@ protected: const nsAString& aContentType) : nsDOMFile(aContentType, aOther->mStart + aStart, aLength), mFile(aOther->mFile), mWholeFile(false), - mStoredFile(aOther->mStoredFile), mCacheToken(aOther->mCacheToken) + mStoredFile(aOther->mStoredFile) { NS_ASSERTION(mFile, "must have file"); mImmutable = aOther->mImmutable; @@ -363,7 +356,6 @@ protected: nsCOMPtr mFile; bool mWholeFile; bool mStoredFile; - nsCOMPtr mCacheToken; }; class nsDOMMemoryFile : public nsDOMFile diff --git a/content/base/src/nsXMLHttpRequest.cpp b/content/base/src/nsXMLHttpRequest.cpp index 533c5dc1ff2c..50adbb844beb 100644 --- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -1077,18 +1077,6 @@ nsXMLHttpRequest::SetResponseType(nsXMLHttpRequest::ResponseTypeEnum aResponseTy // Set the responseType attribute's value to the given value. mResponseType = aResponseType; - // If the state is OPENED, SetCacheAsFile would have no effect here - // because the channel hasn't initialized the cache entry yet. - // SetCacheAsFile will be called from OnStartRequest. - // If the state is HEADERS_RECEIVED, however, we need to call - // it immediately because OnStartRequest is already dispatched. - if (mState & XML_HTTP_REQUEST_HEADERS_RECEIVED) { - nsCOMPtr cc(do_QueryInterface(mChannel)); - if (cc) { - cc->SetCacheAsFile(mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB || - mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB); - } - } } /* readonly attribute jsval response; */ @@ -1965,37 +1953,22 @@ nsXMLHttpRequest::StreamReaderFunc(nsIInputStream* in, bool nsXMLHttpRequest::CreateDOMFile(nsIRequest *request) { nsCOMPtr file; - nsCOMPtr cc(do_QueryInterface(request)); - if (cc) { - cc->GetCacheFile(getter_AddRefs(file)); - } else { - nsCOMPtr fc = do_QueryInterface(request); - if (fc) { - fc->GetFile(getter_AddRefs(file)); - } + nsCOMPtr fc = do_QueryInterface(request); + if (fc) { + fc->GetFile(getter_AddRefs(file)); } - bool fromFile = false; - if (file) { - nsAutoCString contentType; - mChannel->GetContentType(contentType); - nsCOMPtr cacheToken; - if (cc) { - cc->GetCacheToken(getter_AddRefs(cacheToken)); - // We need to call IsFromCache to determine whether the response is - // fully cached (i.e. whether we can skip reading the response). - cc->IsFromCache(&fromFile); - } else { - // If the response is coming from the local resource, we can skip - // reading the response unconditionally. - fromFile = true; - } - mDOMFile = - new nsDOMFileFile(file, NS_ConvertASCIItoUTF16(contentType), cacheToken); - mBlobSet = nullptr; - NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty"); - } - return fromFile; + if (!file) + return false; + + nsAutoCString contentType; + mChannel->GetContentType(contentType); + + mDOMFile = + new nsDOMFileFile(file, EmptyString(), NS_ConvertASCIItoUTF16(contentType)); + mBlobSet = nullptr; + NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty"); + return true; } NS_IMETHODIMP @@ -2127,14 +2100,6 @@ nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt) mState &= ~XML_HTTP_REQUEST_MPART_HEADERS; ChangeState(XML_HTTP_REQUEST_HEADERS_RECEIVED); - if (mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB || - mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) { - nsCOMPtr cc(do_QueryInterface(mChannel)); - if (cc) { - cc->SetCacheAsFile(true); - } - } - ResetResponse(); if (!mOverrideMimeType.IsEmpty()) { diff --git a/content/base/src/nsXMLHttpRequest.h b/content/base/src/nsXMLHttpRequest.h index a4c9fc7237e3..429d086a6ecc 100644 --- a/content/base/src/nsXMLHttpRequest.h +++ b/content/base/src/nsXMLHttpRequest.h @@ -596,8 +596,7 @@ protected: // but is also explicitly set in OnStopRequest. nsCOMPtr mResponseBlob; // Non-null only when we are able to get a os-file representation of the - // response, i.e. when loading from a file, or when the http-stream - // caches into a file or is reading from a cached file. + // response, i.e. when loading from a file. nsRefPtr mDOMFile; // We stream data to mBlobSet when response type is "blob" or "moz-blob" // and mDOMFile is null. diff --git a/dom/devicestorage/nsDeviceStorage.cpp b/dom/devicestorage/nsDeviceStorage.cpp index 70459d5dc4d1..1cf20b1ef2f6 100644 --- a/dom/devicestorage/nsDeviceStorage.cpp +++ b/dom/devicestorage/nsDeviceStorage.cpp @@ -778,7 +778,8 @@ jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile) return JSVAL_NULL; } - nsCOMPtr blob = new nsDOMFileFile(aFile->mFile, aFile->mPath); + nsCOMPtr blob = new nsDOMFileFile(aFile->mFile, aFile->mPath, + EmptyString()); return InterfaceToJsval(aWindow, blob, &NS_GET_IID(nsIDOMBlob)); } From 22979fc6000dbbac10f64a0da5692f03e1d36fdd Mon Sep 17 00:00:00 2001 From: Trevor Saunders Date: Tue, 27 Nov 2012 22:16:12 +0900 Subject: [PATCH 002/160] Bug 767756 - try implementing ISimpleDOMNode with a tear off, r=tbsaunde, f=marcoz --HG-- rename : accessible/src/msaa/nsAccessNodeWrap.cpp => accessible/src/windows/sdn/sdnAccessible.cpp rename : accessible/src/msaa/nsAccessNodeWrap.h => accessible/src/windows/sdn/sdnAccessible.h --- accessible/build/Makefile.in | 1 + accessible/src/msaa/AccessibleWrap.cpp | 9 +- accessible/src/msaa/Makefile.in | 1 + accessible/src/msaa/nsAccessNodeWrap.cpp | 400 +------------ accessible/src/msaa/nsAccessNodeWrap.h | 63 -- accessible/src/windows/Makefile.in | 1 + accessible/src/windows/sdn/Makefile.in | 35 ++ .../src/windows/sdn/sdnAccessible-inl.h | 36 ++ accessible/src/windows/sdn/sdnAccessible.cpp | 537 ++++++++++++++++++ accessible/src/windows/sdn/sdnAccessible.h | 113 ++++ 10 files changed, 737 insertions(+), 459 deletions(-) create mode 100644 accessible/src/windows/sdn/Makefile.in create mode 100644 accessible/src/windows/sdn/sdnAccessible-inl.h create mode 100644 accessible/src/windows/sdn/sdnAccessible.cpp create mode 100644 accessible/src/windows/sdn/sdnAccessible.h diff --git a/accessible/build/Makefile.in b/accessible/build/Makefile.in index 69e9dd27bd8d..7529748691d7 100644 --- a/accessible/build/Makefile.in +++ b/accessible/build/Makefile.in @@ -34,6 +34,7 @@ SHARED_LIBRARY_LIBS = \ ifeq ($(MOZ_WIDGET_TOOLKIT),windows) SHARED_LIBRARY_LIBS += \ ../src/windows/ia2/$(LIB_PREFIX)accessibility_toolkit_ia2_s.$(LIB_SUFFIX) \ + ../src/windows/sdn/$(LIB_PREFIX)accessibility_toolkit_sdn_s.$(LIB_SUFFIX) \ ../src/windows/uia/$(LIB_PREFIX)accessibility_toolkit_uia_s.$(LIB_SUFFIX) \ $(NULL) endif diff --git a/accessible/src/msaa/AccessibleWrap.cpp b/accessible/src/msaa/AccessibleWrap.cpp index ec5cee2c8eaf..66295b71ab55 100644 --- a/accessible/src/msaa/AccessibleWrap.cpp +++ b/accessible/src/msaa/AccessibleWrap.cpp @@ -17,6 +17,7 @@ #include "Relation.h" #include "Role.h" #include "RootAccessible.h" +#include "sdnAccessible.h" #include "States.h" #include "uiaRawElmProvider.h" @@ -93,6 +94,12 @@ __try { *ppv = static_cast(this); else if (IID_IAccessible2 == iid && !Compatibility::IsIA2Off()) *ppv = static_cast(this); + else if (IID_ISimpleDOMNode == iid) { + if (IsDefunct() || !HasOwnContent() && !IsDoc()) + return E_NOINTERFACE; + + *ppv = new sdnAccessible(GetNode()); + } if (NULL == *ppv) { HRESULT hr = ia2AccessibleComponent::QueryInterface(iid, ppv); @@ -113,7 +120,7 @@ __try { } if (NULL == *ppv) - return nsAccessNodeWrap::QueryInterface(iid, ppv); + return E_NOINTERFACE; (reinterpret_cast(*ppv))->AddRef(); } __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } diff --git a/accessible/src/msaa/Makefile.in b/accessible/src/msaa/Makefile.in index 21031ac707b5..c302cbf1312e 100644 --- a/accessible/src/msaa/Makefile.in +++ b/accessible/src/msaa/Makefile.in @@ -67,6 +67,7 @@ LOCAL_INCLUDES += \ -I$(srcdir)/../xpcom \ -I$(srcdir)/../xul \ -I$(srcdir)/../windows/ia2 \ + -I$(srcdir)/../windows/sdn \ -I$(srcdir)/../windows/uia \ -I$(srcdir)/../../../content/base/src \ -I$(srcdir)/../../../content/events/src \ diff --git a/accessible/src/msaa/nsAccessNodeWrap.cpp b/accessible/src/msaa/nsAccessNodeWrap.cpp index 5353a68def69..b29799a779ae 100644 --- a/accessible/src/msaa/nsAccessNodeWrap.cpp +++ b/accessible/src/msaa/nsAccessNodeWrap.cpp @@ -7,7 +7,7 @@ #include "AccessibleApplication.h" #include "ApplicationAccessibleWrap.h" -#include "ISimpleDOMNode_i.c" +#include "sdnAccessible.h" #include "Compatibility.h" #include "nsAccessibilityService.h" @@ -60,24 +60,17 @@ nsAccessNodeWrap::QueryNativeInterface(REFIID aIID, void** aInstancePtr) return static_cast(QueryInterface(aIID, aInstancePtr)); } -//----------------------------------------------------- -// IUnknown interface methods - see iunknown.h for documentation -//----------------------------------------------------- - STDMETHODIMP nsAccessNodeWrap::QueryInterface(REFIID iid, void** ppv) { *ppv = nullptr; if (IID_IUnknown == iid) { - *ppv = static_cast(this); - } else if (IID_ISimpleDOMNode == iid) { - statistics::ISimpleDOMUsed(); - *ppv = static_cast(this); + *ppv = static_cast(this); } else { - return E_NOINTERFACE; //iid not supported. + return E_NOINTERFACE; //iid not supported. } - - (reinterpret_cast(*ppv))->AddRef(); + + (reinterpret_cast(*ppv))->AddRef(); return S_OK; } @@ -161,389 +154,6 @@ nsAccessNodeWrap::QueryService(REFGUID guidService, REFIID iid, void** ppv) return E_INVALIDARG; } - -//----------------------------------------------------- -// ISimpleDOMNode methods -//----------------------------------------------------- - -STDMETHODIMP nsAccessNodeWrap::get_nodeInfo( - /* [out] */ BSTR __RPC_FAR *aNodeName, - /* [out] */ short __RPC_FAR *aNameSpaceID, - /* [out] */ BSTR __RPC_FAR *aNodeValue, - /* [out] */ unsigned int __RPC_FAR *aNumChildren, - /* [out] */ unsigned int __RPC_FAR *aUniqueID, - /* [out] */ unsigned short __RPC_FAR *aNodeType) -{ -__try{ - *aNodeName = nullptr; - *aNodeValue = nullptr; - - nsINode* node = GetNode(); - if (!node) - return E_FAIL; - - nsCOMPtr DOMNode(do_QueryInterface(node)); - - uint16_t nodeType = 0; - DOMNode->GetNodeType(&nodeType); - *aNodeType=static_cast(nodeType); - - if (*aNodeType != NODETYPE_TEXT) { - nsAutoString nodeName; - DOMNode->GetNodeName(nodeName); - *aNodeName = ::SysAllocString(nodeName.get()); - } - - nsAutoString nodeValue; - - DOMNode->GetNodeValue(nodeValue); - *aNodeValue = ::SysAllocString(nodeValue.get()); - - *aNameSpaceID = IsContent() ? - static_cast(mContent->GetNameSpaceID()) : 0; - - // This is a unique ID for every content node. The 3rd party - // accessibility application can compare this to the childID we - // return for events such as focus events, to correlate back to - // data nodes in their internal object model. - *aUniqueID = - NS_PTR_TO_INT32(UniqueID()); - - *aNumChildren = node->GetChildCount(); - -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - return S_OK; -} - - - -STDMETHODIMP nsAccessNodeWrap::get_attributes( - /* [in] */ unsigned short aMaxAttribs, - /* [length_is][size_is][out] */ BSTR __RPC_FAR *aAttribNames, - /* [length_is][size_is][out] */ short __RPC_FAR *aNameSpaceIDs, - /* [length_is][size_is][out] */ BSTR __RPC_FAR *aAttribValues, - /* [out] */ unsigned short __RPC_FAR *aNumAttribs) -{ -__try{ - *aNumAttribs = 0; - - if (!mContent || IsDocumentNode()) - return E_FAIL; - - uint32_t numAttribs = mContent->GetAttrCount(); - if (numAttribs > aMaxAttribs) - numAttribs = aMaxAttribs; - *aNumAttribs = static_cast(numAttribs); - - for (uint32_t index = 0; index < numAttribs; index++) { - aNameSpaceIDs[index] = 0; aAttribValues[index] = aAttribNames[index] = nullptr; - nsAutoString attributeValue; - - const nsAttrName* name = mContent->GetAttrNameAt(index); - aNameSpaceIDs[index] = static_cast(name->NamespaceID()); - aAttribNames[index] = ::SysAllocString(name->LocalName()->GetUTF16String()); - mContent->GetAttr(name->NamespaceID(), name->LocalName(), attributeValue); - aAttribValues[index] = ::SysAllocString(attributeValue.get()); - } -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return S_OK; -} - - -STDMETHODIMP nsAccessNodeWrap::get_attributesForNames( - /* [in] */ unsigned short aNumAttribs, - /* [length_is][size_is][in] */ BSTR __RPC_FAR *aAttribNames, - /* [length_is][size_is][in] */ short __RPC_FAR *aNameSpaceID, - /* [length_is][size_is][retval] */ BSTR __RPC_FAR *aAttribValues) -{ -__try { - if (!mContent || !IsElement()) - return E_FAIL; - - nsCOMPtr domElement(do_QueryInterface(mContent)); - nsCOMPtr nameSpaceManager = - do_GetService(NS_NAMESPACEMANAGER_CONTRACTID); - - int32_t index; - - for (index = 0; index < aNumAttribs; index++) { - aAttribValues[index] = nullptr; - if (aAttribNames[index]) { - nsAutoString attributeValue, nameSpaceURI; - nsAutoString attributeName(nsDependentString(static_cast(aAttribNames[index]))); - nsresult rv; - - if (aNameSpaceID[index]>0 && - NS_SUCCEEDED(nameSpaceManager->GetNameSpaceURI(aNameSpaceID[index], nameSpaceURI))) - rv = domElement->GetAttributeNS(nameSpaceURI, attributeName, attributeValue); - else - rv = domElement->GetAttribute(attributeName, attributeValue); - - if (NS_SUCCEEDED(rv)) - aAttribValues[index] = ::SysAllocString(attributeValue.get()); - } - } -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return S_OK; -} - -/* To do: use media type if not null */ -STDMETHODIMP nsAccessNodeWrap::get_computedStyle( - /* [in] */ unsigned short aMaxStyleProperties, - /* [in] */ boolean aUseAlternateView, - /* [length_is][size_is][out] */ BSTR __RPC_FAR *aStyleProperties, - /* [length_is][size_is][out] */ BSTR __RPC_FAR *aStyleValues, - /* [out] */ unsigned short __RPC_FAR *aNumStyleProperties) -{ -__try{ - *aNumStyleProperties = 0; - - if (!mContent || IsDocumentNode()) - return E_FAIL; - - nsCOMPtr cssDecl = - nsWinUtils::GetComputedStyleDeclaration(mContent); - NS_ENSURE_TRUE(cssDecl, E_FAIL); - - uint32_t length; - cssDecl->GetLength(&length); - - uint32_t index, realIndex; - for (index = realIndex = 0; index < length && realIndex < aMaxStyleProperties; index ++) { - nsAutoString property, value; - if (NS_SUCCEEDED(cssDecl->Item(index, property)) && property.CharAt(0) != '-') // Ignore -moz-* properties - cssDecl->GetPropertyValue(property, value); // Get property value - if (!value.IsEmpty()) { - aStyleProperties[realIndex] = ::SysAllocString(property.get()); - aStyleValues[realIndex] = ::SysAllocString(value.get()); - ++realIndex; - } - } - *aNumStyleProperties = static_cast(realIndex); -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return S_OK; -} - - -STDMETHODIMP nsAccessNodeWrap::get_computedStyleForProperties( - /* [in] */ unsigned short aNumStyleProperties, - /* [in] */ boolean aUseAlternateView, - /* [length_is][size_is][in] */ BSTR __RPC_FAR *aStyleProperties, - /* [length_is][size_is][out] */ BSTR __RPC_FAR *aStyleValues) -{ -__try { - if (!mContent || IsDocumentNode()) - return E_FAIL; - - nsCOMPtr cssDecl = - nsWinUtils::GetComputedStyleDeclaration(mContent); - NS_ENSURE_TRUE(cssDecl, E_FAIL); - - uint32_t index; - for (index = 0; index < aNumStyleProperties; index ++) { - nsAutoString value; - if (aStyleProperties[index]) - cssDecl->GetPropertyValue(nsDependentString(static_cast(aStyleProperties[index])), value); // Get property value - aStyleValues[index] = ::SysAllocString(value.get()); - } -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return S_OK; -} - -STDMETHODIMP nsAccessNodeWrap::scrollTo(/* [in] */ boolean aScrollTopLeft) -{ -__try { - uint32_t scrollType = - aScrollTopLeft ? nsIAccessibleScrollType::SCROLL_TYPE_TOP_LEFT : - nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_RIGHT; - - nsCoreUtils::ScrollTo(mDoc->PresShell(), mContent, scrollType); - return S_OK; -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return E_FAIL; -} - -ISimpleDOMNode* -nsAccessNodeWrap::MakeAccessNode(nsINode *aNode) -{ - if (!aNode) - return NULL; - - nsAccessNodeWrap *newNode = NULL; - - ISimpleDOMNode *iNode = NULL; - Accessible* acc = mDoc->GetAccessible(aNode); - if (acc) { - IAccessible *msaaAccessible = nullptr; - acc->GetNativeInterface((void**)&msaaAccessible); // addrefs - msaaAccessible->QueryInterface(IID_ISimpleDOMNode, (void**)&iNode); // addrefs - msaaAccessible->Release(); // Release IAccessible - } - else { - nsCOMPtr content(do_QueryInterface(aNode)); - if (!content) { - NS_NOTREACHED("The node is a document which is not accessible!"); - return NULL; - } - - newNode = new nsAccessNodeWrap(content, mDoc); - if (!newNode) - return NULL; - - iNode = static_cast(newNode); - iNode->AddRef(); - } - - return iNode; -} - - -STDMETHODIMP nsAccessNodeWrap::get_parentNode(ISimpleDOMNode __RPC_FAR *__RPC_FAR *aNode) -{ -__try { - nsINode* node = GetNode(); - if (!node) - return E_FAIL; - - *aNode = MakeAccessNode(node->GetParentNode()); - -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return S_OK; -} - -STDMETHODIMP nsAccessNodeWrap::get_firstChild(ISimpleDOMNode __RPC_FAR *__RPC_FAR *aNode) -{ -__try { - nsINode* node = GetNode(); - if (!node) - return E_FAIL; - - *aNode = MakeAccessNode(node->GetFirstChild()); - -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return S_OK; -} - -STDMETHODIMP nsAccessNodeWrap::get_lastChild(ISimpleDOMNode __RPC_FAR *__RPC_FAR *aNode) -{ -__try { - nsINode* node = GetNode(); - if (!node) - return E_FAIL; - - *aNode = MakeAccessNode(node->GetLastChild()); - -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return S_OK; -} - -STDMETHODIMP nsAccessNodeWrap::get_previousSibling(ISimpleDOMNode __RPC_FAR *__RPC_FAR *aNode) -{ -__try { - nsINode* node = GetNode(); - if (!node) - return E_FAIL; - - *aNode = MakeAccessNode(node->GetPreviousSibling()); - -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return S_OK; -} - -STDMETHODIMP nsAccessNodeWrap::get_nextSibling(ISimpleDOMNode __RPC_FAR *__RPC_FAR *aNode) -{ -__try { - nsINode* node = GetNode(); - if (!node) - return E_FAIL; - - *aNode = MakeAccessNode(node->GetNextSibling()); - -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return S_OK; -} - -STDMETHODIMP -nsAccessNodeWrap::get_childAt(unsigned aChildIndex, - ISimpleDOMNode __RPC_FAR *__RPC_FAR *aNode) -{ -__try { - *aNode = nullptr; - - nsINode* node = GetNode(); - if (!node) - return E_FAIL; - - *aNode = MakeAccessNode(node->GetChildAt(aChildIndex)); - -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return S_OK; -} - -STDMETHODIMP -nsAccessNodeWrap::get_innerHTML(BSTR __RPC_FAR *aInnerHTML) -{ -__try { - *aInnerHTML = nullptr; - - nsCOMPtr htmlElement = do_QueryInterface(GetNode()); - if (!htmlElement) - return E_FAIL; // Node already shut down - - nsAutoString innerHTML; - htmlElement->GetInnerHTML(innerHTML); - if (innerHTML.IsEmpty()) - return S_FALSE; - - *aInnerHTML = ::SysAllocStringLen(innerHTML.get(), innerHTML.Length()); - if (!*aInnerHTML) - return E_OUTOFMEMORY; - -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return S_OK; -} - -STDMETHODIMP -nsAccessNodeWrap::get_language(BSTR __RPC_FAR *aLanguage) -{ -__try { - *aLanguage = NULL; - - nsAutoString language; - Language(language); - if (language.IsEmpty()) - return S_FALSE; - - *aLanguage = ::SysAllocStringLen(language.get(), language.Length()); - if (!*aLanguage) - return E_OUTOFMEMORY; - -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - - return S_OK; -} - -STDMETHODIMP -nsAccessNodeWrap::get_localInterface( - /* [out] */ void __RPC_FAR *__RPC_FAR *localInterface) -{ -__try { - *localInterface = static_cast(this); - NS_ADDREF_THIS(); -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - return S_OK; -} void nsAccessNodeWrap::InitAccessibility() { diff --git a/accessible/src/msaa/nsAccessNodeWrap.h b/accessible/src/msaa/nsAccessNodeWrap.h index 2f870b1f9fdd..f84930198d1e 100644 --- a/accessible/src/msaa/nsAccessNodeWrap.h +++ b/accessible/src/msaa/nsAccessNodeWrap.h @@ -21,7 +21,6 @@ #include "nsIAccessible.h" #include "nsIAccessibleEvent.h" #include "nsIWinAccessNode.h" -#include "ISimpleDOMNode.h" #include "nsIDOMElement.h" #include "nsIContent.h" #include "nsAccessNode.h" @@ -50,7 +49,6 @@ class AccTextChangeEvent; class nsAccessNodeWrap : public nsAccessNode, public nsIWinAccessNode, - public ISimpleDOMNode, public IServiceProvider { public: @@ -70,59 +68,6 @@ public: // construction, destruction REFIID aIID, void** aInstancePtr); - // ISimpleDOMNode - virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_nodeInfo( - /* [out] */ BSTR __RPC_FAR *tagName, - /* [out] */ short __RPC_FAR *nameSpaceID, - /* [out] */ BSTR __RPC_FAR *nodeValue, - /* [out] */ unsigned int __RPC_FAR *numChildren, - /* [out] */ unsigned int __RPC_FAR *aUniqueID, - /* [out][retval] */ unsigned short __RPC_FAR *nodeType); - - virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_attributes( - /* [in] */ unsigned short maxAttribs, - /* [length_is][size_is][out] */ BSTR __RPC_FAR *attribNames, - /* [length_is][size_is][out] */ short __RPC_FAR *nameSpaceID, - /* [length_is][size_is][out] */ BSTR __RPC_FAR *attribValues, - /* [out][retval] */ unsigned short __RPC_FAR *numAttribs); - - virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_attributesForNames( - /* [in] */ unsigned short maxAttribs, - /* [length_is][size_is][in] */ BSTR __RPC_FAR *attribNames, - /* [length_is][size_is][in] */ short __RPC_FAR *nameSpaceID, - /* [length_is][size_is][retval] */ BSTR __RPC_FAR *attribValues); - - virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_computedStyle( - /* [in] */ unsigned short maxStyleProperties, - /* [in] */ boolean useAlternateView, - /* [length_is][size_is][out] */ BSTR __RPC_FAR *styleProperties, - /* [length_is][size_is][out] */ BSTR __RPC_FAR *styleValues, - /* [out][retval] */ unsigned short __RPC_FAR *numStyleProperties); - - virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_computedStyleForProperties( - /* [in] */ unsigned short numStyleProperties, - /* [in] */ boolean useAlternateView, - /* [length_is][size_is][in] */ BSTR __RPC_FAR *styleProperties, - /* [length_is][size_is][out][retval] */ BSTR __RPC_FAR *styleValues); - - virtual HRESULT STDMETHODCALLTYPE scrollTo(/* [in] */ boolean scrollTopLeft); - - virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_parentNode(ISimpleDOMNode __RPC_FAR *__RPC_FAR *node); - virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_firstChild(ISimpleDOMNode __RPC_FAR *__RPC_FAR *node); - virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_lastChild(ISimpleDOMNode __RPC_FAR *__RPC_FAR *node); - virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_previousSibling(ISimpleDOMNode __RPC_FAR *__RPC_FAR *node); - virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nextSibling(ISimpleDOMNode __RPC_FAR *__RPC_FAR *node); - virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_childAt(unsigned childIndex, - ISimpleDOMNode __RPC_FAR *__RPC_FAR *node); - - virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_innerHTML( - /* [out][retval] */ BSTR __RPC_FAR *innerHTML); - - virtual /* [local][propget] */ HRESULT STDMETHODCALLTYPE get_localInterface( - /* [retval][out] */ void __RPC_FAR *__RPC_FAR *localInterface); - - virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_language( - /* [out][retval] */ BSTR __RPC_FAR *language); static void InitAccessibility(); static void ShutdownAccessibility(); @@ -136,14 +81,6 @@ public: // construction, destruction protected: - /** - * Return ISimpleDOMNode instance for existing accessible object or - * creates new nsAccessNode instance if the accessible doesn't exist. - * - * @note ISimpleDOMNode is returned addrefed - */ - ISimpleDOMNode *MakeAccessNode(nsINode *aNode); - /** * It is used in HyperTextAccessibleWrap for IA2::newText/oldText * implementation. diff --git a/accessible/src/windows/Makefile.in b/accessible/src/windows/Makefile.in index 52e95578f38a..db4e18099bfd 100644 --- a/accessible/src/windows/Makefile.in +++ b/accessible/src/windows/Makefile.in @@ -12,6 +12,7 @@ include $(DEPTH)/config/autoconf.mk DIRS += \ ia2 \ + sdn \ uia \ $(null) diff --git a/accessible/src/windows/sdn/Makefile.in b/accessible/src/windows/sdn/Makefile.in new file mode 100644 index 000000000000..1a6116d00b1f --- /dev/null +++ b/accessible/src/windows/sdn/Makefile.in @@ -0,0 +1,35 @@ +# 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/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = accessibility +LIBRARY_NAME = accessibility_toolkit_sdn_s +EXPORT_LIBRARY = 1 +LIBXUL_LIBRARY = 1 + +CPPSRCS += \ + sdnAccessible.cpp \ + $(NULL) + +# we don't want the shared lib, but we want to force the creation of a static lib. +FORCE_STATIC_LIB = 1 + +include $(topsrcdir)/config/config.mk +include $(topsrcdir)/config/rules.mk + +LOCAL_INCLUDES += \ + -I$(srcdir) \ + -I$(srcdir)/../../base \ + -I$(srcdir)/../../generic \ + -I$(srcdir)/../../html \ + -I$(srcdir)/../../msaa \ + -I$(srcdir)/../../xpcom \ + -I$(srcdir)/../../xul \ + $(NULL) diff --git a/accessible/src/windows/sdn/sdnAccessible-inl.h b/accessible/src/windows/sdn/sdnAccessible-inl.h new file mode 100644 index 000000000000..a2401a81cdda --- /dev/null +++ b/accessible/src/windows/sdn/sdnAccessible-inl.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 mozilla_a11y_sdnAccessible_inl_h_ +#define mozilla_a11y_sdnAccessible_inl_h_ + +#include "sdnAccessible.h" + +#include "DocAccessible.h" +#include "nsAccessibilityService.h" + +namespace mozilla { +namespace a11y { + +inline DocAccessible* +sdnAccessible::GetDocument() const +{ + DocManager* docMgr = GetAccService(); + return docMgr ? + docMgr->GetDocAccessibleFromCache(mNode->OwnerDoc()) : nullptr; +} + +inline Accessible* +sdnAccessible::GetAccessible() const +{ + DocAccessible* document = GetDocument(); + return document ? document->GetAccessible(mNode) : nullptr; +} + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_sdnAccessible_inl_h_ diff --git a/accessible/src/windows/sdn/sdnAccessible.cpp b/accessible/src/windows/sdn/sdnAccessible.cpp new file mode 100644 index 000000000000..19abbe9c4e72 --- /dev/null +++ b/accessible/src/windows/sdn/sdnAccessible.cpp @@ -0,0 +1,537 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "sdnAccessible-inl.h" +#include "ISimpleDOMNode_i.c" + +#include "nsAccessNodeWrap.h" +#include "DocAccessibleWrap.h" + +#include "nsAttrName.h" +#include "nsCoreUtils.h" +#include "nsIDOMHTMLElement.h" +#include "nsIDOMCSSStyleDeclaration.h" +#include "nsServiceManagerUtils.h" +#include "nsWinUtils.h" + +#include "nsAutoPtr.h" + +#include "mozilla/dom/Element.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +STDMETHODIMP +sdnAccessible::QueryInterface(REFIID aREFIID, void** aInstancePtr) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aInstancePtr) + return E_FAIL; + *aInstancePtr = NULL; + + if (aREFIID == IID_ISimpleDOMNode) { + *aInstancePtr = this; + AddRef(); + return S_OK; + } + + AccessibleWrap* accessible = static_cast(GetAccessible()); + if (accessible) + return accessible->QueryInterface(aREFIID, aInstancePtr); + + // IUnknown* is the canonical one if and only if this accessible doesn't have + // an accessible. + if (aREFIID == IID_IUnknown) { + *aInstancePtr = this; + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_nodeInfo(BSTR __RPC_FAR* aNodeName, + short __RPC_FAR* aNameSpaceID, + BSTR __RPC_FAR* aNodeValue, + unsigned int __RPC_FAR* aNumChildren, + unsigned int __RPC_FAR* aUniqueID, + unsigned short __RPC_FAR* aNodeType) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aNodeName || !aNameSpaceID || !aNodeValue || !aNumChildren || + !aUniqueID || !aNodeType) + return E_INVALIDARG; + + *aNodeName = NULL; + *aNameSpaceID = 0; + *aNodeValue = NULL; + *aNumChildren = 0; + *aUniqueID = 0; + *aNodeType = 0; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + nsCOMPtr DOMNode(do_QueryInterface(mNode)); + + uint16_t nodeType = 0; + DOMNode->GetNodeType(&nodeType); + *aNodeType = static_cast(nodeType); + + if (*aNodeType != NODETYPE_TEXT) { + nsAutoString nodeName; + DOMNode->GetNodeName(nodeName); + *aNodeName = ::SysAllocString(nodeName.get()); + } + + nsAutoString nodeValue; + DOMNode->GetNodeValue(nodeValue); + *aNodeValue = ::SysAllocString(nodeValue.get()); + + *aNameSpaceID = mNode->IsNodeOfType(nsINode::eCONTENT) ? + static_cast(mNode->AsContent()->GetNameSpaceID()) : 0; + + // This is a unique ID for every content node. The 3rd party accessibility + // application can compare this to the childID we return for events such as + // focus events, to correlate back to data nodes in their internal object + // model. + Accessible* accessible = GetAccessible(); + *aUniqueID = - NS_PTR_TO_INT32(accessible ? accessible->UniqueID() : + static_cast(this)); + + *aNumChildren = mNode->GetChildCount(); + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_attributes(unsigned short aMaxAttribs, + BSTR __RPC_FAR* aAttribNames, + short __RPC_FAR* aNameSpaceIDs, + BSTR __RPC_FAR* aAttribValues, + unsigned short __RPC_FAR* aNumAttribs) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aAttribNames || !aNameSpaceIDs || !aAttribValues || !aNumAttribs) + return E_INVALIDARG; + + *aNumAttribs = 0; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + if (!mNode->IsElement()) + return S_FALSE; + + dom::Element* elm = mNode->AsElement(); + uint32_t numAttribs = elm->GetAttrCount(); + if (numAttribs > aMaxAttribs) + numAttribs = aMaxAttribs; + + *aNumAttribs = static_cast(numAttribs); + + for (uint32_t index = 0; index < numAttribs; index++) { + aNameSpaceIDs[index] = 0; + aAttribValues[index] = aAttribNames[index] = NULL; + nsAutoString attributeValue; + + const nsAttrName* name = elm->GetAttrNameAt(index); + aNameSpaceIDs[index] = static_cast(name->NamespaceID()); + aAttribNames[index] = ::SysAllocString(name->LocalName()->GetUTF16String()); + elm->GetAttr(name->NamespaceID(), name->LocalName(), attributeValue); + aAttribValues[index] = ::SysAllocString(attributeValue.get()); + } + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_attributesForNames(unsigned short aMaxAttribs, + BSTR __RPC_FAR* aAttribNames, + short __RPC_FAR* aNameSpaceID, + BSTR __RPC_FAR* aAttribValues) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aAttribNames || !aNameSpaceID || !aAttribValues) + return E_INVALIDARG; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + if (!mNode->IsElement()) + return S_FALSE; + + nsCOMPtr domElement(do_QueryInterface(mNode)); + nsCOMPtr nameSpaceManager = + do_GetService(NS_NAMESPACEMANAGER_CONTRACTID); + + int32_t index = 0; + for (index = 0; index < aMaxAttribs; index++) { + aAttribValues[index] = NULL; + if (aAttribNames[index]) { + nsAutoString attributeValue, nameSpaceURI; + nsAutoString attributeName(nsDependentString( + static_cast(aAttribNames[index]))); + + nsresult rv = NS_OK; + if (aNameSpaceID[index]>0 && + NS_SUCCEEDED(nameSpaceManager->GetNameSpaceURI(aNameSpaceID[index], + nameSpaceURI))) { + rv = domElement->GetAttributeNS(nameSpaceURI, attributeName, + attributeValue); + } else { + rv = domElement->GetAttribute(attributeName, attributeValue); + } + + if (NS_SUCCEEDED(rv)) + aAttribValues[index] = ::SysAllocString(attributeValue.get()); + } + } + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_computedStyle(unsigned short aMaxStyleProperties, + boolean aUseAlternateView, + BSTR __RPC_FAR* aStyleProperties, + BSTR __RPC_FAR* aStyleValues, + unsigned short __RPC_FAR* aNumStyleProperties) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aStyleProperties || aStyleValues || !aNumStyleProperties) + return E_INVALIDARG; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + *aNumStyleProperties = 0; + + if (mNode->IsNodeOfType(nsINode::eDOCUMENT)) + return S_FALSE; + + nsCOMPtr cssDecl = + nsWinUtils::GetComputedStyleDeclaration(mNode->AsContent()); + NS_ENSURE_TRUE(cssDecl, E_FAIL); + + uint32_t length = 0; + cssDecl->GetLength(&length); + + uint32_t index = 0, realIndex = 0; + for (index = realIndex = 0; index < length && realIndex < aMaxStyleProperties; + index ++) { + nsAutoString property, value; + + // Ignore -moz-* properties. + if (NS_SUCCEEDED(cssDecl->Item(index, property)) && property.CharAt(0) != '-') + cssDecl->GetPropertyValue(property, value); // Get property value + + if (!value.IsEmpty()) { + aStyleProperties[realIndex] = ::SysAllocString(property.get()); + aStyleValues[realIndex] = ::SysAllocString(value.get()); + ++realIndex; + } + } + + *aNumStyleProperties = static_cast(realIndex); + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_computedStyleForProperties(unsigned short aNumStyleProperties, + boolean aUseAlternateView, + BSTR __RPC_FAR* aStyleProperties, + BSTR __RPC_FAR* aStyleValues) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aStyleProperties || !aStyleValues) + return E_INVALIDARG; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + if (mNode->IsNodeOfType(nsINode::eDOCUMENT)) + return S_FALSE; + + nsCOMPtr cssDecl = + nsWinUtils::GetComputedStyleDeclaration(mNode->AsContent()); + NS_ENSURE_TRUE(cssDecl, E_FAIL); + + uint32_t index = 0; + for (index = 0; index < aNumStyleProperties; index++) { + nsAutoString value; + if (aStyleProperties[index]) + cssDecl->GetPropertyValue(nsDependentString(static_cast( + aStyleProperties[index])), value); // Get property value + aStyleValues[index] = ::SysAllocString(value.get()); + } + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::scrollTo(boolean aScrollTopLeft) +{ + A11Y_TRYBLOCK_BEGIN + + DocAccessible* document = GetDocument(); + if (!document) // that's IsDefunct check + return CO_E_OBJNOTCONNECTED; + + if (!mNode->IsContent()) + return S_FALSE; + + uint32_t scrollType = + aScrollTopLeft ? nsIAccessibleScrollType::SCROLL_TYPE_TOP_LEFT : + nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_RIGHT; + + nsCoreUtils::ScrollTo(document->PresShell(), mNode->AsContent(), scrollType); + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_parentNode(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aNode) + return E_INVALIDARG; + *aNode = NULL; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + nsINode* resultNode = mNode->GetParentNode(); + if (resultNode) { + *aNode = new sdnAccessible(resultNode); + (*aNode)->AddRef(); + } + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_firstChild(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aNode) + return E_INVALIDARG; + *aNode = NULL; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + nsINode* resultNode = mNode->GetFirstChild(); + if (resultNode) { + *aNode = new sdnAccessible(resultNode); + (*aNode)->AddRef(); + } + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_lastChild(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aNode) + return E_INVALIDARG; + *aNode = NULL; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + nsINode* resultNode = mNode->GetLastChild(); + if (resultNode) { + *aNode = new sdnAccessible(resultNode); + (*aNode)->AddRef(); + } + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_previousSibling(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aNode) + return E_INVALIDARG; + *aNode = NULL; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + nsINode* resultNode = mNode->GetPreviousSibling(); + if (resultNode) { + *aNode = new sdnAccessible(resultNode); + (*aNode)->AddRef(); + } + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_nextSibling(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aNode) + return E_INVALIDARG; + *aNode = NULL; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + nsINode* resultNode = mNode->GetNextSibling(); + if (resultNode) { + *aNode = new sdnAccessible(resultNode); + (*aNode)->AddRef(); + } + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_childAt(unsigned aChildIndex, + ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aNode) + return E_INVALIDARG; + *aNode = NULL; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + nsINode* resultNode = mNode->GetChildAt(aChildIndex); + if (resultNode) { + *aNode = new sdnAccessible(resultNode); + (*aNode)->AddRef(); + } + + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_innerHTML(BSTR __RPC_FAR* aInnerHTML) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aInnerHTML) + return E_INVALIDARG; + *aInnerHTML = NULL; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + nsCOMPtr htmlElement = do_QueryInterface(mNode); + if (!htmlElement) + return S_FALSE; + + nsAutoString innerHTML; + htmlElement->GetInnerHTML(innerHTML); + if (innerHTML.IsEmpty()) + return S_FALSE; + + *aInnerHTML = ::SysAllocStringLen(innerHTML.get(), innerHTML.Length()); + if (!*aInnerHTML) + return E_OUTOFMEMORY; + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_localInterface(void __RPC_FAR *__RPC_FAR* aLocalInterface) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aLocalInterface) + return E_INVALIDARG; + *aLocalInterface = NULL; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + *aLocalInterface = this; + AddRef(); + + return S_OK; + + A11Y_TRYBLOCK_END +} + +STDMETHODIMP +sdnAccessible::get_language(BSTR __RPC_FAR* aLanguage) +{ + A11Y_TRYBLOCK_BEGIN + + if (!aLanguage) + return E_INVALIDARG; + *aLanguage = NULL; + + if (IsDefunct()) + return CO_E_OBJNOTCONNECTED; + + nsAutoString language; + if (mNode->IsElement()) + nsCoreUtils::GetLanguageFor(mNode->AsElement(), nullptr, language); + if (language.IsEmpty()) { // Nothing found, so use document's language + mNode->OwnerDoc()->GetHeaderData(nsGkAtoms::headerContentLanguage, + language); + } + + if (language.IsEmpty()) + return S_FALSE; + + *aLanguage = ::SysAllocStringLen(language.get(), language.Length()); + if (!*aLanguage) + return E_OUTOFMEMORY; + + return S_OK; + + A11Y_TRYBLOCK_END +} diff --git a/accessible/src/windows/sdn/sdnAccessible.h b/accessible/src/windows/sdn/sdnAccessible.h new file mode 100644 index 000000000000..84009e44e605 --- /dev/null +++ b/accessible/src/windows/sdn/sdnAccessible.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 mozilla_a11y_sdnAccessible_h_ +#define mozilla_a11y_sdnAccessible_h_ + +#include "ISimpleDOMNode.h" +#include "AccessibleWrap.h" + +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace a11y { + +class sdnAccessible : public ISimpleDOMNode +{ +public: + sdnAccessible(nsINode* aNode) : mNode(aNode) { } + ~sdnAccessible() { } + + /** + * Retrun if the object is defunct. + */ + bool IsDefunct() const { return !GetDocument(); } + + /** + * Return a document accessible it belongs to if any. + */ + DocAccessible* GetDocument() const; + + /* + * Return associated accessible if any. + */ + Accessible* GetAccessible() const; + + //IUnknown + DECL_IUNKNOWN + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_nodeInfo( + /* [out] */ BSTR __RPC_FAR* aNodeName, + /* [out] */ short __RPC_FAR* aNameSpaceID, + /* [out] */ BSTR __RPC_FAR* aNodeValue, + /* [out] */ unsigned int __RPC_FAR* aNumChildren, + /* [out] */ unsigned int __RPC_FAR* aUniqueID, + /* [out][retval] */ unsigned short __RPC_FAR* aNodeType); + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_attributes( + /* [in] */ unsigned short aMaxAttribs, + /* [length_is][size_is][out] */ BSTR __RPC_FAR* aAttribNames, + /* [length_is][size_is][out] */ short __RPC_FAR* aNameSpaceIDs, + /* [length_is][size_is][out] */ BSTR __RPC_FAR* aAttribValues, + /* [out][retval] */ unsigned short __RPC_FAR* aNumAttribs); + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_attributesForNames( + /* [in] */ unsigned short aMaxAttribs, + /* [length_is][size_is][in] */ BSTR __RPC_FAR* aAttribNames, + /* [length_is][size_is][in] */ short __RPC_FAR* aNameSpaceID, + /* [length_is][size_is][retval] */ BSTR __RPC_FAR* aAttribValues); + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_computedStyle( + /* [in] */ unsigned short aMaxStyleProperties, + /* [in] */ boolean aUseAlternateView, + /* [length_is][size_is][out] */ BSTR __RPC_FAR* aStyleProperties, + /* [length_is][size_is][out] */ BSTR __RPC_FAR* aStyleValues, + /* [out][retval] */ unsigned short __RPC_FAR* aNumStyleProperties); + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_computedStyleForProperties( + /* [in] */ unsigned short aNumStyleProperties, + /* [in] */ boolean aUseAlternateView, + /* [length_is][size_is][in] */ BSTR __RPC_FAR* aStyleProperties, + /* [length_is][size_is][out][retval] */ BSTR __RPC_FAR* aStyleValues); + + virtual HRESULT STDMETHODCALLTYPE scrollTo(/* [in] */ boolean aScrollTopLeft); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_parentNode( + /* [out][retval] */ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_firstChild( + /* [out][retval] */ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_lastChild( + /* [out][retval] */ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_previousSibling( + /* [out][retval] */ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nextSibling( + /* [out][retval] */ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_childAt( + /* [in] */ unsigned aChildIndex, + /* [out][retval] */ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_innerHTML( + /* [out][retval] */ BSTR __RPC_FAR* aInnerHTML); + + virtual /* [local][propget] */ HRESULT STDMETHODCALLTYPE get_localInterface( + /* [retval][out] */ void __RPC_FAR *__RPC_FAR* aLocalInterface); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_language( + /* [out][retval] */ BSTR __RPC_FAR* aLanguage); + +private: + nsCOMPtr mNode; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_sdnAccessible_h_ From f18d446d45886899845ec68fc1df0184d629d8f2 Mon Sep 17 00:00:00 2001 From: Vicamo Yang Date: Tue, 27 Nov 2012 21:29:07 +0800 Subject: [PATCH 003/160] Bug 793111 - Part 1 - Add properties in nsIDOMMozMobileICCInfo to indicate if PLMN/SPN is required sr=sicking --- dom/network/interfaces/nsIDOMMobileConnection.idl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/dom/network/interfaces/nsIDOMMobileConnection.idl b/dom/network/interfaces/nsIDOMMobileConnection.idl index 79424d84dfe4..ac092b80f958 100644 --- a/dom/network/interfaces/nsIDOMMobileConnection.idl +++ b/dom/network/interfaces/nsIDOMMobileConnection.idl @@ -415,7 +415,7 @@ interface nsIDOMMozMobileCellInfo: nsISupports readonly attribute unsigned long gsmCellId; }; -[scriptable, uuid(a71c66ed-dfb0-4c33-9942-049e6f03dbed)] +[scriptable, uuid(352e7f1a-c09f-44ed-8fde-a138b09a0ea9)] interface nsIDOMMozMobileICCInfo : nsISupports { /** @@ -438,6 +438,16 @@ interface nsIDOMMozMobileICCInfo : nsISupports */ readonly attribute DOMString spn; + /** + * Network name must be a part of displayed carrier name. + */ + readonly attribute boolean isDisplayNetworkNameRequired; + + /** + * Service provider name must be a part of displayed carrier name. + */ + readonly attribute boolean isDisplaySpnRequired; + /** * Mobile Station ISDN Number (MSISDN) of the subscriber's, aka * his phone number. From f9a4e3cb6596c1221214073c650fcc0f323ad471 Mon Sep 17 00:00:00 2001 From: Vicamo Yang Date: Tue, 27 Nov 2012 21:29:10 +0800 Subject: [PATCH 004/160] Bug 793111 - Part 2 - getting PLMN list and deciding required carrier name component r=vicamo --- dom/system/gonk/RadioInterfaceLayer.js | 2 + dom/system/gonk/ril_consts.js | 11 +- dom/system/gonk/ril_worker.js | 287 ++++++++++++++++++++++++- 3 files changed, 295 insertions(+), 5 deletions(-) diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js index 2c1032336351..50714e4a0a36 100644 --- a/dom/system/gonk/RadioInterfaceLayer.js +++ b/dom/system/gonk/RadioInterfaceLayer.js @@ -1497,6 +1497,8 @@ RadioInterfaceLayer.prototype = { oldIcc.mcc != message.mcc || oldIcc.mnc != message.mnc || oldIcc.spn != message.spn || + oldIcc.isDisplayNetworkNameRequired != message.isDisplayNetworkNameRequired || + oldIcc.isDisplaySpnRequired != message.isDisplaySpnRequired || oldIcc.msisdn != message.msisdn; if (!iccInfoChanged) { return; diff --git a/dom/system/gonk/ril_consts.js b/dom/system/gonk/ril_consts.js index 63e9f2d46b48..d29a0e47250d 100644 --- a/dom/system/gonk/ril_consts.js +++ b/dom/system/gonk/ril_consts.js @@ -405,6 +405,7 @@ this.ICC_COMMAND_UPDATE_RECORD = 0xdc; this.ICC_EF_ICCID = 0x2fe2; this.ICC_EF_IMG = 0x4f20; this.ICC_EF_PBR = 0x4f30; +this.ICC_EF_PLMNsel = 0x6f30; // PLMN for SIM this.ICC_EF_SST = 0x6f38; this.ICC_EF_UST = 0x6f38; // For USIM this.ICC_EF_ADN = 0x6f3a; @@ -563,6 +564,10 @@ this.COMPREHENSIONTLV_TAG_ICON_ID_LIST = 0x1f; this.COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE = 0x2b; this.COMPREHENSIONTLV_TAG_URL = 0x31; +// Tags for Service Provider Display Information TLV +this.SPDI_TAG_SPDI = 0xa3; +this.SPDI_TAG_PLMN_LIST = 0x80; + // Device identifiers, see TS 11.14, clause 12.7 this.STK_DEVICE_ID_KEYPAD = 0x01; this.STK_DEVICE_ID_DISPLAY = 0x02; @@ -906,6 +911,8 @@ this.GECKO_ICC_SERVICES = { sim: { ADN: 2, FDN: 3, + PLMNSEL: 7, + SPN: 17, SDN: 18, DATA_DOWNLOAD_SMS_PP: 26, BDN: 31 @@ -914,7 +921,9 @@ this.GECKO_ICC_SERVICES = { FDN: 2, SDN: 4, BDN: 6, - DATA_DOWNLOAD_SMS_PP: 28 + SPN: 19, + DATA_DOWNLOAD_SMS_PP: 28, + SPDI: 51 } }; diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index f14099baba2e..6300c8856f98 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -703,6 +703,11 @@ let RIL = { this.IMEISV = null; this.SMSC = null; + /** + * ICC information that is not exposed to Gaia. + */ + this.iccInfoPrivate = {}; + /** * ICC information, such as MSISDN, IMSI, ...etc. */ @@ -1171,7 +1176,6 @@ let RIL = { this.getIMSI(); this.getMSISDN(); this.getAD(); - this.getSPN(); this.getSST(); this.getMBDN(); }, @@ -1184,6 +1188,86 @@ let RIL = { this.sendDOMMessage(this.iccInfo); }, + /** + * This will compute the spnDisplay field of the network. + * See TS 22.101 Annex A and TS 51.011 10.3.11 for details. + * + * @return True if some of iccInfo is changed in by this function. + */ + updateDisplayCondition: function updateDisplayCondition() { + // If EFspn isn't existed in SIM or it haven't been read yet, we should + // just set isDisplayNetworkNameRequired = true and + // isDisplaySpnRequired = false + let iccInfo = this.iccInfo; + let iccInfoPriv = this.iccInfoPrivate; + let iccSpn = iccInfoPriv.SPN; + let origIsDisplayNetworkNameRequired = iccInfo.isDisplayNetworkNameRequired; + let origIsDisplaySPNRequired = iccInfo.isDisplaySpnRequired; + + if (!iccSpn) { + iccInfo.isDisplayNetworkNameRequired = true; + iccInfo.isDisplaySpnRequired = false; + } else { + let operatorMnc = this.operator.mnc; + let operatorMcc = this.operator.mcc; + + // First detect if we are on HPLMN or one of the PLMN + // specified by the SIM card. + let isOnMatchingPlmn = false; + + // If the current network is the one defined as mcc/mnc + // in SIM card, it's okay. + if (iccInfo.mcc == operatorMcc && iccInfo.mnc == operatorMnc) { + isOnMatchingPlmn = true; + } + + // Test to see if operator's mcc/mnc match mcc/mnc of PLMN. + if (!isOnMatchingPlmn && iccInfoPriv.PLMN) { + let iccPlmn = iccInfoPriv.PLMN; // PLMN list + for (let plmn in iccPlmn) { + let plmnMcc = iccPlmn[plmn].mcc; + let plmnMnc = iccPlmn[plmn].mnc; + isOnMatchingPlmn = (plmnMcc == operatorMcc) && (plmnMnc == operatorMnc); + if (isOnMatchingPlmn) { + break; + } + } + } + + if (isOnMatchingPlmn) { + // The first bit of display condition tells us if we should display + // registered PLMN. + if (DEBUG) debug("updateDisplayCondition: PLMN is HPLMN or PLMN is in PLMN list"); + if (iccSpn.spnDisplayCondition & 0x01) { + iccInfo.isDisplayNetworkNameRequired = true; + iccInfo.isDisplaySpnRequired = false; + } else { + iccInfo.isDisplayNetworkNameRequired = false; + iccInfo.isDisplaySpnRequired = false; + } + } else { + // The second bit of display condition tells us if we should display + // registered PLMN. + if (DEBUG) debug("updateICCDisplayName: PLMN isn't HPLMN and PLMN isn't in PLMN list"); + if (iccSpn.spnDisplayCondition & 0x02) { + iccInfo.isDisplayNetworkNameRequired = false; + iccInfo.isDisplaySpnRequired = false; + } else { + iccInfo.isDisplayNetworkNameRequired = false; + iccInfo.isDisplaySpnRequired = true; + } + } + } + + if (DEBUG) { + debug("updateDisplayCondition: isDisplayNetworkNameRequired = " + iccInfo.isDisplayNetworkNameRequired); + debug("updateDisplayCondition: isDisplaySpnRequired = " + iccInfo.isDisplaySpnRequired); + } + + return ((origIsDisplayNetworkNameRequired !== iccInfo.isDisplayNetworkNameRequired) || + (origIsDisplaySPNRequired !== iccInfo.isDisplaySpnRequired)); + }, + /** * Get EF_phase. * This EF is only available in SIM. @@ -1345,12 +1429,20 @@ let RIL = { // Minus 1 because first is used to store display condition let len = (length / 2) - 1; let spnDisplayCondition = GsmPDUHelper.readHexOctet(); - this.iccInfo.spn = GsmPDUHelper.readAlphaIdentifier(len); + let spn = GsmPDUHelper.readAlphaIdentifier(len); Buf.readStringDelimiter(length); if (DEBUG) { - debug("SPN: spn=" + this.iccInfo.spn + ", spnDisplayCondition=" + spnDisplayCondition); + debug("SPN: spn = " + spn + + ", spnDisplayCondition = " + spnDisplayCondition); } + + this.iccInfoPrivate.SPN = { + spn : spn, + spnDisplayCondition : spnDisplayCondition, + }; + this.iccInfo.spn = spn; + this.updateDisplayCondition(); this._handleICCInfoChange(); } @@ -1368,6 +1460,104 @@ let RIL = { }); }, + /** + * Read the PLMNsel (Public Land Mobile Network) from the ICC. + * + * See ETSI TS 100.977 section 10.3.4 EF_PLMNsel + */ + getPLMNSelector: function getPLMNSelector() { + function callback() { + if (DEBUG) debug("PLMN: [PLMN Selector] Process PLMN Selector"); + + let length = Buf.readUint32(); + this.iccInfoPrivate.PLMN = this.readPLMNEntries(length/6); + Buf.readStringDelimiter(length); + + if (DEBUG) debug("PLMN: [PLMN Selector] " + JSON.stringify(this.iccInfoPrivate.PLMN)); + + if (this.updateDisplayCondition()) { + this._handleICCInfoChange(); + } + } + + // PLMN List is Service 7 in SIM, EF_PLMNsel + this.iccIO({ + command: ICC_COMMAND_GET_RESPONSE, + fileId: ICC_EF_PLMNsel, + pathId: this._getPathIdForICCRecord(ICC_EF_PLMNsel), + p1: 0, // For GET_RESPONSE, p1 = 0 + p2: 0, // For GET_RESPONSE, p2 = 0 + p3: GET_RESPONSE_EF_SIZE_BYTES, + data: null, + pin2: null, + type: EF_TYPE_TRANSPARENT, + callback: callback, + }); + }, + + /** + * Read the SPDI (Service Provider Display Information) from the ICC. + * + * See TS 131.102 section 4.2.66 + */ + getSPDI: function getSPDI() { + function callback() { + if (DEBUG) debug("PLMN: [SPDI] Process SPDI callback"); + let length = Buf.readUint32(); + let tlvTag; + let tlvLen; + let readLen = 0; + let endLoop = false; + this.iccInfoPrivate.PLMN = null; + while ((readLen < length) && !endLoop) { + tlvTag = GsmPDUHelper.readHexOctet(); + tlvLen = GsmPDUHelper.readHexOctet(); + readLen += 2; // For tag and length. + switch (tlvTag) { + case SPDI_TAG_SPDI: + // The value part itself is a TLV. + continue; + case SPDI_TAG_PLMN_LIST: + // This PLMN list is what we want. + this.iccInfoPrivate.PLMN = readPLMNEntries(tlvLen/6); + readLen += tlvLen; + endLoop = true; + break; + default: + // We don't care about its content if its tag is not SPDI nor + // PLMN_LIST. + GsmPDUHelper.readHexOctetArray(tlvLen); + readLen += tlvLen; + } + } + + // Consume unread octets. + if (length - readLen > 0) { + GsmPDUHelper.readHexOctetArray(length - readLen); + } + Buf.readStringDelimiter(length); + + if (DEBUG) debug("PLMN: [SPDI] " + JSON.stringify(this.iccInfoPrivate.PLMN)); + if (this.updateDisplayCondition()) { + this._handleICCInfoChange(); + } + } + + // PLMN List is Servive 51 in USIM, EF_SPDI + this.iccIO({ + command: ICC_COMMAND_GET_RESPONSE, + fileId: ICC_EF_SPDI, + pathId: this._getPathIdForICCRecord(ICC_EF_SPDI), + p1: 0, // For GET_RESPONSE, p1 = 0 + p2: 0, // For GET_RESPONSE, p2 = 0 + p3: GET_RESPONSE_EF_SIZE_BYTES, + data: null, + pin2: null, + type: EF_TYPE_TRANSPARENT, + callback: callback, + }); + }, + /** * Get whether specificed (U)SIM service is available. * @@ -1448,6 +1638,24 @@ let RIL = { } debug("SST: " + str); } + + // Fetch SPN and PLMN list, if some of them are available. + if (this.isICCServiceAvailable("SPN")) { + if (DEBUG) debug("SPN: SPN is available"); + this.getSPN(); + } else { + if (DEBUG) debug("SPN: SPN service is not available"); + } + + if (this.isICCServiceAvailable("PLMNSEL")) { + if (DEBUG) debug("PLMN: PLMNSEL available."); + this.getPLMNSelector(); + } else if (this.isICCServiceAvailable("SPDI")) { + if (DEBUG) debug("PLMN: SPDI available."); + this.getSPDI(); + } else { + if (DEBUG) debug("PLMN: Both PLMNSEL/SPDI not available"); + } } // ICC_EF_UST has the same value with ICC_EF_SST. @@ -1685,6 +1893,73 @@ let RIL = { return null; }, + /** + * Read the list of PLMN (Public Land Mobile Network) entries + * We cannot directly rely on readSwappedNibbleBcdToString(), + * since it will no correctly handle some corner-cases that are + * not a problem in our case (0xFF 0xFF 0xFF). + * + * @param length The number of PLMN records. + * @return An array of string corresponding to the PLMNs. + */ + readPLMNEntries: function readPLMNEntries(length) { + let plmnList = []; + // each PLMN entry has 3 byte + debug("readPLMNEntries: PLMN entries length = " + length); + let index = 0; + while (index < length) { + // Unused entries will be 0xFFFFFF, according to EF_SPDI + // specs (TS 131 102, section 4.2.66) + try { + let plmn = [GsmPDUHelper.readHexOctet(), + GsmPDUHelper.readHexOctet(), + GsmPDUHelper.readHexOctet()]; + if (DEBUG) debug("readPLMNEntries: Reading PLMN entry: [" + index + + "]: '" + plmn + "'"); + if (plmn[0] != 0xFF && + plmn[1] != 0xFF && + plmn[2] != 0xFF) { + let semiOctets = []; + for (let i = 0; i < plmn.length; i++) { + semiOctets.push((plmn[idx] & 0xF0) >> 4); + semiOctets.push(plmn[idx] & 0x0F); + } + + // According to TS 24.301, 9.9.3.12, the semi octets is arranged + // in format: + // Byte 1: MCC[2] | MCC[1] + // Byte 2: MNC[3] | MCC[3] + // Byte 3: MNC[2] | MNC[1] + // Therefore, we need to rearrage them. + let reformat = [semiOctets[1], semiOctets[0], semiOctets[3], + semiOctets[5], semiOctets[4], semiOctets[2]]; + let buf = ""; + let plmnEntry = {}; + for (let i = 0; i < reformat.length; i++) { + if (reformat[i] != 0xF) { + buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]); + } + if (i === 2) { + // 0-2: MCC + plmnEntry.mcc = parseInt(buf); + buf = ""; + } else if (i === 5) { + // 3-5: MNC + plmnEntry.mnc = parseInt(buf); + } + } + if (DEBUG) debug("readPLMNEntries: PLMN = " + plmnEntry.mcc + ", " + plmnEntry.mnc); + plmnList.push(plmnEntry); + } + } catch (e) { + if (DEBUG) debug("readPLMNEntries: PLMN entry " + index + " is invalid."); + break; + } + index ++; + } + return plmnList; + }, + /** * Get UICC Phonebook. * @@ -3082,6 +3357,7 @@ let RIL = { case ICC_EF_AD: case ICC_EF_MBDN: + case ICC_EF_PLMNsel: case ICC_EF_SPN: case ICC_EF_SST: return EF_PATH_MF_SIM + EF_PATH_DF_GSM; @@ -3094,6 +3370,7 @@ let RIL = { case ICC_EF_UST: case ICC_EF_MSISDN: case ICC_EF_SPN: + case ICC_EF_SPDI: return EF_PATH_MF_SIM + EF_PATH_ADF_USIM; default: @@ -3438,7 +3715,9 @@ let RIL = { debug("Error processing operator tuple: " + e); } } - + if (this.updateDisplayCondition()) { + this._handleICCInfoChange(); + } this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator); } }, From 6874786b5de68d3d8b98979d9a9892d313227dfe Mon Sep 17 00:00:00 2001 From: Vicamo Yang Date: Tue, 27 Nov 2012 21:29:13 +0800 Subject: [PATCH 005/160] Bug 793111 - Part 3 - Test case r=vicamo --- .../tests/marionette/test_mobile_iccinfo.js | 60 ++++++++++++++++++- dom/system/gonk/tests/test_ril_worker_icc.js | 55 +++++++++++++++++ 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/dom/network/tests/marionette/test_mobile_iccinfo.js b/dom/network/tests/marionette/test_mobile_iccinfo.js index 6e027af133c3..f9897f4a45eb 100644 --- a/dom/network/tests/marionette/test_mobile_iccinfo.js +++ b/dom/network/tests/marionette/test_mobile_iccinfo.js @@ -9,6 +9,39 @@ let connection = navigator.mozMobileConnection; ok(connection instanceof MozMobileConnection, "connection is instanceof " + connection.constructor); +let emulatorCmdPendingCount = 0; +function sendEmulatorCommand(cmd, callback) { + emulatorCmdPendingCount++; + runEmulatorCmd(cmd, function (result) { + emulatorCmdPendingCount--; + is(result[result.length - 1], "OK"); + callback(result); + }); +} + +function setEmulatorMccMnc(mcc, mnc, callback) { + let cmd = "operator set 0 Android,Android," + mcc + mnc; + sendEmulatorCommand(cmd, function (result) { + let re = new RegExp("" + mcc + mnc + "$"); + ok(result[0].match(re), "MCC/MNC should be changed."); + if (callback) { + callback(); + } + }); +} + +function waitForIccInfoChange(callback) { + connection.addEventListener("iccinfochange", function handler() { + connection.removeEventListener("iccinfochange", handler); + callback(); + }); +} + +function finalize() { + SpecialPowers.removePermission("mobileconnection", document); + finish(); +} + // The emulator's hard coded iccid value. // See it here {B2G_HOME}/external/qemu/telephony/sim_card.c#L299. is(connection.iccInfo.iccid, 89014103211118510720); @@ -22,5 +55,28 @@ is(connection.iccInfo.spn, "Android"); // See {B2G_HOME}/external/qemu/telephony/sim_card.c, in asimcard_io() is(connection.iccInfo.msisdn, "15555215554"); -SpecialPowers.removePermission("mobileconnection", document); -finish(); +// Test display condition change. +function testDisplayConditionChange(func, caseArray, oncomplete) { + (function do_call(index) { + let next = index < (caseArray.length - 1) ? do_call.bind(null, index + 1) : oncomplete; + caseArray[index].push(next); + func.apply(null, caseArray[index]); + })(0); +} + +function testSPN(mcc, mnc, expectedIsDisplayNetworkNameRequired, + expectedIsDisplaySpnRequired, callback) { + waitForIccInfoChange(function() { + is(connection.iccInfo.isDisplayNetworkNameRequired, + expectedIsDisplayNetworkNameRequired); + is(connection.iccInfo.isDisplaySpnRequired, + expectedIsDisplaySpnRequired); + window.setTimeout(callback, 0); + }); + setEmulatorMccMnc(mcc, mnc); +} + +testDisplayConditionChange(testSPN, [ + [123, 456, false, true], // Not in HPLMN. + [310, 260, true, false], // inside HPLMN. +], finalize); diff --git a/dom/system/gonk/tests/test_ril_worker_icc.js b/dom/system/gonk/tests/test_ril_worker_icc.js index 8bc3298394ef..b33708d27b51 100644 --- a/dom/system/gonk/tests/test_ril_worker_icc.js +++ b/dom/system/gonk/tests/test_ril_worker_icc.js @@ -468,3 +468,58 @@ add_test(function test_stk_proactive_command_event_list() { run_next_test(); }); + +add_test(function test_spn_display_condition() { + let RIL = newWorker({ + postRILMessage: function fakePostRILMessage(data) { + // Do nothing + }, + postMessage: function fakePostMessage(message) { + // Do nothing + } + }).RIL; + + // Test updateDisplayCondition runs before any of SIM file is ready. + do_check_eq(RIL.updateDisplayCondition(), true); + do_check_eq(RIL.iccInfo.isDisplayNetworkNameRequired, true); + do_check_eq(RIL.iccInfo.isDisplaySpnRequired, false); + + // Test with value. + function testDisplayCondition(iccDisplayCondition, + iccMcc, iccMnc, plmnMcc, plmnMnc, + expectedIsDisplayNetworkNameRequired, + expectedIsDisplaySPNRequired, + callback) { + RIL.iccInfoPrivate.SPN = { + spnDisplayCondition: iccDisplayCondition + }; + RIL.iccInfo = { + mcc: iccMcc, + mnc: iccMnc + }; + RIL.operator = { + mcc: plmnMcc, + mnc: plmnMnc + }; + + do_check_eq(RIL.updateDisplayCondition(), true); + do_check_eq(RIL.iccInfo.isDisplayNetworkNameRequired, expectedIsDisplayNetworkNameRequired); + do_check_eq(RIL.iccInfo.isDisplaySpnRequired, expectedIsDisplaySPNRequired); + do_timeout(0, callback); + }; + + function testDisplayConditions(func, caseArray, oncomplete) { + (function do_call(index) { + let next = index < (caseArray.length - 1) ? do_call.bind(null, index + 1) : oncomplete; + caseArray[index].push(next); + func.apply(null, caseArray[index]); + })(0); + } + + testDisplayConditions(testDisplayCondition, [ + [1, 123, 456, 123, 456, true, false], + [0, 123, 456, 123, 456, false, false], + [2, 123, 456, 123, 457, false, false], + [0, 123, 456, 123, 457, false, true], + ], run_next_test); +}); \ No newline at end of file From 4649ad08c10b33827deb75534a908309fd00ce9d Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Fri, 23 Nov 2012 18:33:41 -0500 Subject: [PATCH 006/160] Bug 814789 - Tie the life-time of the AudioContext to the life-time of the document owning it; r=bzbarsky --- content/media/webaudio/AudioContext.cpp | 3 ++- content/media/webaudio/AudioContext.h | 2 ++ content/media/webaudio/Makefile.in | 1 + dom/base/nsGlobalWindow.cpp | 12 ++++++++++++ dom/base/nsPIDOMWindow.h | 17 +++++++++++++++-- 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/content/media/webaudio/AudioContext.cpp b/content/media/webaudio/AudioContext.cpp index ce589933c691..b3e1ba972cd5 100644 --- a/content/media/webaudio/AudioContext.cpp +++ b/content/media/webaudio/AudioContext.cpp @@ -60,7 +60,7 @@ AudioContext::WrapObject(JSContext* aCx, JSObject* aScope, /* static */ already_AddRefed AudioContext::Constructor(nsISupports* aGlobal, ErrorResult& aRv) { - nsCOMPtr window = do_QueryInterface(aGlobal); + nsCOMPtr window = do_QueryInterface(aGlobal); if (!window) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; @@ -68,6 +68,7 @@ AudioContext::Constructor(nsISupports* aGlobal, ErrorResult& aRv) AudioContext* object = new AudioContext(window); NS_ADDREF(object); + window->AddAudioContext(object); return object; } diff --git a/content/media/webaudio/AudioContext.h b/content/media/webaudio/AudioContext.h index 293d99f4798b..5918c46ee2bc 100644 --- a/content/media/webaudio/AudioContext.h +++ b/content/media/webaudio/AudioContext.h @@ -49,6 +49,8 @@ public: return mWindow; } + void Shutdown() {} + virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope, bool* aTriedToWrap); diff --git a/content/media/webaudio/Makefile.in b/content/media/webaudio/Makefile.in index 9d55a682b2ba..9997769a32f5 100644 --- a/content/media/webaudio/Makefile.in +++ b/content/media/webaudio/Makefile.in @@ -35,6 +35,7 @@ EXPORTS_NAMESPACES := mozilla/dom EXPORTS_mozilla/dom := \ AudioBuffer.h \ AudioBufferSourceNode.h \ + AudioContext.h \ AudioDestinationNode.h \ AudioListener.h \ AudioNode.h \ diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 0c2533ae640b..556dac6140ac 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -224,6 +224,7 @@ #include "nsSandboxFlags.h" #include "TimeChangeObserver.h" #include "nsPISocketTransportService.h" +#include "mozilla/dom/AudioContext.h" #ifdef ANDROID #include @@ -1121,6 +1122,11 @@ nsGlobalWindow::FreeInnerObjects() CleanupCachedXBLHandlers(this); + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + mAudioContexts[i]->Shutdown(); + } + mAudioContexts.Clear(); + #ifdef DEBUG nsCycleCollector_DEBUG_shouldBeFreed(static_cast(this)); #endif @@ -2800,6 +2806,12 @@ nsPIDOMWindow::MaybeCreateDoc() } } +void +nsPIDOMWindow::AddAudioContext(AudioContext* aAudioContext) +{ + mAudioContexts.AppendElement(aAudioContext); +} + NS_IMETHODIMP nsGlobalWindow::GetDocument(nsIDOMDocument** aDocument) { diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h index c7ceb553a841..37f73fbb008a 100644 --- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -16,6 +16,8 @@ #include "nsIDOMEventTarget.h" #include "nsIDOMDocument.h" #include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsTArray.h" #include "nsIURI.h" #define DOM_WINDOW_DESTROYED_TOPIC "dom-window-destroyed" @@ -47,9 +49,15 @@ class nsXBLPrototypeHandler; class nsIArray; class nsPIWindowRoot; +namespace mozilla { +namespace dom { +class AudioContext; +} +} + #define NS_PIDOMWINDOW_IID \ -{ 0x7b18e421, 0x2179, 0x4e24, \ - { 0x96, 0x58, 0x26, 0x75, 0xa4, 0x37, 0xf3, 0x8f } } +{ 0xf5af1c3c, 0xebad, 0x4d00, \ + { 0xa2, 0xa4, 0x12, 0x2e, 0x27, 0x16, 0x59, 0x01 } } class nsPIDOMWindow : public nsIDOMWindowInternal { @@ -628,6 +636,8 @@ public: OpenNoNavigate(const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions, nsIDOMWindow **_retval) = 0; + void AddAudioContext(mozilla::dom::AudioContext* aAudioContext); + protected: // The nsPIDOMWindow constructor. The aOuterWindow argument should // be null if and only if the created window itself is an outer @@ -693,6 +703,9 @@ protected: // window is active nsCOMPtr mFocusedNode; + // The AudioContexts created for the current document, if any. + nsTArray > mAudioContexts; + // A unique (as long as our 64-bit counter doesn't roll over) id for // this window. uint64_t mWindowID; From 0e8d2faad2d455869c39d544916aefc371efa0ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20=C3=81vila=20de=20Esp=C3=ADndola?= Date: Tue, 27 Nov 2012 08:52:25 -0500 Subject: [PATCH 007/160] Bug 814704 - Move the creation of a js object from combinedStacks into a static function. r=vladan. --- toolkit/components/telemetry/Telemetry.cpp | 130 +++++++++++---------- 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/toolkit/components/telemetry/Telemetry.cpp b/toolkit/components/telemetry/Telemetry.cpp index 6f609360090b..dd276cffae06 100644 --- a/toolkit/components/telemetry/Telemetry.cpp +++ b/toolkit/components/telemetry/Telemetry.cpp @@ -106,6 +106,9 @@ private: std::vector mStacks; }; +static JSObject * +CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks); + size_t CombinedStacks::GetModuleCount() const { return mModules.size(); @@ -177,11 +180,8 @@ class HangReports { public: size_t SizeOfExcludingThis() const; void AddHang(const Telemetry::ProcessedStack& aStack, uint32_t aDuration); - size_t GetStackCount() const; - const CombinedStacks::Stack& GetStack(unsigned aIndex) const; uint32_t GetDuration(unsigned aIndex) const; - size_t GetModuleCount() const; - const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const; + const CombinedStacks& GetStacks() const; private: CombinedStacks mStacks; std::vector mDurations; @@ -203,14 +203,9 @@ HangReports::SizeOfExcludingThis() const { return n; } -size_t -HangReports::GetModuleCount() const { - return mStacks.GetModuleCount(); -} - -const Telemetry::ProcessedStack::Module& -HangReports::GetModule(unsigned aIndex) const { - return mStacks.GetModule(aIndex); +const CombinedStacks& +HangReports::GetStacks() const { + return mStacks; } uint32_t @@ -218,17 +213,6 @@ HangReports::GetDuration(unsigned aIndex) const { return mDurations[aIndex]; } -const CombinedStacks::Stack& -HangReports::GetStack(unsigned aIndex) const { - return mStacks.GetStack(aIndex); -} - -size_t -HangReports::GetStackCount() const { - MOZ_ASSERT(mDurations.size() == mStacks.GetStackCount()); - return mStacks.GetStackCount(); -} - class TelemetryImpl MOZ_FINAL : public nsITelemetry { NS_DECL_ISUPPORTS @@ -1222,37 +1206,67 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret) { MutexAutoLock hangReportMutex(mHangReportsMutex); - JSObject *fullReportObj = JS_NewObject(cx, nullptr, nullptr, nullptr); + const CombinedStacks& stacks = mHangReports.GetStacks(); + JSObject *fullReportObj = CreateJSStackObject(cx, stacks); if (!fullReportObj) { return NS_ERROR_FAILURE; } *ret = OBJECT_TO_JSVAL(fullReportObj); - JSObject *moduleArray = JS_NewArrayObject(cx, 0, nullptr); - if (!moduleArray) { + JSObject *durationArray = JS_NewArrayObject(cx, 0, nullptr); + if (!durationArray) { return NS_ERROR_FAILURE; } - JSBool ok = JS_DefineProperty(cx, fullReportObj, "memoryMap", - OBJECT_TO_JSVAL(moduleArray), + JSBool ok = JS_DefineProperty(cx, fullReportObj, "durations", + OBJECT_TO_JSVAL(durationArray), NULL, NULL, JSPROP_ENUMERATE); if (!ok) { return NS_ERROR_FAILURE; } - const uint32_t moduleCount = mHangReports.GetModuleCount(); + const size_t length = stacks.GetStackCount(); + for (size_t i = 0; i < length; ++i) { + jsval duration = INT_TO_JSVAL(mHangReports.GetDuration(i)); + if (!JS_SetElement(cx, durationArray, i, &duration)) { + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +static JSObject * +CreateJSStackObject(JSContext *cx, const CombinedStacks &stacks) { + JSObject *ret = JS_NewObject(cx, nullptr, nullptr, nullptr); + if (!ret) { + return nullptr; + } + + JSObject *moduleArray = JS_NewArrayObject(cx, 0, nullptr); + if (!moduleArray) { + return nullptr; + } + JSBool ok = JS_DefineProperty(cx, ret, "memoryMap", + OBJECT_TO_JSVAL(moduleArray), + NULL, NULL, JSPROP_ENUMERATE); + if (!ok) { + return nullptr; + } + + const size_t moduleCount = stacks.GetModuleCount(); for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) { // Current module const Telemetry::ProcessedStack::Module& module = - mHangReports.GetModule(moduleIndex); + stacks.GetModule(moduleIndex); JSObject *moduleInfoArray = JS_NewArrayObject(cx, 0, nullptr); if (!moduleInfoArray) { - return NS_ERROR_FAILURE; + return nullptr; } jsval val = OBJECT_TO_JSVAL(moduleInfoArray); if (!JS_SetElement(cx, moduleArray, moduleIndex, &val)) { - return NS_ERROR_FAILURE; + return nullptr; } unsigned index = 0; @@ -1260,102 +1274,90 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret) // Module name JSString *str = JS_NewStringCopyZ(cx, module.mName.c_str()); if (!str) { - return NS_ERROR_FAILURE; + return nullptr; } val = STRING_TO_JSVAL(str); if (!JS_SetElement(cx, moduleInfoArray, index++, &val)) { - return NS_ERROR_FAILURE; + return nullptr; } // "PDB Age" identifier val = INT_TO_JSVAL(module.mPdbAge); if (!JS_SetElement(cx, moduleInfoArray, index++, &val)) { - return NS_ERROR_FAILURE; + return nullptr; } // "PDB Signature" GUID str = JS_NewStringCopyZ(cx, module.mPdbSignature.c_str()); if (!str) { - return NS_ERROR_FAILURE; + return nullptr; } val = STRING_TO_JSVAL(str); if (!JS_SetElement(cx, moduleInfoArray, index++, &val)) { - return NS_ERROR_FAILURE; + return nullptr; } // Name of associated PDB file str = JS_NewStringCopyZ(cx, module.mPdbName.c_str()); if (!str) { - return NS_ERROR_FAILURE; + return nullptr; } val = STRING_TO_JSVAL(str); if (!JS_SetElement(cx, moduleInfoArray, index++, &val)) { - return NS_ERROR_FAILURE; + return nullptr; } } JSObject *reportArray = JS_NewArrayObject(cx, 0, nullptr); if (!reportArray) { - return NS_ERROR_FAILURE; + return nullptr; } - ok = JS_DefineProperty(cx, fullReportObj, "stacks", + ok = JS_DefineProperty(cx, ret, "stacks", OBJECT_TO_JSVAL(reportArray), NULL, NULL, JSPROP_ENUMERATE); if (!ok) { - return NS_ERROR_FAILURE; + return nullptr; } - JSObject *durationArray = JS_NewArrayObject(cx, 0, nullptr); - ok = JS_DefineProperty(cx, fullReportObj, "durations", - OBJECT_TO_JSVAL(durationArray), - NULL, NULL, JSPROP_ENUMERATE); - if (!ok) { - return NS_ERROR_FAILURE; - } - - for (size_t i = 0, n = mHangReports.GetStackCount(); i < n; ++i) { - jsval duration = INT_TO_JSVAL(mHangReports.GetDuration(i)); - if (!JS_SetElement(cx, durationArray, i, &duration)) { - return NS_ERROR_FAILURE; - } - + const size_t length = stacks.GetStackCount(); + for (size_t i = 0; i < length; ++i) { // Represent call stack PCs as (module index, offset) pairs. JSObject *pcArray = JS_NewArrayObject(cx, 0, nullptr); if (!pcArray) { - return NS_ERROR_FAILURE; + return nullptr; } jsval pcArrayVal = OBJECT_TO_JSVAL(pcArray); if (!JS_SetElement(cx, reportArray, i, &pcArrayVal)) { - return NS_ERROR_FAILURE; + return nullptr; } - const CombinedStacks::Stack& stack = mHangReports.GetStack(i); + const CombinedStacks::Stack& stack = stacks.GetStack(i); const uint32_t pcCount = stack.size(); for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) { const Telemetry::ProcessedStack::Frame& frame = stack[pcIndex]; JSObject *framePair = JS_NewArrayObject(cx, 0, nullptr); if (!framePair) { - return NS_ERROR_FAILURE; + return nullptr; } int modIndex = (std::numeric_limits::max() == frame.mModIndex) ? -1 : frame.mModIndex; jsval modIndexVal = INT_TO_JSVAL(modIndex); if (!JS_SetElement(cx, framePair, 0, &modIndexVal)) { - return NS_ERROR_FAILURE; + return nullptr; } jsval mOffsetVal = INT_TO_JSVAL(frame.mOffset); if (!JS_SetElement(cx, framePair, 1, &mOffsetVal)) { - return NS_ERROR_FAILURE; + return nullptr; } jsval framePairVal = OBJECT_TO_JSVAL(framePair); if (!JS_SetElement(cx, pcArray, pcIndex, &framePairVal)) { - return NS_ERROR_FAILURE; + return nullptr; } } } - return NS_OK; + return ret; } NS_IMETHODIMP From 11592816224f0afaf20919583fafdad5a79ed99e Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Fri, 12 Oct 2012 10:45:29 +0100 Subject: [PATCH 008/160] Bug 790338 - Implement Tarjan's algorithm to find the stongly connected components of the compartment graph r=billm --HG-- rename : accessible/src/windows/sdn/sdnAccessible.cpp => accessible/src/msaa/nsAccessNodeWrap.cpp rename : accessible/src/windows/sdn/sdnAccessible.h => accessible/src/msaa/nsAccessNodeWrap.h extra : rebase_source : f529480202322726c55c23e40529c81092c5b6c3 --- js/src/Makefile.in | 1 + js/src/gc/FindSCCs.cpp | 176 +++++++++++++++++++ js/src/gc/FindSCCs.h | 131 ++++++++++++++ js/src/jsapi-tests/Makefile.in | 1 + js/src/jsapi-tests/testFindSCCs.cpp | 263 ++++++++++++++++++++++++++++ 5 files changed, 572 insertions(+) create mode 100644 js/src/gc/FindSCCs.cpp create mode 100644 js/src/gc/FindSCCs.h create mode 100644 js/src/jsapi-tests/testFindSCCs.cpp diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 0fa1cd658c6b..cbca4656273c 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -146,6 +146,7 @@ CPPSRCS = \ Memory.cpp \ Statistics.cpp \ StoreBuffer.cpp \ + FindSCCs.cpp \ StringBuffer.cpp \ Unicode.cpp \ Xdr.cpp \ diff --git a/js/src/gc/FindSCCs.cpp b/js/src/gc/FindSCCs.cpp new file mode 100644 index 000000000000..505279fe2edf --- /dev/null +++ b/js/src/gc/FindSCCs.cpp @@ -0,0 +1,176 @@ +/* -*- Mode: C++; tab-width: 8; 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 "FindSCCs.h" + +#include "jsfriendapi.h" + +namespace js { +namespace gc { + +ComponentFinder::ComponentFinder(uintptr_t sl) + : clock(1), + stack(NULL), + firstComponent(NULL), + cur(NULL), + stackLimit(sl), + stackFull(false) +{ +} + +ComponentFinder::~ComponentFinder() +{ + JS_ASSERT(!stack); + JS_ASSERT(!firstComponent); +} + +void +ComponentFinder::addNode(GraphNodeBase *v) +{ + if (v->gcDiscoveryTime == Undefined) { + JS_ASSERT(v->gcLowLink == Undefined); + JS_ASSERT(!v->gcNextGraphNode); + processNode(v); + } +} + +void +ComponentFinder::checkStackFull() +{ + /* + * Check for exceeding the size of the C stack. + * + * If this happens we give up and return all vertices in one group, by + * pushing them onto the output list with lowLink set to 1. + */ + + if (!stackFull) { + int stackDummy; + if (!JS_CHECK_STACK_SIZE(stackLimit, &stackDummy)) + stackFull = true; + } + + if (stackFull) { + GraphNodeBase *w; + while (stack) { + w = stack; + stack = w->gcNextGraphNode; + + w->gcLowLink = 1; + w->gcNextGraphNode = firstComponent; + firstComponent = w; + } + } +} + +void +ComponentFinder::processNode(GraphNodeBase *v) +{ + v->gcDiscoveryTime = clock; + v->gcLowLink = clock; + ++clock; + + JS_ASSERT(!v->gcNextGraphNode); + v->gcNextGraphNode = stack; + stack = v; + + checkStackFull(); + if (stackFull) + return; + + GraphNodeBase *old = cur; + cur = v; + cur->findOutgoingEdges(*this); + cur = old; + + if (stackFull) { + JS_ASSERT(!stack); + return; + } + + if (v->gcLowLink == v->gcDiscoveryTime) { + GraphNodeBase *w; + do { + JS_ASSERT(stack); + w = stack; + stack = w->gcNextGraphNode; + + /* + * Record the elements of a component by setting all their gcLowLink + * fields to the same value. + */ + w->gcLowLink = v->gcDiscoveryTime; + + /* + * Record that the element is no longer on the stack by setting the + * discovery time to a special value that's not Undefined. + */ + w->gcDiscoveryTime = Finished; + + /* + * Prepend the component to the beginning of the output list to + * reverse the list and achieve the desired order. + */ + w->gcNextGraphNode = firstComponent; + firstComponent = w; + } while (w != v); + } +} + +void +ComponentFinder::addEdgeTo(GraphNodeBase *w) +{ + if (w->gcDiscoveryTime == Undefined) { + processNode(w); + cur->gcLowLink = Min(cur->gcLowLink, w->gcLowLink); + } else if (w->gcDiscoveryTime != Finished) { + cur->gcLowLink = Min(cur->gcLowLink, w->gcDiscoveryTime); + } +} + +GraphNodeBase * +ComponentFinder::getResultsList() +{ + JS_ASSERT(!stack); + GraphNodeBase *result = firstComponent; + firstComponent = NULL; + return result; +} + +GraphNodeBase * +ComponentFinder::removeFirstGroup(GraphNodeBase *resultsList) +{ + /* Remove the first group from resultsList and return the new list head. */ + + JS_ASSERT(resultsList); + + GraphNodeBase *v = resultsList; + unsigned lowLink = v->gcLowLink; + + GraphNodeBase *last; + do { + v->gcDiscoveryTime = Undefined; + v->gcLowLink = Undefined; + last = v; + v = v->gcNextGraphNode; + } + while (v && v->gcLowLink == lowLink); + + last->gcNextGraphNode = NULL; + return v; +} + +void +ComponentFinder::removeAllRemaining(GraphNodeBase *resultsList) +{ + for (GraphNodeBase *v = resultsList; v; v = v->gcNextGraphNode) { + v->gcDiscoveryTime = Undefined; + v->gcLowLink = Undefined; + } +} + +} /* namespace gc */ +} /* namespace js */ diff --git a/js/src/gc/FindSCCs.h b/js/src/gc/FindSCCs.h new file mode 100644 index 000000000000..571f77b737d6 --- /dev/null +++ b/js/src/gc/FindSCCs.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; 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 gc_findsccs_h___ +#define gc_findsccs_h___ + +#include "jsutil.h" + +namespace js { +namespace gc { + +class ComponentFinder; + +struct GraphNodeBase { + GraphNodeBase *gcNextGraphNode; + unsigned gcDiscoveryTime; + unsigned gcLowLink; + + GraphNodeBase() + : gcNextGraphNode(NULL), + gcDiscoveryTime(0), + gcLowLink(0) {} + + virtual ~GraphNodeBase() {} + virtual void findOutgoingEdges(ComponentFinder& finder) = 0; +}; + +template static T * +NextGraphNode(const T *current) +{ + const GraphNodeBase *node = current; + return static_cast(node->gcNextGraphNode); +} + +template void +AddGraphNode(T *&listHead, T *newFirstNode) +{ + GraphNodeBase *node = newFirstNode; + JS_ASSERT(!node->gcNextGraphNode); + node->gcNextGraphNode = listHead; + listHead = newFirstNode; +} + +template static T * +RemoveGraphNode(T *&listHead) +{ + GraphNodeBase *node = listHead; + if (!node) + return NULL; + + T *result = listHead; + listHead = static_cast(node->gcNextGraphNode); + node->gcNextGraphNode = NULL; + return result; +} + +/* + * Find the strongly connected components of a graph using Tarjan's algorithm, + * and return them in topological order. + * + * Nodes derive from GraphNodeBase and implement findGraphEdges, which calls + * finder.addEdgeTo to describe the outgoing edges from that node: + * + * struct MyGraphNode : public GraphNodeBase + * { + * void findOutgoingEdges(ComponentFinder& finder) + * { + * for edge in my_outgoing_edges: + * if is_relevant(edge): + * finder.addEdgeTo(edge.destination) + * } + * } + */ +class ComponentFinder +{ + public: + ComponentFinder(uintptr_t stackLimit); + ~ComponentFinder(); + void addNode(GraphNodeBase *v); + GraphNodeBase *getResultsList(); + + template static T * + getNextGroup(T *&resultsList) { + T *group = resultsList; + if (resultsList) + resultsList = static_cast(removeFirstGroup(resultsList)); + return group; + } + + template static T * + getAllRemaining(T *&resultsList) { + T *all = resultsList; + removeAllRemaining(resultsList); + resultsList = NULL; + return all; + } + + private: + static GraphNodeBase *removeFirstGroup(GraphNodeBase *resultsList); + static void removeAllRemaining(GraphNodeBase *resultsList); + + public: + /* Call from implementation of GraphNodeBase::findOutgoingEdges(). */ + void addEdgeTo(GraphNodeBase *w); + + private: + /* Constant used to indicate an unprocessed vertex. */ + static const unsigned Undefined = 0; + + /* Constant used to indicate an processed vertex that is no longer on the stack. */ + static const unsigned Finished = (unsigned)-1; + + void processNode(GraphNodeBase *v); + void checkStackFull(); + + private: + unsigned clock; + GraphNodeBase *stack; + GraphNodeBase *firstComponent; + GraphNodeBase *cur; + uintptr_t stackLimit; + bool stackFull; +}; + +} /* namespace gc */ +} /* namespace js */ + +#endif /* gc_findsccs_h___ */ diff --git a/js/src/jsapi-tests/Makefile.in b/js/src/jsapi-tests/Makefile.in index 51f3260f2be0..3c0a6b5c98be 100644 --- a/js/src/jsapi-tests/Makefile.in +++ b/js/src/jsapi-tests/Makefile.in @@ -67,6 +67,7 @@ CPPSRCS = \ testJSEvaluateScript.cpp \ testErrorCopying.cpp \ testEnclosingFunction.cpp \ + testFindSCCs.cpp \ $(NULL) # Disabled: an entirely unrelated test seems to cause this to fail. Moreover, diff --git a/js/src/jsapi-tests/testFindSCCs.cpp b/js/src/jsapi-tests/testFindSCCs.cpp new file mode 100644 index 000000000000..9df1e781ec6d --- /dev/null +++ b/js/src/jsapi-tests/testFindSCCs.cpp @@ -0,0 +1,263 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=99: + */ +/* 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 "tests.h" +#include +#include + +#include "../gc/FindSCCs.h" +#include "jscntxt.h" +#include "jsgc.h" + +static const unsigned MaxVertices = 10; + +using js::gc::GraphNodeBase; +using js::gc::ComponentFinder; + +struct TestNode : public GraphNodeBase +{ + unsigned index; + bool hasEdge[MaxVertices]; + + void findOutgoingEdges(ComponentFinder& finder); +}; + +static TestNode Vertex[MaxVertices]; + +void +TestNode::findOutgoingEdges(ComponentFinder& finder) +{ + for (unsigned i = 0; i < MaxVertices; ++i) { + if (hasEdge[i]) + finder.addEdgeTo(&Vertex[i]); + } +} + +BEGIN_TEST(testFindSCCs) +{ + // no vertices + + setup(0); + run(); + CHECK(end()); + + // no edges + + setup(1); + run(); + CHECK(group(0, -1)); + CHECK(end()); + + setup(3); + run(); + CHECK(group(2, -1)); + CHECK(group(1, -1)); + CHECK(group(0, -1)); + CHECK(end()); + + // linear + + setup(3); + edge(0, 1); + edge(1, 2); + run(); + CHECK(group(0, -1)); + CHECK(group(1, -1)); + CHECK(group(2, -1)); + CHECK(end()); + + // tree + + setup(3); + edge(0, 1); + edge(0, 2); + run(); + CHECK(group(0, -1)); + CHECK(group(2, -1)); + CHECK(group(1, -1)); + CHECK(end()); + + // cycles + + setup(3); + edge(0, 1); + edge(1, 2); + edge(2, 0); + run(); + CHECK(group(0, 1, 2, -1)); + CHECK(end()); + + setup(4); + edge(0, 1); + edge(1, 2); + edge(2, 1); + edge(2, 3); + run(); + CHECK(group(0, -1)); + CHECK(group(1, 2, -1)); + CHECK(group(3, -1)); + CHECK(end()); + + // remaining + + setup(2); + edge(0, 1); + run(); + CHECK(remaining(0, 1, -1)); + CHECK(end()); + + setup(2); + edge(0, 1); + run(); + CHECK(group(0, -1)); + CHECK(remaining(1, -1)); + CHECK(end()); + + setup(2); + edge(0, 1); + run(); + CHECK(group(0, -1)); + CHECK(group(1, -1)); + CHECK(remaining(-1)); + CHECK(end()); + + return true; +} + +unsigned vertex_count; +ComponentFinder *finder; +GraphNodeBase *resultsList; + +void setup(unsigned count) +{ + vertex_count = count; + for (unsigned i = 0; i < MaxVertices; ++i) { + TestNode &v = Vertex[i]; + v.gcNextGraphNode = NULL; + v.index = i; + memset(&v.hasEdge, 0, sizeof(v.hasEdge)); + } +} + +void edge(unsigned src_index, unsigned dest_index) +{ + Vertex[src_index].hasEdge[dest_index] = true; +} + +void run() +{ + finder = new ComponentFinder(rt->nativeStackLimit); + for (unsigned i = 0; i < vertex_count; ++i) + finder->addNode(&Vertex[i]); + resultsList = finder->getResultsList(); +} + +bool group(int vertex, ...) +{ + TestNode *v = (TestNode *)ComponentFinder::getNextGroup(resultsList); + + va_list ap; + va_start(ap, vertex); + while (vertex != -1) { + CHECK(v != NULL); + CHECK(v->index == vertex); + v = (TestNode *)v->gcNextGraphNode; + vertex = va_arg(ap, int); + } + va_end(ap); + + CHECK(v == NULL); + return true; +} + +bool remaining(int vertex, ...) +{ + TestNode *v = (TestNode *)ComponentFinder::getAllRemaining(resultsList); + + va_list ap; + va_start(ap, vertex); + while (vertex != -1) { + CHECK(v != NULL); + CHECK(v->index == vertex); + v = (TestNode *)v->gcNextGraphNode; + vertex = va_arg(ap, int); + } + va_end(ap); + + CHECK(v == NULL); + return true; +} + +bool end() +{ + CHECK(resultsList == NULL); + + delete finder; + finder = NULL; + return true; +} +END_TEST(testFindSCCs) + +struct TestNode2 : public GraphNodeBase +{ + TestNode2 *edge; + + TestNode2() : + edge(NULL) + { + } + + void + findOutgoingEdges(ComponentFinder& finder) + { + if (edge) + finder.addEdgeTo(edge); + } +}; + +BEGIN_TEST(testFindSCCsStackLimit) +{ + /* + * Test what happens if recusion causes the stack to become full while + * traversing the graph. + * + * The test case is a large number of vertices, almost all of which are + * arranged in a linear chain. The last few are left unlinked to exercise + * adding vertices after the stack full condition has already been detected. + * + * Such an arrangement with no cycles would normally result in one group for + * each vertex, but since the stack is exhasted in processing a single group + * is returned containing all the vertices. + */ + const unsigned max = 1000000; + + TestNode2 *vertices = new TestNode2[max](); + for (unsigned i = 0; i < (max - 10); ++i) + vertices[i].edge = &vertices[i + 1]; + + ComponentFinder finder(rt->nativeStackLimit); + for (unsigned i = 0; i < max; ++i) + finder.addNode(&vertices[i]); + + GraphNodeBase *r = finder.getResultsList(); + CHECK(r); + GraphNodeBase *v = finder.getNextGroup(r); + CHECK(v); + + unsigned count = 0; + while (v) { + ++count; + v = v->gcNextGraphNode; + } + CHECK(count == max); + CHECK(finder.getNextGroup(r) == NULL); + + delete [] vertices; + return true; +} +END_TEST(testFindSCCsStackLimit) From d881fbff055e2303cd5105907a9c070b333da107 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Fri, 12 Oct 2012 10:45:30 +0100 Subject: [PATCH 009/160] Bug 790338 - Add Is*AboutToBeFinalized functions r=billm --HG-- extra : rebase_source : 629c81772d673eb4f4c7ce45db57f6111468689c --- js/src/gc/Marking.cpp | 44 +++++++++++++++++++++++++++++++++++ js/src/gc/Marking.h | 48 ++++++++++++++++++++++++++++++++++++--- js/src/jsapi.cpp | 4 ++-- js/src/jscompartment.cpp | 17 +++++++------- js/src/jscompartment.h | 2 +- js/src/jsscope.cpp | 6 ++--- js/src/jswatchpoint.cpp | 2 +- js/src/jsweakcache.h | 8 +++---- js/src/jsweakmap.h | 6 ++--- js/src/vm/Debugger.cpp | 4 ++-- js/src/vm/ScopeObject.cpp | 6 ++--- 11 files changed, 116 insertions(+), 31 deletions(-) diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 884a3cbef89d..a66728db652a 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -221,6 +221,17 @@ IsMarked(T **thingp) return (*thingp)->isMarked(); } +template +static bool +IsAboutToBeFinalized(T **thingp) +{ + JS_ASSERT(thingp); + JS_ASSERT(*thingp); + if (!(*thingp)->compartment()->isGCSweeping()) + return false; + return !(*thingp)->isMarked(); +} + #define DeclMarkerImpl(base, type) \ void \ Mark##base(JSTracer *trc, EncapsulatedPtr *thing, const char *name) \ @@ -262,6 +273,16 @@ bool Is##base##Marked(EncapsulatedPtr *thingp) \ { \ return IsMarked(thingp->unsafeGet()); \ +} \ + \ +bool Is##base##AboutToBeFinalized(type **thingp) \ +{ \ + return IsAboutToBeFinalized(thingp); \ +} \ + \ +bool Is##base##AboutToBeFinalized(EncapsulatedPtr *thingp) \ +{ \ + return IsAboutToBeFinalized(thingp->unsafeGet()); \ } DeclMarkerImpl(BaseShape, BaseShape) @@ -498,6 +519,23 @@ gc::IsValueMarked(Value *v) return rv; } +bool +gc::IsValueAboutToBeFinalized(Value *v) +{ + JS_ASSERT(v->isMarkable()); + bool rv; + if (v->isString()) { + JSString *str = (JSString *)v->toGCThing(); + rv = IsAboutToBeFinalized(&str); + v->setString(str); + } else { + JSObject *obj = (JSObject *)v->toGCThing(); + rv = IsAboutToBeFinalized(&obj); + v->setObject(*obj); + } + return rv; +} + /*** Slot Marking ***/ void @@ -578,6 +616,12 @@ gc::IsCellMarked(Cell **thingp) return IsMarked(thingp); } +bool +gc::IsCellAboutToBeFinalized(Cell **thingp) +{ + return IsAboutToBeFinalized(thingp); +} + /*** Push Mark Stack ***/ #define JS_COMPARTMENT_ASSERT(rt, thing) \ diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index e0719d8b12ec..1a01f77580f5 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -63,6 +63,20 @@ namespace gc { * * Additionally, the functions MarkObjectRange and MarkObjectRootRange are * defined for marking arrays of object pointers. + * + * The following functions are provided to test whether a GC thing is marked + * under different circumstances: + * + * IsObjectAboutToBeFinalized(JSObject **thing); + * This function is indended to be used in code used to sweep GC things. It + * indicates whether the object will will be finialized in the current group + * of compartments being swept. Note that this will return false for any + * object not in the group of compartments currently being swept, as even if + * it is unmarked it may still become marked before it is swept. + * + * IsObjectMarked(JSObject **thing); + * This function is indended to be used in rare cases in code used to mark + * GC things. It indicates whether the object is currently marked. */ #define DeclMarker(base, type) \ void Mark##base(JSTracer *trc, EncapsulatedPtr *thing, const char *name); \ @@ -71,7 +85,9 @@ void Mark##base##Unbarriered(JSTracer *trc, type **thingp, const char *name); void Mark##base##Range(JSTracer *trc, size_t len, HeapPtr *thing, const char *name); \ void Mark##base##RootRange(JSTracer *trc, size_t len, type **thing, const char *name); \ bool Is##base##Marked(type **thingp); \ -bool Is##base##Marked(EncapsulatedPtr *thingp); +bool Is##base##Marked(EncapsulatedPtr *thingp); \ +bool Is##base##AboutToBeFinalized(type **thingp); \ +bool Is##base##AboutToBeFinalized(EncapsulatedPtr *thingp); DeclMarker(BaseShape, BaseShape) DeclMarker(BaseShape, UnownedBaseShape) @@ -164,6 +180,9 @@ MarkTypeRoot(JSTracer *trc, types::Type *v, const char *name); bool IsValueMarked(Value *v); +bool +IsValueAboutToBeFinalized(Value *v); + /*** Slot Marking ***/ void @@ -262,6 +281,9 @@ Mark(JSTracer *trc, HeapPtr *code, const char *name) bool IsCellMarked(Cell **thingp); +bool +IsCellAboutToBeFinalized(Cell **thing); + inline bool IsMarked(EncapsulatedValue *v) { @@ -282,11 +304,31 @@ IsMarked(EncapsulatedPtrScript *scriptp) return IsScriptMarked(scriptp); } +inline bool +IsAboutToBeFinalized(EncapsulatedValue *v) +{ + if (!v->isMarkable()) + return false; + return IsValueAboutToBeFinalized(v->unsafeGet()); +} + +inline bool +IsAboutToBeFinalized(EncapsulatedPtrObject *objp) +{ + return IsObjectAboutToBeFinalized(objp); +} + +inline bool +IsAboutToBeFinalized(EncapsulatedPtrScript *scriptp) +{ + return IsScriptAboutToBeFinalized(scriptp); +} + #ifdef JS_ION /* Nonsense to get WeakCache to work with new Marking semantics. */ inline bool -IsMarked(const js::ion::VMFunction **vmfunc) +IsAboutToBeFinalized(const js::ion::VMFunction **vmfunc) { /* * Preserves entries in the WeakCache @@ -296,7 +338,7 @@ IsMarked(const js::ion::VMFunction **vmfunc) } inline bool -IsMarked(ReadBarriered code) +IsAboutToBeFinalized(ReadBarriered code) { return IsIonCodeMarked(code.unsafeGet()); } diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 8b1ba9a63b32..d62595ca28db 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -2949,9 +2949,9 @@ JS_PUBLIC_API(JSBool) JS_IsAboutToBeFinalized(void *thing) { gc::Cell *t = static_cast(thing); - bool isMarked = IsCellMarked(&t); + bool isDying = IsCellAboutToBeFinalized(&t); JS_ASSERT(t == thing); - return !isMarked; + return isDying; } JS_PUBLIC_API(void) diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index df6bde2423fa..0eef94f4acad 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -598,10 +598,9 @@ JSCompartment::sweep(FreeOp *fop, bool releaseTypes) sweepInitialShapeTable(); sweepNewTypeObjectTable(newTypeObjects); sweepNewTypeObjectTable(lazyTypeObjects); - sweepBreakpoints(fop); - if (global_ && !IsObjectMarked(&global_)) + if (global_ && IsObjectAboutToBeFinalized(&global_)) global_ = NULL; #ifdef JS_ION @@ -693,11 +692,11 @@ JSCompartment::sweepCrossCompartmentWrappers() /* Remove dead wrappers from the table. */ for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { CrossCompartmentKey key = e.front().key; - bool keyMarked = IsCellMarked(&key.wrapped); - bool valMarked = IsValueMarked(e.front().value.unsafeGet()); - bool dbgMarked = !key.debugger || IsObjectMarked(&key.debugger); - JS_ASSERT_IF(!keyMarked && valMarked, key.kind == CrossCompartmentKey::StringWrapper); - if (!keyMarked || !valMarked || !dbgMarked) + bool keyDying = IsCellAboutToBeFinalized(&key.wrapped); + bool valDying = IsValueAboutToBeFinalized(e.front().value.unsafeGet()); + bool dbgDying = key.debugger && IsObjectAboutToBeFinalized(&key.debugger); + JS_ASSERT_IF(keyDying && !valDying, key.kind == CrossCompartmentKey::StringWrapper); + if (keyDying || valDying || dbgDying) e.removeFront(); else if (key.wrapped != e.front().key.wrapped || key.debugger != e.front().key.debugger) e.rekeyFront(key); @@ -895,7 +894,7 @@ JSCompartment::sweepBreakpoints(FreeOp *fop) JSScript *script = i.get(); if (!script->hasAnyBreakpointsOrStepMode()) continue; - bool scriptGone = !IsScriptMarked(&script); + bool scriptGone = IsScriptAboutToBeFinalized(&script); JS_ASSERT(script == i.get()); for (unsigned i = 0; i < script->length; i++) { BreakpointSite *site = script->getBreakpointSite(script->code + i); @@ -906,7 +905,7 @@ JSCompartment::sweepBreakpoints(FreeOp *fop) Breakpoint *nextbp; for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) { nextbp = bp->nextInSite(); - if (scriptGone || !IsObjectMarked(&bp->debugger->toJSObjectRef())) + if (scriptGone || IsObjectAboutToBeFinalized(&bp->debugger->toJSObjectRef())) bp->destroy(fop); } } diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 0fec7d8664bb..a01f3fa80e32 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -452,7 +452,7 @@ struct JSCompartment js::ScriptCountsMap *scriptCountsMap; js::DebugScriptMap *debugScriptMap; - + #ifdef JS_ION private: js::ion::IonCompartment *ionCompartment_; diff --git a/js/src/jsscope.cpp b/js/src/jsscope.cpp index ef07b6f0c6d8..34c6788cd203 100644 --- a/js/src/jsscope.cpp +++ b/js/src/jsscope.cpp @@ -1149,7 +1149,7 @@ JSCompartment::sweepBaseShapeTable() if (baseShapes.initialized()) { for (BaseShapeSet::Enum e(baseShapes); !e.empty(); e.popFront()) { UnownedBaseShape *base = e.front(); - if (!base->isMarked()) + if (IsBaseShapeAboutToBeFinalized(&base)) e.removeFront(); } } @@ -1306,12 +1306,12 @@ JSCompartment::sweepInitialShapeTable() const InitialShapeEntry &entry = e.front(); Shape *shape = entry.shape; JSObject *proto = entry.proto.raw(); - if (!IsShapeMarked(&shape) || (entry.proto.isObject() && !IsObjectMarked(&proto))) { + if (IsShapeAboutToBeFinalized(&shape) || (entry.proto.isObject() && IsObjectAboutToBeFinalized(&proto))) { e.removeFront(); } else { #ifdef DEBUG DebugOnly parent = shape->getObjectParent(); - JS_ASSERT(!parent || IsObjectMarked(&parent)); + JS_ASSERT(!parent || !IsObjectAboutToBeFinalized(&parent)); JS_ASSERT(parent == shape->getObjectParent()); #endif if (shape != entry.shape || proto != entry.proto.raw()) { diff --git a/js/src/jswatchpoint.cpp b/js/src/jswatchpoint.cpp index 526e058b3c89..2ed581f0ad15 100644 --- a/js/src/jswatchpoint.cpp +++ b/js/src/jswatchpoint.cpp @@ -220,7 +220,7 @@ WatchpointMap::sweep() for (Map::Enum e(map); !e.empty(); e.popFront()) { Map::Entry &entry = e.front(); RelocatablePtrObject obj(entry.key.object); - if (!IsObjectMarked(&obj)) { + if (IsObjectAboutToBeFinalized(&obj)) { JS_ASSERT(!entry.value.held); e.removeFront(); } else if (obj != entry.key.object) { diff --git a/js/src/jsweakcache.h b/js/src/jsweakcache.h index 1fc34ddd1346..409d2f4acdc9 100644 --- a/js/src/jsweakcache.h +++ b/js/src/jsweakcache.h @@ -42,9 +42,9 @@ class WeakCache : public HashMap { // Checking IsMarked() may update the location of the Key (or Value). // Pass in a stack local, then manually update the backing heap store. Key k(e.front().key); - bool isKeyMarked = gc::IsMarked(&k); + bool isKeyDying = gc::IsAboutToBeFinalized(&k); - if (!isKeyMarked || !gc::IsMarked(e.front().value)) { + if (isKeyDying || gc::IsAboutToBeFinalized(e.front().value)) { e.removeFront(); } else { // Potentially update the location of the Key. @@ -60,8 +60,8 @@ class WeakCache : public HashMap { for (Range r = Base::all(); !r.empty(); r.popFront()) { Key k(r.front().key); - JS_ASSERT(gc::IsMarked(&k)); - JS_ASSERT(gc::IsMarked(r.front().value)); + JS_ASSERT(!gc::IsAboutToBeFinalized(&k)); + JS_ASSERT(!gc::IsAboutToBeFinalized(r.front().value)); // Assert that IsMarked() did not perform relocation. JS_ASSERT(k == r.front().key); diff --git a/js/src/jsweakmap.h b/js/src/jsweakmap.h index 121f556950b5..4273ebc562da 100644 --- a/js/src/jsweakmap.h +++ b/js/src/jsweakmap.h @@ -180,7 +180,7 @@ class WeakMap : public HashMap, publ /* Remove all entries whose keys remain unmarked. */ for (Enum e(*this); !e.empty(); e.popFront()) { Key k(e.front().key); - if (!gc::IsMarked(&k)) + if (gc::IsAboutToBeFinalized(&k)) e.removeFront(); } @@ -192,8 +192,8 @@ class WeakMap : public HashMap, publ for (Range r = Base::all(); !r.empty(); r.popFront()) { Key k(r.front().key); Value v(r.front().value); - JS_ASSERT(gc::IsMarked(&k)); - JS_ASSERT(gc::IsMarked(&v)); + JS_ASSERT(!gc::IsAboutToBeFinalized(&k)); + JS_ASSERT(!gc::IsAboutToBeFinalized(&v)); JS_ASSERT(k == r.front().key); JS_ASSERT(v == r.front().value); } diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index cfd98a291a52..a5ce9496b37a 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -1528,7 +1528,7 @@ Debugger::sweepAll(FreeOp *fop) for (JSCList *p = &rt->debuggerList; (p = JS_NEXT_LINK(p)) != &rt->debuggerList;) { Debugger *dbg = Debugger::fromLinks(p); - if (!IsObjectMarked(&dbg->object)) { + if (IsObjectAboutToBeFinalized(&dbg->object)) { /* * dbg is being GC'd. Detach it from its debuggees. The debuggee * might be GC'd too. Since detaching requires access to both @@ -1544,7 +1544,7 @@ Debugger::sweepAll(FreeOp *fop) GlobalObjectSet &debuggees = (*c)->getDebuggees(); for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) { GlobalObject *global = e.front(); - if (!IsObjectMarked(&global)) + if (IsObjectAboutToBeFinalized(&global)) detachAllDebuggersFromGlobal(fop, global, &e); else if (global != e.front()) e.rekeyFront(global); diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 3a7ec7b3de61..7e08228fa0c7 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -1559,7 +1559,7 @@ DebugScopes::sweep() * creating an uncollectable cycle with suspended generator frames. */ for (MissingScopeMap::Enum e(missingScopes); !e.empty(); e.popFront()) { - if (!IsObjectMarked(e.front().value.unsafeGet())) + if (IsObjectAboutToBeFinalized(e.front().value.unsafeGet())) e.removeFront(); } @@ -1571,7 +1571,7 @@ DebugScopes::sweep() * Scopes can be finalized when a debugger-synthesized ScopeObject is * no longer reachable via its DebugScopeObject. */ - if (!IsObjectMarked(&scope)) { + if (IsObjectAboutToBeFinalized(&scope)) { e.removeFront(); continue; } @@ -1583,7 +1583,7 @@ DebugScopes::sweep() */ if (JSGenerator *gen = fp->maybeSuspendedGenerator(rt)) { JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN); - if (!IsObjectMarked(&gen->obj)) { + if (IsObjectAboutToBeFinalized(&gen->obj)) { e.removeFront(); continue; } From bfe6c130fb31a8a158172c81e1d5e1d05cf89630 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Fri, 12 Oct 2012 10:45:30 +0100 Subject: [PATCH 010/160] Bug 790338 - Split up xpconnect finalization callback r=billm --HG-- extra : rebase_source : 2b70376622af8104d829c23b0014cbd7d8d73a87 --- js/src/jsapi-tests/testIntern.cpp | 2 +- js/src/jsapi.h | 21 +++++++++++++++++++-- js/src/jsgc.cpp | 10 ++++++++-- js/xpconnect/src/XPCJSRuntime.cpp | 31 +++++++++++++++++++++++++------ 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/js/src/jsapi-tests/testIntern.cpp b/js/src/jsapi-tests/testIntern.cpp index ba1b4c58808e..d8177767669c 100644 --- a/js/src/jsapi-tests/testIntern.cpp +++ b/js/src/jsapi-tests/testIntern.cpp @@ -30,7 +30,7 @@ struct StringWrapper void FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, JSBool isCompartmentGC) { - if (status == JSFINALIZE_START) + if (status == JSFINALIZE_GROUP_START) sw.strOk = !JS_IsAboutToBeFinalized(sw.str); } diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 5a2a97946df7..fc61b92fc1d0 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1862,8 +1862,25 @@ typedef void (* JSGCCallback)(JSRuntime *rt, JSGCStatus status); typedef enum JSFinalizeStatus { - JSFINALIZE_START, - JSFINALIZE_END + /* + * Called when preparing to sweep a group of compartments, before anything + * has been swept. The collector will not yield to the mutator before + * calling the callback with JSFINALIZE_GROUP_END status. + */ + JSFINALIZE_GROUP_START, + + /* + * Called when preparing to sweep a group of compartments. Weak references + * to unmarked things have been removed and things that are not swept + * incrementally have been finalized at this point. The collector may yield + * to the mutator after this point. + */ + JSFINALIZE_GROUP_END, + + /* + * Called at the end of collection when everything has been swept. + */ + JSFINALIZE_COLLECTION_END } JSFinalizeStatus; typedef void diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index d26e6e3b7185..5197a0cc4471 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3830,7 +3830,7 @@ BeginSweepPhase(JSRuntime *rt) { gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_FINALIZE_START); if (rt->gcFinalizeCallback) - rt->gcFinalizeCallback(&fop, JSFINALIZE_START, !isFull); + rt->gcFinalizeCallback(&fop, JSFINALIZE_GROUP_START, !isFull); } /* Finalize unreachable (key,value) pairs in all weak maps. */ @@ -3910,7 +3910,7 @@ BeginSweepPhase(JSRuntime *rt) { gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_FINALIZE_END); if (rt->gcFinalizeCallback) - rt->gcFinalizeCallback(&fop, JSFINALIZE_END, !isFull); + rt->gcFinalizeCallback(&fop, JSFINALIZE_GROUP_END, !isFull); } } @@ -4026,6 +4026,12 @@ EndSweepPhase(JSRuntime *rt, JSGCInvocationKind gckind, bool lastGC) } } + { + gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_FINALIZE_END); + if (rt->gcFinalizeCallback) + rt->gcFinalizeCallback(&fop, JSFINALIZE_COLLECTION_END, !rt->gcIsFull); + } + /* * Reset the list of arenas marked as being allocated during sweep phase. */ diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 9a0fd2ccc610..1bb006b091af 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -732,7 +732,7 @@ XPCJSRuntime::FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, JSBool is return; switch (status) { - case JSFINALIZE_START: + case JSFINALIZE_GROUP_START: { NS_ASSERTION(!self->mDoingFinalization, "bad state"); @@ -760,7 +760,7 @@ XPCJSRuntime::FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, JSBool is self->mDoingFinalization = true; break; } - case JSFINALIZE_END: + case JSFINALIZE_GROUP_END: { NS_ASSERTION(self->mDoingFinalization, "bad state"); self->mDoingFinalization = false; @@ -769,6 +769,29 @@ XPCJSRuntime::FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, JSBool is // to be dead. DoDeferredRelease(self->mWrappedJSToReleaseArray); + // Sweep scopes needing cleanup + XPCWrappedNativeScope::FinishedFinalizationPhaseOfGC(); + + // mThreadRunningGC indicates that GC is running. + // Clear it and notify waiters. + { // scoped lock + XPCAutoLock lock(self->GetMapLock()); + NS_ASSERTION(self->mThreadRunningGC == PR_GetCurrentThread(), "bad state"); + self->mThreadRunningGC = nullptr; + xpc_NotifyAll(self->GetMapLock()); + } + + break; + } + case JSFINALIZE_COLLECTION_END: + { + // mThreadRunningGC indicates that GC is running + { // scoped lock + XPCAutoLock lock(self->GetMapLock()); + NS_ASSERTION(!self->mThreadRunningGC, "bad state"); + self->mThreadRunningGC = PR_GetCurrentThread(); + } + #ifdef XPC_REPORT_NATIVE_INTERFACE_AND_SET_FLUSHING printf("--------------------------------------------------------------\n"); int setsBefore = (int) self->mNativeSetMap->Count(); @@ -869,9 +892,6 @@ XPCJSRuntime::FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, JSBool is printf("--------------------------------------------------------------\n"); #endif - // Sweep scopes needing cleanup - XPCWrappedNativeScope::FinishedFinalizationPhaseOfGC(); - // Now we are going to recycle any unused WrappedNativeTearoffs. // We do this by iterating all the live callcontexts // and marking the tearoffs in use. And then we @@ -925,7 +945,6 @@ XPCJSRuntime::FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, JSBool is self->mDyingWrappedNativeProtoMap-> Enumerate(DyingProtoKiller, nullptr); - // mThreadRunningGC indicates that GC is running. // Clear it and notify waiters. { // scoped lock From 2b8001276607d35c7467954316e7adee9b2528ee Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Fri, 16 Nov 2012 15:34:22 +0000 Subject: [PATCH 011/160] Bug 790338 - Sweep compartments in groups r=billm --HG-- extra : rebase_source : 7831452ee66ae9e10d83e77279e68b463748691c --- js/src/gc/Marking.cpp | 15 +- js/src/jsapi.cpp | 6 +- js/src/jsatom.cpp | 6 +- js/src/jscntxt.h | 12 +- js/src/jscompartment.cpp | 8 +- js/src/jscompartment.h | 21 +- js/src/jsgc.cpp | 605 +++++++++++++++++--------------------- js/src/jsgc.h | 14 +- js/src/jsgcinlines.h | 33 ++- js/src/jsinfer.cpp | 56 +++- js/src/jspropertytree.cpp | 25 +- js/src/jstypedarray.cpp | 50 +++- js/src/jstypedarray.h | 2 + js/src/jswatchpoint.cpp | 2 +- js/src/vm/Debugger.cpp | 4 +- 15 files changed, 466 insertions(+), 393 deletions(-) diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index a66728db652a..1b9a559c09d1 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -133,7 +133,7 @@ MarkInternal(JSTracer *trc, T **thingp) * GC. */ if (!trc->callback) { - if (thing->compartment()->isCollecting()) { + if (thing->compartment()->isGCMarking()) { PushMarkStack(static_cast(trc), thing); thing->compartment()->maybeAlive = true; } @@ -216,7 +216,8 @@ IsMarked(T **thingp) { JS_ASSERT(thingp); JS_ASSERT(*thingp); - if (!(*thingp)->compartment()->isCollecting()) + JSCompartment *c = (*thingp)->compartment(); + if (!c->isGCMarking() && !c->isGCSweeping()) return true; return (*thingp)->isMarked(); } @@ -567,7 +568,7 @@ gc::MarkObjectSlots(JSTracer *trc, JSObject *obj, uint32_t start, uint32_t nslot void gc::MarkCrossCompartmentObjectUnbarriered(JSTracer *trc, JSObject **obj, const char *name) { - if (IS_GC_MARKING_TRACER(trc) && !(*obj)->compartment()->isCollecting()) + if (IS_GC_MARKING_TRACER(trc) && !(*obj)->compartment()->isGCMarking()) return; MarkObjectUnbarriered(trc, obj, name); @@ -576,7 +577,7 @@ gc::MarkCrossCompartmentObjectUnbarriered(JSTracer *trc, JSObject **obj, const c void gc::MarkCrossCompartmentScriptUnbarriered(JSTracer *trc, JSScript **script, const char *name) { - if (IS_GC_MARKING_TRACER(trc) && !(*script)->compartment()->isCollecting()) + if (IS_GC_MARKING_TRACER(trc) && !(*script)->compartment()->isGCMarking()) return; MarkScriptUnbarriered(trc, script, name); @@ -587,7 +588,7 @@ gc::MarkCrossCompartmentSlot(JSTracer *trc, HeapSlot *s, const char *name) { if (s->isMarkable()) { Cell *cell = (Cell *)s->toGCThing(); - if (IS_GC_MARKING_TRACER(trc) && !cell->compartment()->isCollecting()) + if (IS_GC_MARKING_TRACER(trc) && !cell->compartment()->isGCMarking()) return; MarkSlot(trc, s, name); @@ -625,10 +626,10 @@ gc::IsCellAboutToBeFinalized(Cell **thingp) /*** Push Mark Stack ***/ #define JS_COMPARTMENT_ASSERT(rt, thing) \ - JS_ASSERT((thing)->compartment()->isCollecting()) + JS_ASSERT((thing)->compartment()->isGCMarking()) #define JS_COMPARTMENT_ASSERT_STR(rt, thing) \ - JS_ASSERT((thing)->compartment()->isCollecting() || \ + JS_ASSERT((thing)->compartment()->isGCMarking() || \ (thing)->compartment() == (rt)->atomsCompartment); #if JS_HAS_XML_SUPPORT diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index d62595ca28db..48c484463619 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -798,9 +798,13 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads) gcIncrementalState(gc::NO_INCREMENTAL), gcLastMarkSlice(false), gcSweepOnBackgroundThread(false), + gcFoundBlackGrayEdges(false), gcSweepingCompartments(NULL), + gcCompartmentGroupIndex(0), + gcRemainingCompartmentGroups(NULL), + gcCompartmentGroup(NULL), gcSweepPhase(0), - gcSweepCompartmentIndex(0), + gcSweepCompartment(NULL), gcSweepKindIndex(0), gcArenasAllocatedDuringSweep(NULL), gcInterFrameGC(0), diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index d12c3b8016b5..d83588cae437 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -205,12 +205,12 @@ js::SweepAtoms(JSRuntime *rt) for (AtomSet::Enum e(rt->atoms); !e.empty(); e.popFront()) { AtomStateEntry entry = e.front(); JSAtom *atom = entry.asPtr(); - bool isMarked = IsStringMarked(&atom); + bool isDying = IsStringAboutToBeFinalized(&atom); /* Pinned or interned key cannot be finalized. */ - JS_ASSERT_IF(entry.isTagged(), isMarked); + JS_ASSERT_IF(entry.isTagged(), !isDying); - if (!isMarked) + if (isDying) e.removeFront(); } } diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 603b9d3b7bf9..e70b2d82399a 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -701,14 +701,22 @@ struct JSRuntime : js::RuntimeFriendFields /* Whether any sweeping will take place in the separate GC helper thread. */ bool gcSweepOnBackgroundThread; - /* List head of compartments being swept. */ + /* Whether any black->gray edges were found during marking. */ + bool gcFoundBlackGrayEdges; + + /* List head of compartments to be swept in the background. */ JSCompartment *gcSweepingCompartments; + /* Index of current compartment group (for stats). */ + unsigned gcCompartmentGroupIndex; + /* * Incremental sweep state. */ + JSCompartment *gcRemainingCompartmentGroups; + JSCompartment *gcCompartmentGroup; int gcSweepPhase; - ptrdiff_t gcSweepCompartmentIndex; + JSCompartment *gcSweepCompartment; int gcSweepKindIndex; /* diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 0eef94f4acad..55f054facc54 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -58,7 +58,6 @@ JSCompartment::JSCompartment(JSRuntime *rt) gcBytes(0), gcTriggerBytes(0), gcHeapGrowthFactor(3.0), - gcNextCompartment(NULL), hold(false), isSystemCompartment(false), lastCodeRelease(0), @@ -695,11 +694,12 @@ JSCompartment::sweepCrossCompartmentWrappers() bool keyDying = IsCellAboutToBeFinalized(&key.wrapped); bool valDying = IsValueAboutToBeFinalized(e.front().value.unsafeGet()); bool dbgDying = key.debugger && IsObjectAboutToBeFinalized(&key.debugger); - JS_ASSERT_IF(keyDying && !valDying, key.kind == CrossCompartmentKey::StringWrapper); - if (keyDying || valDying || dbgDying) + if (keyDying || valDying || dbgDying) { + JS_ASSERT(key.kind != CrossCompartmentKey::StringWrapper); e.removeFront(); - else if (key.wrapped != e.front().key.wrapped || key.debugger != e.front().key.debugger) + } else if (key.wrapped != e.front().key.wrapped || key.debugger != e.front().key.debugger) { e.rekeyFront(key); + } } } diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index a01f3fa80e32..559483e81d88 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -18,6 +18,7 @@ #include "jsscope.h" #include "gc/StoreBuffer.h" +#include "gc/FindSCCs.h" #include "vm/GlobalObject.h" #include "vm/RegExpObject.h" @@ -116,7 +117,7 @@ namespace js { class AutoDebugModeGC; } -struct JSCompartment +struct JSCompartment : public js::gc::GraphNodeBase { JSRuntime *rt; JSPrincipals *principals; @@ -192,7 +193,8 @@ struct JSCompartment enum CompartmentGCState { NoGC, Mark, - Sweep + Sweep, + Finished }; private: @@ -202,11 +204,10 @@ struct JSCompartment public: bool isCollecting() const { - if (rt->isHeapCollecting()) { + if (rt->isHeapCollecting()) return gcState != NoGC; - } else { + else return needsBarrier(); - } } bool isPreservingCode() const { @@ -248,7 +249,10 @@ struct JSCompartment } bool isGCMarking() { - return gcState == Mark; + if (rt->isHeapCollecting()) + return gcState == Mark; + else + return needsBarrier(); } bool isGCSweeping() { @@ -259,7 +263,6 @@ struct JSCompartment size_t gcTriggerBytes; size_t gcMaxMallocBytes; double gcHeapGrowthFactor; - JSCompartment *gcNextCompartment; bool hold; bool isSystemCompartment; @@ -335,7 +338,7 @@ struct JSCompartment size_t gcTriggerMallocAndFreeBytes; /* During GC, stores the index of this compartment in rt->compartments. */ - unsigned index; + unsigned gcIndex; private: /* @@ -376,6 +379,8 @@ struct JSCompartment void sweepCrossCompartmentWrappers(); void purge(); + virtual void findOutgoingEdges(js::gc::ComponentFinder& finder); + void setGCLastBytes(size_t lastBytes, size_t lastMallocBytes, js::JSGCInvocationKind gckind); void reduceGCTriggerBytes(size_t amount); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 5197a0cc4471..e0b9cd4cb114 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -92,6 +92,8 @@ #include "vm/ScopeObject-inl.h" #include "vm/String-inl.h" +#include "gc/FindSCCs.h" + #ifdef MOZ_VALGRIND # define JS_VALGRIND #endif @@ -1346,7 +1348,7 @@ js_AddRootRT(JSRuntime *rt, jsval *vp, const char *name) * or ModifyBusyCount in workers). We need a read barrier to cover these * cases. */ - if (rt->gcIncrementalState == MARK) + if (rt->gcIncrementalState != NO_INCREMENTAL) IncrementalValueBarrier(*vp); return !!rt->gcRootsHash.put((void *)vp, @@ -1362,7 +1364,7 @@ js_AddGCThingRootRT(JSRuntime *rt, void **rp, const char *name) * or ModifyBusyCount in workers). We need a read barrier to cover these * cases. */ - if (rt->gcIncrementalState == MARK) + if (rt->gcIncrementalState != NO_INCREMENTAL) IncrementalReferenceBarrier(*rp); return !!rt->gcRootsHash.put((void *)rp, @@ -1814,7 +1816,7 @@ js_LockGCThingRT(JSRuntime *rt, void *thing) * or ModifyBusyCount in workers). We need a read barrier to cover these * cases. */ - if (rt->gcIncrementalState == MARK) + if (rt->gcIncrementalState != NO_INCREMENTAL) IncrementalReferenceBarrier(thing); if (GCLocks::Ptr p = rt->gcLocksHash.lookupWithDefault(thing, 0)) { @@ -2817,7 +2819,7 @@ SweepBackgroundThings(JSRuntime* rt, bool onBackgroundThread) */ FreeOp fop(rt, false); for (int phase = 0 ; phase < BackgroundPhaseCount ; ++phase) { - for (JSCompartment *c = rt->gcSweepingCompartments; c; c = c->gcNextCompartment) { + for (JSCompartment *c = rt->gcSweepingCompartments; c; c = NextGraphNode(c)) { for (int index = 0 ; index < BackgroundPhaseLength[phase] ; ++index) { AllocKind kind = BackgroundPhases[phase][index]; ArenaHeader *arenas = c->arenas.arenaListsToSweep[kind]; @@ -2828,10 +2830,8 @@ SweepBackgroundThings(JSRuntime* rt, bool onBackgroundThread) } } - while (JSCompartment *c = rt->gcSweepingCompartments) { - rt->gcSweepingCompartments = c->gcNextCompartment; - c->gcNextCompartment = NULL; - } + while (rt->gcSweepingCompartments) + RemoveGraphNode(rt->gcSweepingCompartments); } #ifdef JS_THREADSAFE @@ -2839,8 +2839,9 @@ static void AssertBackgroundSweepingFinished(JSRuntime *rt) { for (CompartmentsIter c(rt); !c.done(); c.next()) { - JS_ASSERT(!c->gcNextCompartment); + JS_ASSERT(!c->gcNextGraphNode); for (unsigned i = 0 ; i < FINALIZE_LIMIT ; ++i) { + JS_ASSERT(!c->gcNextGraphNode); JS_ASSERT(!c->arenas.arenaListsToSweep[i]); JS_ASSERT(c->arenas.doneBackgroundFinalize(AllocKind(i))); } @@ -3368,6 +3369,8 @@ BeginMarkPhase(JSRuntime *rt) /* Reset weak map list. */ WeakMapBase::resetWeakMapList(rt); + ArrayBufferObject::resetArrayBufferList(rt); + /* * We must purge the runtime at the beginning of an incremental GC. The * danger if we purge later is that the snapshot invariant of incremental @@ -3445,6 +3448,7 @@ BeginMarkPhase(JSRuntime *rt) if (!c->maybeAlive) c->scheduledForDestruction = true; } + rt->gcFoundBlackGrayEdges = false; } void @@ -3491,6 +3495,8 @@ MarkGrayAndWeak(JSRuntime *rt) } JS_ASSERT(gcmarker->isDrained()); + + gcmarker->setMarkColorBlack(); } #ifdef DEBUG @@ -3498,56 +3504,6 @@ static void ValidateIncrementalMarking(JSRuntime *rt); #endif -static void -EndMarkPhase(JSRuntime *rt) -{ - { - gcstats::AutoPhase ap1(rt->gcStats, gcstats::PHASE_MARK); - MarkGrayAndWeak(rt); - } - - JS_ASSERT(rt->gcMarker.isDrained()); - -#ifdef DEBUG - if (rt->gcIsIncremental && rt->gcValidate) - ValidateIncrementalMarking(rt); -#endif - - /* - * Having black->gray edges violates our promise to the cycle - * collector. This can happen if we're collecting a compartment and it has - * an edge to an uncollected compartment: it's possible that the source and - * destination of the cross-compartment edge should be gray, but the source - * was marked black by the conservative scanner. - */ - bool foundBlackGray = false; - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - for (WrapperMap::Enum e(c->crossCompartmentWrappers); !e.empty(); e.popFront()) { - Cell *dst = e.front().key.wrapped; - Cell *src = ToMarkable(e.front().value); - JS_ASSERT(src->compartment() == c.get()); - if (IsCellMarked(&src) && !src->isMarked(GRAY) && dst->isMarked(GRAY)) { - JS_ASSERT(!dst->compartment()->isCollecting()); - foundBlackGray = true; - } - } - } - - /* - * To avoid the black->gray edge, we completely clear the mark bits of all - * uncollected compartments. This is safe, although it may prevent the - * cycle collector from collecting some dead objects. - */ - if (foundBlackGray) { - for (CompartmentsIter c(rt); !c.done(); c.next()) { - if (!c->isCollecting()) - c->arenas.unmarkAll(); - } - } - - /* We do not discard JIT here code as the following sweeping does that. */ -} - #ifdef DEBUG static void ValidateIncrementalMarking(JSRuntime *rt) @@ -3641,9 +3597,28 @@ ValidateIncrementalMarking(JSRuntime *rt) rt->gcIncrementalState = state; } + #endif +static void +DropStringWrappers(JSRuntime *rt) +{ + /* + * String "wrappers" are dropped on GC because their presence would require + * us to sweep the wrappers in all compartments every time we sweep a + * compartment group. + */ + for (CompartmentsIter c(rt); !c.done(); c.next()) { + for (WrapperMap::Enum e(c->crossCompartmentWrappers); !e.empty(); e.popFront()) { + if (e.front().key.kind == CrossCompartmentKey::StringWrapper) + e.removeFront(); + } + } +} + /* + * Group compartments that must be swept at the same time. + * * If compartment A has an edge to an unmarked object in compartment B, then we * must not sweep A in a later slice than we sweep B. That's because a write * barrier in A that could lead to the unmarked object in B becoming @@ -3651,178 +3626,122 @@ ValidateIncrementalMarking(JSRuntime *rt) * * If we consider these dependencies as a graph, then all the compartments in * any strongly-connected component of this graph must be swept in the same - * slice. This class is used to compute these strongly connected components via - * Tarjan's algorithm. + * slice. + * + * Tarjan's algorithm is used to calculate the components. */ -class PartitionCompartments -{ - typedef unsigned Node; - typedef Vector NodeVector; - typedef Vector BoolVector; - static const Node Undefined = Node(-1); - - JSRuntime *runtime; - - /* - * The value of clock ticks monotonically upward as each new compartment is - * discovered by the algorithm. When a new SCC is found, it is assigned a - * number from nextSCC. - */ - Node clock, nextSCC; - - /* - * Compartments have an index based on their order in rt->compartments. The - * index is used as a subscript into the arrays below. - * - * discoveryTime[comp]: The |clock| value when comp was first explored. - * lowLink[comp]: The minimal discovery time of any compartment reachable - * from |comp|. - * stack: List of explored compartments that haven't been assigned to an SCC. - * scc[comp]: SCC number that |comp| is in. - * onStack[comp]: Whether |comp| in in |stack|. - */ - NodeVector discoveryTime, lowLink, stack, scc; - BoolVector onStack; - - bool fail_; - - void processNode(Node v); - void fail() { fail_ = true; } - bool failed() { return fail_; } - - public: - PartitionCompartments(JSRuntime *rt); - void partition(); - unsigned getSCC(JSCompartment *comp) { return failed() ? 0 : scc[comp->index]; } -}; - -const PartitionCompartments::Node PartitionCompartments::Undefined; - -PartitionCompartments::PartitionCompartments(JSRuntime *rt) - : runtime(rt), clock(0), nextSCC(0), fail_(false) -{ - size_t n = runtime->compartments.length(); - if (!discoveryTime.reserve(n) || - !lowLink.reserve(n) || - !scc.reserve(n) || - !onStack.reserve(n) || - !stack.reserve(n)) - { - fail(); - return; - } - - for (Node v = 0; v < runtime->compartments.length(); v++) { - runtime->compartments[v]->index = v; - discoveryTime.infallibleAppend(Undefined); - lowLink.infallibleAppend(Undefined); - scc.infallibleAppend(Undefined); - onStack.infallibleAppend(false); - } -} - -/* See the Wikipedia article "Tarjan's strongly connected components algorithm". */ void -PartitionCompartments::processNode(Node v) +JSCompartment::findOutgoingEdges(ComponentFinder& finder) { - int stackDummy; - if (failed() || !JS_CHECK_STACK_SIZE(js::GetNativeStackLimit(runtime), &stackDummy)) { - fail(); - return; - } - - discoveryTime[v] = clock; - lowLink[v] = clock; - clock++; - stack.infallibleAppend(v); - onStack[v] = true; - - JSCompartment *comp = runtime->compartments[v]; - - for (WrapperMap::Enum e(comp->crossCompartmentWrappers); !e.empty(); e.popFront()) { - if (e.front().key.kind == CrossCompartmentKey::StringWrapper) - continue; + /* + * Any compartment may have a pointer to an atom in the atoms + * compartment, and these aren't in the cross compartment map. + */ + if (rt->atomsCompartment->isGCMarking()) + finder.addEdgeTo(rt->atomsCompartment); + for (js::WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { + JS_ASSERT(e.front().key.kind != CrossCompartmentKey::StringWrapper); Cell *other = e.front().key.wrapped; - if (other->isMarked(BLACK) && !other->isMarked(GRAY)) - continue; - - Node w = other->compartment()->index; - - if (discoveryTime[w] == Undefined) { - processNode(w); - lowLink[v] = Min(lowLink[v], lowLink[w]); - } else if (onStack[w]) { - lowLink[v] = Min(lowLink[v], discoveryTime[w]); + if (!other->isMarked(BLACK) || other->isMarked(GRAY)) { + JSCompartment *w = other->compartment(); + if (w->isGCMarking()) + finder.addEdgeTo(w); } - } - if (lowLink[v] == discoveryTime[v]) { - Node w; - do { - w = stack.popCopy(); - onStack[w] = false; - scc[w] = nextSCC; - } while (w != v); - nextSCC++; - } -} - -void -PartitionCompartments::partition() -{ - if (failed()) - return; - - for (Node n = 0; n < runtime->compartments.length(); n++) { - if (discoveryTime[n] == Undefined) - processNode(n); +#ifdef DEBUG + JSObject *wrapper = &e.front().value.toObject(); + JS_ASSERT_IF(IsFunctionProxy(wrapper), &GetProxyCall(wrapper).toObject() == other); +#endif } } static void -BeginSweepPhase(JSRuntime *rt) +FindCompartmentGroups(JSRuntime *rt) { - /* - * Sweep phase. - * - * Finalize as we sweep, outside of rt->gcLock but with rt->isHeapBusy() - * true so that any attempt to allocate a GC-thing from a finalizer will - * fail, rather than nest badly and leave the unmarked newborn to be swept. - * - * We first sweep atom state so we can use IsAboutToBeFinalized on - * JSString held in a hashtable to check if the hashtable entry can be - * freed. Note that even after the entry is freed, JSObject finalizers can - * continue to access the corresponding JSString* assuming that they are - * unique. This works since the atomization API must not be called during - * the GC. - */ - gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP); - - /* - * Although there is a runtime-wide gcIsFull flag, it is set in - * BeginMarkPhase. More compartments may have been created since then. - */ - bool isFull = true; - for (CompartmentsIter c(rt); !c.done(); c.next()) { - if (c->isCollecting()) { - if (c != rt->atomsCompartment) - c->setGCState(JSCompartment::Sweep); - } else { - isFull = false; + JS_ASSERT(!rt->gcRemainingCompartmentGroups); + if (rt->gcIsIncremental) { + ComponentFinder finder(rt->nativeStackLimit); + for (GCCompartmentsIter c(rt); !c.done(); c.next()) { + JS_ASSERT(c->isGCMarking()); + finder.addNode(c); } + rt->gcRemainingCompartmentGroups = static_cast(finder.getResultsList()); + } else { + for (GCCompartmentsIter c(rt); !c.done(); c.next()) + AddGraphNode(rt->gcRemainingCompartmentGroups, c.get()); } - JS_ASSERT_IF(isFull, rt->gcIsFull); + rt->gcCompartmentGroupIndex = 0; +} -#ifdef JS_THREADSAFE - rt->gcSweepOnBackgroundThread = rt->hasContexts() && rt->useHelperThreads(); +static void +GetNextCompartmentGroup(JSRuntime *rt) +{ + JS_ASSERT(!rt->gcCompartmentGroup); + if (rt->gcIsIncremental) + rt->gcCompartmentGroup = + ComponentFinder::getNextGroup(rt->gcRemainingCompartmentGroups); + else + rt->gcCompartmentGroup = + ComponentFinder::getAllRemaining(rt->gcRemainingCompartmentGroups); + ++rt->gcCompartmentGroupIndex; +} + +static void +EndMarkingCompartmentGroup(JSRuntime *rt) +{ + { + gcstats::AutoPhase ap1(rt->gcStats, gcstats::PHASE_MARK); + MarkGrayAndWeak(rt); + } + +#ifdef DEBUG + if (rt->gcIsIncremental && rt->gcValidate && rt->gcCompartmentGroupIndex == 0) + ValidateIncrementalMarking(rt); #endif - /* Purge the ArenaLists before sweeping. */ - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - if (c->isGCSweeping()) - c->arenas.purge(); + JS_ASSERT(rt->gcMarker.isDrained()); + + /* + * Having black->gray edges violates our promise to the cycle + * collector. This can happen if we're collecting a compartment and it has + * an edge to an uncollected compartment: it's possible that the source and + * destination of the cross-compartment edge should be gray, but the source + * was marked black by the conservative scanner. + */ + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + for (WrapperMap::Enum e(c->crossCompartmentWrappers); !e.empty(); e.popFront()) { + Cell *dst = e.front().key.wrapped; + Cell *src = ToMarkable(e.front().value); + JS_ASSERT(src->compartment() == c); + if (IsCellMarked(&src) && !src->isMarked(GRAY) && dst->isMarked(GRAY)) { + //JS_ASSERT(!dst->compartment()->isCollecting()); + rt->gcFoundBlackGrayEdges = true; + } + } + } +} + +static void +BeginSweepingCompartmentGroup(JSRuntime *rt) +{ + /* + * Begin sweeping the group of compartments in gcCompartmentGroup, + * performing actions that must be done before yielding to caller. + */ + + bool sweepingAtoms = false; + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + /* Set the GC state to sweeping. */ + JS_ASSERT(c->isGCMarking()); + c->setGCState(JSCompartment::Sweep); + + /* Purge the ArenaLists before sweeping. */ + c->arenas.purge(); + + if (c == rt->atomsCompartment) + sweepingAtoms = true; } FreeOp fop(rt, rt->gcSweepOnBackgroundThread); @@ -3830,7 +3749,12 @@ BeginSweepPhase(JSRuntime *rt) { gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_FINALIZE_START); if (rt->gcFinalizeCallback) - rt->gcFinalizeCallback(&fop, JSFINALIZE_GROUP_START, !isFull); + rt->gcFinalizeCallback(&fop, JSFINALIZE_GROUP_START, !rt->gcIsFull /* unused */); + } + + if (sweepingAtoms) { + gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP_ATOMS); + SweepAtoms(rt); } /* Finalize unreachable (key,value) pairs in all weak maps. */ @@ -3846,19 +3770,13 @@ BeginSweepPhase(JSRuntime *rt) /* Detach unreachable debuggers and global objects from each other. */ Debugger::sweepAll(&fop); - PartitionCompartments partition(rt); - partition.partition(); - { gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP_COMPARTMENTS); bool releaseTypes = ReleaseObservedTypes(rt); - for (CompartmentsIter c(rt); !c.done(); c.next()) { - gcstats::AutoSCC scc(rt->gcStats, partition.getSCC(c)); - if (c->isCollecting()) - c->sweep(&fop, releaseTypes); - else - c->sweepCrossCompartmentWrappers(); + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + gcstats::AutoSCC scc(rt->gcStats, rt->gcCompartmentGroupIndex); + c->sweep(&fop, releaseTypes); } } @@ -3870,50 +3788,75 @@ BeginSweepPhase(JSRuntime *rt) * * Objects are finalized immediately but this may change in the future. */ - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - if (c->isGCSweeping()) { - gcstats::AutoSCC scc(rt->gcStats, partition.getSCC(c)); - c->arenas.queueObjectsForSweep(&fop); - } + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + gcstats::AutoSCC scc(rt->gcStats, rt->gcCompartmentGroupIndex); + c->arenas.queueObjectsForSweep(&fop); } - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - if (c->isGCSweeping()) { - gcstats::AutoSCC scc(rt->gcStats, partition.getSCC(c)); - c->arenas.queueStringsForSweep(&fop); - } + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + gcstats::AutoSCC scc(rt->gcStats, rt->gcCompartmentGroupIndex); + c->arenas.queueStringsForSweep(&fop); } - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - if (c->isGCSweeping()) { - gcstats::AutoSCC scc(rt->gcStats, partition.getSCC(c)); - c->arenas.queueScriptsForSweep(&fop); - } + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + gcstats::AutoSCC scc(rt->gcStats, rt->gcCompartmentGroupIndex); + c->arenas.queueScriptsForSweep(&fop); } - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - if (c->isGCSweeping()) { - gcstats::AutoSCC scc(rt->gcStats, partition.getSCC(c)); - c->arenas.queueShapesForSweep(&fop); - } + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + gcstats::AutoSCC scc(rt->gcStats, rt->gcCompartmentGroupIndex); + c->arenas.queueShapesForSweep(&fop); } #ifdef JS_ION - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - if (c->isGCSweeping()) { - gcstats::AutoSCC scc(rt->gcStats, partition.getSCC(c)); - c->arenas.queueIonCodeForSweep(&fop); - } + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + gcstats::AutoSCC scc(rt->gcStats, rt->gcCompartmentGroupIndex); + c->arenas.queueIonCodeForSweep(&fop); } #endif rt->gcSweepPhase = 0; - rt->gcSweepCompartmentIndex = 0; + rt->gcSweepCompartment = rt->gcCompartmentGroup; rt->gcSweepKindIndex = 0; { gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_FINALIZE_END); if (rt->gcFinalizeCallback) - rt->gcFinalizeCallback(&fop, JSFINALIZE_GROUP_END, !isFull); + rt->gcFinalizeCallback(&fop, JSFINALIZE_GROUP_END, !rt->gcIsFull /* unused */); } } +static void +EndSweepingCompartmentGroup(JSRuntime *rt) +{ + /* Update the GC state for compartments we have swept and unlink the list. */ + while (JSCompartment *c = RemoveGraphNode(rt->gcCompartmentGroup)) { + JS_ASSERT(c->isGCSweeping()); + c->setGCState(JSCompartment::Finished); + } +} + +static void +BeginSweepPhase(JSRuntime *rt) +{ + /* + * Sweep phase. + * + * Finalize as we sweep, outside of rt->gcLock but with rt->isHeapBusy() + * true so that any attempt to allocate a GC-thing from a finalizer will + * fail, rather than nest badly and leave the unmarked newborn to be swept. + */ + gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP); + +#ifdef JS_THREADSAFE + rt->gcSweepOnBackgroundThread = rt->hasContexts() && rt->useHelperThreads(); +#endif + + JS_ASSERT(!rt->gcCompartmentGroup); + + DropStringWrappers(rt); + FindCompartmentGroups(rt); + GetNextCompartmentGroup(rt); + EndMarkingCompartmentGroup(rt); + BeginSweepingCompartmentGroup(rt); +} + bool ArenaLists::foregroundFinalize(FreeOp *fop, AllocKind thingKind, SliceBudget &sliceBudget) { @@ -3930,50 +3873,35 @@ SweepPhase(JSRuntime *rt, SliceBudget &sliceBudget) gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP); FreeOp fop(rt, rt->gcSweepOnBackgroundThread); - for (; rt->gcSweepPhase < FinalizePhaseCount ; ++rt->gcSweepPhase) { - gcstats::AutoPhase ap(rt->gcStats, FinalizePhaseStatsPhase[rt->gcSweepPhase]); + for (;;) { + for (; rt->gcSweepPhase < FinalizePhaseCount ; ++rt->gcSweepPhase) { + gcstats::AutoPhase ap(rt->gcStats, FinalizePhaseStatsPhase[rt->gcSweepPhase]); - ptrdiff_t len = rt->compartments.end() - rt->compartments.begin(); - for (; rt->gcSweepCompartmentIndex < len ; ++rt->gcSweepCompartmentIndex) { - JSCompartment *c = rt->compartments.begin()[rt->gcSweepCompartmentIndex]; + for (; rt->gcSweepCompartment; + rt->gcSweepCompartment = NextGraphNode(rt->gcSweepCompartment)) + { + JSCompartment *c = rt->gcSweepCompartment; - if (c->wasGCStarted()) { - while (rt->gcSweepKindIndex < FinalizePhaseLength[rt->gcSweepPhase]) { - AllocKind kind = FinalizePhases[rt->gcSweepPhase][rt->gcSweepKindIndex]; + while (rt->gcSweepKindIndex < FinalizePhaseLength[rt->gcSweepPhase]) { + AllocKind kind = FinalizePhases[rt->gcSweepPhase][rt->gcSweepKindIndex]; - if (!c->arenas.foregroundFinalize(&fop, kind, sliceBudget)) - return false; - ++rt->gcSweepKindIndex; + if (!c->arenas.foregroundFinalize(&fop, kind, sliceBudget)) + return false; /* Yield to the mutator. */ + + ++rt->gcSweepKindIndex; + } + rt->gcSweepKindIndex = 0; } - } - rt->gcSweepKindIndex = 0; + rt->gcSweepCompartment = rt->gcCompartmentGroup; } - rt->gcSweepCompartmentIndex = 0; + + EndSweepingCompartmentGroup(rt); + GetNextCompartmentGroup(rt); + if (!rt->gcCompartmentGroup) + return true; /* We're finished. */ + EndMarkingCompartmentGroup(rt); + BeginSweepingCompartmentGroup(rt); } - - return true; -} - -static void -SweepAtomsCompartment(JSRuntime *rt) -{ - JSCompartment *c = rt->atomsCompartment; - - JS_ASSERT(rt->gcMarker.isDrained()); - - JS_ASSERT(c->isGCMarking()); - c->setGCState(JSCompartment::Sweep); - - c->arenas.purge(); - - { - gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_SWEEP_ATOMS); - SweepAtoms(rt); - } - - FreeOp fop(rt, rt->gcSweepOnBackgroundThread); - - c->arenas.queueStringsForSweep(&fop); } static void @@ -3987,6 +3915,18 @@ EndSweepPhase(JSRuntime *rt, JSGCInvocationKind gckind, bool lastGC) JS_ASSERT(rt->gcMarker.isDrained()); rt->gcMarker.stop(); + /* + * If we found any black->gray edges during marking, we completely clear the + * mark bits of all uncollected compartments. This is safe, although it may + * prevent the cycle collector from collecting some dead objects. + */ + if (rt->gcFoundBlackGrayEdges) { + for (CompartmentsIter c(rt); !c.done(); c.next()) { + if (!c->isCollecting()) + c->arenas.unmarkAll(); + } + } + #ifdef DEBUG PropertyTree::dumpShapes(rt); #endif @@ -4028,8 +3968,16 @@ EndSweepPhase(JSRuntime *rt, JSGCInvocationKind gckind, bool lastGC) { gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_FINALIZE_END); + + bool isFull = true; + for (CompartmentsIter c(rt); !c.done(); c.next()) { + if (!c->isCollecting()) { + rt->gcIsFull = false; + break; + } + } if (rt->gcFinalizeCallback) - rt->gcFinalizeCallback(&fop, JSFINALIZE_COLLECTION_END, !rt->gcIsFull); + rt->gcFinalizeCallback(&fop, JSFINALIZE_COLLECTION_END, !isFull); } /* @@ -4042,10 +3990,8 @@ EndSweepPhase(JSRuntime *rt, JSGCInvocationKind gckind, bool lastGC) /* Set up list of compartments for sweeping of background things. */ JS_ASSERT(!rt->gcSweepingCompartments); - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - c->gcNextCompartment = rt->gcSweepingCompartments; - rt->gcSweepingCompartments = c; - } + for (GCCompartmentsIter c(rt); !c.done(); c.next()) + AddGraphNode(rt->gcSweepingCompartments, c.get()); /* If not sweeping on background thread then we must do it here. */ if (!rt->gcSweepOnBackgroundThread) { @@ -4062,8 +4008,10 @@ EndSweepPhase(JSRuntime *rt, JSGCInvocationKind gckind, bool lastGC) for (CompartmentsIter c(rt); !c.done(); c.next()) { c->setGCLastBytes(c->gcBytes, c->gcMallocAndFreeBytes, gckind); - if (c->wasGCStarted()) + if (c->isCollecting()) { + JS_ASSERT(!c->isGCMarking() && !c->isGCSweeping()); c->setGCState(JSCompartment::NoGC); + } JS_ASSERT(!c->isCollecting()); JS_ASSERT(!c->wasGCStarted()); @@ -4176,46 +4124,54 @@ IncrementalCollectSlice(JSRuntime *rt, static void ResetIncrementalGC(JSRuntime *rt, const char *reason) { - if (rt->gcIncrementalState == NO_INCREMENTAL) + switch (rt->gcIncrementalState) { + case NO_INCREMENTAL: return; - /* Cancel and ongoing marking. */ - bool wasMarking = false; - { + case MARK: { + /* Cancel any ongoing marking. */ AutoCopyFreeListToArenas copy(rt); for (GCCompartmentsIter c(rt); !c.done(); c.next()) { if (c->isGCMarking()) { c->setNeedsBarrier(false, JSCompartment::UpdateIon); c->setGCState(JSCompartment::NoGC); - wasMarking = true; } } - } - if (wasMarking) rt->gcMarker.reset(); - - if (rt->gcIncrementalState >= SWEEP) { - /* If we had started sweeping then sweep to completion here. */ - IncrementalCollectSlice(rt, SliceBudget::Unlimited, gcreason::RESET, GC_NORMAL); - gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_WAIT_BACKGROUND_THREAD); - rt->gcHelperThread.waitBackgroundSweepOrAllocEnd(); - } else { - JS_ASSERT(rt->gcIncrementalState == MARK); - rt->gcIncrementalState = NO_INCREMENTAL; - rt->gcMarker.stop(); + rt->gcIncrementalState = NO_INCREMENTAL; + JS_ASSERT(!rt->gcStrictCompartmentChecking); - rt->gcStats.reset(reason); + break; + } + + case SWEEP: + for (CompartmentsIter c(rt); !c.done(); c.next()) + c->scheduledForDestruction = false; + + /* If we had started sweeping then sweep to completion here. */ + IncrementalCollectSlice(rt, SliceBudget::Unlimited, gcreason::RESET, GC_NORMAL); + + { + gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_WAIT_BACKGROUND_THREAD); + rt->gcHelperThread.waitBackgroundSweepOrAllocEnd(); + } + break; + + default: + JS_NOT_REACHED("Invalid incremental GC state"); } + rt->gcStats.reset(reason); + #ifdef DEBUG for (GCCompartmentsIter c(rt); !c.done(); c.next()) { JS_ASSERT(c->isCollecting()); JS_ASSERT(!c->needsBarrier()); - JS_ASSERT(!c->gcNextCompartment); + JS_ASSERT(!NextGraphNode(c.get())); for (unsigned i = 0 ; i < FINALIZE_LIMIT ; ++i) JS_ASSERT(!c->arenas.arenaListsToSweep[i]); } @@ -4316,6 +4272,7 @@ IncrementalCollectSlice(JSRuntime *rt, } #endif + JS_ASSERT_IF(rt->gcIncrementalState != NO_INCREMENTAL, rt->gcIsIncremental); rt->gcIsIncremental = budget != SliceBudget::Unlimited; if (zeal == ZealIncrementalRootsThenFinish || zeal == ZealIncrementalMarkAllThenFinish) { @@ -4349,7 +4306,6 @@ IncrementalCollectSlice(JSRuntime *rt, /* fall through */ case MARK: { - /* If we needed delayed marking for gray roots, then collect until done. */ if (!rt->gcMarker.hasBufferedGrayRoots()) sliceBudget.reset(); @@ -4373,8 +4329,6 @@ IncrementalCollectSlice(JSRuntime *rt, break; } - EndMarkPhase(rt); - rt->gcIncrementalState = SWEEP; /* @@ -4396,25 +4350,13 @@ IncrementalCollectSlice(JSRuntime *rt, } case SWEEP: { - bool finished = SweepPhase(rt, sliceBudget); + bool finished = DrainMarkStack(rt, sliceBudget); if (!finished) break; - rt->gcIncrementalState = SWEEP_END; - if (rt->gcIsIncremental) + finished = SweepPhase(rt, sliceBudget); + if (!finished) break; - } - - case SWEEP_END: - if (rt->atomsCompartment->isGCMarking()) { - bool finished = DrainMarkStack(rt, sliceBudget); - if (!finished) - break; - - SweepAtomsCompartment(rt); - if (rt->gcIsIncremental) - break; - } EndSweepPhase(rt, gckind, reason == gcreason::LAST_CONTEXT); @@ -4423,6 +4365,7 @@ IncrementalCollectSlice(JSRuntime *rt, rt->gcIncrementalState = NO_INCREMENTAL; break; + } default: JS_ASSERT(false); diff --git a/js/src/jsgc.h b/js/src/jsgc.h index ac698afbf57b..c78d1c293f08 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -48,7 +48,6 @@ enum State { MARK_ROOTS, MARK, SWEEP, - SWEEP_END, INVALID }; @@ -942,10 +941,9 @@ struct GCMarker : public JSTracer { } /* - * The only valid color transition during a GC is from black to gray. It is - * wrong to switch the mark color from gray to black. The reason is that the - * cycle collector depends on the invariant that there are no black to gray - * edges in the GC heap. This invariant lets the CC not trace through black + * Care must be taken changing the mark color from gray to black. The cycle + * collector depends on the invariant that there are no black to gray edges + * in the GC heap. This invariant lets the CC not trace through black * objects. If this invariant is violated, the cycle collector may free * objects that are still reachable. */ @@ -955,6 +953,12 @@ struct GCMarker : public JSTracer { color = gc::GRAY; } + void setMarkColorBlack() { + JS_ASSERT(isDrained()); + JS_ASSERT(color == gc::GRAY); + color = gc::BLACK; + } + inline void delayMarkingArena(gc::ArenaHeader *aheader); void delayMarkingChildren(const void *thing); void markDelayedChildren(gc::ArenaHeader *aheader); diff --git a/js/src/jsgcinlines.h b/js/src/jsgcinlines.h index 8c6e66ce691a..5a09b74cbccc 100644 --- a/js/src/jsgcinlines.h +++ b/js/src/jsgcinlines.h @@ -451,6 +451,32 @@ class GCCompartmentsIter { JSCompartment *operator->() const { return get(); } }; +class GCCompartmentGroupIter { + private: + JSCompartment *current; + + public: + GCCompartmentGroupIter(JSRuntime *rt) { + JS_ASSERT(rt->isHeapBusy()); + current = rt->gcCompartmentGroup; + } + + bool done() const { return current == NULL; } + + void next() { + JS_ASSERT(!done()); + current = NextGraphNode(current); + } + + JSCompartment *get() const { + JS_ASSERT(!done()); + return current; + } + + operator JSCompartment *() const { return get(); } + JSCompartment *operator->() const { return get(); } +}; + /* * Allocates a new GC thing. After a successful allocation the caller must * fully initialize the thing before calling any function that can potentially @@ -486,7 +512,7 @@ NewGCThing(JSContext *cx, js::gc::AllocKind kind, size_t thingSize) if (!t) t = js::gc::ArenaLists::refillFreeList(cx, kind); - JS_ASSERT_IF(t && comp->wasGCStarted() && comp->needsBarrier(), + JS_ASSERT_IF(t && comp->wasGCStarted() && (comp->isGCMarking() || comp->isGCSweeping()), static_cast(t)->arenaHeader()->allocatedDuringIncremental); #if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL) @@ -513,8 +539,9 @@ TryNewGCThing(JSContext *cx, js::gc::AllocKind kind, size_t thingSize) return NULL; #endif - void *t = cx->compartment->arenas.allocateFromFreeList(kind, thingSize); - JS_ASSERT_IF(t && cx->compartment->wasGCStarted() && cx->compartment->needsBarrier(), + JSCompartment *comp = cx->compartment; + void *t = comp->arenas.allocateFromFreeList(kind, thingSize); + JS_ASSERT_IF(t && comp->wasGCStarted() && (comp->isGCMarking() || comp->isGCSweeping()), static_cast(t)->arenaHeader()->allocatedDuringIncremental); #if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL) diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 0e37966abdf4..0e6c51e797f9 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -5257,9 +5257,9 @@ IsAboutToBeFinalized(TypeObjectKey *key) { /* Mask out the low bit indicating whether this is a type or JS object. */ gc::Cell *tmp = reinterpret_cast(uintptr_t(key) & ~1); - bool isMarked = IsCellMarked(&tmp); + bool isAboutToBeFinalized = IsCellAboutToBeFinalized(&tmp); JS_ASSERT(tmp == reinterpret_cast(uintptr_t(key) & ~1)); - return !isMarked; + return isAboutToBeFinalized; } void @@ -5955,6 +5955,7 @@ void TypeSet::sweep(JSCompartment *compartment) { JS_ASSERT(!purged()); + JS_ASSERT(compartment->isGCSweeping()); /* * Purge references to type objects that are no longer live. Type sets hold @@ -6033,14 +6034,15 @@ TypeObject::sweep(FreeOp *fop) return; } + JSCompartment *compartment = this->compartment(); + JS_ASSERT(compartment->isGCSweeping()); + if (!isMarked()) { if (newScript) fop->free_(newScript); return; } - JSCompartment *compartment = this->compartment(); - /* * Properties were allocated from the old arena, and need to be copied over * to the new one. Don't hang onto properties without the OWN_PROPERTY @@ -6117,6 +6119,7 @@ struct SweepTypeObjectOp void SweepTypeObjects(FreeOp *fop, JSCompartment *compartment) { + JS_ASSERT(compartment->isGCSweeping()); SweepTypeObjectOp op(fop); gc::ForEachArenaAndCell(compartment, gc::FINALIZE_TYPE_OBJECT, gc::EmptyArenaOp, op); } @@ -6125,6 +6128,7 @@ void TypeCompartment::sweep(FreeOp *fop) { JSCompartment *compartment = this->compartment(); + JS_ASSERT(compartment->isGCSweeping()); SweepTypeObjects(fop, compartment); @@ -6141,13 +6145,23 @@ TypeCompartment::sweep(FreeOp *fop) JS_ASSERT(!key.type.isSingleObject()); bool remove = false; - if (key.type.isTypeObject() && !key.type.typeObject()->isMarked()) - remove = true; - if (!obj->isMarked()) + TypeObject *typeObject = NULL; + if (key.type.isTypeObject()) { + typeObject = key.type.typeObject(); + if (IsTypeObjectAboutToBeFinalized(&typeObject)) + remove = true; + } + if (IsTypeObjectAboutToBeFinalized(e.front().value.unsafeGet())) remove = true; - if (remove) + if (remove) { e.removeFront(); + } else if (typeObject && typeObject != key.type.typeObject()) { + ArrayTableKey newKey; + newKey.type = Type::ObjectType(typeObject); + newKey.proto = key.proto; + e.rekeyFront(newKey); + } } } @@ -6158,18 +6172,24 @@ TypeCompartment::sweep(FreeOp *fop) JS_ASSERT(uintptr_t(entry.object->proto.get()) == key.proto.toWord()); bool remove = false; - if (!IsTypeObjectMarked(entry.object.unsafeGet())) + if (IsTypeObjectAboutToBeFinalized(entry.object.unsafeGet())) remove = true; for (unsigned i = 0; !remove && i < key.nslots; i++) { if (JSID_IS_STRING(key.ids[i])) { JSString *str = JSID_TO_STRING(key.ids[i]); - if (!IsStringMarked(&str)) + if (IsStringAboutToBeFinalized(&str)) remove = true; JS_ASSERT(AtomToId((JSAtom *)str) == key.ids[i]); } JS_ASSERT(!entry.types[i].isSingleObject()); - if (entry.types[i].isTypeObject() && !entry.types[i].typeObject()->isMarked()) - remove = true; + TypeObject *typeObject = NULL; + if (entry.types[i].isTypeObject()) { + typeObject = entry.types[i].typeObject(); + if (IsTypeObjectAboutToBeFinalized(&typeObject)) + remove = true; + else if (typeObject != entry.types[i].typeObject()) + entry.types[i] = Type::ObjectType(typeObject); + } } if (remove) { @@ -6183,9 +6203,9 @@ TypeCompartment::sweep(FreeOp *fop) if (allocationSiteTable) { for (AllocationSiteTable::Enum e(*allocationSiteTable); !e.empty(); e.popFront()) { AllocationSiteKey key = e.front().key; - bool keyMarked = IsScriptMarked(&key.script); - bool valMarked = IsTypeObjectMarked(e.front().value.unsafeGet()); - if (!keyMarked || !valMarked) + bool keyDying = IsScriptAboutToBeFinalized(&key.script); + bool valDying = IsTypeObjectAboutToBeFinalized(e.front().value.unsafeGet()); + if (keyDying || valDying) e.removeFront(); else if (key.script != e.front().key.script) e.rekeyFront(key); @@ -6244,11 +6264,14 @@ TypeCompartment::sweepCompilerOutputs(FreeOp *fop, bool discardConstraints) void JSCompartment::sweepNewTypeObjectTable(TypeObjectSet &table) { + JS_ASSERT(isGCSweeping()); if (table.initialized()) { for (TypeObjectSet::Enum e(table); !e.empty(); e.popFront()) { TypeObject *type = e.front(); - if (!type->isMarked()) + if (IsTypeObjectAboutToBeFinalized(&type)) e.removeFront(); + else if (type != e.front()) + e.rekeyFront(TaggedProto(type->proto), type); } } } @@ -6272,6 +6295,7 @@ TypeCompartment::~TypeCompartment() TypeScript::Sweep(FreeOp *fop, RawScript script) { JSCompartment *compartment = script->compartment(); + JS_ASSERT(compartment->isGCSweeping()); JS_ASSERT(compartment->types.inferenceEnabled); unsigned num = NumTypeSets(script); diff --git a/js/src/jspropertytree.cpp b/js/src/jspropertytree.cpp index d0391a729fcd..73c50326e8f6 100644 --- a/js/src/jspropertytree.cpp +++ b/js/src/jspropertytree.cpp @@ -202,11 +202,28 @@ Shape::finalize(FreeOp *fop) { if (!inDictionary()) { /* - * Note that due to incremental sweeping, if !parent->isMarked() then - * the parent may point to a new shape allocated in the same cell that - * use to hold our parent. + * We detach the child from the parent if the parent is reachable. + * + * Note that due to incremental sweeping, the parent pointer may point + * to the original reachable parent, or it may point to a new live + * object allocated in the same cell that used to hold the parent. + * + * There are three cases: + * + * Case 1: parent is not marked - parent is unreachable, may have been + * finalized, and the cell may subsequently have been + * reallocated to a compartment that is not being marked (cells + * are marked when allocated in a compartment that is currenly + * being marked by the collector). + * + * Case 2: parent is marked and is in a different compartment - parent + * has been freed and reallocated to compartment that was being + * marked. + * + * Case 3: parent is marked and is in the same compartment - parent is + * stil reachable and we need to detach from it. */ - if (parent && parent->isMarked()) + if (parent && parent->isMarked() && parent->compartment() == compartment()) parent->removeChild(this); if (kids.isHash()) diff --git a/js/src/jstypedarray.cpp b/js/src/jstypedarray.cpp index 39ddba59e9bc..8333ec7c4fd2 100644 --- a/js/src/jstypedarray.cpp +++ b/js/src/jstypedarray.cpp @@ -549,17 +549,24 @@ void ArrayBufferObject::sweepAll(JSRuntime *rt) { JSObject *buffer = rt->liveArrayBuffers; + JS_ASSERT(buffer != UNSET_BUFFER_LINK); + + JSObject *lastBufferViews = NULL; + while (buffer) { JSObject **views = GetViewList(&buffer->asArrayBuffer()); JS_ASSERT(*views); - JSObject *nextBuffer = BufferLink(*views); - // Rebuild the list of views of the ArrayBuffer, discarding dead views + JSObject *nextBuffer = BufferLink(*views); + JS_ASSERT(nextBuffer != UNSET_BUFFER_LINK); + + // Rebuild the list of views of the ArrayBuffer, discarding dead views. + // If there is only one view, it will have already been marked. JSObject *prevLiveView = NULL; JSObject *view = *views; while (view) { - JSObject *nextView = - static_cast(view->getFixedSlot(BufferView::NEXT_VIEW_SLOT).toPrivate()); + JS_ASSERT(buffer->compartment() == view->compartment()); + JSObject *nextView = NextView(view); if (!JS_IsAboutToBeFinalized(view)) { view->setFixedSlot(BufferView::NEXT_VIEW_SLOT, PrivateValue(prevLiveView)); prevLiveView = view; @@ -567,12 +574,43 @@ ArrayBufferObject::sweepAll(JSRuntime *rt) view = nextView; } *views = prevLiveView; - if (*views) - SetBufferLink(*views, UNSET_BUFFER_LINK); + + // Add the buffer to the end of the list if it has any views left. + // Buffers without views are dropped from the list. + if (*views) { + if (lastBufferViews) + SetBufferLink(lastBufferViews, buffer); + else + rt->liveArrayBuffers = buffer; + lastBufferViews = *views; + } buffer = nextBuffer; } + // Terminate the buffer list. + if (lastBufferViews) + SetBufferLink(lastBufferViews, NULL); + else + rt->liveArrayBuffers = NULL; +} + +void +ArrayBufferObject::resetArrayBufferList(JSRuntime *rt) +{ + JSObject *buffer = rt->liveArrayBuffers; + JS_ASSERT(buffer != UNSET_BUFFER_LINK); + + while (buffer) { + JSObject *view = *GetViewList(&buffer->asArrayBuffer()); + JS_ASSERT(view); + + JSObject *nextBuffer = BufferLink(view); + JS_ASSERT(nextBuffer != UNSET_BUFFER_LINK); + + SetBufferLink(view, UNSET_BUFFER_LINK); + buffer = nextBuffer; + } rt->liveArrayBuffers = NULL; } diff --git a/js/src/jstypedarray.h b/js/src/jstypedarray.h index 081776f54e14..7c70837e27b3 100644 --- a/js/src/jstypedarray.h +++ b/js/src/jstypedarray.h @@ -132,6 +132,8 @@ class ArrayBufferObject : public JSObject static void sweepAll(JSRuntime *rt); + static void resetArrayBufferList(JSRuntime *rt); + static bool stealContents(JSContext *cx, JSObject *obj, void **contents, uint8_t **data); diff --git a/js/src/jswatchpoint.cpp b/js/src/jswatchpoint.cpp index 2ed581f0ad15..d927aa96cc63 100644 --- a/js/src/jswatchpoint.cpp +++ b/js/src/jswatchpoint.cpp @@ -148,7 +148,7 @@ WatchpointMap::markAllIteratively(JSTracer *trc) JSRuntime *rt = trc->runtime; bool mutated = false; for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - if (c->watchpointMap) + if (c->isGCMarking() && c->watchpointMap) mutated |= c->watchpointMap->markIteratively(trc); } return mutated; diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index a5ce9496b37a..bf9cd7d6b304 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -1443,12 +1443,12 @@ Debugger::markAllIteratively(GCMarker *trc) /* * dbg is a Debugger with at least one debuggee. Check three things: - * - dbg is actually in a compartment being GC'd + * - dbg is actually in a compartment that is being marked * - it isn't already marked * - it actually has hooks that might be called */ HeapPtrObject &dbgobj = dbg->toJSObjectRef(); - if (!dbgobj->compartment()->isCollecting()) + if (!dbgobj->compartment()->isGCMarking()) continue; bool dbgMarked = IsObjectMarked(&dbgobj); From 51eedf0d1fcc6e5340ee2a811976094dcfda5c1e Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Fri, 12 Oct 2012 15:26:07 +0100 Subject: [PATCH 012/160] Bug 790338 - Fix issues with gray marking r=billm --HG-- extra : rebase_source : 3bf292670ecc2f314404d02d25c2357dadd682e4 --- js/src/gc/Marking.cpp | 82 ++++++++----- js/src/gc/Marking.h | 8 +- js/src/jscompartment.cpp | 1 + js/src/jscompartment.h | 25 +++- js/src/jsgc.cpp | 238 ++++++++++++++++++++++++++++++++------ js/src/jsgc.h | 3 + js/src/jsproxy.cpp | 20 +++- js/src/jsweakmap.h | 1 + js/src/jswrapper.cpp | 2 +- js/src/vm/Debugger.cpp | 20 +++- js/src/vm/Debugger.h | 1 + js/src/vm/ScopeObject.cpp | 1 + 12 files changed, 330 insertions(+), 72 deletions(-) diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 1b9a559c09d1..3a05d5619fa8 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -117,6 +117,17 @@ CheckMarkedThing(JSTracer *trc, T *thing) JS_ASSERT_IF(rt->gcStrictCompartmentChecking, thing->compartment()->isCollecting() || thing->compartment() == rt->atomsCompartment); + + JS_ASSERT_IF(IS_GC_MARKING_TRACER(trc) && ((GCMarker *)trc)->getMarkColor() == GRAY, + thing->compartment()->isGCMarkingGray() || + thing->compartment() == rt->atomsCompartment); +} + +static GCMarker * +AsGCMarker(JSTracer *trc) +{ + JS_ASSERT(IS_GC_MARKING_TRACER(trc)); + return static_cast(trc); } template @@ -134,7 +145,7 @@ MarkInternal(JSTracer *trc, T **thingp) */ if (!trc->callback) { if (thing->compartment()->isGCMarking()) { - PushMarkStack(static_cast(trc), thing); + PushMarkStack(AsGCMarker(trc), thing); thing->compartment()->maybeAlive = true; } } else { @@ -217,7 +228,7 @@ IsMarked(T **thingp) JS_ASSERT(thingp); JS_ASSERT(*thingp); JSCompartment *c = (*thingp)->compartment(); - if (!c->isGCMarking() && !c->isGCSweeping()) + if (!c->isCollecting() || c->isGCFinished()) return true; return (*thingp)->isMarked(); } @@ -565,36 +576,55 @@ gc::MarkObjectSlots(JSTracer *trc, JSObject *obj, uint32_t start, uint32_t nslot } } -void -gc::MarkCrossCompartmentObjectUnbarriered(JSTracer *trc, JSObject **obj, const char *name) +static bool +ShouldMarkCrossCompartment(JSTracer *trc, RawObject src, Cell *cell) { - if (IS_GC_MARKING_TRACER(trc) && !(*obj)->compartment()->isGCMarking()) - return; + if (!IS_GC_MARKING_TRACER(trc)) + return true; - MarkObjectUnbarriered(trc, obj, name); -} + JSCompartment *c = cell->compartment(); + uint32_t color = AsGCMarker(trc)->getMarkColor(); -void -gc::MarkCrossCompartmentScriptUnbarriered(JSTracer *trc, JSScript **script, const char *name) -{ - if (IS_GC_MARKING_TRACER(trc) && !(*script)->compartment()->isGCMarking()) - return; - - MarkScriptUnbarriered(trc, script, name); -} - -void -gc::MarkCrossCompartmentSlot(JSTracer *trc, HeapSlot *s, const char *name) -{ - if (s->isMarkable()) { - Cell *cell = (Cell *)s->toGCThing(); - if (IS_GC_MARKING_TRACER(trc) && !cell->compartment()->isGCMarking()) - return; - - MarkSlot(trc, s, name); + JS_ASSERT(color == BLACK || color == GRAY); + if (color == BLACK) { + return c->isGCMarking(); + } else { + if (c->isGCMarkingBlack()) { + /* + * The destination compartment is being not being marked gray now, + * but it will be later, so record the cell so it can be marked gray + * at the appropriate time. + */ + if (!cell->isMarked()) + DelayCrossCompartmentGrayMarking(src, cell); + return false; + } + return c->isGCMarkingGray(); } } +void +gc::MarkCrossCompartmentObjectUnbarriered(JSTracer *trc, RawObject src, JSObject **dst, const char *name) +{ + if (ShouldMarkCrossCompartment(trc, src, *dst)) + MarkObjectUnbarriered(trc, dst, name); +} + +void +gc::MarkCrossCompartmentScriptUnbarriered(JSTracer *trc, RawObject src, JSScript **dst, + const char *name) +{ + if (ShouldMarkCrossCompartment(trc, src, *dst)) + MarkScriptUnbarriered(trc, dst, name); +} + +void +gc::MarkCrossCompartmentSlot(JSTracer *trc, RawObject src, HeapSlot *dst, const char *name) +{ + if (dst->isMarkable() && ShouldMarkCrossCompartment(trc, src, (Cell *)dst->toGCThing())) + MarkSlot(trc, dst, name); +} + /*** Special Marking ***/ void diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index 1a01f77580f5..544cef77d528 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -195,17 +195,19 @@ void MarkObjectSlots(JSTracer *trc, JSObject *obj, uint32_t start, uint32_t nslots); void -MarkCrossCompartmentObjectUnbarriered(JSTracer *trc, JSObject **obj, const char *name); +MarkCrossCompartmentObjectUnbarriered(JSTracer *trc, RawObject src, JSObject **dst_obj, + const char *name); void -MarkCrossCompartmentScriptUnbarriered(JSTracer *trc, JSScript **script, const char *name); +MarkCrossCompartmentScriptUnbarriered(JSTracer *trc, RawObject src, JSScript **dst_script, + const char *name); /* * Mark a value that may be in a different compartment from the compartment * being GC'd. (Although it won't be marked if it's in the wrong compartment.) */ void -MarkCrossCompartmentSlot(JSTracer *trc, HeapSlot *s, const char *name); +MarkCrossCompartmentSlot(JSTracer *trc, RawObject src, HeapSlot *dst_slot, const char *name); /*** Special Cases ***/ diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 55f054facc54..d66eadd6557f 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -72,6 +72,7 @@ JSCompartment::JSCompartment(JSRuntime *rt) propertyTree(thisForCtor()), gcMallocAndFreeBytes(0), gcTriggerMallocAndFreeBytes(0), + gcIncomingGrayPointers(NULL), gcMallocBytes(0), debugModeBits(rt->debugMode ? DebugFromC : 0), watchpointMap(NULL), diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 559483e81d88..59fdb20b7d83 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -193,6 +193,7 @@ struct JSCompartment : public js::gc::GraphNodeBase enum CompartmentGCState { NoGC, Mark, + MarkGray, Sweep, Finished }; @@ -250,15 +251,28 @@ struct JSCompartment : public js::gc::GraphNodeBase bool isGCMarking() { if (rt->isHeapCollecting()) - return gcState == Mark; + return gcState == Mark || gcState == MarkGray; else return needsBarrier(); } + bool isGCMarkingBlack() { + return gcState == Mark; + } + + bool isGCMarkingGray() { + return gcState == MarkGray; + } + bool isGCSweeping() { return gcState == Sweep; } + bool isGCFinished() { + return gcState == Finished; + } + + size_t gcBytes; size_t gcTriggerBytes; size_t gcMaxMallocBytes; @@ -340,6 +354,15 @@ struct JSCompartment : public js::gc::GraphNodeBase /* During GC, stores the index of this compartment in rt->compartments. */ unsigned gcIndex; + /* + * During GC, stores the head of a list of incoming pointers from gray cells. + * + * The objects in the list are either cross-compartment wrappers, or + * debugger wrapper objects. The list link is either in the second extra + * slot for the former, or a special slot for the latter. + */ + js::RawObject gcIncomingGrayPointers; + private: /* * Malloc counter to measure memory pressure for GC scheduling. It runs from diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index e0b9cd4cb114..637cd53992c4 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1944,12 +1944,11 @@ GCMarker::stop() JS_ASSERT(!unmarkedArenaStackTop); JS_ASSERT(markLaterArenas == 0); - JS_ASSERT(grayRoots.empty()); + grayRoots.clearAndFree(); grayFailed = false; /* Free non-ballast stack memory. */ stack.reset(); - grayRoots.clearAndFree(); } void @@ -2102,19 +2101,30 @@ GCMarker::markBufferedGrayRoots() { JS_ASSERT(!grayFailed); - for (GrayRoot *elem = grayRoots.begin(); elem != grayRoots.end(); elem++) { + unsigned markCount = 0; + + GrayRoot *elem = grayRoots.begin(); + GrayRoot *write = elem; + for (; elem != grayRoots.end(); elem++) { #ifdef DEBUG debugPrinter = elem->debugPrinter; debugPrintArg = elem->debugPrintArg; debugPrintIndex = elem->debugPrintIndex; #endif - JS_SET_TRACING_LOCATION(this, (void *)&elem->thing); void *tmp = elem->thing; - MarkKind(this, &tmp, elem->kind); - JS_ASSERT(tmp == elem->thing); + if (static_cast(tmp)->compartment()->isGCMarkingGray()) { + JS_SET_TRACING_LOCATION(this, (void *)&elem->thing); + MarkKind(this, &tmp, elem->kind); + JS_ASSERT(tmp == elem->thing); + ++markCount; + } else { + if (write != elem) + *write = *elem; + ++write; + } } - - grayRoots.clearAndFree(); + JS_ASSERT(markCount == elem - write); + grayRoots.shrinkBy(elem - write); } void @@ -3335,8 +3345,11 @@ BeginMarkPhase(JSRuntime *rt) * atoms. Otherwise, the non-collected compartments could contain pointers * to atoms that we would miss. */ - if (rt->atomsCompartment->isGCScheduled() && rt->gcIsFull && !rt->gcKeepAtoms) - rt->atomsCompartment->setGCState(JSCompartment::Mark); + JSCompartment *atomsComp = rt->atomsCompartment; + if (atomsComp->isGCScheduled() && rt->gcIsFull && !rt->gcKeepAtoms) { + JS_ASSERT(!atomsComp->isCollecting()); + atomsComp->setGCState(JSCompartment::Mark); + } /* * At the end of each incremental slice, we call prepareForIncrementalGC, @@ -3452,9 +3465,13 @@ BeginMarkPhase(JSRuntime *rt) } void -MarkWeakReferences(GCMarker *gcmarker) +MarkWeakReferences(JSRuntime *rt, gcstats::Phase phase = gcstats::PHASE_MARK_WEAK) { + GCMarker *gcmarker = &rt->gcMarker; JS_ASSERT(gcmarker->isDrained()); + + gcstats::AutoPhase ap(rt->gcStats, phase); + while (WatchpointMap::markAllIteratively(gcmarker) || WeakMapBase::markAllIteratively(gcmarker) || Debugger::markAllIteratively(gcmarker)) @@ -3466,16 +3483,10 @@ MarkWeakReferences(GCMarker *gcmarker) } static void -MarkGrayAndWeak(JSRuntime *rt) +MarkGrayReferences(JSRuntime *rt) { GCMarker *gcmarker = &rt->gcMarker; - { - gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_MARK_WEAK); - JS_ASSERT(gcmarker->isDrained()); - MarkWeakReferences(gcmarker); - } - { gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_MARK_GRAY); gcmarker->setMarkColorGray(); @@ -3489,10 +3500,7 @@ MarkGrayAndWeak(JSRuntime *rt) gcmarker->drainMarkStack(budget); } - { - gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_MARK_GRAY_WEAK); - MarkWeakReferences(gcmarker); - } + MarkWeakReferences(rt, gcstats::PHASE_MARK_GRAY_WEAK); JS_ASSERT(gcmarker->isDrained()); @@ -3555,7 +3563,8 @@ ValidateIncrementalMarking(JSRuntime *rt) SliceBudget budget; rt->gcIncrementalState = MARK; rt->gcMarker.drainMarkStack(budget); - MarkGrayAndWeak(rt); + MarkWeakReferences(rt); + MarkGrayReferences(rt); /* Now verify that we have the same mark bits as before. */ for (GCChunkSet::Range r(rt->gcChunkSet.all()); !r.empty(); r.popFront()) { @@ -3583,7 +3592,20 @@ ValidateIncrementalMarking(JSRuntime *rt) uintptr_t end = arena->thingsEnd(); while (thing < end) { Cell *cell = (Cell *)thing; + + /* + * If a non-incremental GC wouldn't have collected a cell, then + * an incremental GC won't collect it. + */ JS_ASSERT_IF(bitmap->isMarked(cell, BLACK), incBitmap.isMarked(cell, BLACK)); + + /* + * If the cycle collector isn't allowed to collect an object + * after a non-incremental GC has run, then it isn't allowed to + * collected it after an incremental GC. + */ + JS_ASSERT_IF(!bitmap->isMarked(cell, GRAY), !incBitmap.isMarked(cell, GRAY)); + thing += Arena::thingSize(kind); } } @@ -3688,12 +3710,156 @@ GetNextCompartmentGroup(JSRuntime *rt) ++rt->gcCompartmentGroupIndex; } +/* + * Gray marking: + * + * At the end of collection, anything reachable from a gray root that has not + * otherwise been marked black must be marked gray. + * + * This means that when marking things gray we must not allow marking to leave + * the current compartment group, as that could result in things being marked + * grey when they might subsequently be marked black. To acheive this, when we + * find a cross compartment pointer we don't mark the referent but add it to a + * singly-linked list of incoming gray pointers that is stored with each + * compartment. + * + * The list head is stored in JSCompartment::gcIncomingGrayPointers and can + * contain both cross compartment wrapper objects and debugger env, object or + * script wrappers. The next pointer is stored in a slot on the on the object, + * either the second extra slot for cross compartment wrappers, or a dedicated + * slot for debugger objects. + * + * The list is created during gray marking when one of the + * MarkCrossCompartmentXXX functions is called for a pointer that leaves the + * current compartent group. This calls DelayCrossCompartmentGrayMarking to + * push the referring object onto the list. + * + * The list is traversed and then unlinked in + * MarkIncomingCrossCompartmentPointers. + */ + +static unsigned +GrayLinkSlot(RawObject o) +{ + return IsCrossCompartmentWrapper(o) ? JSSLOT_PROXY_EXTRA + 1 : Debugger::gcGrayLinkSlot(); +} + +static Cell * +CrossCompartmentPointerReferent(RawObject o) +{ + return (Cell*)(IsCrossCompartmentWrapper(o) ? GetProxyPrivate(o).toGCThing() : o->getPrivate()); +} + +static RawObject +NextIncomingCrossCompartmentPointer(RawObject prev, bool unlink) +{ + unsigned slot = GrayLinkSlot(prev); + RawObject next = prev->getReservedSlot(slot).toObjectOrNull(); + + if (unlink) + prev->setSlot(slot, UndefinedValue()); + + return next; +} + +void +js::DelayCrossCompartmentGrayMarking(RawObject src, Cell *cell) +{ + /* Called from MarkCrossCompartmentXXX functions. */ + unsigned slot = GrayLinkSlot(src); + JSCompartment *c = cell->compartment(); + + if (src->getReservedSlot(slot).isUndefined()) { + src->setCrossCompartmentSlot(slot, ObjectOrNullValue(c->gcIncomingGrayPointers)); + c->gcIncomingGrayPointers = src; + } else { +#ifdef DEBUG + /* Assert that if the slot is in use, the object is in our list. */ + JS_ASSERT(src->getReservedSlot(slot).isObjectOrNull()); + RawObject o = c->gcIncomingGrayPointers; + while (o && o != src) + o = NextIncomingCrossCompartmentPointer(o, false); + JS_ASSERT(o); +#endif + } +} + +static void +MarkIncomingCrossCompartmentPointers(JSRuntime *rt, const uint32_t color) +{ + JS_ASSERT(color == BLACK || color == GRAY); + + bool unlinkList = color == GRAY; + + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + JS_ASSERT_IF(color == GRAY, c->isGCMarkingGray()); + JS_ASSERT_IF(color == BLACK, c->isGCMarkingBlack()); + + for (RawObject src = c->gcIncomingGrayPointers; + src; + src = NextIncomingCrossCompartmentPointer(src, unlinkList)) { + + Cell *dst = CrossCompartmentPointerReferent(src); + JS_ASSERT(dst->compartment() == c); + + if (color == GRAY) { + if (IsObjectMarked(&src) && src->isMarked(GRAY)) + MarkGCThingUnbarriered(&rt->gcMarker, (void**)&dst, + "cross-compartment gray pointer"); + } else { + if (IsObjectMarked(&src) && !src->isMarked(GRAY)) + MarkGCThingUnbarriered(&rt->gcMarker, (void**)&dst, + "cross-compartment black pointer"); + } + } + + if (unlinkList) + c->gcIncomingGrayPointers = NULL; + } + + SliceBudget budget; + rt->gcMarker.drainMarkStack(budget); +} + static void EndMarkingCompartmentGroup(JSRuntime *rt) { { gcstats::AutoPhase ap1(rt->gcStats, gcstats::PHASE_MARK); - MarkGrayAndWeak(rt); + + /* + * Mark any incoming black pointers from previously swept compartments + * whose referents are not marked. This can occur when gray cells become + * black by the action of UnmarkGray. + */ + MarkIncomingCrossCompartmentPointers(rt, BLACK); + + MarkWeakReferences(rt); + + /* + * Change state of current group to MarkGray to restrict marking to this + * group. Note that there may be pointers to the atoms compartment, and + * these will be marked through, as they are not marked with + * MarkCrossCompartmentXXX. + */ + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + JS_ASSERT(c->isGCMarkingBlack()); + c->setGCState(JSCompartment::MarkGray); + } + + /* Mark incoming gray pointers from previously swept compartments. */ + rt->gcMarker.setMarkColorGray(); + MarkIncomingCrossCompartmentPointers(rt, GRAY); + rt->gcMarker.setMarkColorBlack(); + + /* Mark gray roots and mark transitively inside the current compartment group. */ + MarkGrayReferences(rt); + + /* Restore marking state. */ + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + JS_ASSERT(c->isGCMarkingGray()); + c->setGCState(JSCompartment::Mark); + } } #ifdef DEBUG @@ -3716,7 +3882,7 @@ EndMarkingCompartmentGroup(JSRuntime *rt) Cell *src = ToMarkable(e.front().value); JS_ASSERT(src->compartment() == c); if (IsCellMarked(&src) && !src->isMarked(GRAY) && dst->isMarked(GRAY)) { - //JS_ASSERT(!dst->compartment()->isCollecting()); + JS_ASSERT(!dst->compartment()->isCollecting()); rt->gcFoundBlackGrayEdges = true; } } @@ -3830,6 +3996,12 @@ EndSweepingCompartmentGroup(JSRuntime *rt) JS_ASSERT(c->isGCSweeping()); c->setGCState(JSCompartment::Finished); } + + /* Reset the list of arenas marked as being allocated during sweep phase. */ + while (ArenaHeader *arena = rt->gcArenasAllocatedDuringSweep) { + rt->gcArenasAllocatedDuringSweep = arena->getNextAllocDuringSweep(); + arena->unsetAllocDuringSweep(); + } } static void @@ -3848,7 +4020,11 @@ BeginSweepPhase(JSRuntime *rt) rt->gcSweepOnBackgroundThread = rt->hasContexts() && rt->useHelperThreads(); #endif +#ifdef DEBUG JS_ASSERT(!rt->gcCompartmentGroup); + for (GCCompartmentsIter c(rt); !c.done(); c.next()) + JS_ASSERT(!c->gcIncomingGrayPointers); +#endif DropStringWrappers(rt); FindCompartmentGroups(rt); @@ -3980,14 +4156,6 @@ EndSweepPhase(JSRuntime *rt, JSGCInvocationKind gckind, bool lastGC) rt->gcFinalizeCallback(&fop, JSFINALIZE_COLLECTION_END, !isFull); } - /* - * Reset the list of arenas marked as being allocated during sweep phase. - */ - while (ArenaHeader *arena = rt->gcArenasAllocatedDuringSweep) { - rt->gcArenasAllocatedDuringSweep = arena->getNextAllocDuringSweep(); - arena->unsetAllocDuringSweep(); - } - /* Set up list of compartments for sweeping of background things. */ JS_ASSERT(!rt->gcSweepingCompartments); for (GCCompartmentsIter c(rt); !c.done(); c.next()) @@ -4009,12 +4177,14 @@ EndSweepPhase(JSRuntime *rt, JSGCInvocationKind gckind, bool lastGC) for (CompartmentsIter c(rt); !c.done(); c.next()) { c->setGCLastBytes(c->gcBytes, c->gcMallocAndFreeBytes, gckind); if (c->isCollecting()) { - JS_ASSERT(!c->isGCMarking() && !c->isGCSweeping()); + JS_ASSERT(c->isGCFinished()); c->setGCState(JSCompartment::NoGC); } JS_ASSERT(!c->isCollecting()); JS_ASSERT(!c->wasGCStarted()); + JS_ASSERT(!c->gcIncomingGrayPointers); + for (unsigned i = 0 ; i < FINALIZE_LIMIT ; ++i) { JS_ASSERT_IF(!IsBackgroundFinalized(AllocKind(i)) || !rt->gcSweepOnBackgroundThread, diff --git a/js/src/jsgc.h b/js/src/jsgc.h index c78d1c293f08..18f6479100a5 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -544,6 +544,9 @@ GCDebugSlice(JSRuntime *rt, bool limit, int64_t objCount); extern void PrepareForDebugGC(JSRuntime *rt); +extern void +DelayCrossCompartmentGrayMarking(RawObject src, gc::Cell *cell); + void InitTracer(JSTracer *trc, JSRuntime *rt, JSTraceCallback callback); diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp index 3fdc57ca6a7b..cbad716c8544 100644 --- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -2842,9 +2842,15 @@ proxy_TraceObject(JSTracer *trc, RawObject obj) // NB: If you add new slots here, make sure to change // js::NukeChromeCrossCompartmentWrappers to cope. - MarkCrossCompartmentSlot(trc, &obj->getReservedSlotRef(JSSLOT_PROXY_PRIVATE), "private"); + MarkCrossCompartmentSlot(trc, obj, &obj->getReservedSlotRef(JSSLOT_PROXY_PRIVATE), "private"); MarkSlot(trc, &obj->getReservedSlotRef(JSSLOT_PROXY_EXTRA + 0), "extra0"); - MarkSlot(trc, &obj->getReservedSlotRef(JSSLOT_PROXY_EXTRA + 1), "extra1"); + + /* + * The GC can use the second reserved slot to link the cross compartment + * wrappers into a linked list, in which case we don't want to trace it. + */ + if (!IsCrossCompartmentWrapper(obj)) + MarkSlot(trc, &obj->getReservedSlotRef(JSSLOT_PROXY_EXTRA + 1), "extra1"); } static void @@ -2852,7 +2858,7 @@ proxy_TraceFunction(JSTracer *trc, RawObject obj) { // NB: If you add new slots here, make sure to change // js::NukeChromeCrossCompartmentWrappers to cope. - MarkCrossCompartmentSlot(trc, &GetCall(obj), "call"); + MarkCrossCompartmentSlot(trc, obj, &GetCall(obj), "call"); MarkSlot(trc, &GetFunctionProxyConstruct(obj), "construct"); proxy_TraceObject(trc, obj); } @@ -3155,7 +3161,13 @@ js::RenewProxyObject(JSContext *cx, JSObject *obj, obj->setSlot(JSSLOT_PROXY_HANDLER, PrivateValue(handler)); obj->setCrossCompartmentSlot(JSSLOT_PROXY_PRIVATE, priv); obj->setSlot(JSSLOT_PROXY_EXTRA + 0, UndefinedValue()); - obj->setSlot(JSSLOT_PROXY_EXTRA + 1, UndefinedValue()); + + /* + * The GC can use the second reserved slot to link the cross compartment + * wrappers into a linked list, in which case we don't want to reset it. + */ + if (!IsCrossCompartmentWrapper(obj)) + obj->setSlot(JSSLOT_PROXY_EXTRA + 1, UndefinedValue()); return obj; } diff --git a/js/src/jsweakmap.h b/js/src/jsweakmap.h index 4273ebc562da..36bf60125344 100644 --- a/js/src/jsweakmap.h +++ b/js/src/jsweakmap.h @@ -130,6 +130,7 @@ class WeakMap : public HashMap, publ if (gc::IsMarked(x)) return false; gc::Mark(trc, x, "WeakMap entry"); + JS_ASSERT(gc::IsMarked(x)); return true; } diff --git a/js/src/jswrapper.cpp b/js/src/jswrapper.cpp index 14bc2d3b33f8..dc77fd2a93a7 100644 --- a/js/src/jswrapper.cpp +++ b/js/src/jswrapper.cpp @@ -1017,7 +1017,7 @@ js::NukeCrossCompartmentWrapper(JSContext *cx, JSObject *wrapper) * option of how to handle the global object. */ JS_FRIEND_API(JSBool) -js::NukeCrossCompartmentWrappers(JSContext* cx, +js::NukeCrossCompartmentWrappers(JSContext* cx, const CompartmentFilter& sourceFilter, const CompartmentFilter& targetFilter, js::NukeReferencesToWindow nukeReferencesToWindow) diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index bf9cd7d6b304..549e1c500926 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -55,6 +55,7 @@ extern Class DebuggerEnv_class; enum { JSSLOT_DEBUGENV_OWNER, + JSSLOT_DEBUGENV_GC_GRAY_LINK, JSSLOT_DEBUGENV_COUNT }; @@ -62,6 +63,7 @@ extern Class DebuggerObject_class; enum { JSSLOT_DEBUGOBJECT_OWNER, + JSSLOT_DEBUGOBJECT_GC_GRAY_LINK, JSSLOT_DEBUGOBJECT_COUNT }; @@ -69,6 +71,7 @@ extern Class DebuggerScript_class; enum { JSSLOT_DEBUGSCRIPT_OWNER, + JSSLOT_DEBUGSCRIPT_GC_GRAY_LINK, JSSLOT_DEBUGSCRIPT_COUNT }; @@ -1333,6 +1336,17 @@ Debugger::slowPathOnNewGlobalObject(JSContext *cx, Handle global /*** Debugger JSObjects **************************************************************************/ +JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGENV_GC_GRAY_LINK) == + unsigned(JSSLOT_DEBUGOBJECT_GC_GRAY_LINK)); +JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGENV_GC_GRAY_LINK) == + unsigned(JSSLOT_DEBUGSCRIPT_GC_GRAY_LINK)); + +unsigned +Debugger::gcGrayLinkSlot() +{ + return JSSLOT_DEBUGOBJECT_GC_GRAY_LINK; +} + void Debugger::markKeysInCompartment(JSTracer *tracer) { @@ -2542,7 +2556,7 @@ DebuggerScript_trace(JSTracer *trc, RawObject obj) { /* This comes from a private pointer, so no barrier needed. */ if (JSScript *script = GetScriptReferent(obj)) { - MarkCrossCompartmentScriptUnbarriered(trc, &script, "Debugger.Script referent"); + MarkCrossCompartmentScriptUnbarriered(trc, obj, &script, "Debugger.Script referent"); obj->setPrivateUnbarriered(script); } } @@ -3752,7 +3766,7 @@ DebuggerObject_trace(JSTracer *trc, RawObject obj) * is okay. */ if (JSObject *referent = (JSObject *) obj->getPrivate()) { - MarkCrossCompartmentObjectUnbarriered(trc, &referent, "Debugger.Object referent"); + MarkCrossCompartmentObjectUnbarriered(trc, obj, &referent, "Debugger.Object referent"); obj->setPrivateUnbarriered(referent); } } @@ -4524,7 +4538,7 @@ DebuggerEnv_trace(JSTracer *trc, RawObject obj) * is okay. */ if (Env *referent = (JSObject *) obj->getPrivate()) { - MarkCrossCompartmentObjectUnbarriered(trc, &referent, "Debugger.Environment referent"); + MarkCrossCompartmentObjectUnbarriered(trc, obj, &referent, "Debugger.Environment referent"); obj->setPrivateUnbarriered(referent); } } diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index dd5684bd3356..4f9546795834 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -252,6 +252,7 @@ class Debugger { static void sweepAll(FreeOp *fop); static void detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global, GlobalObjectSet::Enum *compartmentEnum); + static unsigned gcGrayLinkSlot(); static inline JSTrapStatus onEnterFrame(JSContext *cx, Value *vp); static inline bool onLeaveFrame(JSContext *cx, bool ok); diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 7e08228fa0c7..c3629c10cded 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -1465,6 +1465,7 @@ DebugScopeProxy DebugScopeProxy::singleton; /* static */ DebugScopeObject * DebugScopeObject::create(JSContext *cx, ScopeObject &scope, HandleObject enclosing) { + JS_ASSERT(scope.compartment() == cx->compartment); JSObject *obj = NewProxyObject(cx, &DebugScopeProxy::singleton, ObjectValue(scope), NULL /* proto */, &scope.global(), NULL /* call */, NULL /* construct */); From d3eea7074af82480247f6fa2655ee7117607f29f Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Tue, 16 Oct 2012 12:28:32 +0100 Subject: [PATCH 013/160] Bug 790338 - Sweep debugger objects in the same group as their debugees r=billm --HG-- extra : rebase_source : a97bb6aefa4291496a40b643887b4f712bc18119 --- js/src/jsgc.cpp | 2 + js/src/jsweakmap.cpp | 2 + js/src/jsweakmap.h | 26 ++++---- js/src/vm/Debugger.cpp | 44 +++++++------ js/src/vm/Debugger.h | 139 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 177 insertions(+), 36 deletions(-) diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 637cd53992c4..45036b3d087d 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3677,6 +3677,8 @@ JSCompartment::findOutgoingEdges(ComponentFinder& finder) JS_ASSERT_IF(IsFunctionProxy(wrapper), &GetProxyCall(wrapper).toObject() == other); #endif } + + Debugger::findCompartmentEdges(this, finder); } static void diff --git a/js/src/jsweakmap.cpp b/js/src/jsweakmap.cpp index 7f38aeefb2d3..d60bb5add1d1 100644 --- a/js/src/jsweakmap.cpp +++ b/js/src/jsweakmap.cpp @@ -247,6 +247,8 @@ WeakMap_set_impl(JSContext *cx, CallArgs args) } } + JS_ASSERT(key->compartment() == thisObj->compartment()); + JS_ASSERT_IF(value.isObject(), value.toObject().compartment() == thisObj->compartment()); if (!map->put(key, value)) { JS_ReportOutOfMemory(cx); return false; diff --git a/js/src/jsweakmap.h b/js/src/jsweakmap.h index 36bf60125344..80d63aaef4c9 100644 --- a/js/src/jsweakmap.h +++ b/js/src/jsweakmap.h @@ -184,21 +184,11 @@ class WeakMap : public HashMap, publ if (gc::IsAboutToBeFinalized(&k)) e.removeFront(); } - -#if DEBUG /* * Once we've swept, all remaining edges should stay within the * known-live part of the graph. */ - for (Range r = Base::all(); !r.empty(); r.popFront()) { - Key k(r.front().key); - Value v(r.front().value); - JS_ASSERT(!gc::IsAboutToBeFinalized(&k)); - JS_ASSERT(!gc::IsAboutToBeFinalized(&v)); - JS_ASSERT(k == r.front().key); - JS_ASSERT(v == r.front().value); - } -#endif + assertEntriesNotAboutToBeFinalized(); } /* memberOf can be NULL, which means that the map is not part of a JSObject. */ @@ -213,6 +203,20 @@ class WeakMap : public HashMap, publ } } } + +protected: + void assertEntriesNotAboutToBeFinalized() { +#if DEBUG + for (Range r = Base::all(); !r.empty(); r.popFront()) { + Key k(r.front().key); + Value v(r.front().value); + JS_ASSERT(!gc::IsAboutToBeFinalized(&k)); + JS_ASSERT(!gc::IsAboutToBeFinalized(&v)); + JS_ASSERT(k == r.front().key); + JS_ASSERT(v == r.front().value); + } +#endif + } }; } /* namespace js */ diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 549e1c500926..3bf265e675a3 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -1355,29 +1355,9 @@ Debugger::markKeysInCompartment(JSTracer *tracer) * enumerating WeakMap keys. However in this case we need access, so we * make a base-class reference. Range is public in HashMap. */ - ObjectWeakMap::Base &objStorage = objects; - for (ObjectWeakMap::Base::Range r = objStorage.all(); !r.empty(); r.popFront()) { - const EncapsulatedPtrObject key = r.front().key; - HeapPtrObject tmp(key); - gc::MarkObject(tracer, &tmp, "cross-compartment WeakMap key"); - JS_ASSERT(tmp == key); - } - - ObjectWeakMap::Base &envStorage = environments; - for (ObjectWeakMap::Base::Range r = envStorage.all(); !r.empty(); r.popFront()) { - const EncapsulatedPtrObject &key = r.front().key; - HeapPtrObject tmp(key); - js::gc::MarkObject(tracer, &tmp, "cross-compartment WeakMap key"); - JS_ASSERT(tmp == key); - } - - const ScriptWeakMap::Base &scriptStorage = scripts; - for (ScriptWeakMap::Base::Range r = scriptStorage.all(); !r.empty(); r.popFront()) { - const EncapsulatedPtrScript &key = r.front().key; - HeapPtrScript tmp(key); - gc::MarkScript(tracer, &tmp, "cross-compartment WeakMap key"); - JS_ASSERT(tmp == key); - } + objects.markKeys(tracer); + environments.markKeys(tracer); + scripts.markKeys(tracer); } /* @@ -1576,6 +1556,24 @@ Debugger::detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global, debuggers->back()->removeDebuggeeGlobal(fop, global, compartmentEnum, NULL); } +/* static */ void +Debugger::findCompartmentEdges(JSCompartment *comp, js::gc::ComponentFinder &finder) +{ + /* + * For debugger cross compartment wrappers, add edges in the opposite + * direction to those already added by JSCompartment::findOutgoingEdges. + * This ensure that debuggers and their debuggees are finalized in the same + * group. + */ + JSRuntime *rt = comp->rt; + for (JSCList *p = &rt->debuggerList; (p = JS_NEXT_LINK(p)) != &rt->debuggerList;) { + Debugger *dbg = Debugger::fromLinks(p); + dbg->scripts.findCompartmentEdges(comp, finder); + dbg->objects.findCompartmentEdges(comp, finder); + dbg->environments.findCompartmentEdges(comp, finder); + } +} + void Debugger::finalize(FreeOp *fop, RawObject obj) { diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index 4f9546795834..d20942ba00c6 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -19,11 +19,145 @@ #include "jswrapper.h" #include "gc/Barrier.h" +#include "gc/FindSCCs.h" #include "js/HashTable.h" #include "vm/GlobalObject.h" namespace js { +/* + * A weakmap that supports the keys being in different compartments to the + * values, although all values must be in the same compartment. + * + * The Key and Value classes must support the compartment() method. + * + * The purpose of this is to allow the garbage collector to easily find edges + * from debugee object compartments to debugger compartments when calculating + * the compartment groups. Note that these edges are the inverse of the edges + * stored in the cross compartment map. + * + * The current implementation results in all debuggee object compartments being + * swept in the same group as the debugger. This is a conservative approach, + * and compartments may be unnecessarily grouped, however it results in a + * simpler and faster implementation. + */ +template +class DebuggerWeakMap : private WeakMap > +{ + private: + typedef HashMap, + RuntimeAllocPolicy> CountMap; + + JSCompartment *valueCompartment; + CountMap compartmentCounts; + + public: + typedef WeakMap > Base; + explicit DebuggerWeakMap(JSRuntime *rt) + : Base(rt), valueCompartment(NULL), compartmentCounts(rt) { } + explicit DebuggerWeakMap(JSContext *cx) + : Base(cx), valueCompartment(NULL), compartmentCounts(cx) { } + + public: + /* Expose those parts of HashMap public interface that are used by Debugger methods. */ + + typedef typename Base::Ptr Ptr; + typedef typename Base::AddPtr AddPtr; + typedef typename Base::Range Range; + typedef typename Base::Enum Enum; + typedef typename Base::Lookup Lookup; + + bool init(uint32_t len = 16) { + return Base::init(len) && compartmentCounts.init(); + } + + AddPtr lookupForAdd(const Lookup &l) const { + return Base::lookupForAdd(l); + } + + template + bool relookupOrAdd(AddPtr &p, const KeyInput &k, const ValueInput &v) { + if (!valueCompartment) + valueCompartment = v->compartment(); + JS_ASSERT(v->compartment() == valueCompartment); + if (!incCompartmentCount(k->compartment())) + return false; + bool ok = Base::relookupOrAdd(p, k, v); + if (!ok) + decCompartmentCount(k->compartment()); + return ok; + } + + Range all() const { + return Base::all(); + } + + void remove(const Lookup &l) { + Base::remove(l); + decCompartmentCount(l->compartment()); + if (Base::count() == 0) + valueCompartment = NULL; + } + + public: + /* Expose WeakMap public interface*/ + void trace(JSTracer *tracer) { + Base::trace(tracer); + } + + public: + void markKeys(JSTracer *tracer) { + for (Range r = all(); !r.empty(); r.popFront()) { + Key key = r.front().key; + gc::Mark(tracer, &key, "cross-compartment WeakMap key"); + JS_ASSERT(key == r.front().key); + } + } + + void findCompartmentEdges(JSCompartment *v, js::gc::ComponentFinder &finder) { + if (!valueCompartment || valueCompartment == v || !valueCompartment->isGCMarking()) + return; + CountMap::Ptr p = compartmentCounts.lookup(v); + if (!p) + return; + JS_ASSERT(p->value > 0); + finder.addEdgeTo(valueCompartment); + } + + private: + /* Override sweep method to also update our edge cache. */ + void sweep(JSTracer *trc) { + for (Enum e(*static_cast(this)); !e.empty(); e.popFront()) { + Key k(e.front().key); + Value v(e.front().value); + if (gc::IsAboutToBeFinalized(&k)) { + e.removeFront(); + decCompartmentCount(k->compartment()); + } + } + Base::assertEntriesNotAboutToBeFinalized(); + } + + bool incCompartmentCount(JSCompartment *c) { + CountMap::Ptr p = compartmentCounts.lookupWithDefault(c, 0); + if (!p) + return false; + ++p->value; + return true; + } + + void decCompartmentCount(JSCompartment *c) { + CountMap::Ptr p = compartmentCounts.lookup(c); + JS_ASSERT(p); + JS_ASSERT(p->value > 0); + --p->value; + if (p->value == 0) + compartmentCounts.remove(c); + } +}; + class Debugger { friend class Breakpoint; friend JSBool (::JS_DefineDebuggerObject)(JSContext *cx, JSObject *obj); @@ -86,11 +220,11 @@ class Debugger { FrameMap frames; /* An ephemeral map from JSScript* to Debugger.Script instances. */ - typedef WeakMap ScriptWeakMap; + typedef DebuggerWeakMap ScriptWeakMap; ScriptWeakMap scripts; /* The map from debuggee objects to their Debugger.Object instances. */ - typedef WeakMap ObjectWeakMap; + typedef DebuggerWeakMap ObjectWeakMap; ObjectWeakMap objects; /* The map from debuggee Envs to Debugger.Environment instances. */ @@ -253,6 +387,7 @@ class Debugger { static void detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global, GlobalObjectSet::Enum *compartmentEnum); static unsigned gcGrayLinkSlot(); + static void findCompartmentEdges(JSCompartment *v, js::gc::ComponentFinder &finder); static inline JSTrapStatus onEnterFrame(JSContext *cx, Value *vp); static inline bool onLeaveFrame(JSContext *cx, bool ok); From 8695ebcda902dc03f0a79e9a7892ab24b6659de2 Mon Sep 17 00:00:00 2001 From: Jeff Gilbert Date: Mon, 26 Nov 2012 12:51:57 -0800 Subject: [PATCH 014/160] Bug 811958 - Pull GLContext out of Cocoa stuff - r=bgirard --- gfx/gl/GLContext.h | 19 +--- gfx/gl/GLContextTypes.h | 44 ++++++++ gfx/gl/Makefile.in | 1 + gfx/layers/opengl/LayerManagerOGL.cpp | 89 ++++++++++++++- gfx/layers/opengl/LayerManagerOGL.h | 98 ++++------------ gfx/layers/opengl/LayerManagerOGLProgram.cpp | 111 +++++++++++++++++++ gfx/layers/opengl/LayerManagerOGLProgram.h | 96 +++------------- gfx/layers/opengl/ReusableTileStoreOGL.cpp | 2 + widget/cocoa/nsChildView.mm | 10 +- 9 files changed, 287 insertions(+), 183 deletions(-) create mode 100644 gfx/gl/GLContextTypes.h diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index b109b782578c..38b26722f416 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -32,6 +32,7 @@ #include "nsRegion.h" #include "nsAutoPtr.h" #include "nsThreadUtils.h" +#include "GLContextTypes.h" typedef char realGLboolean; @@ -55,24 +56,6 @@ class GLContext; typedef uintptr_t SharedTextureHandle; -enum ShaderProgramType { - RGBALayerProgramType, - RGBALayerExternalProgramType, - BGRALayerProgramType, - RGBXLayerProgramType, - BGRXLayerProgramType, - RGBARectLayerProgramType, - RGBAExternalLayerProgramType, - ColorLayerProgramType, - YCbCrLayerProgramType, - ComponentAlphaPass1ProgramType, - ComponentAlphaPass2ProgramType, - Copy2DProgramType, - Copy2DRectProgramType, - NumProgramTypes -}; - - /** * A TextureImage encapsulates a surface that can be drawn to by a * Thebes gfxContext and (hopefully efficiently!) synchronized to a diff --git a/gfx/gl/GLContextTypes.h b/gfx/gl/GLContextTypes.h new file mode 100644 index 000000000000..e369b07a6361 --- /dev/null +++ b/gfx/gl/GLContextTypes.h @@ -0,0 +1,44 @@ +/* + * GLContextStuff.h + * + * Created on: Nov 13, 2012 + * Author: jgilbert + */ + +#ifndef GLCONTEXTSTUFF_H_ +#define GLCONTEXTSTUFF_H_ + +/** + * We don't include GLDefs.h here since we don't want to drag in all defines + * in for all our users. + */ +typedef unsigned int GLenum; +typedef unsigned int GLbitfield; +typedef unsigned int GLuint; +typedef int GLint; +typedef int GLsizei; + +namespace mozilla { +namespace gl { + +enum ShaderProgramType { + RGBALayerProgramType, + RGBALayerExternalProgramType, + BGRALayerProgramType, + RGBXLayerProgramType, + BGRXLayerProgramType, + RGBARectLayerProgramType, + RGBAExternalLayerProgramType, + ColorLayerProgramType, + YCbCrLayerProgramType, + ComponentAlphaPass1ProgramType, + ComponentAlphaPass2ProgramType, + Copy2DProgramType, + Copy2DRectProgramType, + NumProgramTypes +}; + +} // namespace gl +} // namespace mozilla + +#endif /* GLCONTEXTSTUFF_H_ */ diff --git a/gfx/gl/Makefile.in b/gfx/gl/Makefile.in index 4d8928ee8f8a..e3729a278007 100644 --- a/gfx/gl/Makefile.in +++ b/gfx/gl/Makefile.in @@ -18,6 +18,7 @@ FAIL_ON_WARNINGS = 1 EXPORTS = \ GLDefs.h \ GLContext.h \ + GLContextTypes.h \ GLContextSymbols.h \ GLContextProvider.h \ GLContextProviderImpl.h \ diff --git a/gfx/layers/opengl/LayerManagerOGL.cpp b/gfx/layers/opengl/LayerManagerOGL.cpp index 7f794318f349..932150e4482e 100644 --- a/gfx/layers/opengl/LayerManagerOGL.cpp +++ b/gfx/layers/opengl/LayerManagerOGL.cpp @@ -3,13 +3,13 @@ * 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 "LayerManagerOGL.h" + #include "mozilla/layers/PLayers.h" /* This must occur *after* layers/PLayers.h to avoid typedefs conflicts. */ #include "mozilla/Util.h" - #include "Composer2D.h" -#include "LayerManagerOGL.h" #include "ThebesLayerOGL.h" #include "ContainerLayerOGL.h" #include "ImageLayerOGL.h" @@ -52,6 +52,91 @@ using namespace mozilla::gl; int ShaderProgramOGL::sCurrentProgramKey = 0; #endif +bool +LayerManagerOGL::Initialize(bool force) +{ + return Initialize(CreateContext(), force); +} + +int32_t +LayerManagerOGL::GetMaxTextureSize() const +{ + return mGLContext->GetMaxTextureSize(); +} + +void +LayerManagerOGL::MakeCurrent(bool aForce) +{ + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return; + } + mGLContext->MakeCurrent(aForce); +} + +void* +LayerManagerOGL::GetNSOpenGLContext() const +{ + return gl()->GetNativeData(GLContext::NativeGLContext); +} + + +void +LayerManagerOGL::BindQuadVBO() { + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); +} + +void +LayerManagerOGL::QuadVBOVerticesAttrib(GLuint aAttribIndex) { + mGLContext->fVertexAttribPointer(aAttribIndex, 2, + LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, + (GLvoid*) QuadVBOVertexOffset()); +} + +void +LayerManagerOGL::QuadVBOTexCoordsAttrib(GLuint aAttribIndex) { + mGLContext->fVertexAttribPointer(aAttribIndex, 2, + LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, + (GLvoid*) QuadVBOTexCoordOffset()); +} + +void +LayerManagerOGL::QuadVBOFlippedTexCoordsAttrib(GLuint aAttribIndex) { + mGLContext->fVertexAttribPointer(aAttribIndex, 2, + LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, + (GLvoid*) QuadVBOFlippedTexCoordOffset()); +} + +// Super common + +void +LayerManagerOGL::BindAndDrawQuad(GLuint aVertAttribIndex, + GLuint aTexCoordAttribIndex, + bool aFlipped) +{ + BindQuadVBO(); + QuadVBOVerticesAttrib(aVertAttribIndex); + + if (aTexCoordAttribIndex != GLuint(-1)) { + if (aFlipped) + QuadVBOFlippedTexCoordsAttrib(aTexCoordAttribIndex); + else + QuadVBOTexCoordsAttrib(aTexCoordAttribIndex); + + mGLContext->fEnableVertexAttribArray(aTexCoordAttribIndex); + } + + mGLContext->fEnableVertexAttribArray(aVertAttribIndex); + + mGLContext->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4); + + mGLContext->fDisableVertexAttribArray(aVertAttribIndex); + + if (aTexCoordAttribIndex != GLuint(-1)) { + mGLContext->fDisableVertexAttribArray(aTexCoordAttribIndex); + } +} + static const double kFpsWindowMs = 250.0; static const size_t kNumFrameTimeStamps = 16; struct FPSCounter { diff --git a/gfx/layers/opengl/LayerManagerOGL.h b/gfx/layers/opengl/LayerManagerOGL.h index 623e009e0e64..78059d36a8c9 100644 --- a/gfx/layers/opengl/LayerManagerOGL.h +++ b/gfx/layers/opengl/LayerManagerOGL.h @@ -9,31 +9,23 @@ #include "LayerManagerOGLProgram.h" #include "mozilla/layers/ShadowLayers.h" - #include "mozilla/TimeStamp.h" #ifdef XP_WIN #include #endif -/** - * We don't include GLDefs.h here since we don't want to drag in all defines - * in for all our users. - */ -typedef unsigned int GLenum; -typedef unsigned int GLbitfield; -typedef unsigned int GLuint; -typedef int GLint; -typedef int GLsizei; - #define BUFFER_OFFSET(i) ((char *)NULL + (i)) #include "gfxContext.h" #include "gfx3DMatrix.h" #include "nsIWidget.h" -#include "GLContext.h" +#include "GLContextTypes.h" namespace mozilla { +namespace gl { +class GLContext; +} namespace layers { class Composer2D; @@ -71,9 +63,7 @@ public: * * \return True is initialization was succesful, false when it was not. */ - bool Initialize(bool force = false) { - return Initialize(CreateContext(), force); - } + bool Initialize(bool force = false); bool Initialize(nsRefPtr aContext, bool force = false); @@ -110,18 +100,14 @@ public: virtual void SetRoot(Layer* aLayer) { mRoot = aLayer; } - virtual bool CanUseCanvasLayerForSize(const gfxIntSize &aSize) - { - if (!mGLContext) - return false; - int32_t maxSize = mGLContext->GetMaxTextureSize(); - return aSize <= gfxIntSize(maxSize, maxSize); + virtual bool CanUseCanvasLayerForSize(const gfxIntSize &aSize) { + if (!mGLContext) + return false; + int32_t maxSize = GetMaxTextureSize(); + return aSize <= gfxIntSize(maxSize, maxSize); } - virtual int32_t GetMaxTextureSize() const - { - return mGLContext->GetMaxTextureSize(); - } + virtual int32_t GetMaxTextureSize() const; virtual already_AddRefed CreateThebesLayer(); @@ -151,13 +137,7 @@ public: /** * Helper methods. */ - void MakeCurrent(bool aForce = false) { - if (mDestroyed) { - NS_WARNING("Call on destroyed layer manager"); - return; - } - mGLContext->MakeCurrent(aForce); - } + void MakeCurrent(bool aForce = false); ShaderProgramOGL* GetBasicLayerProgram(bool aOpaque, bool aIsRGB, MaskType aMask = MaskNone) @@ -203,6 +183,9 @@ public: GLContext* gl() const { return mGLContext; } + // |NSOpenGLContext*|: + void* GetNSOpenGLContext() const; + DrawThebesLayerCallback GetThebesLayerCallback() const { return mThebesLayerCallback; } @@ -254,56 +237,15 @@ public: GLintptr QuadVBOTexCoordOffset() { return sizeof(float)*4*2; } GLintptr QuadVBOFlippedTexCoordOffset() { return sizeof(float)*8*2; } - void BindQuadVBO() { - mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); - } - - void QuadVBOVerticesAttrib(GLuint aAttribIndex) { - mGLContext->fVertexAttribPointer(aAttribIndex, 2, - LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, - (GLvoid*) QuadVBOVertexOffset()); - } - - void QuadVBOTexCoordsAttrib(GLuint aAttribIndex) { - mGLContext->fVertexAttribPointer(aAttribIndex, 2, - LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, - (GLvoid*) QuadVBOTexCoordOffset()); - } - - void QuadVBOFlippedTexCoordsAttrib(GLuint aAttribIndex) { - mGLContext->fVertexAttribPointer(aAttribIndex, 2, - LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, - (GLvoid*) QuadVBOFlippedTexCoordOffset()); - } + void BindQuadVBO(); + void QuadVBOVerticesAttrib(GLuint aAttribIndex); + void QuadVBOTexCoordsAttrib(GLuint aAttribIndex); + void QuadVBOFlippedTexCoordsAttrib(GLuint aAttribIndex); // Super common - void BindAndDrawQuad(GLuint aVertAttribIndex, GLuint aTexCoordAttribIndex, - bool aFlipped = false) - { - BindQuadVBO(); - QuadVBOVerticesAttrib(aVertAttribIndex); - - if (aTexCoordAttribIndex != GLuint(-1)) { - if (aFlipped) - QuadVBOFlippedTexCoordsAttrib(aTexCoordAttribIndex); - else - QuadVBOTexCoordsAttrib(aTexCoordAttribIndex); - - mGLContext->fEnableVertexAttribArray(aTexCoordAttribIndex); - } - - mGLContext->fEnableVertexAttribArray(aVertAttribIndex); - - mGLContext->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4); - - mGLContext->fDisableVertexAttribArray(aVertAttribIndex); - - if (aTexCoordAttribIndex != GLuint(-1)) { - mGLContext->fDisableVertexAttribArray(aTexCoordAttribIndex); - } - } + bool aFlipped = false); void BindAndDrawQuad(ShaderProgramOGL *aProg, bool aFlipped = false) diff --git a/gfx/layers/opengl/LayerManagerOGLProgram.cpp b/gfx/layers/opengl/LayerManagerOGLProgram.cpp index 155b28870693..3631e831da50 100644 --- a/gfx/layers/opengl/LayerManagerOGLProgram.cpp +++ b/gfx/layers/opengl/LayerManagerOGLProgram.cpp @@ -3,9 +3,16 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "LayerManagerOGLProgram.h" + +#ifdef GLCONTEXT_H_ +#error GLContext.h should not have been included here. +#endif + #include "LayerManagerOGLShaders.h" #include "LayerManagerOGL.h" +#include "GLContext.h" + namespace mozilla { namespace layers { @@ -227,6 +234,28 @@ ProgramProfileOGL::GetProfileFor(gl::ShaderProgramType aType, const char* const ShaderProgramOGL::VertexCoordAttrib = "aVertexCoord"; const char* const ShaderProgramOGL::TexCoordAttrib = "aTexCoord"; +ShaderProgramOGL::ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile) + : mIsProjectionMatrixStale(false) + , mGL(aGL) + , mProgram(0) + , mProfile(aProfile) + , mProgramState(STATE_NEW) +{} + +ShaderProgramOGL::~ShaderProgramOGL() +{ + if (mProgram <= 0) { + return; + } + + nsRefPtr ctx = mGL->GetSharedContext(); + if (!ctx) { + ctx = mGL; + } + ctx->MakeCurrent(); + ctx->fDeleteProgram(mProgram); +} + bool ShaderProgramOGL::Initialize() { @@ -394,5 +423,87 @@ ShaderProgramOGL::LoadMask(Layer* aMaskLayer) return true; } +void +ShaderProgramOGL::Activate() +{ + if (mProgramState == STATE_NEW) { + if (!Initialize()) { + NS_WARNING("Shader could not be initialised"); + return; + } + } + NS_ASSERTION(HasInitialized(), "Attempting to activate a program that's not in use!"); + mGL->fUseProgram(mProgram); +#if CHECK_CURRENT_PROGRAM + mGL->SetUserData(&sCurrentProgramKey, this); +#endif + // check and set the projection matrix + if (mIsProjectionMatrixStale) { + SetProjectionMatrix(mProjectionMatrix); + } +} + + +void +ShaderProgramOGL::SetUniform(GLint aLocation, float aFloatValue) +{ + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + mGL->fUniform1f(aLocation, aFloatValue); +} + +void +ShaderProgramOGL::SetUniform(GLint aLocation, const gfxRGBA& aColor) +{ + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + mGL->fUniform4f(aLocation, float(aColor.r), float(aColor.g), float(aColor.b), float(aColor.a)); +} + +void +ShaderProgramOGL::SetUniform(GLint aLocation, int aLength, float *aFloatValues) +{ + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + if (aLength == 1) { + mGL->fUniform1fv(aLocation, 1, aFloatValues); + } else if (aLength == 2) { + mGL->fUniform2fv(aLocation, 1, aFloatValues); + } else if (aLength == 3) { + mGL->fUniform3fv(aLocation, 1, aFloatValues); + } else if (aLength == 4) { + mGL->fUniform4fv(aLocation, 1, aFloatValues); + } else { + NS_NOTREACHED("Bogus aLength param"); + } +} + +void +ShaderProgramOGL::SetUniform(GLint aLocation, GLint aIntValue) +{ + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + mGL->fUniform1i(aLocation, aIntValue); +} + +void +ShaderProgramOGL::SetMatrixUniform(GLint aLocation, const gfx3DMatrix& aMatrix) +{ + SetMatrixUniform(aLocation, &aMatrix._11); +} + +void +ShaderProgramOGL::SetMatrixUniform(GLint aLocation, const float *aFloatValues) +{ + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + mGL->fUniformMatrix4fv(aLocation, 1, false, aFloatValues); +} + } /* layers */ } /* mozilla */ diff --git a/gfx/layers/opengl/LayerManagerOGLProgram.h b/gfx/layers/opengl/LayerManagerOGLProgram.h index ac4a2151f9bb..f6ed2b7a64bb 100644 --- a/gfx/layers/opengl/LayerManagerOGLProgram.h +++ b/gfx/layers/opengl/LayerManagerOGLProgram.h @@ -10,11 +10,16 @@ #include "prenv.h" +#include "nsAutoPtr.h" #include "nsString.h" -#include "GLContext.h" +#include "GLContextTypes.h" #include "gfx3DMatrix.h" +#include "gfxColor.h" namespace mozilla { +namespace gl { +class GLContext; +} namespace layers { class Layer; @@ -140,45 +145,16 @@ class ShaderProgramOGL public: typedef mozilla::gl::GLContext GLContext; - ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile) : - mIsProjectionMatrixStale(false), mGL(aGL), mProgram(0), - mProfile(aProfile), mProgramState(STATE_NEW) { } + ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile); - ~ShaderProgramOGL() { - if (mProgram <= 0) { - return; - } - - nsRefPtr ctx = mGL->GetSharedContext(); - if (!ctx) { - ctx = mGL; - } - ctx->MakeCurrent(); - ctx->fDeleteProgram(mProgram); - } + ~ShaderProgramOGL(); bool HasInitialized() { NS_ASSERTION(mProgramState != STATE_OK || mProgram > 0, "Inconsistent program state"); return mProgramState == STATE_OK; } - void Activate() { - if (mProgramState == STATE_NEW) { - if (!Initialize()) { - NS_WARNING("Shader could not be initialised"); - return; - } - } - NS_ASSERTION(HasInitialized(), "Attempting to activate a program that's not in use!"); - mGL->fUseProgram(mProgram); -#if CHECK_CURRENT_PROGRAM - mGL->SetUserData(&sCurrentProgramKey, this); -#endif - // check and set the projection matrix - if (mIsProjectionMatrixStale) { - SetProjectionMatrix(mProjectionMatrix); - } - } + void Activate(); bool Initialize(); @@ -329,54 +305,12 @@ protected: static int sCurrentProgramKey; #endif - void SetUniform(GLint aLocation, float aFloatValue) { - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - mGL->fUniform1f(aLocation, aFloatValue); - } - - void SetUniform(GLint aLocation, const gfxRGBA& aColor) { - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - mGL->fUniform4f(aLocation, float(aColor.r), float(aColor.g), float(aColor.b), float(aColor.a)); - } - - void SetUniform(GLint aLocation, int aLength, float *aFloatValues) { - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - if (aLength == 1) { - mGL->fUniform1fv(aLocation, 1, aFloatValues); - } else if (aLength == 2) { - mGL->fUniform2fv(aLocation, 1, aFloatValues); - } else if (aLength == 3) { - mGL->fUniform3fv(aLocation, 1, aFloatValues); - } else if (aLength == 4) { - mGL->fUniform4fv(aLocation, 1, aFloatValues); - } else { - NS_NOTREACHED("Bogus aLength param"); - } - } - - void SetUniform(GLint aLocation, GLint aIntValue) { - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - mGL->fUniform1i(aLocation, aIntValue); - } - - void SetMatrixUniform(GLint aLocation, const gfx3DMatrix& aMatrix) { - SetMatrixUniform(aLocation, &aMatrix._11); - } - - void SetMatrixUniform(GLint aLocation, const float *aFloatValues) { - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - mGL->fUniformMatrix4fv(aLocation, 1, false, aFloatValues); - } + void SetUniform(GLint aLocation, float aFloatValue); + void SetUniform(GLint aLocation, const gfxRGBA& aColor); + void SetUniform(GLint aLocation, int aLength, float *aFloatValues); + void SetUniform(GLint aLocation, GLint aIntValue); + void SetMatrixUniform(GLint aLocation, const gfx3DMatrix& aMatrix); + void SetMatrixUniform(GLint aLocation, const float *aFloatValues); }; diff --git a/gfx/layers/opengl/ReusableTileStoreOGL.cpp b/gfx/layers/opengl/ReusableTileStoreOGL.cpp index 63e4d531785e..6c52f74c060f 100644 --- a/gfx/layers/opengl/ReusableTileStoreOGL.cpp +++ b/gfx/layers/opengl/ReusableTileStoreOGL.cpp @@ -4,6 +4,8 @@ #include "ReusableTileStoreOGL.h" +#include "GLContext.h" + namespace mozilla { namespace layers { diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index e7ba09c2b627..f8c005534d8d 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -51,7 +51,6 @@ #include "nsRegion.h" #include "Layers.h" #include "LayerManagerOGL.h" -#include "GLContext.h" #include "mozilla/layers/CompositorCocoaWidgetHelper.h" #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" @@ -67,6 +66,10 @@ #include "nsIDOMWheelEvent.h" +#ifdef GLCONTEXT_H_ +#error GLContext.h should not have been included here. +#endif + using namespace mozilla; using namespace mozilla::layers; using namespace mozilla::gl; @@ -1692,8 +1695,7 @@ nsChildView::CreateCompositor() LayerManagerOGL *manager = static_cast(compositor::GetLayerManager(mCompositorParent)); - NSOpenGLContext *glContext = - (NSOpenGLContext *) manager->gl()->GetNativeData(GLContext::NativeGLContext); + NSOpenGLContext *glContext = (NSOpenGLContext *)manager->GetNSOpenGLContext(); [(ChildView *)mView setGLContext:glContext]; [(ChildView *)mView setUsingOMTCompositor:true]; @@ -2457,7 +2459,7 @@ NSEvent* gLastDragMouseDownEvent = nil; LayerManagerOGL *manager = static_cast(layerManager); manager->SetClippingRegion(region); - glContext = (NSOpenGLContext *)manager->gl()->GetNativeData(mozilla::gl::GLContext::NativeGLContext); + glContext = (NSOpenGLContext *)manager->GetNSOpenGLContext(); if (!mGLContext) { [self setGLContext:glContext]; From f8b6d7d46562ad4d6420bdd89ec3ed654856f604 Mon Sep 17 00:00:00 2001 From: Jeff Gilbert Date: Mon, 26 Nov 2012 12:51:57 -0800 Subject: [PATCH 015/160] Bug 811958 - Fix and move ShareType type - r=bgirard --- dom/plugins/base/nsNPAPIPluginInstance.cpp | 9 ++- dom/plugins/base/nsPluginInstanceOwner.cpp | 8 +- gfx/gl/GLContext.h | 48 +++++++----- gfx/gl/GLContextProviderEGL.cpp | 89 +++++++++++----------- gfx/layers/SharedTextureImage.h | 4 +- gfx/layers/basic/BasicCanvasLayer.cpp | 15 ++-- gfx/layers/ipc/LayersSurfaces.ipdlh | 4 +- gfx/layers/ipc/ShadowLayerUtils.h | 4 +- gfx/layers/opengl/CanvasLayerOGL.cpp | 5 +- gfx/layers/opengl/ImageLayerOGL.h | 2 +- 10 files changed, 104 insertions(+), 84 deletions(-) diff --git a/dom/plugins/base/nsNPAPIPluginInstance.cpp b/dom/plugins/base/nsNPAPIPluginInstance.cpp index cfce39aeb98a..6094d5ed9518 100644 --- a/dom/plugins/base/nsNPAPIPluginInstance.cpp +++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp @@ -129,7 +129,10 @@ public: if (mTextureInfo.mWidth == 0 || mTextureInfo.mHeight == 0) return 0; - SharedTextureHandle handle = sPluginContext->CreateSharedHandle(TextureImage::ThreadShared, (void*)mTextureInfo.mTexture, GLContext::TextureID); + SharedTextureHandle handle = + sPluginContext->CreateSharedHandle(GLContext::SameProcess, + (void*)mTextureInfo.mTexture, + GLContext::TextureID); // We want forget about this now, so delete the texture. Assigning it to zero // ensures that we create a new one in Lock() @@ -1000,7 +1003,9 @@ SharedTextureHandle nsNPAPIPluginInstance::CreateSharedHandle() return mContentTexture->CreateSharedHandle(); } else if (mContentSurface) { EnsureGLContext(); - return sPluginContext->CreateSharedHandle(TextureImage::ThreadShared, mContentSurface, GLContext::SurfaceTexture); + return sPluginContext->CreateSharedHandle(GLContext::SameProcess, + mContentSurface, + GLContext::SurfaceTexture); } else return 0; } diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp index bf2d859833ca..55014898a03e 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -186,7 +186,7 @@ nsPluginInstanceOwner::GetImageContainer() SharedTextureImage::Data data; data.mHandle = mInstance->CreateSharedHandle(); - data.mShareType = mozilla::gl::TextureImage::ThreadShared; + data.mShareType = mozilla::gl::GLContext::SameProcess; data.mInverted = mInstance->Inverted(); gfxRect r = GetPluginRect(); @@ -1723,8 +1723,10 @@ already_AddRefed nsPluginInstanceOwner::GetImageContainerForVide SharedTextureImage::Data data; - data.mHandle = mInstance->GLContext()->CreateSharedHandle(gl::TextureImage::ThreadShared, aVideoInfo->mSurfaceTexture, gl::GLContext::SurfaceTexture); - data.mShareType = mozilla::gl::TextureImage::ThreadShared; + data.mShareType = gl::GLContext::SameProcess; + data.mHandle = mInstance->GLContext()->CreateSharedHandle(data.mShareType, + aVideoInfo->mSurfaceTexture, + gl::GLContext::SurfaceTexture); // The logic below for Honeycomb is just a guess, but seems to work. We don't have a separate // inverted flag for video. diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index 38b26722f416..5188d4411157 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -90,11 +90,6 @@ public: ForceSingleTile = 0x4 }; - enum TextureShareType { - ThreadShared = 0x0, - ProcessShared = 0x1 - }; - typedef gfxASurface::gfxContentType ContentType; virtual ~TextureImage() {} @@ -912,6 +907,12 @@ public: return IsExtensionSupported(EXT_framebuffer_blit) || IsExtensionSupported(ANGLE_framebuffer_blit); } + + enum SharedTextureShareType { + SameProcess = 0, + CrossProcess + }; + enum SharedTextureBufferType { TextureID #ifdef MOZ_WIDGET_ANDROID @@ -922,23 +923,26 @@ public: /** * Create new shared GLContext content handle, must be released by ReleaseSharedHandle. */ - virtual SharedTextureHandle CreateSharedHandle(TextureImage::TextureShareType aType) { return 0; } + virtual SharedTextureHandle CreateSharedHandle(SharedTextureShareType shareType) + { return 0; } /* * Create a new shared GLContext content handle, using the passed buffer as a source. * Must be released by ReleaseSharedHandle. UpdateSharedHandle will have no effect * on handles created with this method, as the caller owns the source (the passed buffer) * and is responsible for updating it accordingly. */ - virtual SharedTextureHandle CreateSharedHandle(TextureImage::TextureShareType aType, - void* aBuffer, - SharedTextureBufferType aBufferType) { return 0; } + virtual SharedTextureHandle CreateSharedHandle(SharedTextureShareType shareType, + void* buffer, + SharedTextureBufferType bufferType) + { return 0; } /** * Publish GLContext content to intermediate buffer attached to shared handle. * Shared handle content is ready to be used after call returns, and no need extra Flush/Finish are required. * GLContext must be current before this call */ - virtual void UpdateSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) { } + virtual void UpdateSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) + { } /** * - It is better to call ReleaseSharedHandle before original GLContext destroyed, * otherwise warning will be thrown on attempt to destroy Texture associated with SharedHandle, depends on backend implementation. @@ -952,8 +956,9 @@ public: * SharedHandle (currently EGLImage) does not require GLContext because it is EGL call, and can be destroyed * at any time, unless EGLImage have siblings (which are not expected with current API). */ - virtual void ReleaseSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) { } + virtual void ReleaseSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) + { } typedef struct { @@ -966,21 +971,24 @@ public: * Returns information necessary for rendering a shared handle. * These values change depending on what sharing mechanism is in use */ - virtual bool GetSharedHandleDetails(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle, - SharedHandleDetails& aDetails) { return false; } + virtual bool GetSharedHandleDetails(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle, + SharedHandleDetails& details) + { return false; } /** * Attach Shared GL Handle to GL_TEXTURE_2D target * GLContext must be current before this call */ - virtual bool AttachSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) { return false; } + virtual bool AttachSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) + { return false; } /** * Detach Shared GL Handle from GL_TEXTURE_2D target */ - virtual void DetachSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) { return; } + virtual void DetachSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) + { } private: GLuint mUserBoundDrawFBO; diff --git a/gfx/gl/GLContextProviderEGL.cpp b/gfx/gl/GLContextProviderEGL.cpp index e080cb10159b..85bb1c6aad10 100644 --- a/gfx/gl/GLContextProviderEGL.cpp +++ b/gfx/gl/GLContextProviderEGL.cpp @@ -646,19 +646,19 @@ public: return sEGLLibrary.HasKHRLockSurface(); } - virtual SharedTextureHandle CreateSharedHandle(TextureImage::TextureShareType aType); - virtual SharedTextureHandle CreateSharedHandle(TextureImage::TextureShareType aType, - void* aBuffer, - SharedTextureBufferType aBufferType); - virtual void UpdateSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle); - virtual void ReleaseSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle); - virtual bool GetSharedHandleDetails(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle, - SharedHandleDetails& aDetails); - virtual bool AttachSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle); + virtual SharedTextureHandle CreateSharedHandle(SharedTextureShareType shareType); + virtual SharedTextureHandle CreateSharedHandle(SharedTextureShareType shareType, + void* buffer, + SharedTextureBufferType bufferType); + virtual void UpdateSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle); + virtual void ReleaseSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle); + virtual bool GetSharedHandleDetails(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle, + SharedHandleDetails& details); + virtual bool AttachSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle); protected: friend class GLContextProviderEGL; @@ -854,15 +854,15 @@ private: }; void -GLContextEGL::UpdateSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) +GLContextEGL::UpdateSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) { - if (aType != TextureImage::ThreadShared) { + if (shareType != SameProcess) { NS_ERROR("Implementation not available for this sharing type"); return; } - SharedTextureHandleWrapper* wrapper = reinterpret_cast(aSharedHandle); + SharedTextureHandleWrapper* wrapper = reinterpret_cast(sharedHandle); NS_ASSERTION(wrapper->Type() == SharedHandleType::Image, "Expected EGLImage shared handle"); NS_ASSERTION(mShareWithEGLImage, "EGLImage not supported or disabled in runtime"); @@ -895,9 +895,9 @@ GLContextEGL::UpdateSharedHandle(TextureImage::TextureShareType aType, } SharedTextureHandle -GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType) +GLContextEGL::CreateSharedHandle(SharedTextureShareType shareType) { - if (aType != TextureImage::ThreadShared) + if (shareType != SameProcess) return 0; if (!mShareWithEGLImage) @@ -914,7 +914,7 @@ GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType) if (!ok) { NS_ERROR("EGLImage creation for EGLTextureWrapper failed"); - ReleaseSharedHandle(aType, (SharedTextureHandle)tex); + ReleaseSharedHandle(shareType, (SharedTextureHandle)tex); return 0; } @@ -923,16 +923,16 @@ GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType) } SharedTextureHandle -GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType, - void* aBuffer, - SharedTextureBufferType aBufferType) +GLContextEGL::CreateSharedHandle(SharedTextureShareType shareType, + void* buffer, + SharedTextureBufferType bufferType) { // Both EGLImage and SurfaceTexture only support ThreadShared currently, but // it's possible to make SurfaceTexture work across processes. We should do that. - if (aType != TextureImage::ThreadShared) + if (shareType != SameProcess) return 0; - switch (aBufferType) { + switch (bufferType) { #ifdef MOZ_WIDGET_ANDROID case SharedTextureBufferType::SurfaceTexture: if (!IsExtensionSupported(GLContext::OES_EGL_image_external)) { @@ -940,13 +940,13 @@ GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType, return 0; } - return (SharedTextureHandle) new SurfaceTextureWrapper(reinterpret_cast(aBuffer)); + return (SharedTextureHandle) new SurfaceTextureWrapper(reinterpret_cast(buffer)); #endif case SharedTextureBufferType::TextureID: { if (!mShareWithEGLImage) return 0; - GLuint texture = (uintptr_t)aBuffer; + GLuint texture = (uintptr_t)buffer; EGLTextureWrapper* tex = new EGLTextureWrapper(); if (!tex->CreateEGLImage(this, texture)) { NS_ERROR("EGLImage creation for EGLTextureWrapper failed"); @@ -962,15 +962,15 @@ GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType, } } -void GLContextEGL::ReleaseSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) +void GLContextEGL::ReleaseSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) { - if (aType != TextureImage::ThreadShared) { + if (shareType != SameProcess) { NS_ERROR("Implementation not available for this sharing type"); return; } - SharedTextureHandleWrapper* wrapper = reinterpret_cast(aSharedHandle); + SharedTextureHandleWrapper* wrapper = reinterpret_cast(sharedHandle); switch (wrapper->Type()) { #ifdef MOZ_WIDGET_ANDROID @@ -982,24 +982,25 @@ void GLContextEGL::ReleaseSharedHandle(TextureImage::TextureShareType aType, case SharedHandleType::Image: { NS_ASSERTION(mShareWithEGLImage, "EGLImage not supported or disabled in runtime"); - EGLTextureWrapper* wrap = (EGLTextureWrapper*)aSharedHandle; + EGLTextureWrapper* wrap = (EGLTextureWrapper*)sharedHandle; delete wrap; break; } default: NS_ERROR("Unknown shared handle type"); + return; } } -bool GLContextEGL::GetSharedHandleDetails(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle, - SharedHandleDetails& aDetails) +bool GLContextEGL::GetSharedHandleDetails(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle, + SharedHandleDetails& details) { - if (aType != TextureImage::ThreadShared) + if (shareType != SameProcess) return false; - SharedTextureHandleWrapper* wrapper = reinterpret_cast(aSharedHandle); + SharedTextureHandleWrapper* wrapper = reinterpret_cast(sharedHandle); switch (wrapper->Type()) { #ifdef MOZ_WIDGET_ANDROID @@ -1014,8 +1015,8 @@ bool GLContextEGL::GetSharedHandleDetails(TextureImage::TextureShareType aType, #endif case SharedHandleType::Image: - aDetails.mTarget = LOCAL_GL_TEXTURE_2D; - aDetails.mProgramType = RGBALayerProgramType; + details.mTarget = LOCAL_GL_TEXTURE_2D; + details.mProgramType = RGBALayerProgramType; break; default: @@ -1026,13 +1027,13 @@ bool GLContextEGL::GetSharedHandleDetails(TextureImage::TextureShareType aType, return true; } -bool GLContextEGL::AttachSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) +bool GLContextEGL::AttachSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) { - if (aType != TextureImage::ThreadShared) + if (shareType != SameProcess) return false; - SharedTextureHandleWrapper* wrapper = reinterpret_cast(aSharedHandle); + SharedTextureHandleWrapper* wrapper = reinterpret_cast(sharedHandle); switch (wrapper->Type()) { #ifdef MOZ_WIDGET_ANDROID @@ -1057,7 +1058,7 @@ bool GLContextEGL::AttachSharedHandle(TextureImage::TextureShareType aType, case SharedHandleType::Image: { NS_ASSERTION(mShareWithEGLImage, "EGLImage not supported or disabled in runtime"); - EGLTextureWrapper* wrap = (EGLTextureWrapper*)aSharedHandle; + EGLTextureWrapper* wrap = (EGLTextureWrapper*)sharedHandle; wrap->WaitSync(); fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_2D, wrap->GetEGLImage()); break; diff --git a/gfx/layers/SharedTextureImage.h b/gfx/layers/SharedTextureImage.h index 3f0732023954..2a42154cfcf6 100644 --- a/gfx/layers/SharedTextureImage.h +++ b/gfx/layers/SharedTextureImage.h @@ -20,7 +20,7 @@ class THEBES_API SharedTextureImage : public Image { public: struct Data { gl::SharedTextureHandle mHandle; - gl::TextureImage::TextureShareType mShareType; + gl::GLContext::SharedTextureShareType mShareType; gfxIntSize mSize; bool mInverted; }; @@ -41,4 +41,4 @@ private: } // layers } // mozilla -#endif // GFX_SHAREDTEXTUREIMAGE_H \ No newline at end of file +#endif // GFX_SHAREDTEXTUREIMAGE_H diff --git a/gfx/layers/basic/BasicCanvasLayer.cpp b/gfx/layers/basic/BasicCanvasLayer.cpp index fdc133d38747..e028468f29b1 100644 --- a/gfx/layers/basic/BasicCanvasLayer.cpp +++ b/gfx/layers/basic/BasicCanvasLayer.cpp @@ -387,24 +387,25 @@ BasicShadowableCanvasLayer::Paint(gfxContext* aContext, Layer* aMaskLayer) if (mGLContext && !mForceReadback && - BasicManager()->GetParentBackendType() == mozilla::layers::LAYERS_OPENGL) { - TextureImage::TextureShareType flags; + BasicManager()->GetParentBackendType() == mozilla::layers::LAYERS_OPENGL) + { + GLContext::SharedTextureShareType shareType; // if process type is default, then it is single-process (non-e10s) if (XRE_GetProcessType() == GeckoProcessType_Default) - flags = TextureImage::ThreadShared; + shareType = GLContext::SameProcess; else - flags = TextureImage::ProcessShared; + shareType = GLContext::CrossProcess; SharedTextureHandle handle = GetSharedBackBufferHandle(); if (!handle) { - handle = mGLContext->CreateSharedHandle(flags); + handle = mGLContext->CreateSharedHandle(shareType); if (handle) { - mBackBuffer = SharedTextureDescriptor(flags, handle, mBounds.Size(), false); + mBackBuffer = SharedTextureDescriptor(shareType, handle, mBounds.Size(), false); } } if (handle) { mGLContext->MakeCurrent(); - mGLContext->UpdateSharedHandle(flags, handle); + mGLContext->UpdateSharedHandle(shareType, handle); // call Painted() to reset our dirty 'bit' Painted(); FireDidTransactionCallback(); diff --git a/gfx/layers/ipc/LayersSurfaces.ipdlh b/gfx/layers/ipc/LayersSurfaces.ipdlh index 39db98123782..2d8e763f05b8 100644 --- a/gfx/layers/ipc/LayersSurfaces.ipdlh +++ b/gfx/layers/ipc/LayersSurfaces.ipdlh @@ -22,7 +22,7 @@ using mozilla::layers::SurfaceDescriptorX11; using mozilla::null_t; using mozilla::WindowsHandle; using mozilla::gl::SharedTextureHandle; -using mozilla::gl::TextureImage::TextureShareType; +using mozilla::gl::GLContext::SharedTextureShareType; namespace mozilla { namespace layers { @@ -37,7 +37,7 @@ struct SurfaceDescriptorD3D10 { }; struct SharedTextureDescriptor { - TextureShareType shareType; + SharedTextureShareType shareType; SharedTextureHandle handle; nsIntSize size; bool inverted; diff --git a/gfx/layers/ipc/ShadowLayerUtils.h b/gfx/layers/ipc/ShadowLayerUtils.h index 4d66cef624bd..b7eaa1d4d191 100644 --- a/gfx/layers/ipc/ShadowLayerUtils.h +++ b/gfx/layers/ipc/ShadowLayerUtils.h @@ -52,9 +52,9 @@ struct ParamTraits { #endif // !defined(MOZ_HAVE_XSURFACEDESCRIPTORX11) template<> -struct ParamTraits +struct ParamTraits { - typedef mozilla::gl::TextureImage::TextureShareType paramType; + typedef mozilla::gl::GLContext::SharedTextureShareType paramType; static void Write(Message* msg, const paramType& param) { diff --git a/gfx/layers/opengl/CanvasLayerOGL.cpp b/gfx/layers/opengl/CanvasLayerOGL.cpp index 3bd9e578b40e..6616fb9fde05 100644 --- a/gfx/layers/opengl/CanvasLayerOGL.cpp +++ b/gfx/layers/opengl/CanvasLayerOGL.cpp @@ -420,7 +420,10 @@ ShadowCanvasLayerOGL::Swap(const CanvasSurface& aNewFront, } else if (IsValidSharedTexDescriptor(aNewFront)) { MakeTextureIfNeeded(gl(), mTexture); if (!IsValidSharedTexDescriptor(mFrontBufferDescriptor)) { - mFrontBufferDescriptor = SharedTextureDescriptor(TextureImage::ThreadShared, 0, nsIntSize(0, 0), false); + mFrontBufferDescriptor = SharedTextureDescriptor(GLContext::SameProcess, + 0, + nsIntSize(0, 0), + false); } *aNewBack = mFrontBufferDescriptor; mFrontBufferDescriptor = aNewFront; diff --git a/gfx/layers/opengl/ImageLayerOGL.h b/gfx/layers/opengl/ImageLayerOGL.h index 5db8b6e9c897..5995c12ef7c9 100644 --- a/gfx/layers/opengl/ImageLayerOGL.h +++ b/gfx/layers/opengl/ImageLayerOGL.h @@ -194,7 +194,7 @@ private: // For SharedTextureHandle gl::SharedTextureHandle mSharedHandle; - gl::TextureImage::TextureShareType mShareType; + gl::GLContext::SharedTextureShareType mShareType; bool mInverted; GLuint mTexture; From e7449034084109f4e2da7deed75b35de50a62ff1 Mon Sep 17 00:00:00 2001 From: Jeff Gilbert Date: Mon, 26 Nov 2012 12:51:58 -0800 Subject: [PATCH 016/160] Bug 811958 - Move TextureImage to its own files - r=bgirard --- gfx/gl/GLContext.cpp | 555 +------------------------------ gfx/gl/GLContext.h | 370 +-------------------- gfx/gl/GLContextProviderEGL.cpp | 1 + gfx/gl/GLTextureImage.cpp | 573 ++++++++++++++++++++++++++++++++ gfx/gl/GLTextureImage.h | 385 +++++++++++++++++++++ gfx/gl/Makefile.in | 2 + widget/cocoa/nsChildView.mm | 10 +- 7 files changed, 984 insertions(+), 912 deletions(-) create mode 100644 gfx/gl/GLTextureImage.cpp create mode 100644 gfx/gl/GLTextureImage.h diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 8f183eae5afb..17197535c857 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -23,6 +23,8 @@ #include "mozilla/Preferences.h" #include "mozilla/Util.h" // for DebugOnly +#include "GLTextureImage.h" + using namespace mozilla::gfx; namespace mozilla { @@ -738,6 +740,19 @@ GLContext::CreateTextureImage(const nsIntSize& aSize, return CreateBasicTextureImage(texture, aSize, aWrapMode, aContentType, this, aFlags); } +already_AddRefed +GLContext::CreateBasicTextureImage(GLuint aTexture, + const nsIntSize& aSize, + GLenum aWrapMode, + TextureImage::ContentType aContentType, + GLContext* aContext, + TextureImage::Flags aFlags) +{ + nsRefPtr teximage( + new BasicTextureImage(aTexture, aSize, aWrapMode, aContentType, aContext, aFlags)); + return teximage.forget(); +} + void GLContext::ApplyFilterToBoundTexture(gfxPattern::GraphicsFilter aFilter) { ApplyFilterToBoundTexture(LOCAL_GL_TEXTURE_2D, aFilter); @@ -755,546 +770,6 @@ void GLContext::ApplyFilterToBoundTexture(GLuint aTarget, } } -BasicTextureImage::~BasicTextureImage() -{ - GLContext *ctx = mGLContext; - if (ctx->IsDestroyed() || !ctx->IsOwningThreadCurrent()) { - ctx = ctx->GetSharedContext(); - } - - // If we have a context, then we need to delete the texture; - // if we don't have a context (either real or shared), - // then they went away when the contex was deleted, because it - // was the only one that had access to it. - if (ctx && !ctx->IsDestroyed()) { - mGLContext->MakeCurrent(); - mGLContext->fDeleteTextures(1, &mTexture); - } -} - -gfxASurface* -BasicTextureImage::BeginUpdate(nsIntRegion& aRegion) -{ - NS_ASSERTION(!mUpdateSurface, "BeginUpdate() without EndUpdate()?"); - - // determine the region the client will need to repaint - if (mGLContext->CanUploadSubTextures()) { - GetUpdateRegion(aRegion); - } else { - aRegion = nsIntRect(nsIntPoint(0, 0), mSize); - } - - mUpdateRegion = aRegion; - - nsIntRect rgnSize = mUpdateRegion.GetBounds(); - if (!nsIntRect(nsIntPoint(0, 0), mSize).Contains(rgnSize)) { - NS_ERROR("update outside of image"); - return NULL; - } - - ImageFormat format = - (GetContentType() == gfxASurface::CONTENT_COLOR) ? - gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; - mUpdateSurface = - GetSurfaceForUpdate(gfxIntSize(rgnSize.width, rgnSize.height), format); - - if (!mUpdateSurface || mUpdateSurface->CairoStatus()) { - mUpdateSurface = NULL; - return NULL; - } - - mUpdateSurface->SetDeviceOffset(gfxPoint(-rgnSize.x, -rgnSize.y)); - - return mUpdateSurface; -} - -void -BasicTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) -{ - // if the texture hasn't been initialized yet, or something important - // changed, we need to recreate our backing surface and force the - // client to paint everything - if (mTextureState != Valid) - aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); -} - -void -BasicTextureImage::EndUpdate() -{ - NS_ASSERTION(!!mUpdateSurface, "EndUpdate() without BeginUpdate()?"); - - // FIXME: this is the slow boat. Make me fast (with GLXPixmap?). - - // Undo the device offset that BeginUpdate set; doesn't much matter for us here, - // but important if we ever do anything directly with the surface. - mUpdateSurface->SetDeviceOffset(gfxPoint(0, 0)); - - bool relative = FinishedSurfaceUpdate(); - - mShaderType = - mGLContext->UploadSurfaceToTexture(mUpdateSurface, - mUpdateRegion, - mTexture, - mTextureState == Created, - mUpdateOffset, - relative); - FinishedSurfaceUpload(); - - mUpdateSurface = nullptr; - mTextureState = Valid; -} - -void -BasicTextureImage::BindTexture(GLenum aTextureUnit) -{ - mGLContext->fActiveTexture(aTextureUnit); - mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); - mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); -} - -void -BasicTextureImage::ApplyFilter() -{ - mGLContext->ApplyFilterToBoundTexture(mFilter); -} - - -already_AddRefed -BasicTextureImage::GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt) -{ - return gfxPlatform::GetPlatform()-> - CreateOffscreenSurface(aSize, gfxASurface::ContentFromFormat(aFmt)); -} - -bool -BasicTextureImage::FinishedSurfaceUpdate() -{ - return false; -} - -void -BasicTextureImage::FinishedSurfaceUpload() -{ -} - -bool -BasicTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) -{ - nsIntRect bounds = aRegion.GetBounds(); - nsIntRegion region; - if (mTextureState != Valid) { - bounds = nsIntRect(0, 0, mSize.width, mSize.height); - region = nsIntRegion(bounds); - } else { - region = aRegion; - } - - mShaderType = - mGLContext->UploadSurfaceToTexture(aSurf, - region, - mTexture, - mTextureState == Created, - bounds.TopLeft() + aFrom, - false); - mTextureState = Valid; - return true; -} - -void -BasicTextureImage::Resize(const nsIntSize& aSize) -{ - NS_ASSERTION(!mUpdateSurface, "Resize() while in update?"); - - mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); - - mGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D, - 0, - LOCAL_GL_RGBA, - aSize.width, - aSize.height, - 0, - LOCAL_GL_RGBA, - LOCAL_GL_UNSIGNED_BYTE, - NULL); - - mTextureState = Allocated; - mSize = aSize; -} - -TiledTextureImage::TiledTextureImage(GLContext* aGL, - nsIntSize aSize, - TextureImage::ContentType aContentType, - TextureImage::Flags aFlags) - : TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aFlags) - , mCurrentImage(0) - , mIterationCallback(nullptr) - , mInUpdate(false) - , mRows(0) - , mColumns(0) - , mGL(aGL) - , mTextureState(Created) -{ - mTileSize = (!(aFlags & TextureImage::ForceSingleTile) && mGL->WantsSmallTiles()) - ? 256 : mGL->GetMaxTextureSize(); - if (aSize != nsIntSize(0,0)) { - Resize(aSize); - } -} - -TiledTextureImage::~TiledTextureImage() -{ -} - -bool -TiledTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) -{ - nsIntRegion region; - - if (mTextureState != Valid) { - nsIntRect bounds = nsIntRect(0, 0, mSize.width, mSize.height); - region = nsIntRegion(bounds); - } else { - region = aRegion; - } - - bool result = true; - int oldCurrentImage = mCurrentImage; - BeginTileIteration(); - do { - nsIntRect tileRect = GetSrcTileRect(); - int xPos = tileRect.x; - int yPos = tileRect.y; - - nsIntRegion tileRegion; - tileRegion.And(region, tileRect); // intersect with tile - - if (tileRegion.IsEmpty()) - continue; - - if (mGL->CanUploadSubTextures()) { - tileRegion.MoveBy(-xPos, -yPos); // translate into tile local space - } else { - // If sub-textures are unsupported, expand to tile boundaries - tileRect.x = tileRect.y = 0; - tileRegion = nsIntRegion(tileRect); - } - - result &= mImages[mCurrentImage]-> - DirectUpdate(aSurf, tileRegion, aFrom + nsIntPoint(xPos, yPos)); - - if (mCurrentImage == mImages.Length() - 1) { - // We know we're done, but we still need to ensure that the callback - // gets called (e.g. to update the uploaded region). - NextTile(); - break; - } - // Override a callback cancelling iteration if the texture wasn't valid. - // We need to force the update in that situation, or we may end up - // showing invalid/out-of-date texture data. - } while (NextTile() || (mTextureState != Valid)); - mCurrentImage = oldCurrentImage; - - mShaderType = mImages[0]->GetShaderProgramType(); - mTextureState = Valid; - return result; -} - -void -TiledTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) -{ - if (mTextureState != Valid) { - // if the texture hasn't been initialized yet, or something important - // changed, we need to recreate our backing surface and force the - // client to paint everything - aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); - return; - } - - nsIntRegion newRegion; - - // We need to query each texture with the region it will be drawing and - // set aForRegion to be the combination of all of these regions - for (unsigned i = 0; i < mImages.Length(); i++) { - int xPos = (i % mColumns) * mTileSize; - int yPos = (i / mColumns) * mTileSize; - nsIntRect imageRect = nsIntRect(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); - - if (aForRegion.Intersects(imageRect)) { - // Make a copy of the region - nsIntRegion subRegion; - subRegion.And(aForRegion, imageRect); - // Translate it into tile-space - subRegion.MoveBy(-xPos, -yPos); - // Query region - mImages[i]->GetUpdateRegion(subRegion); - // Translate back - subRegion.MoveBy(xPos, yPos); - // Add to the accumulated region - newRegion.Or(newRegion, subRegion); - } - } - - aForRegion = newRegion; -} - -gfxASurface* -TiledTextureImage::BeginUpdate(nsIntRegion& aRegion) -{ - NS_ASSERTION(!mInUpdate, "nested update"); - mInUpdate = true; - - // Note, we don't call GetUpdateRegion here as if the updated region is - // fully contained in a single tile, we get to avoid iterating through - // the tiles again (and a little copying). - if (mTextureState != Valid) - { - // if the texture hasn't been initialized yet, or something important - // changed, we need to recreate our backing surface and force the - // client to paint everything - aRegion = nsIntRect(nsIntPoint(0, 0), mSize); - } - - nsIntRect bounds = aRegion.GetBounds(); - - for (unsigned i = 0; i < mImages.Length(); i++) { - int xPos = (i % mColumns) * mTileSize; - int yPos = (i / mColumns) * mTileSize; - nsIntRegion imageRegion = nsIntRegion(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); - - // a single Image can handle this update request - if (imageRegion.Contains(aRegion)) { - // adjust for tile offset - aRegion.MoveBy(-xPos, -yPos); - // forward the actual call - nsRefPtr surface = mImages[i]->BeginUpdate(aRegion); - // caller expects container space - aRegion.MoveBy(xPos, yPos); - // Correct the device offset - gfxPoint offset = surface->GetDeviceOffset(); - surface->SetDeviceOffset(gfxPoint(offset.x - xPos, - offset.y - yPos)); - // we don't have a temp surface - mUpdateSurface = nullptr; - // remember which image to EndUpdate - mCurrentImage = i; - return surface.get(); - } - } - - // Get the real updated region, taking into account the capabilities of - // each TextureImage tile - GetUpdateRegion(aRegion); - mUpdateRegion = aRegion; - bounds = aRegion.GetBounds(); - - // update covers multiple Images - create a temp surface to paint in - gfxASurface::gfxImageFormat format = - (GetContentType() == gfxASurface::CONTENT_COLOR) ? - gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; - mUpdateSurface = gfxPlatform::GetPlatform()-> - CreateOffscreenSurface(gfxIntSize(bounds.width, bounds.height), gfxASurface::ContentFromFormat(format)); - mUpdateSurface->SetDeviceOffset(gfxPoint(-bounds.x, -bounds.y)); - - return mUpdateSurface; -} - -void -TiledTextureImage::EndUpdate() -{ - NS_ASSERTION(mInUpdate, "EndUpdate not in update"); - if (!mUpdateSurface) { // update was to a single TextureImage - mImages[mCurrentImage]->EndUpdate(); - mInUpdate = false; - mTextureState = Valid; - mShaderType = mImages[mCurrentImage]->GetShaderProgramType(); - return; - } - - // upload tiles from temp surface - for (unsigned i = 0; i < mImages.Length(); i++) { - int xPos = (i % mColumns) * mTileSize; - int yPos = (i / mColumns) * mTileSize; - nsIntRect imageRect = nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize()); - - nsIntRegion subregion; - subregion.And(mUpdateRegion, imageRect); - if (subregion.IsEmpty()) - continue; - subregion.MoveBy(-xPos, -yPos); // Tile-local space - // copy tile from temp surface - gfxASurface* surf = mImages[i]->BeginUpdate(subregion); - nsRefPtr ctx = new gfxContext(surf); - gfxUtils::ClipToRegion(ctx, subregion); - ctx->SetOperator(gfxContext::OPERATOR_SOURCE); - ctx->SetSource(mUpdateSurface, gfxPoint(-xPos, -yPos)); - ctx->Paint(); - mImages[i]->EndUpdate(); - } - - mUpdateSurface = nullptr; - mInUpdate = false; - mShaderType = mImages[0]->GetShaderProgramType(); - mTextureState = Valid; -} - -void TiledTextureImage::BeginTileIteration() -{ - mCurrentImage = 0; -} - -bool TiledTextureImage::NextTile() -{ - bool continueIteration = true; - - if (mIterationCallback) - continueIteration = mIterationCallback(this, mCurrentImage, - mIterationCallbackData); - - if (mCurrentImage + 1 < mImages.Length()) { - mCurrentImage++; - return continueIteration; - } - return false; -} - -void TiledTextureImage::SetIterationCallback(TileIterationCallback aCallback, - void* aCallbackData) -{ - mIterationCallback = aCallback; - mIterationCallbackData = aCallbackData; -} - -nsIntRect TiledTextureImage::GetTileRect() -{ - nsIntRect rect = mImages[mCurrentImage]->GetTileRect(); - unsigned int xPos = (mCurrentImage % mColumns) * mTileSize; - unsigned int yPos = (mCurrentImage / mColumns) * mTileSize; - rect.MoveBy(xPos, yPos); - return rect; -} - -nsIntRect TiledTextureImage::GetSrcTileRect() -{ - nsIntRect rect = GetTileRect(); - unsigned int srcY = mFlags & NeedsYFlip - ? mSize.height - rect.height - rect.y - : rect.y; - return nsIntRect(rect.x, srcY, rect.width, rect.height); -} - -void -TiledTextureImage::BindTexture(GLenum aTextureUnit) -{ - mImages[mCurrentImage]->BindTexture(aTextureUnit); -} - -void -TiledTextureImage::ApplyFilter() -{ - mGL->ApplyFilterToBoundTexture(mFilter); -} - -/* - * Resize, trying to reuse tiles. The reuse strategy is to decide on reuse per - * column. A tile on a column is reused if it hasn't changed size, otherwise it - * is discarded/replaced. Extra tiles on a column are pruned after iterating - * each column, and extra rows are pruned after iteration over the entire image - * finishes. - */ -void TiledTextureImage::Resize(const nsIntSize& aSize) -{ - if (mSize == aSize && mTextureState != Created) { - return; - } - - // calculate rows and columns, rounding up - unsigned int columns = (aSize.width + mTileSize - 1) / mTileSize; - unsigned int rows = (aSize.height + mTileSize - 1) / mTileSize; - - // Iterate over old tile-store and insert/remove tiles as necessary - int row; - unsigned int i = 0; - for (row = 0; row < (int)rows; row++) { - // If we've gone beyond how many rows there were before, set mColumns to - // zero so that we only create new tiles. - if (row >= (int)mRows) - mColumns = 0; - - // Similarly, if we're on the last row of old tiles and the height has - // changed, discard all tiles in that row. - // This will cause the pruning of columns not to work, but we don't need - // to worry about that, as no more tiles will be reused past this point - // anyway. - if ((row == (int)mRows - 1) && (aSize.height != mSize.height)) - mColumns = 0; - - int col; - for (col = 0; col < (int)columns; col++) { - nsIntSize size( // use tilesize first, then the remainder - (col+1) * mTileSize > (unsigned int)aSize.width ? aSize.width % mTileSize : mTileSize, - (row+1) * mTileSize > (unsigned int)aSize.height ? aSize.height % mTileSize : mTileSize); - - bool replace = false; - - // Check if we can re-use old tiles. - if (col < (int)mColumns) { - // Reuse an existing tile. If the tile is an end-tile and the - // width differs, replace it instead. - if (mSize.width != aSize.width) { - if (col == (int)mColumns - 1) { - // Tile at the end of the old column, replace it with - // a new one. - replace = true; - } else if (col == (int)columns - 1) { - // Tile at the end of the new column, create a new one. - } else { - // Before the last column on both the old and new sizes, - // reuse existing tile. - i++; - continue; - } - } else { - // Width hasn't changed, reuse existing tile. - i++; - continue; - } - } - - // Create a new tile. - nsRefPtr teximg = - mGL->TileGenFunc(size, mContentType, mFlags); - if (replace) - mImages.ReplaceElementAt(i, teximg.forget()); - else - mImages.InsertElementAt(i, teximg.forget()); - i++; - } - - // Prune any unused tiles on the end of the column. - if (row < (int)mRows) { - for (col = (int)mColumns - col; col > 0; col--) { - mImages.RemoveElementAt(i); - } - } - } - - // Prune any unused tiles at the end of the store. - unsigned int length = mImages.Length(); - for (; i < length; i++) - mImages.RemoveElementAt(mImages.Length()-1); - - // Reset tile-store properties. - mRows = rows; - mColumns = columns; - mSize = aSize; - mTextureState = Allocated; - mCurrentImage = 0; -} - -uint32_t TiledTextureImage::GetTileCount() -{ - return mImages.Length(); -} GLContext::GLFormats GLContext::ChooseGLFormats(ContextFormat& aCF, ColorByteOrder aByteOrder) diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index 5188d4411157..d8a327b26d41 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -33,6 +33,7 @@ #include "nsAutoPtr.h" #include "nsThreadUtils.h" #include "GLContextTypes.h" +#include "GLTextureImage.h" typedef char realGLboolean; @@ -56,368 +57,6 @@ class GLContext; typedef uintptr_t SharedTextureHandle; -/** - * A TextureImage encapsulates a surface that can be drawn to by a - * Thebes gfxContext and (hopefully efficiently!) synchronized to a - * texture in the server. TextureImages are associated with one and - * only one GLContext. - * - * Implementation note: TextureImages attempt to unify two categories - * of backends - * - * (1) proxy to server-side object that can be bound to a texture; - * e.g. Pixmap on X11. - * - * (2) efficient manager of texture memory; e.g. by having clients draw - * into a scratch buffer which is then uploaded with - * glTexSubImage2D(). - */ -class TextureImage -{ - NS_INLINE_DECL_REFCOUNTING(TextureImage) -public: - enum TextureState - { - Created, // Texture created, but has not had glTexImage called to initialize it. - Allocated, // Texture memory exists, but contents are invalid. - Valid // Texture fully ready to use. - }; - - enum Flags { - NoFlags = 0x0, - UseNearestFilter = 0x1, - NeedsYFlip = 0x2, - ForceSingleTile = 0x4 - }; - - typedef gfxASurface::gfxContentType ContentType; - - virtual ~TextureImage() {} - - /** - * Returns a gfxASurface for updating |aRegion| of the client's - * image if successul, NULL if not. |aRegion|'s bounds must fit - * within Size(); its coordinate space (if any) is ignored. If - * the update begins successfully, the returned gfxASurface is - * owned by this. Otherwise, NULL is returned. - * - * |aRegion| is an inout param: the returned region is what the - * client must repaint. Category (1) regions above can - * efficiently handle repaints to "scattered" regions, while (2) - * can only efficiently handle repaints to rects. - * - * Painting the returned surface outside of |aRegion| results - * in undefined behavior. - * - * BeginUpdate() calls cannot be "nested", and each successful - * BeginUpdate() must be followed by exactly one EndUpdate() (see - * below). Failure to do so can leave this in a possibly - * inconsistent state. Unsuccessful BeginUpdate()s must not be - * followed by EndUpdate(). - */ - virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion) = 0; - /** - * Retrieves the region that will require updating, given a - * region that needs to be updated. This can be used for - * making decisions about updating before calling BeginUpdate(). - * - * |aRegion| is an inout param. - */ - virtual void GetUpdateRegion(nsIntRegion& aForRegion) { - } - /** - * Finish the active update and synchronize with the server, if - * necessary. - * - * BeginUpdate() must have been called exactly once before - * EndUpdate(). - */ - virtual void EndUpdate() = 0; - - /** - * The Image may contain several textures for different regions (tiles). - * These functions iterate over each sub texture image tile. - */ - virtual void BeginTileIteration() { - } - - virtual bool NextTile() { - return false; - } - - // Function prototype for a tile iteration callback. Returning false will - // cause iteration to be interrupted (i.e. the corresponding NextTile call - // will return false). - typedef bool (* TileIterationCallback)(TextureImage* aImage, - int aTileNumber, - void* aCallbackData); - - // Sets a callback to be called every time NextTile is called. - virtual void SetIterationCallback(TileIterationCallback aCallback, - void* aCallbackData) { - } - - virtual nsIntRect GetTileRect() { - return nsIntRect(nsIntPoint(0,0), mSize); - } - - virtual GLuint GetTextureID() = 0; - - virtual uint32_t GetTileCount() { - return 1; - } - - /** - * Set this TextureImage's size, and ensure a texture has been - * allocated. Must not be called between BeginUpdate and EndUpdate. - * After a resize, the contents are undefined. - * - * If this isn't implemented by a subclass, it will just perform - * a dummy BeginUpdate/EndUpdate pair. - */ - virtual void Resize(const nsIntSize& aSize) { - mSize = aSize; - nsIntRegion r(nsIntRect(0, 0, aSize.width, aSize.height)); - BeginUpdate(r); - EndUpdate(); - } - - /** - * Mark this texture as having valid contents. Call this after modifying - * the texture contents externally. - */ - virtual void MarkValid() {} - - /** - * aSurf - the source surface to update from - * aRegion - the region in this image to update - * aFrom - offset in the source to update from - */ - virtual bool DirectUpdate(gfxASurface *aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)) = 0; - - virtual void BindTexture(GLenum aTextureUnit) = 0; - virtual void ReleaseTexture() {} - - void BindTextureAndApplyFilter(GLenum aTextureUnit) { - BindTexture(aTextureUnit); - ApplyFilter(); - } - - class ScopedBindTexture - { - public: - ScopedBindTexture(TextureImage *aTexture, GLenum aTextureUnit) : - mTexture(aTexture) - { - if (mTexture) { - mTexture->BindTexture(aTextureUnit); - } - } - - ~ScopedBindTexture() - { - if (mTexture) { - mTexture->ReleaseTexture(); - } - } - - protected: - TextureImage *mTexture; - }; - - class ScopedBindTextureAndApplyFilter - : public ScopedBindTexture - { - public: - ScopedBindTextureAndApplyFilter(TextureImage *aTexture, GLenum aTextureUnit) : - ScopedBindTexture(aTexture, aTextureUnit) - { - if (mTexture) { - mTexture->ApplyFilter(); - } - } - }; - - /** - * Returns the shader program type that should be used to render - * this texture. Only valid after a matching BeginUpdate/EndUpdate - * pair have been called. - */ - virtual ShaderProgramType GetShaderProgramType() - { - return mShaderType; - } - - /** Can be called safely at any time. */ - - /** - * If this TextureImage has a permanent gfxASurface backing, - * return it. Otherwise return NULL. - */ - virtual already_AddRefed GetBackingSurface() - { return NULL; } - - const nsIntSize& GetSize() const { return mSize; } - ContentType GetContentType() const { return mContentType; } - virtual bool InUpdate() const = 0; - GLenum GetWrapMode() const { return mWrapMode; } - - void SetFilter(gfxPattern::GraphicsFilter aFilter) { mFilter = aFilter; } - - /** - * Applies this TextureImage's filter, assuming that its texture is - * the currently bound texture. - */ - virtual void ApplyFilter() = 0; - -protected: - friend class GLContext; - - /** - * After the ctor, the TextureImage is invalid. Implementations - * must allocate resources successfully before returning the new - * TextureImage from GLContext::CreateTextureImage(). That is, - * clients must not be given partially-constructed TextureImages. - */ - TextureImage(const nsIntSize& aSize, - GLenum aWrapMode, ContentType aContentType, - Flags aFlags = NoFlags) - : mSize(aSize) - , mWrapMode(aWrapMode) - , mContentType(aContentType) - , mFilter(gfxPattern::FILTER_GOOD) - , mFlags(aFlags) - {} - - virtual nsIntRect GetSrcTileRect() { - return nsIntRect(nsIntPoint(0,0), mSize); - } - - nsIntSize mSize; - GLenum mWrapMode; - ContentType mContentType; - ShaderProgramType mShaderType; - gfxPattern::GraphicsFilter mFilter; - Flags mFlags; -}; - -/** - * BasicTextureImage is the baseline TextureImage implementation --- - * it updates its texture by allocating a scratch buffer for the - * client to draw into, then using glTexSubImage2D() to upload the new - * pixels. Platforms must provide the code to create a new surface - * into which the updated pixels will be drawn, and the code to - * convert the update surface's pixels into an image on which we can - * glTexSubImage2D(). - */ -class BasicTextureImage - : public TextureImage -{ -public: - typedef gfxASurface::gfxImageFormat ImageFormat; - virtual ~BasicTextureImage(); - - BasicTextureImage(GLuint aTexture, - const nsIntSize& aSize, - GLenum aWrapMode, - ContentType aContentType, - GLContext* aContext, - TextureImage::Flags aFlags = TextureImage::NoFlags) - : TextureImage(aSize, aWrapMode, aContentType, aFlags) - , mTexture(aTexture) - , mTextureState(Created) - , mGLContext(aContext) - , mUpdateOffset(0, 0) - {} - - virtual void BindTexture(GLenum aTextureUnit); - - virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); - virtual void GetUpdateRegion(nsIntRegion& aForRegion); - virtual void EndUpdate(); - virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); - virtual GLuint GetTextureID() { return mTexture; } - // Returns a surface to draw into - virtual already_AddRefed - GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt); - - virtual void MarkValid() { mTextureState = Valid; } - - // Call when drawing into the update surface is complete. - // Returns true if textures should be upload with a relative - // offset - See UploadSurfaceToTexture. - virtual bool FinishedSurfaceUpdate(); - - // Call after surface data has been uploaded to a texture. - virtual void FinishedSurfaceUpload(); - - virtual bool InUpdate() const { return !!mUpdateSurface; } - - virtual void Resize(const nsIntSize& aSize); - - virtual void ApplyFilter(); -protected: - - GLuint mTexture; - TextureState mTextureState; - GLContext* mGLContext; - nsRefPtr mUpdateSurface; - nsIntRegion mUpdateRegion; - - // The offset into the update surface at which the update rect is located. - nsIntPoint mUpdateOffset; -}; - -/** - * A container class that complements many sub TextureImages into a big TextureImage. - * Aims to behave just like the real thing. - */ - -class TiledTextureImage - : public TextureImage -{ -public: - TiledTextureImage(GLContext* aGL, nsIntSize aSize, - TextureImage::ContentType, TextureImage::Flags aFlags = TextureImage::NoFlags); - ~TiledTextureImage(); - void DumpDiv(); - virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); - virtual void GetUpdateRegion(nsIntRegion& aForRegion); - virtual void EndUpdate(); - virtual void Resize(const nsIntSize& aSize); - virtual uint32_t GetTileCount(); - virtual void BeginTileIteration(); - virtual bool NextTile(); - virtual void SetIterationCallback(TileIterationCallback aCallback, - void* aCallbackData); - virtual nsIntRect GetTileRect(); - virtual GLuint GetTextureID() { - return mImages[mCurrentImage]->GetTextureID(); - } - virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); - virtual bool InUpdate() const { return mInUpdate; } - virtual void BindTexture(GLenum); - virtual void ApplyFilter(); - -protected: - virtual nsIntRect GetSrcTileRect(); - - unsigned int mCurrentImage; - TileIterationCallback mIterationCallback; - void* mIterationCallbackData; - nsTArray< nsRefPtr > mImages; - bool mInUpdate; - nsIntSize mSize; - unsigned int mTileSize; - unsigned int mRows, mColumns; - GLContext* mGL; - // A temporary surface to faciliate cross-tile updates. - nsRefPtr mUpdateSurface; - // The region of update requested - nsIntRegion mUpdateRegion; - TextureState mTextureState; -}; - struct THEBES_API ContextFormat { static const ContextFormat BasicRGBA32Format; @@ -1927,12 +1566,7 @@ protected: GLenum aWrapMode, TextureImage::ContentType aContentType, GLContext* aContext, - TextureImage::Flags aFlags = TextureImage::NoFlags) - { - nsRefPtr teximage( - new BasicTextureImage(aTexture, aSize, aWrapMode, aContentType, aContext, aFlags)); - return teximage.forget(); - } + TextureImage::Flags aFlags = TextureImage::NoFlags); bool IsOffscreenSizeAllowed(const gfxIntSize& aSize) const { int32_t biggerDimension = NS_MAX(aSize.width, aSize.height); diff --git a/gfx/gl/GLContextProviderEGL.cpp b/gfx/gl/GLContextProviderEGL.cpp index 85bb1c6aad10..0f7b7cbc1efd 100644 --- a/gfx/gl/GLContextProviderEGL.cpp +++ b/gfx/gl/GLContextProviderEGL.cpp @@ -1072,6 +1072,7 @@ bool GLContextEGL::AttachSharedHandle(SharedTextureShareType shareType, return true; } + bool GLContextEGL::BindTex2DOffscreen(GLContext *aOffscreen) { diff --git a/gfx/gl/GLTextureImage.cpp b/gfx/gl/GLTextureImage.cpp new file mode 100644 index 000000000000..fbfef04fa4e6 --- /dev/null +++ b/gfx/gl/GLTextureImage.cpp @@ -0,0 +1,573 @@ +/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */ +/* 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 "GLTextureImage.h" +#include "GLContext.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" + +using namespace mozilla::gl; + +already_AddRefed +TextureImage::Create(GLContext* gl, + const nsIntSize& size, + TextureImage::ContentType contentType, + GLenum wrapMode, + TextureImage::Flags flags) +{ + return gl->CreateTextureImage(size, contentType, wrapMode, flags); +} + +BasicTextureImage::~BasicTextureImage() +{ + GLContext *ctx = mGLContext; + if (ctx->IsDestroyed() || !ctx->IsOwningThreadCurrent()) { + ctx = ctx->GetSharedContext(); + } + + // If we have a context, then we need to delete the texture; + // if we don't have a context (either real or shared), + // then they went away when the contex was deleted, because it + // was the only one that had access to it. + if (ctx && !ctx->IsDestroyed()) { + mGLContext->MakeCurrent(); + mGLContext->fDeleteTextures(1, &mTexture); + } +} + +gfxASurface* +BasicTextureImage::BeginUpdate(nsIntRegion& aRegion) +{ + NS_ASSERTION(!mUpdateSurface, "BeginUpdate() without EndUpdate()?"); + + // determine the region the client will need to repaint + if (mGLContext->CanUploadSubTextures()) { + GetUpdateRegion(aRegion); + } else { + aRegion = nsIntRect(nsIntPoint(0, 0), mSize); + } + + mUpdateRegion = aRegion; + + nsIntRect rgnSize = mUpdateRegion.GetBounds(); + if (!nsIntRect(nsIntPoint(0, 0), mSize).Contains(rgnSize)) { + NS_ERROR("update outside of image"); + return NULL; + } + + ImageFormat format = + (GetContentType() == gfxASurface::CONTENT_COLOR) ? + gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; + mUpdateSurface = + GetSurfaceForUpdate(gfxIntSize(rgnSize.width, rgnSize.height), format); + + if (!mUpdateSurface || mUpdateSurface->CairoStatus()) { + mUpdateSurface = NULL; + return NULL; + } + + mUpdateSurface->SetDeviceOffset(gfxPoint(-rgnSize.x, -rgnSize.y)); + + return mUpdateSurface; +} + +void +BasicTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) +{ + // if the texture hasn't been initialized yet, or something important + // changed, we need to recreate our backing surface and force the + // client to paint everything + if (mTextureState != Valid) + aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); +} + +void +BasicTextureImage::EndUpdate() +{ + NS_ASSERTION(!!mUpdateSurface, "EndUpdate() without BeginUpdate()?"); + + // FIXME: this is the slow boat. Make me fast (with GLXPixmap?). + + // Undo the device offset that BeginUpdate set; doesn't much matter for us here, + // but important if we ever do anything directly with the surface. + mUpdateSurface->SetDeviceOffset(gfxPoint(0, 0)); + + bool relative = FinishedSurfaceUpdate(); + + mShaderType = + mGLContext->UploadSurfaceToTexture(mUpdateSurface, + mUpdateRegion, + mTexture, + mTextureState == Created, + mUpdateOffset, + relative); + FinishedSurfaceUpload(); + + mUpdateSurface = nullptr; + mTextureState = Valid; +} + +void +BasicTextureImage::BindTexture(GLenum aTextureUnit) +{ + mGLContext->fActiveTexture(aTextureUnit); + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); + mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); +} + +void +BasicTextureImage::ApplyFilter() +{ + mGLContext->ApplyFilterToBoundTexture(mFilter); +} + + +already_AddRefed +BasicTextureImage::GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt) +{ + return gfxPlatform::GetPlatform()-> + CreateOffscreenSurface(aSize, gfxASurface::ContentFromFormat(aFmt)); +} + +bool +BasicTextureImage::FinishedSurfaceUpdate() +{ + return false; +} + +void +BasicTextureImage::FinishedSurfaceUpload() +{ +} + +bool +BasicTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) +{ + nsIntRect bounds = aRegion.GetBounds(); + nsIntRegion region; + if (mTextureState != Valid) { + bounds = nsIntRect(0, 0, mSize.width, mSize.height); + region = nsIntRegion(bounds); + } else { + region = aRegion; + } + + mShaderType = + mGLContext->UploadSurfaceToTexture(aSurf, + region, + mTexture, + mTextureState == Created, + bounds.TopLeft() + aFrom, + false); + mTextureState = Valid; + return true; +} + +void +BasicTextureImage::Resize(const nsIntSize& aSize) +{ + NS_ASSERTION(!mUpdateSurface, "Resize() while in update?"); + + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); + + mGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D, + 0, + LOCAL_GL_RGBA, + aSize.width, + aSize.height, + 0, + LOCAL_GL_RGBA, + LOCAL_GL_UNSIGNED_BYTE, + NULL); + + mTextureState = Allocated; + mSize = aSize; +} + +TiledTextureImage::TiledTextureImage(GLContext* aGL, + nsIntSize aSize, + TextureImage::ContentType aContentType, + TextureImage::Flags aFlags) + : TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aFlags) + , mCurrentImage(0) + , mIterationCallback(nullptr) + , mInUpdate(false) + , mRows(0) + , mColumns(0) + , mGL(aGL) + , mTextureState(Created) +{ + mTileSize = (!(aFlags & TextureImage::ForceSingleTile) && mGL->WantsSmallTiles()) + ? 256 : mGL->GetMaxTextureSize(); + if (aSize != nsIntSize(0,0)) { + Resize(aSize); + } +} + +TiledTextureImage::~TiledTextureImage() +{ +} + +bool +TiledTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) +{ + nsIntRegion region; + + if (mTextureState != Valid) { + nsIntRect bounds = nsIntRect(0, 0, mSize.width, mSize.height); + region = nsIntRegion(bounds); + } else { + region = aRegion; + } + + bool result = true; + int oldCurrentImage = mCurrentImage; + BeginTileIteration(); + do { + nsIntRect tileRect = GetSrcTileRect(); + int xPos = tileRect.x; + int yPos = tileRect.y; + + nsIntRegion tileRegion; + tileRegion.And(region, tileRect); // intersect with tile + + if (tileRegion.IsEmpty()) + continue; + + if (mGL->CanUploadSubTextures()) { + tileRegion.MoveBy(-xPos, -yPos); // translate into tile local space + } else { + // If sub-textures are unsupported, expand to tile boundaries + tileRect.x = tileRect.y = 0; + tileRegion = nsIntRegion(tileRect); + } + + result &= mImages[mCurrentImage]-> + DirectUpdate(aSurf, tileRegion, aFrom + nsIntPoint(xPos, yPos)); + + if (mCurrentImage == mImages.Length() - 1) { + // We know we're done, but we still need to ensure that the callback + // gets called (e.g. to update the uploaded region). + NextTile(); + break; + } + // Override a callback cancelling iteration if the texture wasn't valid. + // We need to force the update in that situation, or we may end up + // showing invalid/out-of-date texture data. + } while (NextTile() || (mTextureState != Valid)); + mCurrentImage = oldCurrentImage; + + mShaderType = mImages[0]->GetShaderProgramType(); + mTextureState = Valid; + return result; +} + +void +TiledTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) +{ + if (mTextureState != Valid) { + // if the texture hasn't been initialized yet, or something important + // changed, we need to recreate our backing surface and force the + // client to paint everything + aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); + return; + } + + nsIntRegion newRegion; + + // We need to query each texture with the region it will be drawing and + // set aForRegion to be the combination of all of these regions + for (unsigned i = 0; i < mImages.Length(); i++) { + int xPos = (i % mColumns) * mTileSize; + int yPos = (i / mColumns) * mTileSize; + nsIntRect imageRect = nsIntRect(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); + + if (aForRegion.Intersects(imageRect)) { + // Make a copy of the region + nsIntRegion subRegion; + subRegion.And(aForRegion, imageRect); + // Translate it into tile-space + subRegion.MoveBy(-xPos, -yPos); + // Query region + mImages[i]->GetUpdateRegion(subRegion); + // Translate back + subRegion.MoveBy(xPos, yPos); + // Add to the accumulated region + newRegion.Or(newRegion, subRegion); + } + } + + aForRegion = newRegion; +} + +gfxASurface* +TiledTextureImage::BeginUpdate(nsIntRegion& aRegion) +{ + NS_ASSERTION(!mInUpdate, "nested update"); + mInUpdate = true; + + // Note, we don't call GetUpdateRegion here as if the updated region is + // fully contained in a single tile, we get to avoid iterating through + // the tiles again (and a little copying). + if (mTextureState != Valid) + { + // if the texture hasn't been initialized yet, or something important + // changed, we need to recreate our backing surface and force the + // client to paint everything + aRegion = nsIntRect(nsIntPoint(0, 0), mSize); + } + + nsIntRect bounds = aRegion.GetBounds(); + + for (unsigned i = 0; i < mImages.Length(); i++) { + int xPos = (i % mColumns) * mTileSize; + int yPos = (i / mColumns) * mTileSize; + nsIntRegion imageRegion = nsIntRegion(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); + + // a single Image can handle this update request + if (imageRegion.Contains(aRegion)) { + // adjust for tile offset + aRegion.MoveBy(-xPos, -yPos); + // forward the actual call + nsRefPtr surface = mImages[i]->BeginUpdate(aRegion); + // caller expects container space + aRegion.MoveBy(xPos, yPos); + // Correct the device offset + gfxPoint offset = surface->GetDeviceOffset(); + surface->SetDeviceOffset(gfxPoint(offset.x - xPos, + offset.y - yPos)); + // we don't have a temp surface + mUpdateSurface = nullptr; + // remember which image to EndUpdate + mCurrentImage = i; + return surface.get(); + } + } + + // Get the real updated region, taking into account the capabilities of + // each TextureImage tile + GetUpdateRegion(aRegion); + mUpdateRegion = aRegion; + bounds = aRegion.GetBounds(); + + // update covers multiple Images - create a temp surface to paint in + gfxASurface::gfxImageFormat format = + (GetContentType() == gfxASurface::CONTENT_COLOR) ? + gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; + mUpdateSurface = gfxPlatform::GetPlatform()-> + CreateOffscreenSurface(gfxIntSize(bounds.width, bounds.height), gfxASurface::ContentFromFormat(format)); + mUpdateSurface->SetDeviceOffset(gfxPoint(-bounds.x, -bounds.y)); + + return mUpdateSurface; +} + +void +TiledTextureImage::EndUpdate() +{ + NS_ASSERTION(mInUpdate, "EndUpdate not in update"); + if (!mUpdateSurface) { // update was to a single TextureImage + mImages[mCurrentImage]->EndUpdate(); + mInUpdate = false; + mTextureState = Valid; + mShaderType = mImages[mCurrentImage]->GetShaderProgramType(); + return; + } + + // upload tiles from temp surface + for (unsigned i = 0; i < mImages.Length(); i++) { + int xPos = (i % mColumns) * mTileSize; + int yPos = (i / mColumns) * mTileSize; + nsIntRect imageRect = nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize()); + + nsIntRegion subregion; + subregion.And(mUpdateRegion, imageRect); + if (subregion.IsEmpty()) + continue; + subregion.MoveBy(-xPos, -yPos); // Tile-local space + // copy tile from temp surface + gfxASurface* surf = mImages[i]->BeginUpdate(subregion); + nsRefPtr ctx = new gfxContext(surf); + gfxUtils::ClipToRegion(ctx, subregion); + ctx->SetOperator(gfxContext::OPERATOR_SOURCE); + ctx->SetSource(mUpdateSurface, gfxPoint(-xPos, -yPos)); + ctx->Paint(); + mImages[i]->EndUpdate(); + } + + mUpdateSurface = nullptr; + mInUpdate = false; + mShaderType = mImages[0]->GetShaderProgramType(); + mTextureState = Valid; +} + +void TiledTextureImage::BeginTileIteration() +{ + mCurrentImage = 0; +} + +bool TiledTextureImage::NextTile() +{ + bool continueIteration = true; + + if (mIterationCallback) + continueIteration = mIterationCallback(this, mCurrentImage, + mIterationCallbackData); + + if (mCurrentImage + 1 < mImages.Length()) { + mCurrentImage++; + return continueIteration; + } + return false; +} + +void TiledTextureImage::SetIterationCallback(TileIterationCallback aCallback, + void* aCallbackData) +{ + mIterationCallback = aCallback; + mIterationCallbackData = aCallbackData; +} + +nsIntRect TiledTextureImage::GetTileRect() +{ + nsIntRect rect = mImages[mCurrentImage]->GetTileRect(); + unsigned int xPos = (mCurrentImage % mColumns) * mTileSize; + unsigned int yPos = (mCurrentImage / mColumns) * mTileSize; + rect.MoveBy(xPos, yPos); + return rect; +} + +nsIntRect TiledTextureImage::GetSrcTileRect() +{ + nsIntRect rect = GetTileRect(); + unsigned int srcY = mFlags & NeedsYFlip + ? mSize.height - rect.height - rect.y + : rect.y; + return nsIntRect(rect.x, srcY, rect.width, rect.height); +} + +void +TiledTextureImage::BindTexture(GLenum aTextureUnit) +{ + mImages[mCurrentImage]->BindTexture(aTextureUnit); +} + +void +TiledTextureImage::ApplyFilter() +{ + mGL->ApplyFilterToBoundTexture(mFilter); +} + +/* + * Resize, trying to reuse tiles. The reuse strategy is to decide on reuse per + * column. A tile on a column is reused if it hasn't changed size, otherwise it + * is discarded/replaced. Extra tiles on a column are pruned after iterating + * each column, and extra rows are pruned after iteration over the entire image + * finishes. + */ +void TiledTextureImage::Resize(const nsIntSize& aSize) +{ + if (mSize == aSize && mTextureState != Created) { + return; + } + + // calculate rows and columns, rounding up + unsigned int columns = (aSize.width + mTileSize - 1) / mTileSize; + unsigned int rows = (aSize.height + mTileSize - 1) / mTileSize; + + // Iterate over old tile-store and insert/remove tiles as necessary + int row; + unsigned int i = 0; + for (row = 0; row < (int)rows; row++) { + // If we've gone beyond how many rows there were before, set mColumns to + // zero so that we only create new tiles. + if (row >= (int)mRows) + mColumns = 0; + + // Similarly, if we're on the last row of old tiles and the height has + // changed, discard all tiles in that row. + // This will cause the pruning of columns not to work, but we don't need + // to worry about that, as no more tiles will be reused past this point + // anyway. + if ((row == (int)mRows - 1) && (aSize.height != mSize.height)) + mColumns = 0; + + int col; + for (col = 0; col < (int)columns; col++) { + nsIntSize size( // use tilesize first, then the remainder + (col+1) * mTileSize > (unsigned int)aSize.width ? aSize.width % mTileSize : mTileSize, + (row+1) * mTileSize > (unsigned int)aSize.height ? aSize.height % mTileSize : mTileSize); + + bool replace = false; + + // Check if we can re-use old tiles. + if (col < (int)mColumns) { + // Reuse an existing tile. If the tile is an end-tile and the + // width differs, replace it instead. + if (mSize.width != aSize.width) { + if (col == (int)mColumns - 1) { + // Tile at the end of the old column, replace it with + // a new one. + replace = true; + } else if (col == (int)columns - 1) { + // Tile at the end of the new column, create a new one. + } else { + // Before the last column on both the old and new sizes, + // reuse existing tile. + i++; + continue; + } + } else { + // Width hasn't changed, reuse existing tile. + i++; + continue; + } + } + + // Create a new tile. + nsRefPtr teximg = + mGL->TileGenFunc(size, mContentType, mFlags); + if (replace) + mImages.ReplaceElementAt(i, teximg.forget()); + else + mImages.InsertElementAt(i, teximg.forget()); + i++; + } + + // Prune any unused tiles on the end of the column. + if (row < (int)mRows) { + for (col = (int)mColumns - col; col > 0; col--) { + mImages.RemoveElementAt(i); + } + } + } + + // Prune any unused tiles at the end of the store. + unsigned int length = mImages.Length(); + for (; i < length; i++) + mImages.RemoveElementAt(mImages.Length()-1); + + // Reset tile-store properties. + mRows = rows; + mColumns = columns; + mSize = aSize; + mTextureState = Allocated; + mCurrentImage = 0; +} + +uint32_t TiledTextureImage::GetTileCount() +{ + return mImages.Length(); +} + +TextureImage::ScopedBindTexture::ScopedBindTexture(TextureImage* aTexture, + GLenum aTextureUnit) + : mTexture(aTexture) +{ + if (mTexture) { + MOZ_ASSERT(aTextureUnit >= LOCAL_GL_TEXTURE0); + mTexture->BindTexture(aTextureUnit); + } +} diff --git a/gfx/gl/GLTextureImage.h b/gfx/gl/GLTextureImage.h new file mode 100644 index 000000000000..4217778fb0ef --- /dev/null +++ b/gfx/gl/GLTextureImage.h @@ -0,0 +1,385 @@ +/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */ +/* 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 GLTEXTUREIMAGE_H_ +#define GLTEXTUREIMAGE_H_ + +#include "nsAutoPtr.h" +#include "nsRegion.h" +#include "gfxASurface.h" +#include "GLContextTypes.h" +#include "gfxPattern.h" + +namespace mozilla { +namespace gl { +class GLContext; + +/** + * A TextureImage encapsulates a surface that can be drawn to by a + * Thebes gfxContext and (hopefully efficiently!) synchronized to a + * texture in the server. TextureImages are associated with one and + * only one GLContext. + * + * Implementation note: TextureImages attempt to unify two categories + * of backends + * + * (1) proxy to server-side object that can be bound to a texture; + * e.g. Pixmap on X11. + * + * (2) efficient manager of texture memory; e.g. by having clients draw + * into a scratch buffer which is then uploaded with + * glTexSubImage2D(). + */ +class TextureImage +{ + NS_INLINE_DECL_REFCOUNTING(TextureImage) +public: + enum TextureState + { + Created, // Texture created, but has not had glTexImage called to initialize it. + Allocated, // Texture memory exists, but contents are invalid. + Valid // Texture fully ready to use. + }; + + enum Flags { + NoFlags = 0x0, + UseNearestFilter = 0x1, + NeedsYFlip = 0x2, + ForceSingleTile = 0x4 + }; + + typedef gfxASurface::gfxContentType ContentType; + + static already_AddRefed Create( + GLContext* gl, + const nsIntSize& aSize, + TextureImage::ContentType aContentType, + GLenum aWrapMode, + TextureImage::Flags aFlags = TextureImage::NoFlags); + + virtual ~TextureImage() {} + + /** + * Returns a gfxASurface for updating |aRegion| of the client's + * image if successul, NULL if not. |aRegion|'s bounds must fit + * within Size(); its coordinate space (if any) is ignored. If + * the update begins successfully, the returned gfxASurface is + * owned by this. Otherwise, NULL is returned. + * + * |aRegion| is an inout param: the returned region is what the + * client must repaint. Category (1) regions above can + * efficiently handle repaints to "scattered" regions, while (2) + * can only efficiently handle repaints to rects. + * + * Painting the returned surface outside of |aRegion| results + * in undefined behavior. + * + * BeginUpdate() calls cannot be "nested", and each successful + * BeginUpdate() must be followed by exactly one EndUpdate() (see + * below). Failure to do so can leave this in a possibly + * inconsistent state. Unsuccessful BeginUpdate()s must not be + * followed by EndUpdate(). + */ + virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion) = 0; + /** + * Retrieves the region that will require updating, given a + * region that needs to be updated. This can be used for + * making decisions about updating before calling BeginUpdate(). + * + * |aRegion| is an inout param. + */ + virtual void GetUpdateRegion(nsIntRegion& aForRegion) { + } + /** + * Finish the active update and synchronize with the server, if + * necessary. + * + * BeginUpdate() must have been called exactly once before + * EndUpdate(). + */ + virtual void EndUpdate() = 0; + + /** + * The Image may contain several textures for different regions (tiles). + * These functions iterate over each sub texture image tile. + */ + virtual void BeginTileIteration() { + } + + virtual bool NextTile() { + return false; + } + + // Function prototype for a tile iteration callback. Returning false will + // cause iteration to be interrupted (i.e. the corresponding NextTile call + // will return false). + typedef bool (* TileIterationCallback)(TextureImage* aImage, + int aTileNumber, + void* aCallbackData); + + // Sets a callback to be called every time NextTile is called. + virtual void SetIterationCallback(TileIterationCallback aCallback, + void* aCallbackData) { + } + + virtual nsIntRect GetTileRect() { + return nsIntRect(nsIntPoint(0,0), mSize); + } + + virtual GLuint GetTextureID() = 0; + + virtual uint32_t GetTileCount() { + return 1; + } + + /** + * Set this TextureImage's size, and ensure a texture has been + * allocated. Must not be called between BeginUpdate and EndUpdate. + * After a resize, the contents are undefined. + * + * If this isn't implemented by a subclass, it will just perform + * a dummy BeginUpdate/EndUpdate pair. + */ + virtual void Resize(const nsIntSize& aSize) { + mSize = aSize; + nsIntRegion r(nsIntRect(0, 0, aSize.width, aSize.height)); + BeginUpdate(r); + EndUpdate(); + } + + /** + * Mark this texture as having valid contents. Call this after modifying + * the texture contents externally. + */ + virtual void MarkValid() {} + + /** + * aSurf - the source surface to update from + * aRegion - the region in this image to update + * aFrom - offset in the source to update from + */ + virtual bool DirectUpdate(gfxASurface *aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)) = 0; + + virtual void BindTexture(GLenum aTextureUnit) = 0; + virtual void ReleaseTexture() {} + + void BindTextureAndApplyFilter(GLenum aTextureUnit) { + BindTexture(aTextureUnit); + ApplyFilter(); + } + + class ScopedBindTexture + { + public: + ScopedBindTexture(TextureImage *aTexture, GLenum aTextureUnit); + + ~ScopedBindTexture() + { + if (mTexture) { + mTexture->ReleaseTexture(); + } + } + + protected: + TextureImage *mTexture; + }; + + class ScopedBindTextureAndApplyFilter + : public ScopedBindTexture + { + public: + ScopedBindTextureAndApplyFilter(TextureImage *aTexture, GLenum aTextureUnit) : + ScopedBindTexture(aTexture, aTextureUnit) + { + if (mTexture) { + mTexture->ApplyFilter(); + } + } + }; + + /** + * Returns the shader program type that should be used to render + * this texture. Only valid after a matching BeginUpdate/EndUpdate + * pair have been called. + */ + virtual ShaderProgramType GetShaderProgramType() + { + return mShaderType; + } + + /** Can be called safely at any time. */ + + /** + * If this TextureImage has a permanent gfxASurface backing, + * return it. Otherwise return NULL. + */ + virtual already_AddRefed GetBackingSurface() + { return NULL; } + + const nsIntSize& GetSize() const { return mSize; } + ContentType GetContentType() const { return mContentType; } + virtual bool InUpdate() const = 0; + GLenum GetWrapMode() const { return mWrapMode; } + + void SetFilter(gfxPattern::GraphicsFilter aFilter) { mFilter = aFilter; } + + /** + * Applies this TextureImage's filter, assuming that its texture is + * the currently bound texture. + */ + virtual void ApplyFilter() = 0; + +protected: + friend class GLContext; + + /** + * After the ctor, the TextureImage is invalid. Implementations + * must allocate resources successfully before returning the new + * TextureImage from GLContext::CreateTextureImage(). That is, + * clients must not be given partially-constructed TextureImages. + */ + TextureImage(const nsIntSize& aSize, + GLenum aWrapMode, ContentType aContentType, + Flags aFlags = NoFlags) + : mSize(aSize) + , mWrapMode(aWrapMode) + , mContentType(aContentType) + , mFilter(gfxPattern::FILTER_GOOD) + , mFlags(aFlags) + {} + + virtual nsIntRect GetSrcTileRect() { + return nsIntRect(nsIntPoint(0,0), mSize); + } + + nsIntSize mSize; + GLenum mWrapMode; + ContentType mContentType; + ShaderProgramType mShaderType; + gfxPattern::GraphicsFilter mFilter; + Flags mFlags; +}; + +/** + * BasicTextureImage is the baseline TextureImage implementation --- + * it updates its texture by allocating a scratch buffer for the + * client to draw into, then using glTexSubImage2D() to upload the new + * pixels. Platforms must provide the code to create a new surface + * into which the updated pixels will be drawn, and the code to + * convert the update surface's pixels into an image on which we can + * glTexSubImage2D(). + */ +class BasicTextureImage + : public TextureImage +{ +public: + typedef gfxASurface::gfxImageFormat ImageFormat; + virtual ~BasicTextureImage(); + + BasicTextureImage(GLuint aTexture, + const nsIntSize& aSize, + GLenum aWrapMode, + ContentType aContentType, + GLContext* aContext, + TextureImage::Flags aFlags = TextureImage::NoFlags) + : TextureImage(aSize, aWrapMode, aContentType, aFlags) + , mTexture(aTexture) + , mTextureState(Created) + , mGLContext(aContext) + , mUpdateOffset(0, 0) + {} + + virtual void BindTexture(GLenum aTextureUnit); + + virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); + virtual void GetUpdateRegion(nsIntRegion& aForRegion); + virtual void EndUpdate(); + virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); + virtual GLuint GetTextureID() { return mTexture; } + // Returns a surface to draw into + virtual already_AddRefed + GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt); + + virtual void MarkValid() { mTextureState = Valid; } + + // Call when drawing into the update surface is complete. + // Returns true if textures should be upload with a relative + // offset - See UploadSurfaceToTexture. + virtual bool FinishedSurfaceUpdate(); + + // Call after surface data has been uploaded to a texture. + virtual void FinishedSurfaceUpload(); + + virtual bool InUpdate() const { return !!mUpdateSurface; } + + virtual void Resize(const nsIntSize& aSize); + + virtual void ApplyFilter(); +protected: + + GLuint mTexture; + TextureState mTextureState; + GLContext* mGLContext; + nsRefPtr mUpdateSurface; + nsIntRegion mUpdateRegion; + + // The offset into the update surface at which the update rect is located. + nsIntPoint mUpdateOffset; +}; + +/** + * A container class that complements many sub TextureImages into a big TextureImage. + * Aims to behave just like the real thing. + */ + +class TiledTextureImage + : public TextureImage +{ +public: + TiledTextureImage(GLContext* aGL, nsIntSize aSize, + TextureImage::ContentType, TextureImage::Flags aFlags = TextureImage::NoFlags); + ~TiledTextureImage(); + void DumpDiv(); + virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); + virtual void GetUpdateRegion(nsIntRegion& aForRegion); + virtual void EndUpdate(); + virtual void Resize(const nsIntSize& aSize); + virtual uint32_t GetTileCount(); + virtual void BeginTileIteration(); + virtual bool NextTile(); + virtual void SetIterationCallback(TileIterationCallback aCallback, + void* aCallbackData); + virtual nsIntRect GetTileRect(); + virtual GLuint GetTextureID() { + return mImages[mCurrentImage]->GetTextureID(); + } + virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); + virtual bool InUpdate() const { return mInUpdate; } + virtual void BindTexture(GLenum); + virtual void ApplyFilter(); + +protected: + virtual nsIntRect GetSrcTileRect(); + + unsigned int mCurrentImage; + TileIterationCallback mIterationCallback; + void* mIterationCallbackData; + nsTArray< nsRefPtr > mImages; + bool mInUpdate; + nsIntSize mSize; + unsigned int mTileSize; + unsigned int mRows, mColumns; + GLContext* mGL; + // A temporary surface to faciliate cross-tile updates. + nsRefPtr mUpdateSurface; + // The region of update requested + nsIntRegion mUpdateRegion; + TextureState mTextureState; +}; + +} // namespace gl +} // namespace mozilla + +#endif /* GLTEXTUREIMAGE_H_ */ diff --git a/gfx/gl/Makefile.in b/gfx/gl/Makefile.in index e3729a278007..8f1cde8fe9de 100644 --- a/gfx/gl/Makefile.in +++ b/gfx/gl/Makefile.in @@ -24,6 +24,7 @@ EXPORTS = \ GLContextProviderImpl.h \ GLLibraryLoader.h \ ForceDiscreteGPUHelperCGL.h \ + GLTextureImage.h \ $(NULL) ifdef MOZ_X11 @@ -48,6 +49,7 @@ CPPSRCS = \ GLContext.cpp \ GLContextUtils.cpp \ GLLibraryLoader.cpp \ + GLTextureImage.cpp \ $(NULL) GL_PROVIDER = Null diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index f8c005534d8d..403183554a59 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -51,6 +51,7 @@ #include "nsRegion.h" #include "Layers.h" #include "LayerManagerOGL.h" +#include "GLTextureImage.h" #include "mozilla/layers/CompositorCocoaWidgetHelper.h" #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" @@ -1758,10 +1759,11 @@ nsChildView::DrawWindowOverlay(LayerManager* aManager, nsIntRect aRect) } if (!mResizerImage) { - mResizerImage = manager->gl()->CreateTextureImage(nsIntSize(15, 15), - gfxASurface::CONTENT_COLOR_ALPHA, - LOCAL_GL_CLAMP_TO_EDGE, - TextureImage::UseNearestFilter); + mResizerImage = TextureImage::Create(manager->gl(), + nsIntSize(15, 15), + gfxASurface::CONTENT_COLOR_ALPHA, + LOCAL_GL_CLAMP_TO_EDGE, + TextureImage::UseNearestFilter); // Creation of texture images can fail. if (!mResizerImage) From 5a0f0bbd1ab11d2b3663f5c66128cded22703df3 Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Mon, 26 Nov 2012 16:20:03 -0500 Subject: [PATCH 017/160] Backed out changeset ac304d3c250e (bug 806742) because of test failures --- .../places/tests/browser/Makefile.in | 6 - .../places/tests/browser/browser_bug248970.js | 256 ------------------ 2 files changed, 262 deletions(-) delete mode 100644 toolkit/components/places/tests/browser/browser_bug248970.js diff --git a/toolkit/components/places/tests/browser/Makefile.in b/toolkit/components/places/tests/browser/Makefile.in index 73f8edf55f50..e06863f98c2a 100644 --- a/toolkit/components/places/tests/browser/Makefile.in +++ b/toolkit/components/places/tests/browser/Makefile.in @@ -30,12 +30,6 @@ MOCHITEST_BROWSER_FILES = \ colorAnalyzer/localeGeneric.png \ $(NULL) -ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING -MOCHITEST_BROWSER_FILES += \ - browser_bug248970.js \ - $(NULL) -endif - # These are files that need to be loaded via the HTTP proxy server # Access them through http://example.com/ MOCHITEST_FILES = \ diff --git a/toolkit/components/places/tests/browser/browser_bug248970.js b/toolkit/components/places/tests/browser/browser_bug248970.js deleted file mode 100644 index 82e51657e36f..000000000000 --- a/toolkit/components/places/tests/browser/browser_bug248970.js +++ /dev/null @@ -1,256 +0,0 @@ -/* 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/. */ - -// This test performs checks on the history testing area as outlined -// https://wiki.mozilla.org/Firefox3.1/PrivateBrowsing/TestPlan#History -// http://developer.mozilla.org/en/Using_the_Places_history_service - -let visitedURIs = [ - "http://www.test-link.com/", - "http://www.test-typed.com/", - "http://www.test-bookmark.com/", - "http://www.test-redirect-permanent.com/", - "http://www.test-redirect-temporary.com/", - "http://www.test-embed.com/", - "http://www.test-framed.com/", - "http://www.test-download.com/" -]; - -function test() { - waitForExplicitFinish(); - - let windowsToClose = []; - let windowCount = 0; - let placeItemsCount = getPlacesItemsCount(window); - - registerCleanupFunction(function() { - windowsToClose.forEach(function(win) { - win.close(); - }); - }); - - function testOnWindow(aIsPrivate, aCallback) { - whenNewWindowLoaded(aIsPrivate, function(win) { - windowsToClose.push(win); - checkPlaces(win, aIsPrivate, aCallback); - }); - } - - function checkPlaces(aWindow, aIsPrivate, aCallback) { - // Updates the place items count - placeItemsCount = getPlacesItemsCount(aWindow); - // History items should be retrievable by query - checkHistoryItems(aWindow); - - // Create Bookmark - let bookmarkTitle = "title " + windowCount; - let bookmarkKeyword = "keyword " + windowCount; - let bookmarkUri = NetUtil.newURI("http://test-a-" + windowCount + ".com/"); - createBookmark(aWindow, bookmarkUri, bookmarkTitle, bookmarkKeyword); - placeItemsCount++; - windowCount++; - ok(aWindow.PlacesUtils.bookmarks.isBookmarked(bookmarkUri), - "Bookmark should be bookmarked, data should be retrievable"); - is(bookmarkKeyword, aWindow.PlacesUtils.bookmarks.getKeywordForURI(bookmarkUri), - "Check bookmark uri keyword"); - is(getPlacesItemsCount(aWindow), placeItemsCount, - "Check the new bookmark items count"); - is(isBookmarkAltered(aWindow), false, "Check if bookmark has been visited"); - - aCallback(); - } - - clearHistory(function() { - // History database should be empty - is(PlacesUtils.history.hasHistoryEntries, false, - "History database should be empty"); - // Create a handful of history items with various visit types - fillHistoryVisitedURI(window); - placeItemsCount += 7; - // History database should have entries - is(PlacesUtils.history.hasHistoryEntries, true, - "History database should have entries"); - // We added 7 new items to history. - is(getPlacesItemsCount(window), placeItemsCount, "Check the total items count"); - // Test on windows. - testOnWindow(false, function() { - testOnWindow(true, function() { - testOnWindow(false, finish); - }); - }); - }); -} - -function whenNewWindowLoaded(aIsPrivate, aCallback) { - let win = OpenBrowserWindow({private: aIsPrivate}); - win.addEventListener("load", function onLoad() { - win.removeEventListener("load", onLoad, false); - aCallback(win); - }, false); -} - -function clearHistory(aCallback) { - Services.obs.addObserver(function observer(aSubject, aTopic, aData) { - Services.obs.removeObserver(observer, PlacesUtils.TOPIC_EXPIRATION_FINISHED); - aCallback(); - }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false); - PlacesUtils.bhistory.removeAllPages(); -} - -/** - * Function performs a really simple query on our places entries, - * and makes sure that the number of entries equal num_places_entries. - */ -function getPlacesItemsCount(aWin){ - // Get bookmarks count - let options = aWin.PlacesUtils.history.getNewQueryOptions(); - options.includeHidden = true; - options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS; - let root = aWin.PlacesUtils.history.executeQuery( - aWin.PlacesUtils.history.getNewQuery(), options).root; - root.containerOpen = true; - let cc = root.childCount; - root.containerOpen = false; - - // Get history item count - options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY; - let root = aWin.PlacesUtils.history.executeQuery( - aWin.PlacesUtils.history.getNewQuery(), options).root; - root.containerOpen = true; - cc += root.childCount; - root.containerOpen = false; - - return cc; -} - -function addVisit(aWin, aURI, aType) { - aWin.PlacesUtils.history.addVisit( - NetUtil.newURI(aURI), Date.now() * 1000, null, aType, false, 0); -} - -function fillHistoryVisitedURI(aWin) { - aWin.PlacesUtils.history.runInBatchMode({ - runBatched: function (aUserData) { - addVisit(aWin, visitedURIs[0], PlacesUtils.history.TRANSITION_LINK); - addVisit(aWin, visitedURIs[1], PlacesUtils.history.TRANSITION_TYPED); - addVisit(aWin, visitedURIs[2], PlacesUtils.history.TRANSITION_BOOKMARK); - addVisit(aWin, visitedURIs[3], PlacesUtils.history.TRANSITION_REDIRECT_PERMANENT); - addVisit(aWin, visitedURIs[4], PlacesUtils.history.TRANSITION_REDIRECT_TEMPORARY); - addVisit(aWin, visitedURIs[5], PlacesUtils.history.TRANSITION_EMBED); - addVisit(aWin, visitedURIs[6], PlacesUtils.history.TRANSITION_FRAMED_LINK); - addVisit(aWin, visitedURIs[7], PlacesUtils.history.TRANSITION_DOWNLOAD); - } - }, null); -} - -function checkHistoryItems(aWin) { - visitedURIs.forEach(function (visitedUri) { - ok(aWin.PlacesUtils.bhistory.isVisited(NetUtil.newURI(visitedUri)), ""); - if (/embed/.test(visitedUri)) { - is(!!pageInDatabase(visitedUri), false, "Check if URI is in database"); - } else { - ok(!!pageInDatabase(visitedUri), "Check if URI is in database"); - } - }); -} - -/** - * Checks if an address is found in the database. - * @param aURI - * nsIURI or address to look for. - * @return place id of the page or 0 if not found - */ -function pageInDatabase(aURI) { - let url = (aURI instanceof Ci.nsIURI ? aURI.spec : aURI); - let stmt = DBConn().createStatement( - "SELECT id FROM moz_places WHERE url = :url" - ); - stmt.params.url = url; - try { - if (!stmt.executeStep()) - return 0; - return stmt.getInt64(0); - } finally { - stmt.finalize(); - } -} - -/** - * Gets the database connection. If the Places connection is invalid it will - * try to create a new connection. - * - * @param [optional] aForceNewConnection - * Forces creation of a new connection to the database. When a - * connection is asyncClosed it cannot anymore schedule async statements, - * though connectionReady will keep returning true (Bug 726990). - * - * @return The database connection or null if unable to get one. - */ -let gDBConn; -function DBConn(aForceNewConnection) { - if (!aForceNewConnection) { - let db = - PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; - if (db.connectionReady) - return db; - } - - // If the Places database connection has been closed, create a new connection. - if (!gDBConn || aForceNewConnection) { - let file = Services.dirsvc.get('ProfD', Ci.nsIFile); - file.append("places.sqlite"); - let dbConn = gDBConn = Services.storage.openDatabase(file); - - // Be sure to cleanly close this connection. - Services.obs.addObserver(function DBCloseCallback(aSubject, aTopic, aData) { - Services.obs.removeObserver(DBCloseCallback, aTopic); - dbConn.asyncClose(); - }, "profile-before-change", false); - } - - return gDBConn.connectionReady ? gDBConn : null; -}; - -/** - * Function creates a bookmark - * @param aURI - * The URI for the bookmark - * @param aTitle - * The title for the bookmark - * @param aKeyword - * The keyword for the bookmark - * @returns the bookmark - */ -function createBookmark(aWin, aURI, aTitle, aKeyword) { - let bookmarkID = aWin.PlacesUtils.bookmarks.insertBookmark( - aWin.PlacesUtils.bookmarksMenuFolderId, aURI, - aWin.PlacesUtils.bookmarks.DEFAULT_INDEX, aTitle); - aWin.PlacesUtils.bookmarks.setKeywordForBookmark(bookmarkID, aKeyword); - return bookmarkID; -} - -/** - * Function attempts to check if Bookmark-A has been visited - * during private browsing mode, function should return false - * - * @returns false if the accessCount has not changed - * true if the accessCount has changed - */ -function isBookmarkAltered(aWin){ - let options = aWin.PlacesUtils.history.getNewQueryOptions(); - options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS; - options.maxResults = 1; // should only expect a new bookmark - options.resultType = options.RESULT_TYPE_VISIT; - - let query = aWin.PlacesUtils.history.getNewQuery(); - query.setFolders([aWin.PlacesUtils.bookmarks.bookmarksMenuFolder], 1); - - let root = aWin.PlacesUtils.history.executeQuery(query, options).root; - root.containerOpen = true; - is(root.childCount, options.maxResults, "Check new bookmarks results"); - let node = root.getChild(0); - root.containerOpen = false; - - return (node.accessCount != 0); -} From efa5ec829c5c396734be9bc278c42fcb1307bd6c Mon Sep 17 00:00:00 2001 From: Andres Hernandez Date: Mon, 26 Nov 2012 14:09:24 -0600 Subject: [PATCH 018/160] Bug 806742 - Port toolkit/components/places/tests/unit/test_248970.js to the new per-window PB APIs; r=ehsan --HG-- rename : toolkit/components/places/tests/unit/test_248970.js => toolkit/components/places/tests/browser/browser_bug248970.js --- .../places/tests/browser/Makefile.in | 6 + .../places/tests/browser/browser_bug248970.js | 259 ++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 toolkit/components/places/tests/browser/browser_bug248970.js diff --git a/toolkit/components/places/tests/browser/Makefile.in b/toolkit/components/places/tests/browser/Makefile.in index e06863f98c2a..73f8edf55f50 100644 --- a/toolkit/components/places/tests/browser/Makefile.in +++ b/toolkit/components/places/tests/browser/Makefile.in @@ -30,6 +30,12 @@ MOCHITEST_BROWSER_FILES = \ colorAnalyzer/localeGeneric.png \ $(NULL) +ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING +MOCHITEST_BROWSER_FILES += \ + browser_bug248970.js \ + $(NULL) +endif + # These are files that need to be loaded via the HTTP proxy server # Access them through http://example.com/ MOCHITEST_FILES = \ diff --git a/toolkit/components/places/tests/browser/browser_bug248970.js b/toolkit/components/places/tests/browser/browser_bug248970.js new file mode 100644 index 000000000000..f3c3155789fa --- /dev/null +++ b/toolkit/components/places/tests/browser/browser_bug248970.js @@ -0,0 +1,259 @@ +/* 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/. */ + +// This test performs checks on the history testing area as outlined +// https://wiki.mozilla.org/Firefox3.1/PrivateBrowsing/TestPlan#History +// http://developer.mozilla.org/en/Using_the_Places_history_service + +let visitedURIs = [ + "http://www.test-link.com/", + "http://www.test-typed.com/", + "http://www.test-bookmark.com/", + "http://www.test-redirect-permanent.com/", + "http://www.test-redirect-temporary.com/", + "http://www.test-embed.com/", + "http://www.test-framed.com/", + "http://www.test-download.com/" +]; + +function test() { + waitForExplicitFinish(); + + let windowsToClose = []; + let windowCount = 0; + let placeItemsCount = 0; + + registerCleanupFunction(function() { + windowsToClose.forEach(function(win) { + win.close(); + }); + }); + + function testOnWindow(aIsPrivate, aCallback) { + whenNewWindowLoaded(aIsPrivate, function(win) { + windowsToClose.push(win); + checkPlaces(win, aIsPrivate, aCallback); + }); + } + + function checkPlaces(aWindow, aIsPrivate, aCallback) { + // Updates the place items count + placeItemsCount = getPlacesItemsCount(aWindow); + // History items should be retrievable by query + checkHistoryItems(aWindow); + + // Create Bookmark + let bookmarkTitle = "title " + windowCount; + let bookmarkKeyword = "keyword " + windowCount; + let bookmarkUri = NetUtil.newURI("http://test-a-" + windowCount + ".com/"); + createBookmark(aWindow, bookmarkUri, bookmarkTitle, bookmarkKeyword); + placeItemsCount++; + windowCount++; + ok(aWindow.PlacesUtils.bookmarks.isBookmarked(bookmarkUri), + "Bookmark should be bookmarked, data should be retrievable"); + is(bookmarkKeyword, aWindow.PlacesUtils.bookmarks.getKeywordForURI(bookmarkUri), + "Check bookmark uri keyword"); + is(getPlacesItemsCount(aWindow), placeItemsCount, + "Check the new bookmark items count"); + is(isBookmarkAltered(aWindow), false, "Check if bookmark has been visited"); + + aCallback(); + } + + clearHistory(function() { + // Updates the place items count + placeItemsCount = getPlacesItemsCount(window); + // History database should be empty + is(PlacesUtils.history.hasHistoryEntries, false, + "History database should be empty"); + // Create a handful of history items with various visit types + fillHistoryVisitedURI(window); + placeItemsCount += 7; + // History database should have entries + is(PlacesUtils.history.hasHistoryEntries, true, + "History database should have entries"); + // We added 7 new items to history. + is(getPlacesItemsCount(window), placeItemsCount, + "Check the total items count"); + // Test on windows. + testOnWindow(false, function() { + testOnWindow(true, function() { + testOnWindow(false, finish); + }); + }); + }); +} + +function whenNewWindowLoaded(aIsPrivate, aCallback) { + let win = OpenBrowserWindow({private: aIsPrivate}); + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad, false); + aCallback(win); + }, false); +} + +function clearHistory(aCallback) { + Services.obs.addObserver(function observer(aSubject, aTopic, aData) { + Services.obs.removeObserver(observer, PlacesUtils.TOPIC_EXPIRATION_FINISHED); + aCallback(); + }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false); + PlacesUtils.bhistory.removeAllPages(); +} + +/** + * Function performs a really simple query on our places entries, + * and makes sure that the number of entries equal num_places_entries. + */ +function getPlacesItemsCount(aWin){ + // Get bookmarks count + let options = aWin.PlacesUtils.history.getNewQueryOptions(); + options.includeHidden = true; + options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS; + let root = aWin.PlacesUtils.history.executeQuery( + aWin.PlacesUtils.history.getNewQuery(), options).root; + root.containerOpen = true; + let cc = root.childCount; + root.containerOpen = false; + + // Get history item count + options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY; + let root = aWin.PlacesUtils.history.executeQuery( + aWin.PlacesUtils.history.getNewQuery(), options).root; + root.containerOpen = true; + cc += root.childCount; + root.containerOpen = false; + + return cc; +} + +function addVisit(aWin, aURI, aType) { + aWin.PlacesUtils.history.addVisit( + NetUtil.newURI(aURI), Date.now() * 1000, null, aType, false, 0); +} + +function fillHistoryVisitedURI(aWin) { + aWin.PlacesUtils.history.runInBatchMode({ + runBatched: function (aUserData) { + addVisit(aWin, visitedURIs[0], PlacesUtils.history.TRANSITION_LINK); + addVisit(aWin, visitedURIs[1], PlacesUtils.history.TRANSITION_TYPED); + addVisit(aWin, visitedURIs[2], PlacesUtils.history.TRANSITION_BOOKMARK); + addVisit(aWin, visitedURIs[3], PlacesUtils.history.TRANSITION_REDIRECT_PERMANENT); + addVisit(aWin, visitedURIs[4], PlacesUtils.history.TRANSITION_REDIRECT_TEMPORARY); + addVisit(aWin, visitedURIs[5], PlacesUtils.history.TRANSITION_EMBED); + addVisit(aWin, visitedURIs[6], PlacesUtils.history.TRANSITION_FRAMED_LINK); + addVisit(aWin, visitedURIs[7], PlacesUtils.history.TRANSITION_DOWNLOAD); + } + }, null); +} + +function checkHistoryItems(aWin) { + visitedURIs.forEach(function (visitedUri) { + ok(aWin.PlacesUtils.bhistory.isVisited(NetUtil.newURI(visitedUri)), ""); + if (/embed/.test(visitedUri)) { + is(!!pageInDatabase(visitedUri), false, "Check if URI is in database"); + } else { + ok(!!pageInDatabase(visitedUri), "Check if URI is in database"); + } + }); +} + +/** + * Checks if an address is found in the database. + * @param aURI + * nsIURI or address to look for. + * @return place id of the page or 0 if not found + */ +function pageInDatabase(aURI) { + let url = (aURI instanceof Ci.nsIURI ? aURI.spec : aURI); + let stmt = DBConn().createStatement( + "SELECT id FROM moz_places WHERE url = :url" + ); + stmt.params.url = url; + try { + if (!stmt.executeStep()) + return 0; + return stmt.getInt64(0); + } finally { + stmt.finalize(); + } +} + +/** + * Gets the database connection. If the Places connection is invalid it will + * try to create a new connection. + * + * @param [optional] aForceNewConnection + * Forces creation of a new connection to the database. When a + * connection is asyncClosed it cannot anymore schedule async statements, + * though connectionReady will keep returning true (Bug 726990). + * + * @return The database connection or null if unable to get one. + */ +let gDBConn; +function DBConn(aForceNewConnection) { + if (!aForceNewConnection) { + let db = + PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; + if (db.connectionReady) + return db; + } + + // If the Places database connection has been closed, create a new connection. + if (!gDBConn || aForceNewConnection) { + let file = Services.dirsvc.get('ProfD', Ci.nsIFile); + file.append("places.sqlite"); + let dbConn = gDBConn = Services.storage.openDatabase(file); + + // Be sure to cleanly close this connection. + Services.obs.addObserver(function DBCloseCallback(aSubject, aTopic, aData) { + Services.obs.removeObserver(DBCloseCallback, aTopic); + dbConn.asyncClose(); + }, "profile-before-change", false); + } + + return gDBConn.connectionReady ? gDBConn : null; +}; + +/** + * Function creates a bookmark + * @param aURI + * The URI for the bookmark + * @param aTitle + * The title for the bookmark + * @param aKeyword + * The keyword for the bookmark + * @returns the bookmark + */ +function createBookmark(aWin, aURI, aTitle, aKeyword) { + let bookmarkID = aWin.PlacesUtils.bookmarks.insertBookmark( + aWin.PlacesUtils.bookmarksMenuFolderId, aURI, + aWin.PlacesUtils.bookmarks.DEFAULT_INDEX, aTitle); + aWin.PlacesUtils.bookmarks.setKeywordForBookmark(bookmarkID, aKeyword); + return bookmarkID; +} + +/** + * Function attempts to check if Bookmark-A has been visited + * during private browsing mode, function should return false + * + * @returns false if the accessCount has not changed + * true if the accessCount has changed + */ +function isBookmarkAltered(aWin){ + let options = aWin.PlacesUtils.history.getNewQueryOptions(); + options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS; + options.maxResults = 1; // should only expect a new bookmark + options.resultType = options.RESULT_TYPE_VISIT; + + let query = aWin.PlacesUtils.history.getNewQuery(); + query.setFolders([aWin.PlacesUtils.bookmarks.bookmarksMenuFolder], 1); + + let root = aWin.PlacesUtils.history.executeQuery(query, options).root; + root.containerOpen = true; + is(root.childCount, options.maxResults, "Check new bookmarks results"); + let node = root.getChild(0); + root.containerOpen = false; + + return (node.accessCount != 0); +} From 43949a522286863aa57cf62fd4adb8a58e8f4378 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Mon, 26 Nov 2012 16:30:37 -0500 Subject: [PATCH 019/160] Add config option for off thread compilation, bug 815199. r=dvander --- dom/base/nsJSEnvironment.cpp | 4 ++++ js/src/jsapi.cpp | 8 ++++++++ js/src/jsapi.h | 3 +++ modules/libpref/src/init/all.js | 1 + 4 files changed, 16 insertions(+) diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 227a96c8b17c..84c98f847390 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -968,6 +968,7 @@ static const char js_memnotify_option_str[] = JS_OPTIONS_DOT_STR "mem.notify"; static const char js_disable_explicit_compartment_gc[] = JS_OPTIONS_DOT_STR "mem.disable_explicit_compartment_gc"; static const char js_ion_content_str[] = JS_OPTIONS_DOT_STR "ion.content"; +static const char js_ion_parallel_compilation_str[] = JS_OPTIONS_DOT_STR "ion.parallel_compilation"; int nsJSContext::JSOptionChangedCallback(const char *pref, void *data) @@ -1010,6 +1011,7 @@ nsJSContext::JSOptionChangedCallback(const char *pref, void *data) "javascript.options.xml.content"); bool useHardening = Preferences::GetBool(js_jit_hardening_str); bool useIon = Preferences::GetBool(js_ion_content_str); + bool parallelIonCompilation = Preferences::GetBool(js_ion_parallel_compilation_str); nsCOMPtr xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID); if (xr) { bool safeMode = false; @@ -1074,6 +1076,8 @@ nsJSContext::JSOptionChangedCallback(const char *pref, void *data) ::JS_SetOptions(context->mContext, newDefaultJSOptions & (JSRUNOPTION_MASK | JSOPTION_ALLOW_XML)); + ::JS_SetParallelCompilationEnabled(context->mContext, parallelIonCompilation); + // Save the new defaults for the next page load (InitContext). context->mDefaultJSOptions = newDefaultJSOptions; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index fc8fdc0bdef2..8b1ba9a63b32 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -7045,6 +7045,14 @@ JS_ScheduleGC(JSContext *cx, uint32_t count) } #endif +JS_PUBLIC_API(void) +JS_SetParallelCompilationEnabled(JSContext *cx, bool enabled) +{ +#ifdef JS_ION + ion::js_IonOptions.parallelCompilation = enabled; +#endif +} + /************************************************************************/ #if !defined(STATIC_EXPORTABLE_JS_API) && !defined(STATIC_JS_API) && defined(XP_WIN) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index ba4ed5550f40..ad1909dc15ef 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -5963,6 +5963,9 @@ extern JS_PUBLIC_API(void) JS_ScheduleGC(JSContext *cx, uint32_t count); #endif +extern JS_PUBLIC_API(void) +JS_SetParallelCompilationEnabled(JSContext *cx, bool enabled); + /* * Convert a uint32_t index into a jsid. */ diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 1a36f0db8135..484b84b68594 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -712,6 +712,7 @@ pref("javascript.options.strict.debug", true); pref("javascript.options.methodjit.content", true); pref("javascript.options.methodjit.chrome", true); pref("javascript.options.ion.content", true); +pref("javascript.options.ion.parallel_compilation", false); pref("javascript.options.pccounts.content", false); pref("javascript.options.pccounts.chrome", false); pref("javascript.options.methodjit_always", false); From 4a40134208100a3d7b7bad1994a1f129df23f35b Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Mon, 26 Nov 2012 16:32:45 -0500 Subject: [PATCH 020/160] Clone type sets to avoid races when compiling off thread, bug 815258. r=dvander --- js/src/ion/CodeGenerator.cpp | 2 +- js/src/ion/IonBuilder.cpp | 28 ++++++++++++++++++++++++---- js/src/ion/IonBuilder.h | 2 ++ js/src/ion/IonMacroAssembler.cpp | 6 +++--- js/src/ion/IonMacroAssembler.h | 2 +- js/src/ion/MIR.cpp | 2 +- js/src/ion/MIR.h | 24 ++++++++++++------------ js/src/jsinfer.cpp | 24 ++++++++++++++++++++++++ js/src/jsinfer.h | 16 +++++++++++----- js/src/jsinferinlines.h | 10 +++++----- 10 files changed, 84 insertions(+), 32 deletions(-) diff --git a/js/src/ion/CodeGenerator.cpp b/js/src/ion/CodeGenerator.cpp index 62071975c3e7..8a9f5fcb36d4 100644 --- a/js/src/ion/CodeGenerator.cpp +++ b/js/src/ion/CodeGenerator.cpp @@ -1271,7 +1271,7 @@ CodeGenerator::generateArgumentsChecks() for (uint32 i = START_SLOT; i < CountArgSlots(info.fun()); i++) { // All initial parameters are guaranteed to be MParameters. MParameter *param = rp->getOperand(i)->toParameter(); - types::TypeSet *types = param->typeSet(); + const types::TypeSet *types = param->typeSet(); if (!types || types->unknown()) continue; diff --git a/js/src/ion/IonBuilder.cpp b/js/src/ion/IonBuilder.cpp index 2ea2d9472198..5ef9ef76c516 100644 --- a/js/src/ion/IonBuilder.cpp +++ b/js/src/ion/IonBuilder.cpp @@ -500,7 +500,14 @@ IonBuilder::rewriteParameters() for (uint32 i = START_SLOT; i < CountArgSlots(info().fun()); i++) { MParameter *param = current->getSlot(i)->toParameter(); - types::StackTypeSet *types = param->typeSet(); + + // Find the original (not cloned) type set for the MParameter, as we + // will be adding constraints to it. + types::StackTypeSet *types; + if (param->index() == MParameter::THIS_SLOT) + types = oracle->thisTypeSet(script_); + else + types = oracle->parameterTypeSet(script_, param->index()); if (!types) continue; @@ -544,7 +551,7 @@ IonBuilder::initParameters() return true; MParameter *param = MParameter::New(MParameter::THIS_SLOT, - oracle->thisTypeSet(script_)); + cloneTypeSet(oracle->thisTypeSet(script_))); current->add(param); current->initSlot(info().thisSlot(), param); @@ -4536,7 +4543,7 @@ IonBuilder::pushTypeBarrier(MInstruction *ins, types::StackTypeSet *actual, case JSVAL_TYPE_UNKNOWN: case JSVAL_TYPE_UNDEFINED: case JSVAL_TYPE_NULL: - barrier = MTypeBarrier::New(ins, observed); + barrier = MTypeBarrier::New(ins, cloneTypeSet(observed)); current->add(barrier); if (type == JSVAL_TYPE_UNDEFINED) @@ -4569,7 +4576,7 @@ IonBuilder::monitorResult(MInstruction *ins, types::TypeSet *barrier, types::Typ if (!types || types->unknown()) return; - MInstruction *monitor = MMonitorTypes::New(ins, types); + MInstruction *monitor = MMonitorTypes::New(ins, cloneTypeSet(types)); current->add(monitor); } @@ -6561,3 +6568,16 @@ IonBuilder::addShapeGuard(MDefinition *obj, const Shape *shape, BailoutKind bail return guard; } + +const types::TypeSet * +IonBuilder::cloneTypeSet(const types::TypeSet *types) +{ + if (!js_IonOptions.parallelCompilation) + return types; + + // Clone a type set so that it can be stored into the MIR and accessed + // during off thread compilation. This is necessary because main thread + // updates to type sets can race with reads in the compiler backend, and + // after bug 804676 this code can be removed. + return types->clone(GetIonContext()->temp->lifoAlloc()); +} diff --git a/js/src/ion/IonBuilder.h b/js/src/ion/IonBuilder.h index cda8708eddef..2546e6285297 100644 --- a/js/src/ion/IonBuilder.h +++ b/js/src/ion/IonBuilder.h @@ -437,6 +437,8 @@ class IonBuilder : public MIRGenerator MBasicBlock *bottom, Vector &retvalDefns); + const types::TypeSet *cloneTypeSet(const types::TypeSet *types); + // A builder is inextricably tied to a particular script. HeapPtrScript script_; diff --git a/js/src/ion/IonMacroAssembler.cpp b/js/src/ion/IonMacroAssembler.cpp index 64c1e02ea890..e0964f62a409 100644 --- a/js/src/ion/IonMacroAssembler.cpp +++ b/js/src/ion/IonMacroAssembler.cpp @@ -15,7 +15,7 @@ using namespace js; using namespace js::ion; template void -MacroAssembler::guardTypeSet(const T &address, types::TypeSet *types, +MacroAssembler::guardTypeSet(const T &address, const types::TypeSet *types, Register scratch, Label *mismatched) { JS_ASSERT(!types->unknown()); @@ -64,9 +64,9 @@ MacroAssembler::guardTypeSet(const T &address, types::TypeSet *types, bind(&matched); } -template void MacroAssembler::guardTypeSet(const Address &address, types::TypeSet *types, +template void MacroAssembler::guardTypeSet(const Address &address, const types::TypeSet *types, Register scratch, Label *mismatched); -template void MacroAssembler::guardTypeSet(const ValueOperand &value, types::TypeSet *types, +template void MacroAssembler::guardTypeSet(const ValueOperand &value, const types::TypeSet *types, Register scratch, Label *mismatched); void diff --git a/js/src/ion/IonMacroAssembler.h b/js/src/ion/IonMacroAssembler.h index 6e4a0ad49068..8ad65e15f1e8 100644 --- a/js/src/ion/IonMacroAssembler.h +++ b/js/src/ion/IonMacroAssembler.h @@ -119,7 +119,7 @@ class MacroAssembler : public MacroAssemblerSpecific // Emits a test of a value against all types in a TypeSet. A scratch // register is required. template - void guardTypeSet(const T &address, types::TypeSet *types, Register scratch, + void guardTypeSet(const T &address, const types::TypeSet *types, Register scratch, Label *mismatched); void loadObjShape(Register objReg, Register dest) { diff --git a/js/src/ion/MIR.cpp b/js/src/ion/MIR.cpp index 1b0c0d5af706..2f2c46face83 100644 --- a/js/src/ion/MIR.cpp +++ b/js/src/ion/MIR.cpp @@ -347,7 +347,7 @@ MConstantElements::printOpcode(FILE *fp) } MParameter * -MParameter::New(int32 index, types::StackTypeSet *types) +MParameter::New(int32 index, const types::TypeSet *types) { return new MParameter(index, types); } diff --git a/js/src/ion/MIR.h b/js/src/ion/MIR.h index 2a0081af31d9..83fc476bb021 100644 --- a/js/src/ion/MIR.h +++ b/js/src/ion/MIR.h @@ -632,12 +632,12 @@ class MConstant : public MNullaryInstruction class MParameter : public MNullaryInstruction { int32 index_; - types::StackTypeSet *typeSet_; + const types::TypeSet *typeSet_; public: static const int32 THIS_SLOT = -1; - MParameter(int32 index, types::StackTypeSet *types) + MParameter(int32 index, const types::TypeSet *types) : index_(index), typeSet_(types) { @@ -646,12 +646,12 @@ class MParameter : public MNullaryInstruction public: INSTRUCTION_HEADER(Parameter); - static MParameter *New(int32 index, types::StackTypeSet *types); + static MParameter *New(int32 index, const types::TypeSet *types); int32 index() const { return index_; } - types::StackTypeSet *typeSet() const { + const types::TypeSet *typeSet() const { return typeSet_; } void printOpcode(FILE *fp); @@ -5353,9 +5353,9 @@ class MGetArgument class MTypeBarrier : public MUnaryInstruction { BailoutKind bailoutKind_; - types::TypeSet *typeSet_; + const types::TypeSet *typeSet_; - MTypeBarrier(MDefinition *def, types::TypeSet *types) + MTypeBarrier(MDefinition *def, const types::TypeSet *types) : MUnaryInstruction(def), typeSet_(types) { @@ -5370,7 +5370,7 @@ class MTypeBarrier : public MUnaryInstruction public: INSTRUCTION_HEADER(TypeBarrier); - static MTypeBarrier *New(MDefinition *def, types::TypeSet *types) { + static MTypeBarrier *New(MDefinition *def, const types::TypeSet *types) { return new MTypeBarrier(def, types); } bool congruentTo(MDefinition * const &def) const { @@ -5382,7 +5382,7 @@ class MTypeBarrier : public MUnaryInstruction BailoutKind bailoutKind() const { return bailoutKind_; } - types::TypeSet *typeSet() const { + const types::TypeSet *typeSet() const { return typeSet_; } AliasSet getAliasSet() const { @@ -5395,9 +5395,9 @@ class MTypeBarrier : public MUnaryInstruction // TypeScript::Monitor inside these stubs. class MMonitorTypes : public MUnaryInstruction { - types::TypeSet *typeSet_; + const types::TypeSet *typeSet_; - MMonitorTypes(MDefinition *def, types::TypeSet *types) + MMonitorTypes(MDefinition *def, const types::TypeSet *types) : MUnaryInstruction(def), typeSet_(types) { @@ -5409,13 +5409,13 @@ class MMonitorTypes : public MUnaryInstruction public: INSTRUCTION_HEADER(MonitorTypes); - static MMonitorTypes *New(MDefinition *def, types::TypeSet *types) { + static MMonitorTypes *New(MDefinition *def, const types::TypeSet *types) { return new MMonitorTypes(def, types); } MDefinition *input() const { return getOperand(0); } - types::TypeSet *typeSet() const { + const types::TypeSet *typeSet() const { return typeSet_; } AliasSet getAliasSet() const { diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 941e2b4f24ed..0e37966abdf4 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -456,6 +456,30 @@ StackTypeSet::make(JSContext *cx, const char *name) return res; } +const TypeSet * +TypeSet::clone(LifoAlloc *alloc) const +{ + unsigned objectCount = baseObjectCount(); + unsigned capacity = (objectCount >= 2) ? HashSetCapacity(objectCount) : 0; + + TypeSet *res = alloc->new_(); + if (!res) + return NULL; + + TypeObjectKey **newSet; + if (capacity) { + newSet = alloc->newArray(capacity); + if (!newSet) + return NULL; + PodCopy(newSet, objectSet, capacity); + } + + res->flags = this->flags; + res->objectSet = capacity ? newSet : this->objectSet; + + return res; +} + ///////////////////////////////////////////////////////////////////// // TypeSet constraints ///////////////////////////////////////////////////////////////////// diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index 6e50a52c48e9..1b3fb4718237 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -444,7 +444,7 @@ class TypeSet inline void sweep(JSCompartment *compartment); /* Whether this set contains a specific type. */ - inline bool hasType(Type type); + inline bool hasType(Type type) const; TypeFlags baseFlags() const { return flags & TYPE_FLAG_BASE_MASK; } bool unknown() const { return !!(flags & TYPE_FLAG_UNKNOWN); } @@ -466,6 +466,12 @@ class TypeSet return flags >> TYPE_FLAG_DEFINITE_SHIFT; } + /* + * Clone a type set into an arbitrary allocator. The result should not be + * modified further. + */ + const TypeSet *clone(LifoAlloc *alloc) const; + /* * Add a type to this set, calling any constraint handlers if this is a new * possible type. @@ -480,10 +486,10 @@ class TypeSet * in the hash case (see SET_ARRAY_SIZE in jsinferinlines.h), and getObject * may return NULL. */ - inline unsigned getObjectCount(); - inline TypeObjectKey *getObject(unsigned i); - inline RawObject getSingleObject(unsigned i); - inline TypeObject *getTypeObject(unsigned i); + inline unsigned getObjectCount() const; + inline TypeObjectKey *getObject(unsigned i) const; + inline RawObject getSingleObject(unsigned i) const; + inline TypeObject *getTypeObject(unsigned i) const; void setOwnProperty(bool configurable) { flags |= TYPE_FLAG_OWN_PROPERTY; diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index 5ab7599c8f6b..6365903ec913 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -1294,7 +1294,7 @@ Type::typeObject() const } inline bool -TypeSet::hasType(Type type) +TypeSet::hasType(Type type) const { if (unknown()) return true; @@ -1431,7 +1431,7 @@ TypeSet::setOwnProperty(JSContext *cx, bool configured) } inline unsigned -TypeSet::getObjectCount() +TypeSet::getObjectCount() const { JS_ASSERT(!unknownObject()); uint32_t count = baseObjectCount(); @@ -1441,7 +1441,7 @@ TypeSet::getObjectCount() } inline TypeObjectKey * -TypeSet::getObject(unsigned i) +TypeSet::getObject(unsigned i) const { JS_ASSERT(i < getObjectCount()); if (baseObjectCount() == 1) { @@ -1452,14 +1452,14 @@ TypeSet::getObject(unsigned i) } inline RawObject -TypeSet::getSingleObject(unsigned i) +TypeSet::getSingleObject(unsigned i) const { TypeObjectKey *key = getObject(i); return (uintptr_t(key) & 1) ? (JSObject *)(uintptr_t(key) ^ 1) : NULL; } inline TypeObject * -TypeSet::getTypeObject(unsigned i) +TypeSet::getTypeObject(unsigned i) const { TypeObjectKey *key = getObject(i); return (key && !(uintptr_t(key) & 1)) ? (TypeObject *) key : NULL; From f4bd2d33b753250efeeba2c405c75083900565f9 Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Mon, 26 Nov 2012 16:12:24 -0500 Subject: [PATCH 021/160] Bug 815226 - Make the MediaPluginReader not depend on the concrete type of the AbstractMediaDecoder passed to it; r=cpearce --- content/media/plugins/MediaPluginDecoder.cpp | 2 +- content/media/plugins/MediaPluginReader.cpp | 5 +++-- content/media/plugins/MediaPluginReader.h | 5 ++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/content/media/plugins/MediaPluginDecoder.cpp b/content/media/plugins/MediaPluginDecoder.cpp index 8751d826446f..df20598c5e34 100644 --- a/content/media/plugins/MediaPluginDecoder.cpp +++ b/content/media/plugins/MediaPluginDecoder.cpp @@ -16,7 +16,7 @@ MediaPluginDecoder::MediaPluginDecoder(const nsACString& aType) : mType(aType) MediaDecoderStateMachine* MediaPluginDecoder::CreateStateMachine() { - return new MediaDecoderStateMachine(this, new MediaPluginReader(this)); + return new MediaDecoderStateMachine(this, new MediaPluginReader(this, mType)); } } // namespace mozilla diff --git a/content/media/plugins/MediaPluginReader.cpp b/content/media/plugins/MediaPluginReader.cpp index aef9e90758de..b8bec26a2507 100644 --- a/content/media/plugins/MediaPluginReader.cpp +++ b/content/media/plugins/MediaPluginReader.cpp @@ -15,8 +15,10 @@ namespace mozilla { -MediaPluginReader::MediaPluginReader(AbstractMediaDecoder *aDecoder) : +MediaPluginReader::MediaPluginReader(AbstractMediaDecoder *aDecoder, + const nsACString& aContentType) : MediaDecoderReader(aDecoder), + mType(aContentType), mPlugin(NULL), mHasAudio(false), mHasVideo(false), @@ -24,7 +26,6 @@ MediaPluginReader::MediaPluginReader(AbstractMediaDecoder *aDecoder) : mAudioSeekTimeUs(-1), mLastVideoFrame(NULL) { - static_cast(aDecoder)->GetContentType(mType); } MediaPluginReader::~MediaPluginReader() diff --git a/content/media/plugins/MediaPluginReader.h b/content/media/plugins/MediaPluginReader.h index f86f7f1fef3d..ea44397fc9e6 100644 --- a/content/media/plugins/MediaPluginReader.h +++ b/content/media/plugins/MediaPluginReader.h @@ -11,6 +11,8 @@ #include "MPAPI.h" +class nsACString; + namespace mozilla { class AbstractMediaDecoder; @@ -27,7 +29,8 @@ class MediaPluginReader : public MediaDecoderReader int64_t mAudioSeekTimeUs; VideoData *mLastVideoFrame; public: - MediaPluginReader(AbstractMediaDecoder* aDecoder); + MediaPluginReader(AbstractMediaDecoder* aDecoder, + const nsACString& aContentType); ~MediaPluginReader(); virtual nsresult Init(MediaDecoderReader* aCloneDonor); From d90aa800b34b6f92d774e370170fda7a28d34633 Mon Sep 17 00:00:00 2001 From: Benoit Girard Date: Thu, 18 Oct 2012 13:56:47 -0400 Subject: [PATCH 022/160] Bug 802400 - Restore pan timer to 60 Hz because of trobopan regression. r=kats --- mobile/android/base/ui/Axis.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/android/base/ui/Axis.java b/mobile/android/base/ui/Axis.java index 57772b221716..06ac81dde85f 100644 --- a/mobile/android/base/ui/Axis.java +++ b/mobile/android/base/ui/Axis.java @@ -84,7 +84,7 @@ abstract class Axis { }); } - static final float MS_PER_FRAME = 4.0f; + static final float MS_PER_FRAME = 1000.0f / 60.0f; private static final float FRAMERATE_MULTIPLIER = (1000f/60f) / MS_PER_FRAME; // The values we use for friction are based on a 16.6ms frame, adjust them to MS_PER_FRAME: From 1df394a0e3a25c649351f59d147fa46adadf1bef Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Wed, 24 Oct 2012 13:08:09 -0700 Subject: [PATCH 023/160] Bug 790338 - Update GC stats with time spent marking in the sweep phase and add more detail to sweep tables phase r=billm --HG-- extra : rebase_source : 1e05d780ec815320985e031250e378a38ce8ab36 --- js/src/gc/Statistics.cpp | 18 +++++-- js/src/gc/Statistics.h | 15 ++++-- js/src/jscompartment.cpp | 5 +- js/src/jsgc.cpp | 106 +++++++++++++++++++++------------------ js/src/jsinfer.cpp | 2 + js/src/jsscope.cpp | 4 ++ 6 files changed, 91 insertions(+), 59 deletions(-) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index a1dd2797790f..bec743e6d9d1 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -280,14 +280,22 @@ static PhaseInfo phases[] = { { PHASE_MARK_ROOTS, "Mark Roots" }, { PHASE_MARK_TYPES, "Mark Types" }, { PHASE_MARK_DELAYED, "Mark Delayed" }, - { PHASE_MARK_WEAK, "Mark Weak" }, - { PHASE_MARK_GRAY, "Mark Gray" }, - { PHASE_MARK_GRAY_WEAK, "Mark Gray and Weak" }, - { PHASE_FINALIZE_START, "Finalize Start Callback" }, { PHASE_SWEEP, "Sweep" }, + { PHASE_SWEEP_MARK_INCOMING_BLACK, "Mark Incoming Black Pointers" }, + { PHASE_SWEEP_MARK_WEAK, "Mark Weak" }, + { PHASE_SWEEP_MARK_GRAY, "Mark Gray" }, + { PHASE_SWEEP_MARK_GRAY_WEAK, "Mark Gray and Weak" }, + { PHASE_SWEEP_MARK_INCOMING_GRAY, "Mark Incoming Gray Pointers" }, + { PHASE_FINALIZE_START, "Finalize Start Callback" }, { PHASE_SWEEP_ATOMS, "Sweep Atoms" }, { PHASE_SWEEP_COMPARTMENTS, "Sweep Compartments" }, { PHASE_SWEEP_TABLES, "Sweep Tables" }, + { PHASE_SWEEP_TABLES_WRAPPER, "Sweep Cross Compartment Wrappers" }, + { PHASE_SWEEP_TABLES_BASE_SHAPE, "Sweep Base Shapes" }, + { PHASE_SWEEP_TABLES_INITIAL_SHAPE, "Sweep Intital Shapes" }, + { PHASE_SWEEP_TABLES_TYPE_OBJECT, "Sweep Type Objects" }, + { PHASE_SWEEP_TABLES_BREAKPOINT, "Sweep Breakpoints" }, + { PHASE_SWEEP_TABLES_REGEXP, "Sweep Regexps" }, { PHASE_SWEEP_OBJECT, "Sweep Object" }, { PHASE_SWEEP_STRING, "Sweep String" }, { PHASE_SWEEP_SCRIPT, "Sweep Script" }, @@ -536,7 +544,7 @@ Statistics::endGC() (*cb)(JS_TELEMETRY_GC_MARK_MS, t(phaseTimes[PHASE_MARK])); (*cb)(JS_TELEMETRY_GC_SWEEP_MS, t(phaseTimes[PHASE_SWEEP])); (*cb)(JS_TELEMETRY_GC_MARK_ROOTS_MS, t(phaseTimes[PHASE_MARK_ROOTS])); - (*cb)(JS_TELEMETRY_GC_MARK_GRAY_MS, t(phaseTimes[PHASE_MARK_GRAY])); + (*cb)(JS_TELEMETRY_GC_MARK_GRAY_MS, t(phaseTimes[PHASE_SWEEP_MARK_GRAY])); (*cb)(JS_TELEMETRY_GC_NON_INCREMENTAL, !!nonincrementalReason); (*cb)(JS_TELEMETRY_GC_INCREMENTAL_DISABLED, !runtime->gcIncrementalEnabled); (*cb)(JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS, t(sccTotal)); diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index e0434b4d6162..0665406cd88b 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -28,14 +28,23 @@ enum Phase { PHASE_MARK_ROOTS, PHASE_MARK_TYPES, PHASE_MARK_DELAYED, - PHASE_MARK_WEAK, - PHASE_MARK_GRAY, - PHASE_MARK_GRAY_WEAK, PHASE_FINALIZE_START, PHASE_SWEEP, + PHASE_SWEEP_MARK_INCOMING_BLACK, + PHASE_SWEEP_MARK_WEAK, + PHASE_SWEEP_MARK_INCOMING_GRAY, + PHASE_SWEEP_MARK_GRAY, + PHASE_SWEEP_MARK_GRAY_WEAK, + PHASE_SWEEP_FIND_BLACK_GRAY, PHASE_SWEEP_ATOMS, PHASE_SWEEP_COMPARTMENTS, PHASE_SWEEP_TABLES, + PHASE_SWEEP_TABLES_WRAPPER, + PHASE_SWEEP_TABLES_BASE_SHAPE, + PHASE_SWEEP_TABLES_INITIAL_SHAPE, + PHASE_SWEEP_TABLES_TYPE_OBJECT, + PHASE_SWEEP_TABLES_BREAKPOINT, + PHASE_SWEEP_TABLES_REGEXP, PHASE_SWEEP_OBJECT, PHASE_SWEEP_STRING, PHASE_SWEEP_SCRIPT, diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index d66eadd6557f..98f5e55c0056 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -687,7 +687,8 @@ JSCompartment::sweep(FreeOp *fop, bool releaseTypes) void JSCompartment::sweepCrossCompartmentWrappers() { - gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP_TABLES); + gcstats::AutoPhase ap1(rt->gcStats, gcstats::PHASE_SWEEP_TABLES); + gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_SWEEP_TABLES_WRAPPER); /* Remove dead wrappers from the table. */ for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { @@ -888,6 +889,8 @@ JSCompartment::clearTraps(FreeOp *fop) void JSCompartment::sweepBreakpoints(FreeOp *fop) { + gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP_TABLES_BREAKPOINT); + if (JS_CLIST_IS_EMPTY(&rt->debuggerList)) return; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 45036b3d087d..ff21db9bbb65 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3465,7 +3465,7 @@ BeginMarkPhase(JSRuntime *rt) } void -MarkWeakReferences(JSRuntime *rt, gcstats::Phase phase = gcstats::PHASE_MARK_WEAK) +MarkWeakReferences(JSRuntime *rt, gcstats::Phase phase) { GCMarker *gcmarker = &rt->gcMarker; JS_ASSERT(gcmarker->isDrained()); @@ -3488,7 +3488,7 @@ MarkGrayReferences(JSRuntime *rt) GCMarker *gcmarker = &rt->gcMarker; { - gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_MARK_GRAY); + gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP_MARK_GRAY); gcmarker->setMarkColorGray(); if (gcmarker->hasBufferedGrayRoots()) { gcmarker->markBufferedGrayRoots(); @@ -3500,7 +3500,7 @@ MarkGrayReferences(JSRuntime *rt) gcmarker->drainMarkStack(budget); } - MarkWeakReferences(rt, gcstats::PHASE_MARK_GRAY_WEAK); + MarkWeakReferences(rt, gcstats::PHASE_SWEEP_MARK_GRAY_WEAK); JS_ASSERT(gcmarker->isDrained()); @@ -3563,7 +3563,7 @@ ValidateIncrementalMarking(JSRuntime *rt) SliceBudget budget; rt->gcIncrementalState = MARK; rt->gcMarker.drainMarkStack(budget); - MarkWeakReferences(rt); + MarkWeakReferences(rt, gcstats::PHASE_SWEEP_MARK_WEAK); MarkGrayReferences(rt); /* Now verify that we have the same mark bits as before. */ @@ -3791,6 +3791,12 @@ MarkIncomingCrossCompartmentPointers(JSRuntime *rt, const uint32_t color) { JS_ASSERT(color == BLACK || color == GRAY); + static const gcstats::Phase statsPhases[] = { + gcstats::PHASE_SWEEP_MARK_INCOMING_BLACK, + gcstats::PHASE_SWEEP_MARK_INCOMING_GRAY + }; + gcstats::AutoPhase ap1(rt->gcStats, statsPhases[color]); + bool unlinkList = color == GRAY; for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { @@ -3826,42 +3832,38 @@ MarkIncomingCrossCompartmentPointers(JSRuntime *rt, const uint32_t color) static void EndMarkingCompartmentGroup(JSRuntime *rt) { - { - gcstats::AutoPhase ap1(rt->gcStats, gcstats::PHASE_MARK); + /* + * Mark any incoming black pointers from previously swept compartments + * whose referents are not marked. This can occur when gray cells become + * black by the action of UnmarkGray. + */ + MarkIncomingCrossCompartmentPointers(rt, BLACK); - /* - * Mark any incoming black pointers from previously swept compartments - * whose referents are not marked. This can occur when gray cells become - * black by the action of UnmarkGray. - */ - MarkIncomingCrossCompartmentPointers(rt, BLACK); + MarkWeakReferences(rt, gcstats::PHASE_SWEEP_MARK_WEAK); - MarkWeakReferences(rt); + /* + * Change state of current group to MarkGray to restrict marking to this + * group. Note that there may be pointers to the atoms compartment, and + * these will be marked through, as they are not marked with + * MarkCrossCompartmentXXX. + */ + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + JS_ASSERT(c->isGCMarkingBlack()); + c->setGCState(JSCompartment::MarkGray); + } - /* - * Change state of current group to MarkGray to restrict marking to this - * group. Note that there may be pointers to the atoms compartment, and - * these will be marked through, as they are not marked with - * MarkCrossCompartmentXXX. - */ - for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { - JS_ASSERT(c->isGCMarkingBlack()); - c->setGCState(JSCompartment::MarkGray); - } + /* Mark incoming gray pointers from previously swept compartments. */ + rt->gcMarker.setMarkColorGray(); + MarkIncomingCrossCompartmentPointers(rt, GRAY); + rt->gcMarker.setMarkColorBlack(); - /* Mark incoming gray pointers from previously swept compartments. */ - rt->gcMarker.setMarkColorGray(); - MarkIncomingCrossCompartmentPointers(rt, GRAY); - rt->gcMarker.setMarkColorBlack(); + /* Mark gray roots and mark transitively inside the current compartment group. */ + MarkGrayReferences(rt); - /* Mark gray roots and mark transitively inside the current compartment group. */ - MarkGrayReferences(rt); - - /* Restore marking state. */ - for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { - JS_ASSERT(c->isGCMarkingGray()); - c->setGCState(JSCompartment::Mark); - } + /* Restore marking state. */ + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + JS_ASSERT(c->isGCMarkingGray()); + c->setGCState(JSCompartment::Mark); } #ifdef DEBUG @@ -3871,21 +3873,25 @@ EndMarkingCompartmentGroup(JSRuntime *rt) JS_ASSERT(rt->gcMarker.isDrained()); - /* - * Having black->gray edges violates our promise to the cycle - * collector. This can happen if we're collecting a compartment and it has - * an edge to an uncollected compartment: it's possible that the source and - * destination of the cross-compartment edge should be gray, but the source - * was marked black by the conservative scanner. - */ - for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { - for (WrapperMap::Enum e(c->crossCompartmentWrappers); !e.empty(); e.popFront()) { - Cell *dst = e.front().key.wrapped; - Cell *src = ToMarkable(e.front().value); - JS_ASSERT(src->compartment() == c); - if (IsCellMarked(&src) && !src->isMarked(GRAY) && dst->isMarked(GRAY)) { - JS_ASSERT(!dst->compartment()->isCollecting()); - rt->gcFoundBlackGrayEdges = true; + { + gcstats::AutoPhase ap1(rt->gcStats, gcstats::PHASE_SWEEP_FIND_BLACK_GRAY); + + /* + * Having black->gray edges violates our promise to the cycle + * collector. This can happen if we're collecting a compartment and it has + * an edge to an uncollected compartment: it's possible that the source and + * destination of the cross-compartment edge should be gray, but the source + * was marked black by the conservative scanner. + */ + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + for (WrapperMap::Enum e(c->crossCompartmentWrappers); !e.empty(); e.popFront()) { + Cell *dst = e.front().key.wrapped; + Cell *src = ToMarkable(e.front().value); + JS_ASSERT(src->compartment() == c); + if (IsCellMarked(&src) && !src->isMarked(GRAY) && dst->isMarked(GRAY)) { + JS_ASSERT(!dst->compartment()->isCollecting()); + rt->gcFoundBlackGrayEdges = true; + } } } } diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 0e6c51e797f9..3c79d4ea9a59 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -6264,6 +6264,8 @@ TypeCompartment::sweepCompilerOutputs(FreeOp *fop, bool discardConstraints) void JSCompartment::sweepNewTypeObjectTable(TypeObjectSet &table) { + gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP_TABLES_TYPE_OBJECT); + JS_ASSERT(isGCSweeping()); if (table.initialized()) { for (TypeObjectSet::Enum e(table); !e.empty(); e.popFront()) { diff --git a/js/src/jsscope.cpp b/js/src/jsscope.cpp index 34c6788cd203..cd08ed0df63c 100644 --- a/js/src/jsscope.cpp +++ b/js/src/jsscope.cpp @@ -1146,6 +1146,8 @@ BaseShape::getUnowned(JSContext *cx, const StackBaseShape &base) void JSCompartment::sweepBaseShapeTable() { + gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP_TABLES_BASE_SHAPE); + if (baseShapes.initialized()) { for (BaseShapeSet::Enum e(baseShapes); !e.empty(); e.popFront()) { UnownedBaseShape *base = e.front(); @@ -1301,6 +1303,8 @@ EmptyShape::insertInitialShape(JSContext *cx, Shape *shape, JSObject *proto) void JSCompartment::sweepInitialShapeTable() { + gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP_TABLES_INITIAL_SHAPE); + if (initialShapes.initialized()) { for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) { const InitialShapeEntry &entry = e.front(); From 049e9c416c5a6160034412373346039c3ad95c67 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Wed, 31 Oct 2012 17:59:22 +0000 Subject: [PATCH 024/160] Bug 790338 - Store the list of arraybufs per compartment rather than on the runtime r=billm --HG-- extra : rebase_source : 38ba69d144983afbc2dc1e3996c21ca20f7f9c30 --- js/src/jsapi.cpp | 1 - js/src/jscntxt.h | 3 --- js/src/jscompartment.cpp | 1 + js/src/jscompartment.h | 3 +++ js/src/jsgc.cpp | 9 +++++--- js/src/jstypedarray.cpp | 45 ++++++++++++++-------------------------- js/src/jstypedarray.h | 4 ++-- 7 files changed, 28 insertions(+), 38 deletions(-) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 48c484463619..00e9a6d206fe 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -846,7 +846,6 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads) alwaysPreserveCode(false), hadOutOfMemory(false), debugScopes(NULL), - liveArrayBuffers(NULL), data(NULL), gcLock(NULL), gcHelperThread(thisFromCtor()), diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index e70b2d82399a..04b3182f0d58 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -915,9 +915,6 @@ struct JSRuntime : js::RuntimeFriendFields /* Bookkeeping information for debug scope objects. */ js::DebugScopes *debugScopes; - /* Linked list of live array buffers with >1 view */ - JSObject *liveArrayBuffers; - /* Client opaque pointers */ void *data; diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 98f5e55c0056..55805902ec54 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -73,6 +73,7 @@ JSCompartment::JSCompartment(JSRuntime *rt) gcMallocAndFreeBytes(0), gcTriggerMallocAndFreeBytes(0), gcIncomingGrayPointers(NULL), + gcLiveArrayBuffers(NULL), gcMallocBytes(0), debugModeBits(rt->debugMode ? DebugFromC : 0), watchpointMap(NULL), diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 59fdb20b7d83..3222859c8376 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -363,6 +363,9 @@ struct JSCompartment : public js::gc::GraphNodeBase */ js::RawObject gcIncomingGrayPointers; + /* Linked list of live array buffers with >1 view. */ + JSObject *gcLiveArrayBuffers; + private: /* * Malloc counter to measure memory pressure for GC scheduling. It runs from diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index ff21db9bbb65..7dd66cf42341 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3320,6 +3320,7 @@ BeginMarkPhase(JSRuntime *rt) JS_ASSERT(!c->isCollecting()); for (unsigned i = 0; i < FINALIZE_LIMIT; ++i) JS_ASSERT(!c->arenas.arenaListsToSweep[i]); + JS_ASSERT(!c->gcLiveArrayBuffers); /* Set up which compartments will be collected. */ if (c->isGCScheduled()) { @@ -3382,8 +3383,6 @@ BeginMarkPhase(JSRuntime *rt) /* Reset weak map list. */ WeakMapBase::resetWeakMapList(rt); - ArrayBufferObject::resetArrayBufferList(rt); - /* * We must purge the runtime at the beginning of an incremental GC. The * danger if we purge later is that the snapshot invariant of incremental @@ -3936,7 +3935,8 @@ BeginSweepingCompartmentGroup(JSRuntime *rt) rt->debugScopes->sweep(); /* Prune out dead views from ArrayBuffer's view lists. */ - ArrayBufferObject::sweepAll(rt); + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) + ArrayBufferObject::sweep(c); /* Collect watch points associated with unreachable objects. */ WatchpointMap::sweepAll(rt); @@ -4192,6 +4192,7 @@ EndSweepPhase(JSRuntime *rt, JSGCInvocationKind gckind, bool lastGC) JS_ASSERT(!c->isCollecting()); JS_ASSERT(!c->wasGCStarted()); JS_ASSERT(!c->gcIncomingGrayPointers); + JS_ASSERT(!c->gcLiveArrayBuffers); for (unsigned i = 0 ; i < FINALIZE_LIMIT ; ++i) { JS_ASSERT_IF(!IsBackgroundFinalized(AllocKind(i)) || @@ -4313,6 +4314,7 @@ ResetIncrementalGC(JSRuntime *rt, const char *reason) if (c->isGCMarking()) { c->setNeedsBarrier(false, JSCompartment::UpdateIon); c->setGCState(JSCompartment::NoGC); + ArrayBufferObject::resetArrayBufferList(c); } } @@ -4350,6 +4352,7 @@ ResetIncrementalGC(JSRuntime *rt, const char *reason) JS_ASSERT(c->isCollecting()); JS_ASSERT(!c->needsBarrier()); JS_ASSERT(!NextGraphNode(c.get())); + JS_ASSERT(!c->gcLiveArrayBuffers); for (unsigned i = 0 ; i < FINALIZE_LIMIT ; ++i) JS_ASSERT(!c->arenas.arenaListsToSweep[i]); } diff --git a/js/src/jstypedarray.cpp b/js/src/jstypedarray.cpp index 8333ec7c4fd2..92078b032b7b 100644 --- a/js/src/jstypedarray.cpp +++ b/js/src/jstypedarray.cpp @@ -535,23 +535,25 @@ ArrayBufferObject::obj_trace(JSTracer *trc, RawObject obj) } else { // Multiple views: do not mark, but append buffer to list. - // obj_trace may be called multiple times before sweepAll(), so avoid - // adding this buffer to the list multiple times. - if (BufferLink(firstView) == UNSET_BUFFER_LINK) { - JSObject **bufList = &trc->runtime->liveArrayBuffers; - SetBufferLink(firstView, *bufList); - *bufList = obj; + if (IS_GC_MARKING_TRACER(trc)) { + // obj_trace may be called multiple times before sweep(), so avoid + // adding this buffer to the list multiple times. + if (BufferLink(firstView) == UNSET_BUFFER_LINK) { + JS_ASSERT(obj->compartment() == firstView->compartment()); + JSObject **bufList = &obj->compartment()->gcLiveArrayBuffers; + SetBufferLink(firstView, *bufList); + *bufList = obj; + } } } } void -ArrayBufferObject::sweepAll(JSRuntime *rt) +ArrayBufferObject::sweep(JSCompartment *compartment) { - JSObject *buffer = rt->liveArrayBuffers; + JSObject *buffer = compartment->gcLiveArrayBuffers; JS_ASSERT(buffer != UNSET_BUFFER_LINK); - - JSObject *lastBufferViews = NULL; + compartment->gcLiveArrayBuffers = NULL; while (buffer) { JSObject **views = GetViewList(&buffer->asArrayBuffer()); @@ -559,6 +561,7 @@ ArrayBufferObject::sweepAll(JSRuntime *rt) JSObject *nextBuffer = BufferLink(*views); JS_ASSERT(nextBuffer != UNSET_BUFFER_LINK); + SetBufferLink(*views, UNSET_BUFFER_LINK); // Rebuild the list of views of the ArrayBuffer, discarding dead views. // If there is only one view, it will have already been marked. @@ -575,31 +578,16 @@ ArrayBufferObject::sweepAll(JSRuntime *rt) } *views = prevLiveView; - // Add the buffer to the end of the list if it has any views left. - // Buffers without views are dropped from the list. - if (*views) { - if (lastBufferViews) - SetBufferLink(lastBufferViews, buffer); - else - rt->liveArrayBuffers = buffer; - lastBufferViews = *views; - } - buffer = nextBuffer; } - - // Terminate the buffer list. - if (lastBufferViews) - SetBufferLink(lastBufferViews, NULL); - else - rt->liveArrayBuffers = NULL; } void -ArrayBufferObject::resetArrayBufferList(JSRuntime *rt) +ArrayBufferObject::resetArrayBufferList(JSCompartment *compartment) { - JSObject *buffer = rt->liveArrayBuffers; + JSObject *buffer = compartment->gcLiveArrayBuffers; JS_ASSERT(buffer != UNSET_BUFFER_LINK); + compartment->gcLiveArrayBuffers = NULL; while (buffer) { JSObject *view = *GetViewList(&buffer->asArrayBuffer()); @@ -611,7 +599,6 @@ ArrayBufferObject::resetArrayBufferList(JSRuntime *rt) SetBufferLink(view, UNSET_BUFFER_LINK); buffer = nextBuffer; } - rt->liveArrayBuffers = NULL; } JSBool diff --git a/js/src/jstypedarray.h b/js/src/jstypedarray.h index 7c70837e27b3..95d76e63b2ec 100644 --- a/js/src/jstypedarray.h +++ b/js/src/jstypedarray.h @@ -130,9 +130,9 @@ class ArrayBufferObject : public JSObject static JSBool obj_enumerate(JSContext *cx, HandleObject obj, JSIterateOp enum_op, MutableHandleValue statep, MutableHandleId idp); - static void sweepAll(JSRuntime *rt); + static void sweep(JSCompartment *rt); - static void resetArrayBufferList(JSRuntime *rt); + static void resetArrayBufferList(JSCompartment *rt); static bool stealContents(JSContext *cx, JSObject *obj, void **contents, uint8_t **data); From 31cd034aa10b3aa5c03bc83049b7b5d1163c37b1 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Fri, 2 Nov 2012 18:02:25 +0000 Subject: [PATCH 025/160] Bug 790338 - Make debug scopes object part of compartment not runtime r=billm --HG-- extra : rebase_source : 61e87a7b79541b70cf9d46451b69aef7368436fc --- js/src/jsapi.cpp | 9 -- js/src/jscntxt.h | 4 - js/src/jscompartment.cpp | 14 ++- js/src/jscompartment.h | 4 + js/src/jsgc.cpp | 6 +- js/src/jsweakmap.cpp | 12 +++ js/src/jsweakmap.h | 3 + js/src/vm/ScopeObject.cpp | 189 ++++++++++++++++++++++++++------------ js/src/vm/ScopeObject.h | 39 ++++---- js/src/vm/Stack.cpp | 10 +- 10 files changed, 189 insertions(+), 101 deletions(-) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 00e9a6d206fe..38e6b903d0bc 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -845,7 +845,6 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads) profilingScripts(false), alwaysPreserveCode(false), hadOutOfMemory(false), - debugScopes(NULL), data(NULL), gcLock(NULL), gcHelperThread(thisFromCtor()), @@ -960,12 +959,6 @@ JSRuntime::init(uint32_t maxbytes) if (!evalCache.init()) return false; - debugScopes = this->new_(this); - if (!debugScopes || !debugScopes->init()) { - js_delete(debugScopes); - return false; - } - nativeStackBase = GetNativeStackBase(); return true; } @@ -976,8 +969,6 @@ JSRuntime::~JSRuntime() clearOwnerThread(); #endif - js_delete(debugScopes); - /* * Even though all objects in the compartment are dead, we may have keep * some filenames around because of gcKeepAtoms. diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 04b3182f0d58..83df659831f3 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -98,7 +98,6 @@ class IonActivation; class WeakMapBase; class InterpreterFrames; -class DebugScopes; class WorkerThreadState; /* @@ -912,9 +911,6 @@ struct JSRuntime : js::RuntimeFriendFields */ JSCList onNewGlobalObjectWatchers; - /* Bookkeeping information for debug scope objects. */ - js::DebugScopes *debugScopes; - /* Client opaque pointers */ void *data; diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 55805902ec54..b4d85a67a66b 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -78,7 +78,8 @@ JSCompartment::JSCompartment(JSRuntime *rt) debugModeBits(rt->debugMode ? DebugFromC : 0), watchpointMap(NULL), scriptCountsMap(NULL), - debugScriptMap(NULL) + debugScriptMap(NULL), + debugScopes(NULL) #ifdef JS_ION , ionCompartment_(NULL) #endif @@ -95,6 +96,10 @@ JSCompartment::~JSCompartment() js_delete(watchpointMap); js_delete(scriptCountsMap); js_delete(debugScriptMap); + if (debugScopes) { + debugScopes->finalize(rt); + js_delete(debugScopes); + } } bool @@ -615,6 +620,9 @@ JSCompartment::sweep(FreeOp *fop, bool releaseTypes) * sweeping after clearing jit code. */ regExps.sweep(rt); + + if (debugScopes) + debugScopes->sweep(rt); } if (!activeAnalysis && !gcPreserveCode) { @@ -787,7 +795,7 @@ JSCompartment::setDebugModeFromC(JSContext *cx, bool b, AutoDebugModeGC &dmgc) if (enabledBefore != enabledAfter) { updateForDebugMode(cx->runtime->defaultFreeOp(), dmgc); if (!enabledAfter) - cx->runtime->debugScopes->onCompartmentLeaveDebugMode(this); + DebugScopes::onCompartmentLeaveDebugMode(this); } return true; } @@ -861,7 +869,7 @@ JSCompartment::removeDebuggee(FreeOp *fop, debugModeBits &= ~DebugFromJS; if (wasEnabled && !debugMode()) { AutoDebugModeGC dmgc(rt); - fop->runtime()->debugScopes->onCompartmentLeaveDebugMode(this); + DebugScopes::onCompartmentLeaveDebugMode(this); updateForDebugMode(fop, dmgc); } } diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 3222859c8376..1a3d3bee370a 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -115,6 +115,7 @@ struct TypeInferenceSizes; namespace js { class AutoDebugModeGC; +struct DebugScopes; } struct JSCompartment : public js::gc::GraphNodeBase @@ -484,6 +485,9 @@ struct JSCompartment : public js::gc::GraphNodeBase js::DebugScriptMap *debugScriptMap; + /* Bookkeeping information for debug scope objects. */ + js::DebugScopes *debugScopes; + #ifdef JS_ION private: js::ion::IonCompartment *ionCompartment_; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 7dd66cf42341..abf72ce5ce0d 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -2554,6 +2554,10 @@ MarkRuntime(JSTracer *trc, bool useSavedRoots = false) } } } + + /* Mark debug scopes, if present */ + if (c->debugScopes) + c->debugScopes->mark(trc); } #ifdef JS_METHODJIT @@ -2563,7 +2567,6 @@ MarkRuntime(JSTracer *trc, bool useSavedRoots = false) #endif rt->stackSpace.mark(trc); - rt->debugScopes->mark(trc); #ifdef JS_ION ion::MarkIonActivations(rt, trc); @@ -3932,7 +3935,6 @@ BeginSweepingCompartmentGroup(JSRuntime *rt) /* Finalize unreachable (key,value) pairs in all weak maps. */ WeakMapBase::sweepAll(&rt->gcMarker); - rt->debugScopes->sweep(); /* Prune out dead views from ArrayBuffer's view lists. */ for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) diff --git a/js/src/jsweakmap.cpp b/js/src/jsweakmap.cpp index d60bb5add1d1..f3eb6005299e 100644 --- a/js/src/jsweakmap.cpp +++ b/js/src/jsweakmap.cpp @@ -87,6 +87,18 @@ WeakMapBase::restoreWeakMapList(JSRuntime *rt, WeakMapVector &vector) } } +void +WeakMapBase::removeWeakMapFromList(JSRuntime *rt, WeakMapBase *weakmap) +{ + for (WeakMapBase **p = &rt->gcWeakMapList; *p; p = &(*p)->next) { + if (*p == weakmap) { + *p = (*p)->next; + weakmap->next = WeakMapNotInList; + break; + } + } +} + typedef WeakMap ObjectValueMap; static ObjectValueMap * diff --git a/js/src/jsweakmap.h b/js/src/jsweakmap.h index 80d63aaef4c9..adba4ef72a7a 100644 --- a/js/src/jsweakmap.h +++ b/js/src/jsweakmap.h @@ -93,6 +93,9 @@ class WeakMapBase { static bool saveWeakMapList(JSRuntime *rt, WeakMapVector &vector); static void restoreWeakMapList(JSRuntime *rt, WeakMapVector &vector); + // Remove a weakmap from the live weakmap list + static void removeWeakMapFromList(JSRuntime *rt, WeakMapBase *weakmap); + protected: // Instance member functions called by the above. Instantiations of WeakMap override // these with definitions appropriate for their Key and Value types. diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index c3629c10cded..290506997f53 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -1117,7 +1117,7 @@ class DebugScopeProxy : public BaseProxyHandler jsid id, Action action, Value *vp) { JS_ASSERT(&debugScope->scope() == scope); - StackFrame *maybefp = cx->runtime->debugScopes->hasLiveFrame(*scope); + StackFrame *maybefp = DebugScopes::hasLiveFrame(*scope); /* Handle unaliased formals, vars, and consts at function scope. */ if (scope->isCall() && !scope->asCall().isForEval()) { @@ -1268,7 +1268,7 @@ class DebugScopeProxy : public BaseProxyHandler if (scope.asCall().callee().nonLazyScript()->needsArgsObj()) return true; - StackFrame *maybefp = cx->runtime->debugScopes->hasLiveFrame(scope); + StackFrame *maybefp = DebugScopes::hasLiveFrame(scope); if (!maybefp) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_LIVE, "Debugger scope"); @@ -1521,11 +1521,10 @@ js_IsDebugScopeSlow(RawObject obj) /*****************************************************************************/ -DebugScopes::DebugScopes(JSRuntime *rt) - : rt(rt), - proxiedScopes(rt), - missingScopes(rt), - liveScopes(rt) +DebugScopes::DebugScopes(JSContext *cx) + : proxiedScopes(cx), + missingScopes(cx), + liveScopes(cx) {} DebugScopes::~DebugScopes() @@ -1545,6 +1544,12 @@ DebugScopes::init() return true; } +void +DebugScopes::finalize(JSRuntime *rt) +{ + WeakMapBase::removeWeakMapFromList(rt, &proxiedScopes); +} + void DebugScopes::mark(JSTracer *trc) { @@ -1552,7 +1557,7 @@ DebugScopes::mark(JSTracer *trc) } void -DebugScopes::sweep() +DebugScopes::sweep(JSRuntime *rt) { /* * Note: missingScopes points to debug scopes weakly not just so that debug @@ -1605,36 +1610,69 @@ CanUseDebugScopeMaps(JSContext *cx) return cx->compartment->debugMode(); } -DebugScopeObject * -DebugScopes::hasDebugScope(JSContext *cx, ScopeObject &scope) const +DebugScopes * +DebugScopes::ensureCompartmentData(JSContext *cx) { - if (ObjectWeakMap::Ptr p = proxiedScopes.lookup(&scope)) { + JSCompartment *c = cx->compartment; + if (c->debugScopes) + return c->debugScopes; + + c->debugScopes = c->rt->new_(cx); + if (c->debugScopes && c->debugScopes->init()) + return c->debugScopes; + + js_ReportOutOfMemory(cx); + return NULL; +} + +DebugScopeObject * +DebugScopes::hasDebugScope(JSContext *cx, ScopeObject &scope) +{ + DebugScopes *scopes = scope.compartment()->debugScopes; + if (!scopes) + return NULL; + + if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&scope)) { JS_ASSERT(CanUseDebugScopeMaps(cx)); return &p->value->asDebugScope(); } + return NULL; } bool DebugScopes::addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope) { + JS_ASSERT(cx->compartment == scope.compartment()); + JS_ASSERT(cx->compartment == debugScope.compartment()); + if (!CanUseDebugScopeMaps(cx)) return true; - JS_ASSERT(!proxiedScopes.has(&scope)); - if (!proxiedScopes.put(&scope, &debugScope)) { + DebugScopes *scopes = ensureCompartmentData(cx); + if (!scopes) + return false; + + JS_ASSERT(!scopes->proxiedScopes.has(&scope)); + if (!scopes->proxiedScopes.put(&scope, &debugScope)) { js_ReportOutOfMemory(cx); return false; } - HashTableWriteBarrierPost(debugScope.compartment(), &proxiedScopes, &scope); + + HashTableWriteBarrierPost(cx->compartment, &scopes->proxiedScopes, &scope); return true; } DebugScopeObject * -DebugScopes::hasDebugScope(JSContext *cx, const ScopeIter &si) const +DebugScopes::hasDebugScope(JSContext *cx, const ScopeIter &si) { JS_ASSERT(!si.hasScopeObject()); - if (MissingScopeMap::Ptr p = missingScopes.lookup(si)) { + + DebugScopes *scopes = cx->compartment->debugScopes; + if (!scopes) + return NULL; + + if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { JS_ASSERT(CanUseDebugScopeMaps(cx)); return p->value; } @@ -1645,20 +1683,27 @@ bool DebugScopes::addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope) { JS_ASSERT(!si.hasScopeObject()); + JS_ASSERT(cx->compartment == debugScope.compartment()); + if (!CanUseDebugScopeMaps(cx)) return true; - JS_ASSERT(!missingScopes.has(si)); - if (!missingScopes.put(si, &debugScope)) { + DebugScopes *scopes = ensureCompartmentData(cx); + if (!scopes) + return false; + + JS_ASSERT(!scopes->missingScopes.has(si)); + if (!scopes->missingScopes.put(si, &debugScope)) { js_ReportOutOfMemory(cx); return false; } - JS_ASSERT(!liveScopes.has(&debugScope.scope())); - if (!liveScopes.put(&debugScope.scope(), si.fp())) { + JS_ASSERT(!scopes->liveScopes.has(&debugScope.scope())); + if (!scopes->liveScopes.put(&debugScope.scope(), si.fp())) { js_ReportOutOfMemory(cx); return false; } + return true; } @@ -1668,6 +1713,10 @@ DebugScopes::onPopCall(StackFrame *fp, JSContext *cx) JS_ASSERT(!fp->isYielding()); assertSameCompartment(cx, fp); + DebugScopes *scopes = cx->compartment->debugScopes; + if (!scopes) + return; + DebugScopeObject *debugScope = NULL; if (fp->fun()->isHeavyweight()) { @@ -1679,15 +1728,15 @@ DebugScopes::onPopCall(StackFrame *fp, JSContext *cx) return; CallObject &callobj = fp->scopeChain()->asCall(); - liveScopes.remove(&callobj); - if (ObjectWeakMap::Ptr p = proxiedScopes.lookup(&callobj)) + scopes->liveScopes.remove(&callobj); + if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&callobj)) debugScope = &p->value->asDebugScope(); } else { ScopeIter si(fp, cx); - if (MissingScopeMap::Ptr p = missingScopes.lookup(si)) { + if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { debugScope = p->value; - liveScopes.remove(&debugScope->scope().asCall()); - missingScopes.remove(p); + scopes->liveScopes.remove(&debugScope->scope().asCall()); + scopes->missingScopes.remove(p); } } @@ -1742,18 +1791,22 @@ DebugScopes::onPopBlock(JSContext *cx, StackFrame *fp) { assertSameCompartment(cx, fp); + DebugScopes *scopes = cx->compartment->debugScopes; + if (!scopes) + return; + StaticBlockObject &staticBlock = *fp->maybeBlockChain(); if (staticBlock.needsClone()) { ClonedBlockObject &clone = fp->scopeChain()->asClonedBlock(); clone.copyUnaliasedValues(fp); - liveScopes.remove(&clone); + scopes->liveScopes.remove(&clone); } else { ScopeIter si(fp, cx); - if (MissingScopeMap::Ptr p = missingScopes.lookup(si)) { + if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { ClonedBlockObject &clone = p->value->scope().asClonedBlock(); clone.copyUnaliasedValues(fp); - liveScopes.remove(&clone); - missingScopes.remove(p); + scopes->liveScopes.remove(&clone); + scopes->missingScopes.remove(p); } } } @@ -1761,24 +1814,34 @@ DebugScopes::onPopBlock(JSContext *cx, StackFrame *fp) void DebugScopes::onPopWith(StackFrame *fp) { - liveScopes.remove(&fp->scopeChain()->asWith()); + DebugScopes *scopes = fp->compartment()->debugScopes; + if (scopes) + scopes->liveScopes.remove(&fp->scopeChain()->asWith()); } void DebugScopes::onPopStrictEvalScope(StackFrame *fp) { + DebugScopes *scopes = fp->compartment()->debugScopes; + if (!scopes) + return; + /* * The StackFrame may be observed before the prologue has created the * CallObject. See ScopeIter::settle. */ if (fp->hasCallObj()) - liveScopes.remove(&fp->scopeChain()->asCall()); + scopes->liveScopes.remove(&fp->scopeChain()->asCall()); } void DebugScopes::onGeneratorFrameChange(StackFrame *from, StackFrame *to, JSContext *cx) { for (ScopeIter toIter(to, cx); !toIter.done(); ++toIter) { + DebugScopes *scopes = ensureCompartmentData(cx); + if (!scopes) + return; + if (toIter.hasScopeObject()) { /* * Not only must we correctly replace mappings [scope -> from] with @@ -1787,18 +1850,20 @@ DebugScopes::onGeneratorFrameChange(StackFrame *from, StackFrame *to, JSContext * scope while it is suspended, we can find its frame (which would * otherwise not be found by AllFramesIter). */ - LiveScopeMap::AddPtr livePtr = liveScopes.lookupForAdd(&toIter.scope()); + JS_ASSERT(toIter.scope().compartment() == cx->compartment); + LiveScopeMap::AddPtr livePtr = scopes->liveScopes.lookupForAdd(&toIter.scope()); if (livePtr) livePtr->value = to; else - liveScopes.add(livePtr, &toIter.scope(), to); + scopes->liveScopes.add(livePtr, &toIter.scope(), to); // OOM here? } else { ScopeIter si(toIter, from, cx); - if (MissingScopeMap::Ptr p = missingScopes.lookup(si)) { + JS_ASSERT(si.fp()->compartment() == cx->compartment); + if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { DebugScopeObject &debugScope = *p->value; - liveScopes.lookup(&debugScope.scope())->value = to; - missingScopes.remove(p); - missingScopes.put(toIter, &debugScope); + scopes->liveScopes.lookup(&debugScope.scope())->value = to; + scopes->missingScopes.remove(p); + scopes->missingScopes.put(toIter, &debugScope); // OOM here? } } } @@ -1807,17 +1872,11 @@ DebugScopes::onGeneratorFrameChange(StackFrame *from, StackFrame *to, JSContext void DebugScopes::onCompartmentLeaveDebugMode(JSCompartment *c) { - for (ObjectWeakMap::Enum e(proxiedScopes); !e.empty(); e.popFront()) { - if (e.front().key->compartment() == c) - e.removeFront(); - } - for (MissingScopeMap::Enum e(missingScopes); !e.empty(); e.popFront()) { - if (e.front().key.fp()->compartment() == c) - e.removeFront(); - } - for (LiveScopeMap::Enum e(liveScopes); !e.empty(); e.popFront()) { - if (e.front().key->compartment() == c) - e.removeFront(); + DebugScopes *scopes = c->debugScopes; + if (scopes) { + scopes->proxiedScopes.clear(); + scopes->missingScopes.clear(); + scopes->liveScopes.clear(); } } @@ -1850,8 +1909,14 @@ DebugScopes::updateLiveScopes(JSContext *cx) continue; for (ScopeIter si(fp, cx); !si.done(); ++si) { - if (si.hasScopeObject() && !liveScopes.put(&si.scope(), fp)) - return false; + if (si.hasScopeObject()) { + JS_ASSERT(si.scope().compartment() == cx->compartment); + DebugScopes *scopes = ensureCompartmentData(cx); + if (!scopes) + return false; + if (!scopes->liveScopes.put(&si.scope(), fp)) + return false; + } } if (fp->prevUpToDate()) @@ -1866,7 +1931,11 @@ DebugScopes::updateLiveScopes(JSContext *cx) StackFrame * DebugScopes::hasLiveFrame(ScopeObject &scope) { - if (LiveScopeMap::Ptr p = liveScopes.lookup(&scope)) { + DebugScopes *scopes = scope.compartment()->debugScopes; + if (!scopes) + return NULL; + + if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope)) { StackFrame *fp = p->value; /* @@ -1880,7 +1949,7 @@ DebugScopes::hasLiveFrame(ScopeObject &scope) * 4. GC completes, live objects may now point to values that weren't * marked and thus may point to swept GC things */ - if (JSGenerator *gen = fp->maybeSuspendedGenerator(rt)) + if (JSGenerator *gen = fp->maybeSuspendedGenerator(scope.compartment()->rt)) JSObject::readBarrier(gen->obj); return fp; @@ -1896,8 +1965,7 @@ GetDebugScope(JSContext *cx, const ScopeIter &si); static DebugScopeObject * GetDebugScopeForScope(JSContext *cx, Handle scope, const ScopeIter &enclosing) { - DebugScopes &debugScopes = *cx->runtime->debugScopes; - if (DebugScopeObject *debugScope = debugScopes.hasDebugScope(cx, *scope)) + if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, *scope)) return debugScope; RootedObject enclosingDebug(cx, GetDebugScope(cx, enclosing)); @@ -1916,7 +1984,7 @@ GetDebugScopeForScope(JSContext *cx, Handle scope, const ScopeIter if (!debugScope) return NULL; - if (!debugScopes.addDebugScope(cx, *scope, *debugScope)) + if (!DebugScopes::addDebugScope(cx, *scope, *debugScope)) return NULL; return debugScope; @@ -1925,8 +1993,7 @@ GetDebugScopeForScope(JSContext *cx, Handle scope, const ScopeIter static DebugScopeObject * GetDebugScopeForMissing(JSContext *cx, const ScopeIter &si) { - DebugScopes &debugScopes = *cx->runtime->debugScopes; - if (DebugScopeObject *debugScope = debugScopes.hasDebugScope(cx, si)) + if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, si)) return debugScope; ScopeIter copy(si, cx); @@ -1979,7 +2046,7 @@ GetDebugScopeForMissing(JSContext *cx, const ScopeIter &si) if (!debugScope) return NULL; - if (!debugScopes.addDebugScope(cx, si, *debugScope)) + if (!DebugScopes::addDebugScope(cx, si, *debugScope)) return NULL; return debugScope; @@ -2004,7 +2071,7 @@ GetDebugScope(JSContext *cx, JSObject &obj) } Rooted scope(cx, &obj.asScope()); - if (StackFrame *fp = cx->runtime->debugScopes->hasLiveFrame(*scope)) { + if (StackFrame *fp = DebugScopes::hasLiveFrame(*scope)) { ScopeIter si(fp, *scope, cx); return GetDebugScope(cx, si); } @@ -2034,7 +2101,7 @@ js::GetDebugScopeForFunction(JSContext *cx, JSFunction *fun) { assertSameCompartment(cx, fun); JS_ASSERT(cx->compartment->debugMode()); - if (!cx->runtime->debugScopes->updateLiveScopes(cx)) + if (!DebugScopes::updateLiveScopes(cx)) return NULL; return GetDebugScope(cx, *fun->environment()); } @@ -2043,7 +2110,7 @@ JSObject * js::GetDebugScopeForFrame(JSContext *cx, StackFrame *fp) { assertSameCompartment(cx, fp); - if (CanUseDebugScopeMaps(cx) && !cx->runtime->debugScopes->updateLiveScopes(cx)) + if (CanUseDebugScopeMaps(cx) && !DebugScopes::updateLiveScopes(cx)) return NULL; ScopeIter si(fp, cx); return GetDebugScope(cx, si); diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index 628b7bf64a11..d490c73b3f95 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -534,11 +534,9 @@ class DebugScopeObject : public JSObject bool isForDeclarative() const; }; -/* Maintains runtime-wide debug scope bookkeeping information. */ +/* Maintains per-compartment debug scope bookkeeping information. */ class DebugScopes { - JSRuntime *rt; - /* The map from (non-debug) scopes to debug scopes. */ typedef WeakMap ObjectWeakMap; ObjectWeakMap proxiedScopes; @@ -567,32 +565,39 @@ class DebugScopes LiveScopeMap liveScopes; public: - DebugScopes(JSRuntime *rt); + DebugScopes(JSContext *c); ~DebugScopes(); + + void finalize(JSRuntime *rt); + + private: bool init(); + static DebugScopes *ensureCompartmentData(JSContext *cx); + + public: void mark(JSTracer *trc); - void sweep(); + void sweep(JSRuntime *rt); - DebugScopeObject *hasDebugScope(JSContext *cx, ScopeObject &scope) const; - bool addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope); + static DebugScopeObject *hasDebugScope(JSContext *cx, ScopeObject &scope); + static bool addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope); - DebugScopeObject *hasDebugScope(JSContext *cx, const ScopeIter &si) const; - bool addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope); + static DebugScopeObject *hasDebugScope(JSContext *cx, const ScopeIter &si); + static bool addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope); - bool updateLiveScopes(JSContext *cx); - StackFrame *hasLiveFrame(ScopeObject &scope); + static bool updateLiveScopes(JSContext *cx); + static StackFrame *hasLiveFrame(ScopeObject &scope); /* * In debug-mode, these must be called whenever exiting a call/block or * when activating/yielding a generator. */ - void onPopCall(StackFrame *fp, JSContext *cx); - void onPopBlock(JSContext *cx, StackFrame *fp); - void onPopWith(StackFrame *fp); - void onPopStrictEvalScope(StackFrame *fp); - void onGeneratorFrameChange(StackFrame *from, StackFrame *to, JSContext *cx); - void onCompartmentLeaveDebugMode(JSCompartment *c); + static void onPopCall(StackFrame *fp, JSContext *cx); + static void onPopBlock(JSContext *cx, StackFrame *fp); + static void onPopWith(StackFrame *fp); + static void onPopStrictEvalScope(StackFrame *fp); + static void onGeneratorFrameChange(StackFrame *from, StackFrame *to, JSContext *cx); + static void onCompartmentLeaveDebugMode(JSCompartment *c); }; } /* namespace js */ diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index e7a7b96ee755..136a9491328e 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -126,7 +126,7 @@ StackFrame::copyFrameAndValues(JSContext *cx, Value *vp, StackFrame *otherfp, } if (cx->compartment->debugMode()) - cx->runtime->debugScopes->onGeneratorFrameChange(otherfp, this, cx); + DebugScopes::onGeneratorFrameChange(otherfp, this, cx); } /* Note: explicit instantiation for js_NewGenerator located in jsiter.cpp. */ @@ -333,7 +333,7 @@ StackFrame::epilogue(JSContext *cx) if (isStrictEvalFrame()) { JS_ASSERT_IF(hasCallObj(), scopeChain()->asCall().isForEval()); if (cx->compartment->debugMode()) - cx->runtime->debugScopes->onPopStrictEvalScope(this); + DebugScopes::onPopStrictEvalScope(this); } else if (isDirectEvalFrame()) { if (isDebuggerFrame()) JS_ASSERT(!scopeChain()->isScope()); @@ -368,7 +368,7 @@ StackFrame::epilogue(JSContext *cx) AssertDynamicScopeMatchesStaticScope(script, scopeChain()); if (cx->compartment->debugMode()) - cx->runtime->debugScopes->onPopCall(this, cx); + DebugScopes::onPopCall(this, cx); if (isConstructing() && returnValue().isPrimitive()) @@ -416,7 +416,7 @@ StackFrame::popBlock(JSContext *cx) JS_ASSERT(hasBlockChain()); if (cx->compartment->debugMode()) - cx->runtime->debugScopes->onPopBlock(cx, this); + DebugScopes::onPopBlock(cx, this); if (blockChain_->needsClone()) { JS_ASSERT(scopeChain_->asClonedBlock().staticBlock() == *blockChain_); @@ -430,7 +430,7 @@ void StackFrame::popWith(JSContext *cx) { if (cx->compartment->debugMode()) - cx->runtime->debugScopes->onPopWith(this); + DebugScopes::onPopWith(this); JS_ASSERT(scopeChain()->isWith()); popOffScopeChain(); From 897ff0e375c062f4be482c9d3f4aa6167f4749b8 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Fri, 2 Nov 2012 18:03:59 +0000 Subject: [PATCH 026/160] Bug 790338 - Make weakmap list per-compartment rather than per-runtime r=billm --HG-- extra : rebase_source : 3f3ceee949ae9fc2a0ac232038e3858ff838c193 --- js/src/jsapi.cpp | 1 - js/src/jscntxt.h | 1 - js/src/jscompartment.cpp | 9 ++++--- js/src/jscompartment.h | 3 +++ js/src/jsgc.cpp | 46 +++++++++++++++++++------------- js/src/jsweakmap.cpp | 54 +++++++++++++++++++++++-------------- js/src/jsweakmap.h | 56 +++++++++++++++++++++------------------ js/src/vm/Debugger.h | 4 +-- js/src/vm/ScopeObject.cpp | 7 +---- js/src/vm/ScopeObject.h | 2 -- 10 files changed, 102 insertions(+), 81 deletions(-) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 38e6b903d0bc..3f4299464a7b 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -787,7 +787,6 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads) gcDynamicMarkSlice(false), gcShouldCleanUpEverything(false), gcIsNeeded(0), - gcWeakMapList(NULL), gcStats(thisFromCtor()), gcNumber(0), gcStartNumber(0), diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 83df659831f3..acf949e663eb 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -656,7 +656,6 @@ struct JSRuntime : js::RuntimeFriendFields */ volatile uintptr_t gcIsNeeded; - js::WeakMapBase *gcWeakMapList; js::gcstats::Statistics gcStats; /* Incremented on every GC slice. */ diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index b4d85a67a66b..72e4c8250a31 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -74,6 +74,7 @@ JSCompartment::JSCompartment(JSRuntime *rt) gcTriggerMallocAndFreeBytes(0), gcIncomingGrayPointers(NULL), gcLiveArrayBuffers(NULL), + gcWeakMapList(NULL), gcMallocBytes(0), debugModeBits(rt->debugMode ? DebugFromC : 0), watchpointMap(NULL), @@ -96,10 +97,7 @@ JSCompartment::~JSCompartment() js_delete(watchpointMap); js_delete(scriptCountsMap); js_delete(debugScriptMap); - if (debugScopes) { - debugScopes->finalize(rt); - js_delete(debugScopes); - } + js_delete(debugScopes); } bool @@ -623,6 +621,9 @@ JSCompartment::sweep(FreeOp *fop, bool releaseTypes) if (debugScopes) debugScopes->sweep(rt); + + /* Finalize unreachable (key,value) pairs in all weak maps. */ + WeakMapBase::sweepCompartment(this); } if (!activeAnalysis && !gcPreserveCode) { diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 1a3d3bee370a..38b04dc52c2e 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -367,6 +367,9 @@ struct JSCompartment : public js::gc::GraphNodeBase /* Linked list of live array buffers with >1 view. */ JSObject *gcLiveArrayBuffers; + /* Linked list of live weakmaps in this compartment. */ + js::WeakMapBase *gcWeakMapList; + private: /* * Malloc counter to measure memory pressure for GC scheduling. It runs from diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index abf72ce5ce0d..26334379e99c 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3383,9 +3383,6 @@ BeginMarkPhase(JSRuntime *rt) rt->gcStartNumber = rt->gcNumber; - /* Reset weak map list. */ - WeakMapBase::resetWeakMapList(rt); - /* * We must purge the runtime at the beginning of an incremental GC. The * danger if we purge later is that the snapshot invariant of incremental @@ -3406,10 +3403,14 @@ BeginMarkPhase(JSRuntime *rt) gcstats::AutoPhase ap1(rt->gcStats, gcstats::PHASE_MARK); gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_MARK_ROOTS); - /* Unmark everything in the compartments being collected. */ - for (GCCompartmentsIter c(rt); !c.done(); c.next()) + for (GCCompartmentsIter c(rt); !c.done(); c.next()) { + /* Unmark everything in the compartments being collected. */ c->arenas.unmarkAll(); + /* Reset weak map list for the compartments being collected. */ + WeakMapBase::resetCompartmentWeakMapList(c); + } + MarkRuntime(gcmarker); /* @@ -3466,6 +3467,16 @@ BeginMarkPhase(JSRuntime *rt) rt->gcFoundBlackGrayEdges = false; } +bool +MarkWeakMapsIteratively(JSRuntime *rt) +{ + bool markedAny = false; + GCMarker *gcmarker = &rt->gcMarker; + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) + markedAny |= WeakMapBase::markCompartmentIteratively(c, gcmarker); + return markedAny; +} + void MarkWeakReferences(JSRuntime *rt, gcstats::Phase phase) { @@ -3475,7 +3486,7 @@ MarkWeakReferences(JSRuntime *rt, gcstats::Phase phase) gcstats::AutoPhase ap(rt->gcStats, phase); while (WatchpointMap::markAllIteratively(gcmarker) || - WeakMapBase::markAllIteratively(gcmarker) || + MarkWeakMapsIteratively(rt) || Debugger::markAllIteratively(gcmarker)) { SliceBudget budget; @@ -3537,10 +3548,14 @@ ValidateIncrementalMarking(JSRuntime *rt) return; } - /* Save the existing weakmaps. */ + /* Save and reset the lists of live weakmaps for the compartments we are collecting. */ WeakMapVector weakmaps; - if (!WeakMapBase::saveWeakMapList(rt, weakmaps)) - return; + for (GCCompartmentsIter c(rt); !c.done(); c.next()) { + if (!WeakMapBase::saveCompartmentWeakMapList(c, weakmaps)) + return; + } + for (GCCompartmentsIter c(rt); !c.done(); c.next()) + WeakMapBase::resetCompartmentWeakMapList(c); /* * After this point, the function should run to completion, so we shouldn't @@ -3551,9 +3566,6 @@ ValidateIncrementalMarking(JSRuntime *rt) js::gc::State state = rt->gcIncrementalState; rt->gcIncrementalState = MARK_ROOTS; - /* As we're re-doing marking, we need to reset the weak map list. */ - WeakMapBase::resetWeakMapList(rt); - JS_ASSERT(gcmarker->isDrained()); gcmarker->reset(); @@ -3615,9 +3627,10 @@ ValidateIncrementalMarking(JSRuntime *rt) memcpy(bitmap->bitmap, incBitmap.bitmap, sizeof(incBitmap.bitmap)); } - /* Restore the weak map list. */ - WeakMapBase::resetWeakMapList(rt); - WeakMapBase::restoreWeakMapList(rt, weakmaps); + /* Restore the weak map lists. */ + for (GCCompartmentsIter c(rt); !c.done(); c.next()) + WeakMapBase::resetCompartmentWeakMapList(c); + WeakMapBase::restoreCompartmentWeakMapLists(weakmaps); rt->gcIncrementalState = state; } @@ -3933,9 +3946,6 @@ BeginSweepingCompartmentGroup(JSRuntime *rt) SweepAtoms(rt); } - /* Finalize unreachable (key,value) pairs in all weak maps. */ - WeakMapBase::sweepAll(&rt->gcMarker); - /* Prune out dead views from ArrayBuffer's view lists. */ for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) ArrayBufferObject::sweep(c); diff --git a/js/src/jsweakmap.cpp b/js/src/jsweakmap.cpp index f3eb6005299e..fdc7933d43d9 100644 --- a/js/src/jsweakmap.cpp +++ b/js/src/jsweakmap.cpp @@ -21,12 +21,24 @@ using namespace js; +WeakMapBase::WeakMapBase(JSObject *memOf, JSCompartment *c) + : memberOf(memOf), + compartment(c), + next(WeakMapNotInList) +{ + JS_ASSERT_IF(memberOf, memberOf->compartment() == c); +} + +WeakMapBase::~WeakMapBase() +{ + JS_ASSERT(next == WeakMapNotInList); +} + bool -WeakMapBase::markAllIteratively(JSTracer *tracer) +WeakMapBase::markCompartmentIteratively(JSCompartment *c, JSTracer *tracer) { bool markedAny = false; - JSRuntime *rt = tracer->runtime; - for (WeakMapBase *m = rt->gcWeakMapList; m; m = m->next) { + for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) { if (m->markIteratively(tracer)) markedAny = true; } @@ -34,28 +46,29 @@ WeakMapBase::markAllIteratively(JSTracer *tracer) } void -WeakMapBase::sweepAll(JSTracer *tracer) +WeakMapBase::sweepCompartment(JSCompartment *c) { - JSRuntime *rt = tracer->runtime; - for (WeakMapBase *m = rt->gcWeakMapList; m; m = m->next) - m->sweep(tracer); + for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) + m->sweep(); } void WeakMapBase::traceAllMappings(WeakMapTracer *tracer) { JSRuntime *rt = tracer->runtime; - for (WeakMapBase *m = rt->gcWeakMapList; m; m = m->next) - m->traceMappings(tracer); + for (CompartmentsIter c(rt); !c.done(); c.next()) { + for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) + m->traceMappings(tracer); + } } void -WeakMapBase::resetWeakMapList(JSRuntime *rt) +WeakMapBase::resetCompartmentWeakMapList(JSCompartment *c) { JS_ASSERT(WeakMapNotInList != NULL); - WeakMapBase *m = rt->gcWeakMapList; - rt->gcWeakMapList = NULL; + WeakMapBase *m = c->gcWeakMapList; + c->gcWeakMapList = NULL; while (m) { WeakMapBase *n = m->next; m->next = WeakMapNotInList; @@ -64,9 +77,9 @@ WeakMapBase::resetWeakMapList(JSRuntime *rt) } bool -WeakMapBase::saveWeakMapList(JSRuntime *rt, WeakMapVector &vector) +WeakMapBase::saveCompartmentWeakMapList(JSCompartment *c, WeakMapVector &vector) { - WeakMapBase *m = rt->gcWeakMapList; + WeakMapBase *m = c->gcWeakMapList; while (m) { if (!vector.append(m)) return false; @@ -76,21 +89,22 @@ WeakMapBase::saveWeakMapList(JSRuntime *rt, WeakMapVector &vector) } void -WeakMapBase::restoreWeakMapList(JSRuntime *rt, WeakMapVector &vector) +WeakMapBase::restoreCompartmentWeakMapLists(WeakMapVector &vector) { - JS_ASSERT(!rt->gcWeakMapList); for (WeakMapBase **p = vector.begin(); p != vector.end(); p++) { WeakMapBase *m = *p; JS_ASSERT(m->next == WeakMapNotInList); - m->next = rt->gcWeakMapList; - rt->gcWeakMapList = m; + JSCompartment *c = m->compartment; + m->next = c->gcWeakMapList; + c->gcWeakMapList = m; } } void -WeakMapBase::removeWeakMapFromList(JSRuntime *rt, WeakMapBase *weakmap) +WeakMapBase::removeWeakMapFromList(WeakMapBase *weakmap) { - for (WeakMapBase **p = &rt->gcWeakMapList; *p; p = &(*p)->next) { + JSCompartment *c = weakmap->compartment; + for (WeakMapBase **p = &c->gcWeakMapList; *p; p = &(*p)->next) { if (*p == weakmap) { *p = (*p)->next; weakmap->next = WeakMapNotInList; diff --git a/js/src/jsweakmap.h b/js/src/jsweakmap.h index adba4ef72a7a..903e56621892 100644 --- a/js/src/jsweakmap.h +++ b/js/src/jsweakmap.h @@ -10,7 +10,7 @@ #include "jsapi.h" #include "jsfriendapi.h" -#include "jscntxt.h" +#include "jscompartment.h" #include "jsobj.h" #include "gc/Marking.h" @@ -41,8 +41,8 @@ typedef Vector WeakMapVector; // their markIteratively and sweep methods. class WeakMapBase { public: - WeakMapBase(JSObject *memOf) : memberOf(memOf), next(WeakMapNotInList) { } - virtual ~WeakMapBase() { } + WeakMapBase(JSObject *memOf, JSCompartment *c); + virtual ~WeakMapBase(); void trace(JSTracer *tracer) { if (IS_GC_MARKING_TRACER(tracer)) { @@ -55,9 +55,8 @@ class WeakMapBase { // Add ourselves to the list if we are not already in the list. We can already // be in the list if the weak map is marked more than once due delayed marking. if (next == WeakMapNotInList) { - JSRuntime *rt = tracer->runtime; - next = rt->gcWeakMapList; - rt->gcWeakMapList = this; + next = compartment->gcWeakMapList; + compartment->gcWeakMapList = this; } } else { // If we're not actually doing garbage collection, the keys won't be marked @@ -71,48 +70,53 @@ class WeakMapBase { // Garbage collector entry points. - // Check all weak maps that have been marked as live so far in this garbage + // Check all weak maps in a compartment that have been marked as live in this garbage // collection, and mark the values of all entries that have become strong references // to them. Return true if we marked any new values, indicating that we need to make // another pass. In other words, mark my marked maps' marked members' mid-collection. - static bool markAllIteratively(JSTracer *tracer); + static bool markCompartmentIteratively(JSCompartment *c, JSTracer *tracer); - // Remove entries whose keys are dead from all weak maps marked as live in this - // garbage collection. - static void sweepAll(JSTracer *tracer); + // Remove entries whose keys are dead from all weak maps in a compartment marked as + // live in this garbage collection. + static void sweepCompartment(JSCompartment *c); // Trace all delayed weak map bindings. Used by the cycle collector. static void traceAllMappings(WeakMapTracer *tracer); void check() { JS_ASSERT(next == WeakMapNotInList); } - // Remove everything from the live weak map list. - static void resetWeakMapList(JSRuntime *rt); + // Remove everything from the weak map list for a compartment. + static void resetCompartmentWeakMapList(JSCompartment *c); - // Save and restore the live weak map list to a vector. - static bool saveWeakMapList(JSRuntime *rt, WeakMapVector &vector); - static void restoreWeakMapList(JSRuntime *rt, WeakMapVector &vector); + // Save the live weak map list for a compartment, appending the data to a vector. + static bool saveCompartmentWeakMapList(JSCompartment *c, WeakMapVector &vector); - // Remove a weakmap from the live weakmap list - static void removeWeakMapFromList(JSRuntime *rt, WeakMapBase *weakmap); + // Restore live weak map lists for multiple compartments from a vector. + static void restoreCompartmentWeakMapLists(WeakMapVector &vector); + + // Remove a weakmap from the live weakmaps list + static void removeWeakMapFromList(WeakMapBase *weakmap); protected: // Instance member functions called by the above. Instantiations of WeakMap override // these with definitions appropriate for their Key and Value types. virtual void nonMarkingTrace(JSTracer *tracer) = 0; virtual bool markIteratively(JSTracer *tracer) = 0; - virtual void sweep(JSTracer *tracer) = 0; + virtual void sweep() = 0; virtual void traceMappings(WeakMapTracer *tracer) = 0; // Object that this weak map is part of, if any. JSObject *memberOf; + // Compartment that this weak map is part of. + JSCompartment *compartment; + private: // Link in a list of WeakMaps to mark iteratively and sweep in this garbage - // collection, headed by JSRuntime::gcWeakMapList. The last element of the list - // has NULL as its next. Maps not in the list have WeakMapNotInList as their - // next. We must distinguish these cases to avoid creating infinite lists - // when a weak map gets traced twice due to delayed marking. + // collection, headed by JSCompartment::gcWeakMapList. The last element of + // the list has NULL as its next. Maps not in the list have WeakMapNotInList + // as their next. We must distinguish these cases to avoid creating + // infinite lists when a weak map gets traced twice due to delayed marking. WeakMapBase *next; }; @@ -125,8 +129,8 @@ class WeakMap : public HashMap, publ typedef typename Base::Enum Enum; typedef typename Base::Range Range; - explicit WeakMap(JSRuntime *rt, JSObject *memOf=NULL) : Base(rt), WeakMapBase(memOf) { } - explicit WeakMap(JSContext *cx, JSObject *memOf=NULL) : Base(cx), WeakMapBase(memOf) { } + explicit WeakMap(JSContext *cx, JSObject *memOf=NULL) + : Base(cx), WeakMapBase(memOf, cx->compartment) { } private: bool markValue(JSTracer *trc, Value *x) { @@ -180,7 +184,7 @@ class WeakMap : public HashMap, publ return markedAny; } - void sweep(JSTracer *trc) { + void sweep() { /* Remove all entries whose keys remain unmarked. */ for (Enum e(*this); !e.empty(); e.popFront()) { Key k(e.front().key); diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index d20942ba00c6..8f1b7db3f003 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -55,8 +55,6 @@ class DebuggerWeakMap : private WeakMap > public: typedef WeakMap > Base; - explicit DebuggerWeakMap(JSRuntime *rt) - : Base(rt), valueCompartment(NULL), compartmentCounts(rt) { } explicit DebuggerWeakMap(JSContext *cx) : Base(cx), valueCompartment(NULL), compartmentCounts(cx) { } @@ -128,7 +126,7 @@ class DebuggerWeakMap : private WeakMap > private: /* Override sweep method to also update our edge cache. */ - void sweep(JSTracer *trc) { + void sweep() { for (Enum e(*static_cast(this)); !e.empty(); e.popFront()) { Key k(e.front().key); Value v(e.front().value); diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 290506997f53..357c70906284 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -1530,6 +1530,7 @@ DebugScopes::DebugScopes(JSContext *cx) DebugScopes::~DebugScopes() { JS_ASSERT(missingScopes.empty()); + WeakMapBase::removeWeakMapFromList(&proxiedScopes); } bool @@ -1544,12 +1545,6 @@ DebugScopes::init() return true; } -void -DebugScopes::finalize(JSRuntime *rt) -{ - WeakMapBase::removeWeakMapFromList(rt, &proxiedScopes); -} - void DebugScopes::mark(JSTracer *trc) { diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index d490c73b3f95..75bd5d0d7cec 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -568,8 +568,6 @@ class DebugScopes DebugScopes(JSContext *c); ~DebugScopes(); - void finalize(JSRuntime *rt); - private: bool init(); From 627169c8798fdb3280c672e61d6b597c1b0282e4 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Tue, 6 Nov 2012 17:45:58 +0000 Subject: [PATCH 027/160] Bug 790338 - Improve debugger find edges code r=billm --HG-- extra : rebase_source : 25574a7ca88533ce78ab06e18c4b1625df0fa70d --- js/src/vm/Debugger.cpp | 11 ++++++++--- js/src/vm/Debugger.h | 21 ++++++--------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 3bf265e675a3..a46557771a9c 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -1568,9 +1568,14 @@ Debugger::findCompartmentEdges(JSCompartment *comp, js::gc::ComponentFinder &fin JSRuntime *rt = comp->rt; for (JSCList *p = &rt->debuggerList; (p = JS_NEXT_LINK(p)) != &rt->debuggerList;) { Debugger *dbg = Debugger::fromLinks(p); - dbg->scripts.findCompartmentEdges(comp, finder); - dbg->objects.findCompartmentEdges(comp, finder); - dbg->environments.findCompartmentEdges(comp, finder); + JSCompartment *w = dbg->object->compartment(); + if (w == comp || !w->isGCMarking()) + continue; + if (dbg->scripts.hasKeyInCompartment(comp) || + dbg->objects.hasKeyInCompartment(comp) || + dbg->environments.hasKeyInCompartment(comp)) { + finder.addEdgeTo(w); + } } } diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index 8f1b7db3f003..2b76a840247e 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -50,13 +50,12 @@ class DebuggerWeakMap : private WeakMap > DefaultHasher, RuntimeAllocPolicy> CountMap; - JSCompartment *valueCompartment; CountMap compartmentCounts; public: typedef WeakMap > Base; explicit DebuggerWeakMap(JSContext *cx) - : Base(cx), valueCompartment(NULL), compartmentCounts(cx) { } + : Base(cx), compartmentCounts(cx) { } public: /* Expose those parts of HashMap public interface that are used by Debugger methods. */ @@ -77,9 +76,7 @@ class DebuggerWeakMap : private WeakMap > template bool relookupOrAdd(AddPtr &p, const KeyInput &k, const ValueInput &v) { - if (!valueCompartment) - valueCompartment = v->compartment(); - JS_ASSERT(v->compartment() == valueCompartment); + JS_ASSERT(v->compartment() == Base::compartment); if (!incCompartmentCount(k->compartment())) return false; bool ok = Base::relookupOrAdd(p, k, v); @@ -95,8 +92,6 @@ class DebuggerWeakMap : private WeakMap > void remove(const Lookup &l) { Base::remove(l); decCompartmentCount(l->compartment()); - if (Base::count() == 0) - valueCompartment = NULL; } public: @@ -114,14 +109,10 @@ class DebuggerWeakMap : private WeakMap > } } - void findCompartmentEdges(JSCompartment *v, js::gc::ComponentFinder &finder) { - if (!valueCompartment || valueCompartment == v || !valueCompartment->isGCMarking()) - return; - CountMap::Ptr p = compartmentCounts.lookup(v); - if (!p) - return; - JS_ASSERT(p->value > 0); - finder.addEdgeTo(valueCompartment); + bool hasKeyInCompartment(JSCompartment *c) { + CountMap::Ptr p = compartmentCounts.lookup(c); + JS_ASSERT_IF(p, p->value > 0); + return p; } private: From 134466a3585cc8cb624f775e4ae437b173f56e65 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Tue, 6 Nov 2012 16:01:18 +0000 Subject: [PATCH 028/160] Bug 790338 - Mark weak references in the current compartment group only rather than for all collecting compartments r=billm --HG-- extra : rebase_source : 46d236a1f70a2f8fa80ef7d0b1eb8f73ccdd7c34 --- js/src/jsgc.cpp | 25 +++++++++++-------------- js/src/jswatchpoint.cpp | 12 ++++-------- js/src/jswatchpoint.h | 2 +- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 26334379e99c..1c0be34e0037 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3467,16 +3467,6 @@ BeginMarkPhase(JSRuntime *rt) rt->gcFoundBlackGrayEdges = false; } -bool -MarkWeakMapsIteratively(JSRuntime *rt) -{ - bool markedAny = false; - GCMarker *gcmarker = &rt->gcMarker; - for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) - markedAny |= WeakMapBase::markCompartmentIteratively(c, gcmarker); - return markedAny; -} - void MarkWeakReferences(JSRuntime *rt, gcstats::Phase phase) { @@ -3485,10 +3475,17 @@ MarkWeakReferences(JSRuntime *rt, gcstats::Phase phase) gcstats::AutoPhase ap(rt->gcStats, phase); - while (WatchpointMap::markAllIteratively(gcmarker) || - MarkWeakMapsIteratively(rt) || - Debugger::markAllIteratively(gcmarker)) - { + for (;;) { + bool markedAny = false; + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { + markedAny |= WatchpointMap::markCompartmentIteratively(c, gcmarker); + markedAny |= WeakMapBase::markCompartmentIteratively(c, gcmarker); + } + markedAny |= Debugger::markAllIteratively(gcmarker); + + if (!markedAny) + break; + SliceBudget budget; gcmarker->drainMarkStack(budget); } diff --git a/js/src/jswatchpoint.cpp b/js/src/jswatchpoint.cpp index d927aa96cc63..c7d9ff31666b 100644 --- a/js/src/jswatchpoint.cpp +++ b/js/src/jswatchpoint.cpp @@ -143,15 +143,11 @@ WatchpointMap::triggerWatchpoint(JSContext *cx, HandleObject obj, HandleId id, M } bool -WatchpointMap::markAllIteratively(JSTracer *trc) +WatchpointMap::markCompartmentIteratively(JSCompartment *c, JSTracer *trc) { - JSRuntime *rt = trc->runtime; - bool mutated = false; - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - if (c->isGCMarking() && c->watchpointMap) - mutated |= c->watchpointMap->markIteratively(trc); - } - return mutated; + if (!c->watchpointMap) + return false; + return c->watchpointMap->markIteratively(trc); } bool diff --git a/js/src/jswatchpoint.h b/js/src/jswatchpoint.h index 5d97bcf7043e..80eb418e2bd5 100644 --- a/js/src/jswatchpoint.h +++ b/js/src/jswatchpoint.h @@ -55,7 +55,7 @@ class WatchpointMap { bool triggerWatchpoint(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp); - static bool markAllIteratively(JSTracer *trc); + static bool markCompartmentIteratively(JSCompartment *c, JSTracer *trc); bool markIteratively(JSTracer *trc); void markAll(JSTracer *trc); static void sweepAll(JSRuntime *rt); From 92581e14d8ce18f0aaea91a7927db962601b30c1 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Fri, 16 Nov 2012 15:52:09 +0000 Subject: [PATCH 029/160] Bug 790338 - Handle nuked wrappers in list of incoming gray pointers r=billm --HG-- extra : rebase_source : 869b862af6ce9035f8bd8ccc247d3e4d453a4f9f --- js/src/gc/Marking.cpp | 2 +- js/src/jsgc.cpp | 143 +++++++++++++++++++++++++++++++++++++---- js/src/jsgc.h | 13 +++- js/src/jsobj.cpp | 2 + js/src/jsproxy.cpp | 9 +-- js/src/jswrapper.cpp | 4 ++ js/src/vm/Debugger.cpp | 11 +++- js/src/vm/Debugger.h | 1 + 8 files changed, 163 insertions(+), 22 deletions(-) diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 3a05d5619fa8..f9d4d16a491e 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -596,7 +596,7 @@ ShouldMarkCrossCompartment(JSTracer *trc, RawObject src, Cell *cell) * at the appropriate time. */ if (!cell->isMarked()) - DelayCrossCompartmentGrayMarking(src, cell); + DelayCrossCompartmentGrayMarking(src); return false; } return c->isGCMarkingGray(); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 1c0be34e0037..d06905ffcde2 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3752,16 +3752,37 @@ GetNextCompartmentGroup(JSRuntime *rt) * MarkIncomingCrossCompartmentPointers. */ +static bool +IsGrayListObject(RawObject o) +{ + JS_ASSERT(o); + return (IsCrossCompartmentWrapper(o) && !IsDeadProxyObject(o)) || + Debugger::isDebugWrapper(o); +} + +const unsigned JSSLOT_GC_GRAY_LINK = JSSLOT_PROXY_EXTRA + 1; + static unsigned GrayLinkSlot(RawObject o) { - return IsCrossCompartmentWrapper(o) ? JSSLOT_PROXY_EXTRA + 1 : Debugger::gcGrayLinkSlot(); + JS_ASSERT(IsGrayListObject(o)); + return IsCrossCompartmentWrapper(o) ? JSSLOT_GC_GRAY_LINK : Debugger::gcGrayLinkSlot(); +} + +static void +AssertNotOnGrayList(RawObject o) +{ + JS_ASSERT_IF(IsGrayListObject(o), o->getReservedSlot(GrayLinkSlot(o)).isUndefined()); } static Cell * CrossCompartmentPointerReferent(RawObject o) { - return (Cell*)(IsCrossCompartmentWrapper(o) ? GetProxyPrivate(o).toGCThing() : o->getPrivate()); + JS_ASSERT(IsGrayListObject(o)); + if (IsCrossCompartmentWrapper(o)) + return (Cell *)GetProxyPrivate(o).toGCThing(); + else + return (Cell *)o->getPrivate(); } static RawObject @@ -3769,6 +3790,7 @@ NextIncomingCrossCompartmentPointer(RawObject prev, bool unlink) { unsigned slot = GrayLinkSlot(prev); RawObject next = prev->getReservedSlot(slot).toObjectOrNull(); + JS_ASSERT_IF(next, IsGrayListObject(next)); if (unlink) prev->setSlot(slot, UndefinedValue()); @@ -3777,25 +3799,36 @@ NextIncomingCrossCompartmentPointer(RawObject prev, bool unlink) } void -js::DelayCrossCompartmentGrayMarking(RawObject src, Cell *cell) +js::DelayCrossCompartmentGrayMarking(RawObject src) { + JS_ASSERT(IsGrayListObject(src)); + /* Called from MarkCrossCompartmentXXX functions. */ unsigned slot = GrayLinkSlot(src); - JSCompartment *c = cell->compartment(); + Cell *dest = CrossCompartmentPointerReferent(src); + JSCompartment *c = dest->compartment(); if (src->getReservedSlot(slot).isUndefined()) { src->setCrossCompartmentSlot(slot, ObjectOrNullValue(c->gcIncomingGrayPointers)); c->gcIncomingGrayPointers = src; } else { -#ifdef DEBUG - /* Assert that if the slot is in use, the object is in our list. */ JS_ASSERT(src->getReservedSlot(slot).isObjectOrNull()); - RawObject o = c->gcIncomingGrayPointers; - while (o && o != src) - o = NextIncomingCrossCompartmentPointer(o, false); - JS_ASSERT(o); -#endif } + +#ifdef DEBUG + /* + * Assert that the object is in our list, also walking the list to check its + * integrity. + */ + RawObject o = c->gcIncomingGrayPointers; + bool found = false; + while (o) { + if (o == src) + found = true; + o = NextIncomingCrossCompartmentPointer(o, false); + } + JS_ASSERT(found); +#endif } static void @@ -3814,6 +3847,7 @@ MarkIncomingCrossCompartmentPointers(JSRuntime *rt, const uint32_t color) for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { JS_ASSERT_IF(color == GRAY, c->isGCMarkingGray()); JS_ASSERT_IF(color == BLACK, c->isGCMarkingBlack()); + JS_ASSERT_IF(c->gcIncomingGrayPointers, IsGrayListObject(c->gcIncomingGrayPointers)); for (RawObject src = c->gcIncomingGrayPointers; src; @@ -3841,6 +3875,78 @@ MarkIncomingCrossCompartmentPointers(JSRuntime *rt, const uint32_t color) rt->gcMarker.drainMarkStack(budget); } +static bool +RemoveFromGrayList(RawObject wrapper) +{ + if (!IsGrayListObject(wrapper)) + return false; + + unsigned slot = GrayLinkSlot(wrapper); + if (wrapper->getReservedSlot(slot).isUndefined()) + return false; /* Not on our list. */ + + RawObject tail = wrapper->getReservedSlot(slot).toObjectOrNull(); + wrapper->setReservedSlot(slot, UndefinedValue()); + + JSCompartment *c = CrossCompartmentPointerReferent(wrapper)->compartment(); + RawObject o = c->gcIncomingGrayPointers; + if (o == wrapper) { + c->gcIncomingGrayPointers = tail; + return true; + } + + while (o) { + unsigned slot = GrayLinkSlot(o); + RawObject next = o->getReservedSlot(slot).toObjectOrNull(); + if (next == wrapper) { + o->setCrossCompartmentSlot(slot, ObjectOrNullValue(tail)); + return true; + } + o = next; + } + JS_NOT_REACHED(); +} + +void +js::NotifyGCNukeWrapper(RawObject o) +{ + /* + * References to target of wrapper are being removed, we no longer have to + * remember to mark it. + */ + RemoveFromGrayList(o); +} + +enum { + JS_GC_SWAP_OBJECT_A_REMOVED = 1 << 0, + JS_GC_SWAP_OBJECT_B_REMOVED = 1 << 1 +}; + +unsigned +js::NotifyGCPreSwap(RawObject a, RawObject b) +{ + /* + * Two objects in the same compartment are about to have had their contents + * swapped. If either of them are in our gray pointer list, then we remove + * them from the lists, returning a bitset indicating what happened. + */ + return (RemoveFromGrayList(a) ? JS_GC_SWAP_OBJECT_A_REMOVED : 0) | + (RemoveFromGrayList(b) ? JS_GC_SWAP_OBJECT_B_REMOVED : 0); +} + +void +js::NotifyGCPostSwap(RawObject a, RawObject b, unsigned removedFlags) +{ + /* + * Two objects in the same compartment have had their contents swapped. If + * either of them were in our gray pointer list, we re-add them again. + */ + if (removedFlags & JS_GC_SWAP_OBJECT_A_REMOVED) + DelayCrossCompartmentGrayMarking(b); + if (removedFlags & JS_GC_SWAP_OBJECT_B_REMOVED) + DelayCrossCompartmentGrayMarking(a); +} + static void EndMarkingCompartmentGroup(JSRuntime *rt) { @@ -4039,8 +4145,13 @@ BeginSweepPhase(JSRuntime *rt) #ifdef DEBUG JS_ASSERT(!rt->gcCompartmentGroup); - for (GCCompartmentsIter c(rt); !c.done(); c.next()) + for (CompartmentsIter c(rt); !c.done(); c.next()) { JS_ASSERT(!c->gcIncomingGrayPointers); + for (WrapperMap::Enum e(c->crossCompartmentWrappers); !e.empty(); e.popFront()) { + if (e.front().key.kind != CrossCompartmentKey::StringWrapper) + AssertNotOnGrayList(&e.front().value.get().toObject()); + } + } #endif DropStringWrappers(rt); @@ -4198,16 +4309,24 @@ EndSweepPhase(JSRuntime *rt, JSGCInvocationKind gckind, bool lastGC) c->setGCState(JSCompartment::NoGC); } +#ifdef DEBUG JS_ASSERT(!c->isCollecting()); JS_ASSERT(!c->wasGCStarted()); + JS_ASSERT(!c->gcIncomingGrayPointers); JS_ASSERT(!c->gcLiveArrayBuffers); + for (WrapperMap::Enum e(c->crossCompartmentWrappers); !e.empty(); e.popFront()) { + if (e.front().key.kind != CrossCompartmentKey::StringWrapper) + AssertNotOnGrayList(&e.front().value.get().toObject()); + } + for (unsigned i = 0 ; i < FINALIZE_LIMIT ; ++i) { JS_ASSERT_IF(!IsBackgroundFinalized(AllocKind(i)) || !rt->gcSweepOnBackgroundThread, !c->arenas.arenaListsToSweep[i]); } +#endif } rt->gcLastGCTime = PRMJ_Now(); diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 18f6479100a5..6166c5b3854a 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -544,8 +544,19 @@ GCDebugSlice(JSRuntime *rt, bool limit, int64_t objCount); extern void PrepareForDebugGC(JSRuntime *rt); +/* Functions for managing cross compartment gray pointers. */ + extern void -DelayCrossCompartmentGrayMarking(RawObject src, gc::Cell *cell); +DelayCrossCompartmentGrayMarking(RawObject src); + +extern void +NotifyGCNukeWrapper(RawObject o); + +extern unsigned +NotifyGCPreSwap(RawObject a, RawObject b); + +extern void +NotifyGCPostSwap(RawObject a, RawObject b, unsigned preResult); void InitTracer(JSTracer *trc, JSRuntime *rt, JSTraceCallback callback); diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 5d875b3bd804..7c2c6a81884e 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -2935,7 +2935,9 @@ JSObject::swap(JSContext *cx, JSObject *other_) TradeGutsReserved reserved(cx); if (!ReserveForTradeGuts(cx, this, other, reserved)) return false; + unsigned r = NotifyGCPreSwap(this, other); TradeGuts(cx, this, other, reserved); + NotifyGCPostSwap(this, other, r); return true; } diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp index cbad716c8544..6968263544fb 100644 --- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -3153,6 +3153,7 @@ JSObject * js::RenewProxyObject(JSContext *cx, JSObject *obj, BaseProxyHandler *handler, Value priv) { + JS_ASSERT_IF(IsCrossCompartmentWrapper(obj), IsDeadProxyObject(obj)); JS_ASSERT(obj->getParent() == cx->global()); JS_ASSERT(obj->getClass() == &ObjectProxyClass); JS_ASSERT(obj->getTaggedProto().isLazy()); @@ -3161,13 +3162,7 @@ js::RenewProxyObject(JSContext *cx, JSObject *obj, obj->setSlot(JSSLOT_PROXY_HANDLER, PrivateValue(handler)); obj->setCrossCompartmentSlot(JSSLOT_PROXY_PRIVATE, priv); obj->setSlot(JSSLOT_PROXY_EXTRA + 0, UndefinedValue()); - - /* - * The GC can use the second reserved slot to link the cross compartment - * wrappers into a linked list, in which case we don't want to reset it. - */ - if (!IsCrossCompartmentWrapper(obj)) - obj->setSlot(JSSLOT_PROXY_EXTRA + 1, UndefinedValue()); + obj->setSlot(JSSLOT_PROXY_EXTRA + 1, UndefinedValue()); return obj; } diff --git a/js/src/jswrapper.cpp b/js/src/jswrapper.cpp index dc77fd2a93a7..7ca4ae2d89ce 100644 --- a/js/src/jswrapper.cpp +++ b/js/src/jswrapper.cpp @@ -996,6 +996,8 @@ js::NukeCrossCompartmentWrapper(JSContext *cx, JSObject *wrapper) { JS_ASSERT(IsCrossCompartmentWrapper(wrapper)); + NotifyGCNukeWrapper(wrapper); + NukeSlot(wrapper, JSSLOT_PROXY_PRIVATE, NullValue()); SetProxyHandler(wrapper, &DeadObjectProxy::singleton); @@ -1006,6 +1008,8 @@ js::NukeCrossCompartmentWrapper(JSContext *cx, JSObject *wrapper) NukeSlot(wrapper, JSSLOT_PROXY_EXTRA + 0, NullValue()); NukeSlot(wrapper, JSSLOT_PROXY_EXTRA + 1, NullValue()); + + JS_ASSERT(IsDeadProxyObject(wrapper)); } /* diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index a46557771a9c..2d9ce67b8f2c 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -1341,12 +1341,21 @@ JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGENV_GC_GRAY_LINK) == JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGENV_GC_GRAY_LINK) == unsigned(JSSLOT_DEBUGSCRIPT_GC_GRAY_LINK)); -unsigned +/* static */ unsigned Debugger::gcGrayLinkSlot() { return JSSLOT_DEBUGOBJECT_GC_GRAY_LINK; } +/* static */ bool +Debugger::isDebugWrapper(RawObject o) +{ + Class *c = o->getClass(); + return c == &DebuggerObject_class || + c == &DebuggerEnv_class || + c == &DebuggerScript_class; +} + void Debugger::markKeysInCompartment(JSTracer *tracer) { diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index 2b76a840247e..750da285d5fd 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -376,6 +376,7 @@ class Debugger { static void detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global, GlobalObjectSet::Enum *compartmentEnum); static unsigned gcGrayLinkSlot(); + static bool isDebugWrapper(RawObject o); static void findCompartmentEdges(JSCompartment *v, js::gc::ComponentFinder &finder); static inline JSTrapStatus onEnterFrame(JSContext *cx, Value *vp); From 57cf75c47a8687dba8cce594a9c4dc4f542836f8 Mon Sep 17 00:00:00 2001 From: Mounir Lamouri Date: Mon, 26 Nov 2012 11:35:26 +0000 Subject: [PATCH 030/160] Bug 813936 - Update nsHTMLInputElement::GetStepBase() to return the default value if there is no min. r=smaug --- .../html/content/src/nsHTMLInputElement.cpp | 13 +++++ .../test/forms/test_step_attribute.html | 40 ++++++++++++---- .../test/forms/test_stepup_stepdown.html | 48 ++++++++----------- 3 files changed, 65 insertions(+), 36 deletions(-) diff --git a/content/html/content/src/nsHTMLInputElement.cpp b/content/html/content/src/nsHTMLInputElement.cpp index 5fa870ffd92a..85dfbc9149f1 100644 --- a/content/html/content/src/nsHTMLInputElement.cpp +++ b/content/html/content/src/nsHTMLInputElement.cpp @@ -1204,6 +1204,19 @@ nsHTMLInputElement::GetStepBase() const { double stepBase = GetMinAsDouble(); + // If @min is not a double, we should use defaultValue. + if (MOZ_DOUBLE_IS_NaN(stepBase)) { + nsAutoString stringValue; + GetAttr(kNameSpaceID_None, nsGkAtoms::value, stringValue); + + nsresult ec; + stepBase = stringValue.ToDouble(&ec); + + if (NS_FAILED(ec)) { + stepBase = MOZ_DOUBLE_NaN(); + } + } + return MOZ_DOUBLE_IS_NaN(stepBase) ? kDefaultStepBase : stepBase; } diff --git a/content/html/content/test/forms/test_step_attribute.html b/content/html/content/test/forms/test_step_attribute.html index 5e1ffe663503..c368a71679e6 100644 --- a/content/html/content/test/forms/test_step_attribute.html +++ b/content/html/content/test/forms/test_step_attribute.html @@ -45,8 +45,11 @@ var types = [ [ 'button', false ], ]; -var input = document.createElement("input"); -document.getElementById('content').appendChild(input); +function getFreshElement(type) { + var elmt = document.createElement('input'); + elmt.type = type; + return elmt; +} function checkValidity(aElement, aValidity, aApply, aData) { @@ -74,7 +77,7 @@ function checkValidity(aElement, aValidity, aApply, aData) SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() { for each (var data in types) { - input.type = data[0]; + var input = getFreshElement(data[0]); var apply = data[1]; if (data[2]) { @@ -227,10 +230,35 @@ for each (var data in types) { input.max = '10'; input.value = '-9'; checkValidity(input, false, apply, {low: -10, high: -8}); + + // If there is a value defined but no min, the step base is the value. + input = getFreshElement(data[0]); + input.setAttribute('value', '1'); + input.step = 2; + checkValidity(input, true, apply); + + input.value = 3; + checkValidity(input, true, apply); + + input.value = 2; + checkValidity(input, false, apply, {low: 1, high: 3}); + + // Should also work with defaultValue. + input = getFreshElement(data[0]); + input.defaultValue = 1; + input.step = 2; + checkValidity(input, true, apply); + + input.value = 3; + checkValidity(input, true, apply); + + input.value = 2; + checkValidity(input, false, apply, {low: 1, high: 3}); } if (input.type == 'number') { // Check that when the higher value is higher than max, we don't show it. + input = getFreshElement(data[0]); input.step = '2'; input.min = '1'; input.max = '10.9'; @@ -240,12 +268,6 @@ for each (var data in types) { "The nearest valid value is 9.", "The validation message should not include the higher value."); } - - // Cleaning up, - input.removeAttribute('step'); - input.removeAttribute('max'); - input.removeAttribute('min'); - input.value = ''; } SimpleTest.finish(); diff --git a/content/html/content/test/forms/test_stepup_stepdown.html b/content/html/content/test/forms/test_stepup_stepdown.html index 00b3b6439be2..19f3b809104f 100644 --- a/content/html/content/test/forms/test_stepup_stepdown.html +++ b/content/html/content/test/forms/test_stepup_stepdown.html @@ -124,8 +124,6 @@ function checkStepDownForNumber() [ '1', null, null, null, 1.1, '0', false ], // With step values. [ '1', '0.5', null, null, null, '0.5', false ], - [ null, '0.5', null, null, null, '0', false ], - [ null, '0.5', null, null, null, '-0.5', false ], [ '1', '0.25', null, null, 4, '0', false ], // step = 0 isn't allowed (-> step = 1). [ '1', '0', null, null, null, '0', false ], @@ -147,21 +145,21 @@ function checkStepDownForNumber() [ '1', null, null, '-10', null, '-10', false ], [ '1', null, null, '1', null, '0', false ], [ '5', null, null, '3', '3', '2', false ], - [ '5', '2', null, '3', '2', '2', false ], + [ '5', '2', '-6', '3', '2', '2', false ], [ '-3', '5', '-10', '-3', null, '-5', false ], // Step mismatch. [ '1', '2', '-2', null, null, '0', false ], [ '3', '2', '-2', null, null, '2', false ], [ '3', '2', '-2', null, '2', '0', false ], [ '3', '2', '-2', null, '-2', '6', false ], - [ '1', '2', null, null, null, '0', false ], + [ '1', '2', '-6', null, null, '0', false ], [ '1', '2', '-2', null, null, '0', false ], - [ '1', '3', null, null, null, '0', false ], - [ '2', '3', null, null, null, '0', false ], + [ '1', '3', '-6', null, null, '0', false ], + [ '2', '3', '-6', null, null, '0', false ], [ '2', '3', '1', null, null, '1', false ], [ '5', '3', '1', null, null, '4', false ], - [ '3', '2', null, null, null, '2', false ], - [ '5', '2', null, null, null, '4', false ], + [ '3', '2', '-6', null, null, '2', false ], + [ '5', '2', '-6', null, null, '4', false ], [ '6', '2', '1', null, null, '5', false ], [ '8', '3', '1', null, null, '7', false ], [ '9', '2', '-10', null, null, '8', false ], @@ -178,13 +176,16 @@ function checkStepDownForNumber() [ '0', 'ANY', null, null, 1, null, true ], [ '0', 'AnY', null, null, 1, null, true ], [ '0', 'aNy', null, null, 1, null, true ], + // With @value = step base. + [ '1', '2', null, null, null, '-1', false ], ]; - var element = document.createElement("input"); - element.type = 'number'; for each (var data in testData) { + var element = document.createElement("input"); + element.type = 'number'; + if (data[0] != null) { - element.value = data[0]; + element.setAttribute('value', data[0]); } if (data[1] != null) { @@ -216,10 +217,6 @@ function checkStepDownForNumber() } finally { is(exceptionCaught, data[6], "exception status should be " + data[6]); } - - element.removeAttribute('step'); - element.removeAttribute('min'); - element.removeAttribute('max'); } } @@ -240,8 +237,6 @@ function checkStepUpForNumber() [ '1', null, null, null, 1.1, '2', false ], // With step values. [ '1', '0.5', null, null, null, '1.5', false ], - [ null, '0.5', null, null, null, '2', false ], - [ null, '0.5', null, null, null, '2.5', false ], [ '1', '0.25', null, null, 4, '2', false ], // step = 0 isn't allowed (-> step = 1). [ '1', '0', null, null, null, '2', false ], @@ -269,7 +264,7 @@ function checkStepUpForNumber() [ '1', '2', '0', null, null, '2', false ], [ '1', '2', '0', null, '2', '4', false ], [ '8', '2', null, '9', null, '8', false ], - [ '-3', '2', null, null, null, '-2', false ], + [ '-3', '2', '-6', null, null, '-2', false ], [ '9', '3', '-10', null, null, '11', false ], [ '7', '3', '-10', null, null, '8', false ], [ '7', '3', '5', null, null, '8', false ], @@ -282,8 +277,8 @@ function checkStepUpForNumber() [ '-9', '3', '-8', '-1', '5', '-2', false ], [ '-9', '3', '8', '15', '15', '14', false ], [ '-1', '3', '-1', '4', '3', '2', false ], - [ '-3', '2', null, '-2', null, '-2', false ], - [ '-3', '2', null, '-1', null, '-2', false ], + [ '-3', '2', '-6', '-2', null, '-2', false ], + [ '-3', '2', '-6', '-1', null, '-2', false ], // value = "" (NaN). [ '', null, null, null, null, '', false ], // With step = 'any'. @@ -291,13 +286,16 @@ function checkStepUpForNumber() [ '0', 'ANY', null, null, 1, null, true ], [ '0', 'AnY', null, null, 1, null, true ], [ '0', 'aNy', null, null, 1, null, true ], + // With @value = step base. + [ '1', '2', null, null, null, '3', false ], ]; - var element = document.createElement("input"); - element.type = 'number'; for each (var data in testData) { + var element = document.createElement("input"); + element.type = 'number'; + if (data[0] != null) { - element.value = data[0]; + element.setAttribute('value', data[0]); } if (data[1] != null) { @@ -329,10 +327,6 @@ function checkStepUpForNumber() } finally { is(exceptionCaught, data[6], "exception status should be " + data[6]); } - - element.removeAttribute('step'); - element.removeAttribute('min'); - element.removeAttribute('max'); } } From 3c21e292266788257d611fd7c95162cf9026f8b5 Mon Sep 17 00:00:00 2001 From: Eric Chou Date: Mon, 26 Nov 2012 20:51:29 +0800 Subject: [PATCH 031/160] Bug 814376 - Improve the mechanism for setting file extension of a blob, r=sicking --- dom/bluetooth/BluetoothOppManager.cpp | 47 +++++++++++++++++++++------ 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/dom/bluetooth/BluetoothOppManager.cpp b/dom/bluetooth/BluetoothOppManager.cpp index 203e0a2bdf93..d1b5b9a830cf 100644 --- a/dom/bluetooth/BluetoothOppManager.cpp +++ b/dom/bluetooth/BluetoothOppManager.cpp @@ -17,13 +17,14 @@ #include "mozilla/RefPtr.h" #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" +#include "nsCExternalHandlerService.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsIDOMFile.h" #include "nsIFile.h" #include "nsIInputStream.h" +#include "nsIMIMEService.h" #include "nsIOutputStream.h" -#include "nsLocalFile.h" #include "nsNetUtil.h" #define TARGET_FOLDER "/sdcard/downloads/bluetooth/" @@ -283,22 +284,48 @@ BluetoothOppManager::SendFile(BlobParent* aActor) */ mBlob = aActor->GetBlob(); - nsCOMPtr domFile = do_QueryInterface(mBlob); - nsString fullPath; + sFileName.Truncate(); - if (domFile && NS_SUCCEEDED(domFile->GetMozFullPathInternal(fullPath))) { - nsCOMPtr localFile = new nsLocalFile(); - NS_NewLocalFile(fullPath, false, getter_AddRefs(localFile)); - - if (localFile) { - localFile->GetLeafName(sFileName); - } + nsCOMPtr file = do_QueryInterface(mBlob); + if (file) { + file->GetName(sFileName); } + /** + * We try our best to get the file extention to avoid interoperability issues. + * However, once we found that we are unable to get suitable extension or + * information about the content type, sending a pre-defined file name without + * extension would be fine. + */ if (sFileName.IsEmpty()) { sFileName.AssignLiteral("Unknown"); } + int32_t offset = sFileName.RFindChar('/'); + if (offset != kNotFound) { + sFileName = Substring(sFileName, offset + 1); + } + + offset = sFileName.RFindChar('.'); + if (offset == kNotFound) { + nsCOMPtr mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID); + + if (mimeSvc) { + nsString mimeType; + mBlob->GetType(mimeType); + + nsCString extension; + nsresult rv = + mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType), + EmptyCString(), + extension); + if (NS_SUCCEEDED(rv)) { + sFileName.AppendLiteral("."); + AppendUTF8toUTF16(extension, sFileName); + } + } + } + SendConnectRequest(); return true; From a1dcf9ad81ca2a325fec87ec9985667d455d60ee Mon Sep 17 00:00:00 2001 From: Mike Conley Date: Mon, 26 Nov 2012 09:52:54 -0500 Subject: [PATCH 032/160] Bug 814509 - Downloads summary background is different from "Show all downloads" button background on winstripe. r=mak. --- browser/themes/winstripe/downloads/downloads-aero.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/browser/themes/winstripe/downloads/downloads-aero.css b/browser/themes/winstripe/downloads/downloads-aero.css index 4600bfc4ec5e..46db5c6c7959 100644 --- a/browser/themes/winstripe/downloads/downloads-aero.css +++ b/browser/themes/winstripe/downloads/downloads-aero.css @@ -7,7 +7,8 @@ %undef WINSTRIPE_AERO @media (-moz-windows-default-theme) { - #downloadsPanel[hasdownloads] #downloadsHistory { + #downloadsPanel[hasdownloads] > #downloadsHistory, + #downloadsSummary { background-color: #f1f5fb; } From 895e7127a8f9c2754c174bff930cc94a35cfdd06 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Mon, 26 Nov 2012 11:02:47 -0500 Subject: [PATCH 033/160] Bug 807998 - Turn on memory.free_dirty_pages in Fennec. r=jlebar --- mobile/android/app/mobile.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index 33af782c1eea..487db1932d0b 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -667,3 +667,7 @@ pref("dom.event.touch.coalescing.enabled", false); // default orientation for the app, default to undefined // the java GeckoScreenOrientationListener needs this to be defined pref("app.orientation.default", ""); + +// On memory pressure, release dirty but unused pages held by jemalloc +// back to the system. +pref("memory.free_dirty_pages", true); From 9bbf39372f136a569355f8a69bd41514b32db730 Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Mon, 26 Nov 2012 16:18:25 +0000 Subject: [PATCH 034/160] bug 814383 - fix javascript strict-mode warnings in browser.js. r=gavin --- browser/base/content/browser-addons.js | 2 +- browser/base/content/browser-fullScreen.js | 4 ++-- browser/base/content/browser-plugins.js | 4 ++-- browser/base/content/browser.js | 14 ++++++++------ 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/browser/base/content/browser-addons.js b/browser/base/content/browser-addons.js index 4866bd03a200..b638f31f9ea2 100644 --- a/browser/base/content/browser-addons.js +++ b/browser/base/content/browser-addons.js @@ -91,7 +91,7 @@ const gXPInstallObserver = { action, null, options); break; case "addon-install-started": - function needsDownload(aInstall) { + var needsDownload = function needsDownload(aInstall) { return aInstall.state != AddonManager.STATE_DOWNLOADED; } // If all installs have already been downloaded then there is no need to diff --git a/browser/base/content/browser-fullScreen.js b/browser/base/content/browser-fullScreen.js index f737dc5783c6..da5f0e09655e 100644 --- a/browser/base/content/browser-fullScreen.js +++ b/browser/base/content/browser-fullScreen.js @@ -358,7 +358,7 @@ var FullScreen = { Services.perms.ALLOW_ACTION, Services.perms.EXPIRE_SESSION); let host = uri.host; - function onFullscreenchange(event) { + var onFullscreenchange = function onFullscreenchange(event) { if (event.target == document && document.mozFullScreenElement == null) { // The chrome document has left fullscreen. Remove the temporary permission grant. Services.perms.remove(host, "fullscreen"); @@ -545,7 +545,7 @@ var FullScreen = { el.setAttribute("inFullscreen", true); } else { - function restoreAttr(attrName) { + var restoreAttr = function restoreAttr(attrName) { var savedAttr = "saved-" + attrName; if (el.hasAttribute(savedAttr)) { el.setAttribute(attrName, el.getAttribute(savedAttr)); diff --git a/browser/base/content/browser-plugins.js b/browser/base/content/browser-plugins.js index bbcee11b0a85..0c3e760a27d0 100644 --- a/browser/base/content/browser-plugins.js +++ b/browser/base/content/browser-plugins.js @@ -116,7 +116,7 @@ var gPluginHandler = { // Helper to get the binding handler type from a plugin object _getBindingType : function(plugin) { if (!(plugin instanceof Ci.nsIObjectLoadingContent)) - return; + return null; switch (plugin.pluginFallbackType) { case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED: @@ -137,7 +137,7 @@ var gPluginHandler = { return "PluginPlayPreview"; default: // Not all states map to a handler - return; + return null; } }, diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 230ef9e8cb68..e603ee8e2075 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -105,6 +105,7 @@ XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () { document.getElementById("notification-popup-box")); } catch (ex) { Cu.reportError(ex); + return null; } }); @@ -3310,7 +3311,7 @@ const BrowserSearch = { win.BrowserSearch.webSearch(); } else { // If there are no open browser windows, open a new one - function observer(subject, topic, data) { + var observer = function observer(subject, topic, data) { if (subject == win) { BrowserSearch.webSearch(); Services.obs.removeObserver(observer, "browser-delayed-startup-finished"); @@ -4156,12 +4157,12 @@ var XULBrowserWindow = { } // Utility functions for disabling find - function shouldDisableFind(aDocument) { + var shouldDisableFind = function shouldDisableFind(aDocument) { let docElt = aDocument.documentElement; return docElt && docElt.getAttribute("disablefastfind") == "true"; } - function disableFindCommands(aDisable) { + var disableFindCommands = function disableFindCommands(aDisable) { let findCommands = [document.getElementById("cmd_find"), document.getElementById("cmd_findAgain"), document.getElementById("cmd_findPrevious")]; @@ -4173,7 +4174,7 @@ var XULBrowserWindow = { } } - function onContentRSChange(e) { + var onContentRSChange = function onContentRSChange(e) { if (e.target.readyState != "interactive" && e.target.readyState != "complete") return; @@ -5153,7 +5154,7 @@ function getBrowserSelection(aCharLen) { // try getting a selected text in text input. if (!selection) { let element = commandDispatcher.focusedElement; - function isOnTextInput(elem) { + var isOnTextInput = function isOnTextInput(elem) { // we avoid to return a value if a selection is in password field. // ref. bug 565717 return elem instanceof HTMLTextAreaElement || @@ -6276,7 +6277,7 @@ function BrowserOpenAddonsMgr(aView) { let emWindow; let browserWindow; - function receivePong(aSubject, aTopic, aData) { + var receivePong = function receivePong(aSubject, aTopic, aData) { let browserWin = aSubject.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem) @@ -7454,6 +7455,7 @@ XPCOMUtils.defineLazyGetter(this, "HUDConsoleUI", function () { } catch (ex) { Components.utils.reportError(ex); + return null; } }); From 9ce11fb93cc15a252b206a12512f0f0f56664536 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Mon, 26 Nov 2012 08:54:17 -0800 Subject: [PATCH 035/160] Bug 813766: Set EXPORTED_SYMBOLS on this instead of the global. r=gps --- services/sync/modules/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/sync/modules/constants.js b/services/sync/modules/constants.js index 37b413c7147d..58a09e120a6d 100644 --- a/services/sync/modules/constants.js +++ b/services/sync/modules/constants.js @@ -4,7 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Process each item in the "constants hash" to add to "global" and give a name -let EXPORTED_SYMBOLS = [((this[key] = val), key) for ([key, val] in Iterator({ +this.EXPORTED_SYMBOLS = [((this[key] = val), key) for ([key, val] in Iterator({ WEAVE_CHANNEL: "@weave_channel@", WEAVE_VERSION: "@weave_version@", From 439a5889faa5c887173cb13070344018589992af Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Mon, 26 Nov 2012 08:55:12 -0800 Subject: [PATCH 036/160] Bug 813762: Teach OS.File about compartment sharing. r=mrbkap --- dom/system/Makefile.in | 5 ++++- dom/system/OSFileConstants.cpp | 13 ++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/dom/system/Makefile.in b/dom/system/Makefile.in index c39f962d9e34..b4007e018091 100644 --- a/dom/system/Makefile.in +++ b/dom/system/Makefile.in @@ -78,7 +78,10 @@ EXPORTS_mozilla = \ $(NULL) # We fire the nsDOMDeviceAcceleration -LOCAL_INCLUDES += -I$(topsrcdir)/content/events/src +LOCAL_INCLUDES += \ + -I$(topsrcdir)/content/events/src \ + -I$(topsrcdir)/js/xpconnect/loader \ + $(NULL) ifdef ENABLE_TESTS DIRS += tests diff --git a/dom/system/OSFileConstants.cpp b/dom/system/OSFileConstants.cpp index c9159566786a..41bf8b6007e3 100644 --- a/dom/system/OSFileConstants.cpp +++ b/dom/system/OSFileConstants.cpp @@ -36,6 +36,7 @@ #include "nsAutoPtr.h" #include "nsDirectoryServiceDefs.h" #include "nsAppDirectoryServiceDefs.h" +#include "mozJSComponentLoader.h" #include "OSFileConstants.h" #include "nsIOSFileConstantsService.h" @@ -699,11 +700,13 @@ OSFileConstantsService::Init(JSContext *aCx) return rv; } - JSObject *global = JS_GetGlobalForScopeChain(aCx); - if (!global) { - return NS_ERROR_NOT_AVAILABLE; - } - if (!mozilla::DefineOSFileConstants(aCx, global)) { + JSObject *targetObj = nullptr; + + mozJSComponentLoader* loader = mozJSComponentLoader::Get(); + rv = loader->FindTargetObject(aCx, &targetObj); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mozilla::DefineOSFileConstants(aCx, targetObj)) { return NS_ERROR_FAILURE; } From d91ef531e3aeda1ce1b8ef69237be1a853ce2c26 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Mon, 26 Nov 2012 08:56:56 -0800 Subject: [PATCH 037/160] Bug 814104: Create the function in the right compartment. r=dcamp --- .../devtools/debugger/server/dbg-server.jsm | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/toolkit/devtools/debugger/server/dbg-server.jsm b/toolkit/devtools/debugger/server/dbg-server.jsm index ece7be88dc5a..5dc7cf07aa98 100644 --- a/toolkit/devtools/debugger/server/dbg-server.jsm +++ b/toolkit/devtools/debugger/server/dbg-server.jsm @@ -17,18 +17,20 @@ const Cu = Components.utils; this.EXPORTED_SYMBOLS = ["DebuggerServer"]; -function loadSubScript(aURL) -{ - try { - let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Ci.mozIJSSubScriptLoader); - loader.loadSubScript(aURL, this); - } catch(e) { - dump("Error loading: " + aURL + ": " + e + " - " + e.stack + "\n"); - - throw e; - } -} +var loadSubScript = + "function loadSubScript(aURL)\n" + + "{\n" + + "const Ci = Components.interfaces;\n" + + "const Cc = Components.classes;\n" + + " try {\n" + + " let loader = Cc[\"@mozilla.org/moz/jssubscript-loader;1\"]\n" + + " .getService(Ci.mozIJSSubScriptLoader);\n" + + " loader.loadSubScript(aURL, this);\n" + + " } catch(e) {\n" + + " dump(\"Error loading: \" + aURL + \": \" + e + \" - \" + e.stack + \"\\n\");\n" + + " throw e;\n" + + " }\n" + + "}"; Cu.import("resource://gre/modules/devtools/dbg-client.jsm"); @@ -37,7 +39,7 @@ var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"] .createInstance(Ci.nsIPrincipal); var gGlobal = Cu.Sandbox(systemPrincipal); -gGlobal.importFunction(loadSubScript); +Cu.evalInSandbox(loadSubScript, gGlobal, "1.8"); gGlobal.loadSubScript("chrome://global/content/devtools/dbg-server.js"); this.DebuggerServer = gGlobal.DebuggerServer; From fbc44adf924c3596629e67bc5f245d214624e6c2 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Mon, 26 Nov 2012 12:27:05 -0500 Subject: [PATCH 038/160] Factor common register allocation code and add integrity checker to RegisterAllocator.h, add baseline StupidAllocator, bug 812945. r=jandem --- js/src/Makefile.in | 2 + js/src/ion/Ion.cpp | 38 ++- js/src/ion/Ion.h | 15 +- js/src/ion/IonSpewer.h | 2 +- js/src/ion/LIR-Common.h | 11 +- js/src/ion/LIR.cpp | 38 +++ js/src/ion/LIR.h | 148 +++++++++- js/src/ion/LinearScan.cpp | 49 +--- js/src/ion/LinearScan.h | 222 ++------------- js/src/ion/RegisterAllocator.cpp | 464 +++++++++++++++++++++++++++++++ js/src/ion/RegisterAllocator.h | 336 ++++++++++++++++++++++ js/src/ion/Safepoints.cpp | 19 +- js/src/ion/StupidAllocator.cpp | 419 ++++++++++++++++++++++++++++ js/src/ion/StupidAllocator.h | 84 ++++++ js/src/shell/js.cpp | 7 +- 15 files changed, 1582 insertions(+), 272 deletions(-) create mode 100644 js/src/ion/RegisterAllocator.cpp create mode 100644 js/src/ion/RegisterAllocator.h create mode 100644 js/src/ion/StupidAllocator.cpp create mode 100644 js/src/ion/StupidAllocator.h diff --git a/js/src/Makefile.in b/js/src/Makefile.in index ae1fa49b6975..0fa1cd658c6b 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -294,8 +294,10 @@ CPPSRCS += MIR.cpp \ MIRGraph.cpp \ MoveResolver.cpp \ EdgeCaseAnalysis.cpp \ + RegisterAllocator.cpp \ Snapshots.cpp \ Safepoints.cpp \ + StupidAllocator.cpp \ TypeOracle.cpp \ TypePolicy.cpp \ ValueNumbering.cpp \ diff --git a/js/src/ion/Ion.cpp b/js/src/ion/Ion.cpp index 250d5bc126f8..8530025e499c 100644 --- a/js/src/ion/Ion.cpp +++ b/js/src/ion/Ion.cpp @@ -21,6 +21,7 @@ #include "jsworkers.h" #include "IonCompartment.h" #include "CodeGenerator.h" +#include "StupidAllocator.h" #if defined(JS_CPU_X86) # include "x86/Lowering-x86.h" @@ -949,16 +950,47 @@ CompileBackEnd(MIRGenerator *mir) if (mir->shouldCancel("Generate LIR")) return NULL; - if (js_IonOptions.lsra) { + AllocationIntegrityState integrity(*lir); + + switch (js_IonOptions.registerAllocator) { + case RegisterAllocator_LSRA: { +#ifdef DEBUG + integrity.record(); +#endif + LinearScanAllocator regalloc(mir, &lirgen, *lir); if (!regalloc.go()) return NULL; - IonSpewPass("Allocate Registers", ®alloc); - if (mir->shouldCancel("Allocate Registers")) +#ifdef DEBUG + integrity.check(false); +#endif + + IonSpewPass("Allocate Registers [LSRA]", ®alloc); + break; + } + + case RegisterAllocator_Stupid: { + // Use the integrity checker to populate safepoint information, so + // run it in all builds. + integrity.record(); + + StupidAllocator regalloc(mir, &lirgen, *lir); + if (!regalloc.go()) return NULL; + if (!integrity.check(true)) + return NULL; + IonSpewPass("Allocate Registers [Stupid]"); + break; + } + + default: + JS_NOT_REACHED("Bad regalloc"); } + if (mir->shouldCancel("Allocate Registers")) + return NULL; + CodeGenerator *codegen = js_new(mir, lir); if (!codegen || !codegen->generate()) { js_delete(codegen); diff --git a/js/src/ion/Ion.h b/js/src/ion/Ion.h index 21db9dabee8e..d8bcd20effc9 100644 --- a/js/src/ion/Ion.h +++ b/js/src/ion/Ion.h @@ -19,6 +19,12 @@ namespace ion { class TempAllocator; +// Possible register allocators which may be used. +enum IonRegisterAllocator { + RegisterAllocator_LSRA, + RegisterAllocator_Stupid +}; + struct IonOptions { // Toggles whether global value numbering is used. @@ -47,11 +53,10 @@ struct IonOptions // Default: true bool limitScriptSize; - // Toggles whether Linear Scan Register Allocation is used. If LSRA is not - // used, then Greedy Register Allocation is used instead. + // Describes which register allocator to use. // - // Default: true - bool lsra; + // Default: LSRA + IonRegisterAllocator registerAllocator; // Toggles whether inlining is performed. // @@ -162,7 +167,7 @@ struct IonOptions licm(true), osr(true), limitScriptSize(true), - lsra(true), + registerAllocator(RegisterAllocator_LSRA), inlining(true), edgeCaseAnalysis(true), rangeAnalysis(true), diff --git a/js/src/ion/IonSpewer.h b/js/src/ion/IonSpewer.h index 507912a12f53..b21070a5b2b4 100644 --- a/js/src/ion/IonSpewer.h +++ b/js/src/ion/IonSpewer.h @@ -32,7 +32,7 @@ namespace ion { _(Range) \ /* Information during LICM */ \ _(LICM) \ - /* Information during LSRA */ \ + /* Information during regalloc */ \ _(RegAlloc) \ /* Information during inlining */ \ _(Inlining) \ diff --git a/js/src/ion/LIR-Common.h b/js/src/ion/LIR-Common.h index 00b91ddc2b26..62002eb56bde 100644 --- a/js/src/ion/LIR-Common.h +++ b/js/src/ion/LIR-Common.h @@ -105,10 +105,13 @@ class LMoveGroup : public LInstructionHelper<0, 0, 0> LIR_HEADER(MoveGroup); void printOperands(FILE *fp); - bool add(LAllocation *from, LAllocation *to) { - JS_ASSERT(*from != *to); - return moves_.append(LMove(from, to)); - } + + // Add a move which takes place simultaneously with all others in the group. + bool add(LAllocation *from, LAllocation *to); + + // Add a move which takes place after existing moves in the group. + bool addAfter(LAllocation *from, LAllocation *to); + size_t numMoves() const { return moves_.length(); } diff --git a/js/src/ion/LIR.cpp b/js/src/ion/LIR.cpp index ce67eb0c237f..85294396deb6 100644 --- a/js/src/ion/LIR.cpp +++ b/js/src/ion/LIR.cpp @@ -314,6 +314,44 @@ LInstruction::initSafepoint() JS_ASSERT(safepoint_); } +bool +LMoveGroup::add(LAllocation *from, LAllocation *to) +{ +#ifdef DEBUG + JS_ASSERT(*from != *to); + for (size_t i = 0; i < moves_.length(); i++) + JS_ASSERT(*to != *moves_[i].to()); +#endif + return moves_.append(LMove(from, to)); +} + +bool +LMoveGroup::addAfter(LAllocation *from, LAllocation *to) +{ + // Transform the operands to this move so that performing the result + // simultaneously with existing moves in the group will have the same + // effect as if the original move took place after the existing moves. + + for (size_t i = 0; i < moves_.length(); i++) { + if (*moves_[i].to() == *from) { + from = moves_[i].from(); + break; + } + } + + if (*from == *to) + return true; + + for (size_t i = 0; i < moves_.length(); i++) { + if (*to == *moves_[i].to()) { + moves_[i] = LMove(from, to); + return true; + } + } + + return add(from, to); +} + void LMoveGroup::printOperands(FILE *fp) { diff --git a/js/src/ion/LIR.h b/js/src/ion/LIR.h index 05bfa3007ae0..870bc299c978 100644 --- a/js/src/ion/LIR.h +++ b/js/src/ion/LIR.h @@ -194,6 +194,10 @@ class LAllocation : public TempObject return bits_ != other.bits_; } + HashNumber hash() const { + return bits_; + } + static void PrintAllocation(FILE *fp, const LAllocation *a); }; @@ -740,6 +744,9 @@ class LBlock : public TempObject LInstructionReverseIterator rbegin() { return instructions_.rbegin(); } + LInstructionReverseIterator rbegin(LInstruction *at) { + return instructions_.rbegin(at); + } LInstructionReverseIterator rend() { return instructions_.rend(); } @@ -935,6 +942,9 @@ class LSafepoint : public TempObject #ifdef JS_NUNBOX32 // List of registers which contain pieces of values. NunboxList nunboxParts_; + + // Number of nunboxParts which are not completely filled in. + uint32 partialNunboxes_; #elif JS_PUNBOX64 // List of registers which contain values. GeneralRegisterSet valueRegs_; @@ -942,17 +952,20 @@ class LSafepoint : public TempObject public: LSafepoint() - : safepointOffset_(INVALID_SAFEPOINT_OFFSET), - osiCallPointOffset_(0) + : safepointOffset_(INVALID_SAFEPOINT_OFFSET) + , osiCallPointOffset_(0) +#ifdef JS_NUNBOX32 + , partialNunboxes_(0) +#endif { } void addLiveRegister(AnyRegister reg) { - liveRegs_.add(reg); + liveRegs_.addUnchecked(reg); } const RegisterSet &liveRegs() const { return liveRegs_; } void addGcRegister(Register reg) { - gcRegs_.add(reg); + gcRegs_.addUnchecked(reg); } GeneralRegisterSet gcRegs() const { return gcRegs_; @@ -964,6 +977,27 @@ class LSafepoint : public TempObject return gcSlots_; } + void addGcPointer(LAllocation alloc) { + if (alloc.isRegister()) + addGcRegister(alloc.toRegister().gpr()); + else if (alloc.isStackSlot()) + addGcSlot(alloc.toStackSlot()->slot()); + } + + bool hasGcPointer(LAllocation alloc) { + if (alloc.isRegister()) + return gcRegs().has(alloc.toRegister().gpr()); + if (alloc.isStackSlot()) { + for (size_t i = 0; i < gcSlots_.length(); i++) { + if (gcSlots_[i] == alloc.toStackSlot()->slot()) + return true; + } + return false; + } + JS_ASSERT(alloc.isArgument()); + return true; + } + bool addValueSlot(uint32 slot) { return valueSlots_.append(slot); } @@ -971,21 +1005,125 @@ class LSafepoint : public TempObject return valueSlots_; } + bool hasValueSlot(uint32 slot) { + for (size_t i = 0; i < valueSlots_.length(); i++) { + if (valueSlots_[i] == slot) + return true; + } + return false; + } + #ifdef JS_NUNBOX32 + bool addNunboxParts(LAllocation type, LAllocation payload) { return nunboxParts_.append(NunboxEntry(type, payload)); } + + bool addNunboxType(uint32 typeVreg, LAllocation type) { + for (size_t i = 0; i < nunboxParts_.length(); i++) { + if (nunboxParts_[i].type == type) + return true; + if (nunboxParts_[i].type == LUse(LUse::ANY, typeVreg)) { + nunboxParts_[i].type = type; + partialNunboxes_--; + return true; + } + } + partialNunboxes_++; + + // vregs for nunbox pairs are adjacent, with the type coming first. + uint32 payloadVreg = typeVreg + 1; + return nunboxParts_.append(NunboxEntry(type, LUse(payloadVreg, LUse::ANY))); + } + + bool hasNunboxType(LAllocation type) { + if (type.isArgument()) + return true; + if (type.isStackSlot() && hasValueSlot(type.toStackSlot()->slot() + 1)) + return true; + for (size_t i = 0; i < nunboxParts_.length(); i++) { + if (nunboxParts_[i].type == type) + return true; + } + return false; + } + + bool addNunboxPayload(uint32 payloadVreg, LAllocation payload) { + for (size_t i = 0; i < nunboxParts_.length(); i++) { + if (nunboxParts_[i].payload == payload) + return true; + if (nunboxParts_[i].payload == LUse(LUse::ANY, payloadVreg)) { + partialNunboxes_--; + nunboxParts_[i].payload = payload; + return true; + } + } + partialNunboxes_++; + + // vregs for nunbox pairs are adjacent, with the type coming first. + uint32 typeVreg = payloadVreg - 1; + return nunboxParts_.append(NunboxEntry(LUse(typeVreg, LUse::ANY), payload)); + } + + bool hasNunboxPayload(LAllocation payload) { + if (payload.isArgument()) + return true; + if (payload.isStackSlot() && hasValueSlot(payload.toStackSlot()->slot())) + return true; + for (size_t i = 0; i < nunboxParts_.length(); i++) { + if (nunboxParts_[i].payload == payload) + return true; + } + return false; + } + NunboxList &nunboxParts() { return nunboxParts_; } + + uint32 partialNunboxes() { + return partialNunboxes_; + } + #elif JS_PUNBOX64 + void addValueRegister(Register reg) { valueRegs_.add(reg); } GeneralRegisterSet valueRegs() { return valueRegs_; } -#endif + + bool addBoxedValue(LAllocation alloc) { + if (alloc.isRegister()) { + Register reg = alloc.toRegister().gpr(); + if (!valueRegs().has(reg)) + addValueRegister(reg); + return true; + } + if (alloc.isStackSlot()) { + uint32 slot = alloc.toStackSlot()->slot(); + for (size_t i = 0; i < valueSlots().length(); i++) { + if (valueSlots()[i] == slot) + return true; + } + return addValueSlot(slot); + } + JS_ASSERT(alloc.isArgument()); + return true; + } + + bool hasBoxedValue(LAllocation alloc) { + if (alloc.isRegister()) + return valueRegs().has(alloc.toRegister().gpr()); + if (alloc.isStackSlot()) + return hasValueSlot(alloc.toStackSlot()->slot()); + JS_ASSERT(alloc.isArgument()); + return true; + } + +#endif // JS_PUNBOX64 + bool encoded() const { return safepointOffset_ != INVALID_SAFEPOINT_OFFSET; } diff --git a/js/src/ion/LinearScan.cpp b/js/src/ion/LinearScan.cpp index 14b431536716..e31393525ecb 100644 --- a/js/src/ion/LinearScan.cpp +++ b/js/src/ion/LinearScan.cpp @@ -360,9 +360,6 @@ LiveInterval::firstIncompatibleUse(LAllocation alloc) return CodePosition::MAX; } -const CodePosition CodePosition::MAX(UINT_MAX); -const CodePosition CodePosition::MIN(0); - /* * This function pre-allocates and initializes as much global state as possible * to avoid littering the algorithms with memory management cruft. @@ -370,6 +367,9 @@ const CodePosition CodePosition::MIN(0); bool LinearScanAllocator::createDataStructures() { + if (!RegisterAllocator::init()) + return false; + liveIn = lir->mir()->allocate(graph.numBlockIds()); if (!liveIn) return false; @@ -387,9 +387,6 @@ LinearScanAllocator::createDataStructures() if (!vregs.init(lir->mir(), graph.numVirtualRegisters())) return false; - if (!insData.init(lir->mir(), graph.numInstructions())) - return false; - // Build virtual register objects for (size_t i = 0; i < graph.numBlocks(); i++) { if (mir->shouldCancel("LSRA create data structures (main loop)")) @@ -405,7 +402,6 @@ LinearScanAllocator::createDataStructures() return false; } } - insData[*ins].init(*ins, block); for (size_t j = 0; j < ins->numTemps(); j++) { LDefinition *def = ins->getTemp(j); @@ -420,7 +416,6 @@ LinearScanAllocator::createDataStructures() LDefinition *def = phi->getDef(0); if (!vregs[def].init(phi->id(), block, phi, def, /* isTemp */ false)) return false; - insData[phi].init(phi, block); } } @@ -1870,44 +1865,6 @@ LinearScanAllocator::canCoexist(LiveInterval *a, LiveInterval *b) return true; } -LMoveGroup * -LinearScanAllocator::getInputMoveGroup(CodePosition pos) -{ - InstructionData *data = &insData[pos]; - JS_ASSERT(!data->ins()->isPhi()); - JS_ASSERT(!data->ins()->isLabel());; - JS_ASSERT(inputOf(data->ins()) == pos); - - if (data->inputMoves()) - return data->inputMoves(); - - LMoveGroup *moves = new LMoveGroup; - data->setInputMoves(moves); - data->block()->insertBefore(data->ins(), moves); - - return moves; -} - -LMoveGroup * -LinearScanAllocator::getMoveGroupAfter(CodePosition pos) -{ - InstructionData *data = &insData[pos]; - JS_ASSERT(!data->ins()->isPhi()); - JS_ASSERT(outputOf(data->ins()) == pos); - - if (data->movesAfter()) - return data->movesAfter(); - - LMoveGroup *moves = new LMoveGroup; - data->setMovesAfter(moves); - - if (data->ins()->isLabel()) - data->block()->insertAfter(data->block()->getEntryMoveGroup(), moves); - else - data->block()->insertAfter(data->ins(), moves); - return moves; -} - bool LinearScanAllocator::addMove(LMoveGroup *moves, LiveInterval *from, LiveInterval *to) { diff --git a/js/src/ion/LinearScan.h b/js/src/ion/LinearScan.h index bb5ac6922a13..e26b88666ddc 100644 --- a/js/src/ion/LinearScan.h +++ b/js/src/ion/LinearScan.h @@ -5,15 +5,10 @@ * 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 js_ion_registerallocator_h__ -#define js_ion_registerallocator_h__ +#ifndef js_ion_linearscan_h__ +#define js_ion_linearscan_h__ -#include "Ion.h" -#include "MIR.h" -#include "MIRGraph.h" -#include "InlineList.h" -#include "LIR.h" -#include "Lowering.h" +#include "RegisterAllocator.h" #include "BitSet.h" #include "StackSlotAllocator.h" @@ -24,109 +19,6 @@ namespace ion { class VirtualRegister; -/* - * Represents with better-than-instruction precision a position in the - * instruction stream. - * - * An issue comes up when dealing with live intervals as to how to represent - * information such as "this register is only needed for the input of - * this instruction, it can be clobbered in the output". Just having ranges - * of instruction IDs is insufficiently expressive to denote all possibilities. - * This class solves this issue by associating an extra bit with the instruction - * ID which indicates whether the position is the input half or output half of - * an instruction. - */ -class CodePosition -{ - private: - CodePosition(const uint32 &bits) - : bits_(bits) - { } - - static const unsigned int INSTRUCTION_SHIFT = 1; - static const unsigned int SUBPOSITION_MASK = 1; - uint32 bits_; - - public: - static const CodePosition MAX; - static const CodePosition MIN; - - /* - * This is the half of the instruction this code position represents, as - * described in the huge comment above. - */ - enum SubPosition { - INPUT, - OUTPUT - }; - - CodePosition() : bits_(0) - { } - - CodePosition(uint32 instruction, SubPosition where) { - JS_ASSERT(instruction < 0x80000000u); - JS_ASSERT(((uint32)where & SUBPOSITION_MASK) == (uint32)where); - bits_ = (instruction << INSTRUCTION_SHIFT) | (uint32)where; - } - - uint32 ins() const { - return bits_ >> INSTRUCTION_SHIFT; - } - - uint32 pos() const { - return bits_; - } - - SubPosition subpos() const { - return (SubPosition)(bits_ & SUBPOSITION_MASK); - } - - bool operator <(const CodePosition &other) const { - return bits_ < other.bits_; - } - - bool operator <=(const CodePosition &other) const { - return bits_ <= other.bits_; - } - - bool operator !=(const CodePosition &other) const { - return bits_ != other.bits_; - } - - bool operator ==(const CodePosition &other) const { - return bits_ == other.bits_; - } - - bool operator >(const CodePosition &other) const { - return bits_ > other.bits_; - } - - bool operator >=(const CodePosition &other) const { - return bits_ >= other.bits_; - } - - CodePosition previous() const { - JS_ASSERT(*this != MIN); - return CodePosition(bits_ - 1); - } - CodePosition next() const { - JS_ASSERT(*this != MAX); - return CodePosition(bits_ + 1); - } -}; - -struct UsePosition : public TempObject, - public InlineForwardListNode -{ - LUse *use; - CodePosition pos; - - UsePosition(LUse *use, CodePosition pos) : - use(use), - pos(pos) - { } -}; - class Requirement { public: @@ -198,6 +90,18 @@ class Requirement CodePosition position_; }; +struct UsePosition : public TempObject, + public InlineForwardListNode +{ + LUse *use; + CodePosition pos; + + UsePosition(LUse *use, CodePosition pos) : + use(use), + pos(pos) + { } +}; + typedef InlineForwardListIterator UsePositionIterator; /* @@ -494,70 +398,6 @@ class VirtualRegisterMap } }; -class InstructionData -{ - LInstruction *ins_; - LBlock *block_; - LMoveGroup *inputMoves_; - LMoveGroup *movesAfter_; - - public: - void init(LInstruction *ins, LBlock *block) { - JS_ASSERT(!ins_); - JS_ASSERT(!block_); - ins_ = ins; - block_ = block; - } - LInstruction *ins() const { - return ins_; - } - LBlock *block() const { - return block_; - } - void setInputMoves(LMoveGroup *moves) { - inputMoves_ = moves; - } - LMoveGroup *inputMoves() const { - return inputMoves_; - } - void setMovesAfter(LMoveGroup *moves) { - movesAfter_ = moves; - } - LMoveGroup *movesAfter() const { - return movesAfter_; - } -}; - -class InstructionDataMap -{ - InstructionData *insData_; - uint32 numIns_; - - public: - InstructionDataMap() - : insData_(NULL), - numIns_(0) - { } - - bool init(MIRGenerator *gen, uint32 numInstructions) { - insData_ = gen->allocate(numInstructions); - numIns_ = numInstructions; - if (!insData_) - return false; - memset(insData_, 0, sizeof(InstructionData) * numInstructions); - return true; - } - - InstructionData &operator[](const CodePosition &pos) { - JS_ASSERT(pos.ins() < numIns_); - return insData_[pos.ins()]; - } - InstructionData &operator[](LInstruction *ins) { - JS_ASSERT(ins->id() < numIns_); - return insData_[ins->id()]; - } -}; - typedef HashMap, @@ -571,7 +411,7 @@ typedef HashMap::iterator IntervalIterator; typedef InlineList::reverse_iterator IntervalReverseIterator; -class LinearScanAllocator +class LinearScanAllocator : public RegisterAllocator { friend class C1Spewer; friend class JSONSpewer; @@ -590,15 +430,9 @@ class LinearScanAllocator LiveInterval *dequeue(); }; - // Context - MIRGenerator *mir; - LIRGenerator *lir; - LIRGraph &graph; - // Computed inforamtion BitSet **liveIn; VirtualRegisterMap vregs; - InstructionDataMap insData; FixedArityList fixedIntervals; // Union of all ranges in fixedIntervals, used to quickly determine @@ -612,9 +446,6 @@ class LinearScanAllocator SlotList finishedSlots_; SlotList finishedDoubleSlots_; - // Pool of all registers that should be considered allocateable - RegisterSet allRegisters_; - // Run-time state UnhandledQueue unhandled; InlineList active; @@ -643,8 +474,6 @@ class LinearScanAllocator AnyRegister::Code findBestFreeRegister(CodePosition *freeUntil); AnyRegister::Code findBestBlockedRegister(CodePosition *nextUsed); bool canCoexist(LiveInterval *a, LiveInterval *b); - LMoveGroup *getInputMoveGroup(CodePosition pos); - LMoveGroup *getMoveGroupAfter(CodePosition pos); bool addMove(LMoveGroup *moves, LiveInterval *from, LiveInterval *to); bool moveInput(CodePosition pos, LiveInterval *from, LiveInterval *to); bool moveInputAlloc(CodePosition pos, LAllocation *from, LAllocation *to); @@ -669,31 +498,14 @@ class LinearScanAllocator inline void validateVirtualRegisters() { }; #endif - CodePosition outputOf(uint32 pos) { - return CodePosition(pos, CodePosition::OUTPUT); - } - CodePosition outputOf(LInstruction *ins) { - return CodePosition(ins->id(), CodePosition::OUTPUT); - } - CodePosition inputOf(uint32 pos) { - return CodePosition(pos, CodePosition::INPUT); - } - CodePosition inputOf(LInstruction *ins) { - return CodePosition(ins->id(), CodePosition::INPUT); - } #ifdef JS_NUNBOX32 VirtualRegister *otherHalfOfNunbox(VirtualRegister *vreg); #endif public: LinearScanAllocator(MIRGenerator *mir, LIRGenerator *lir, LIRGraph &graph) - : mir(mir), - lir(lir), - graph(graph), - allRegisters_(RegisterSet::All()) + : RegisterAllocator(mir, lir, graph) { - if (FramePointer != InvalidReg && lir->mir()->instrumentedProfiling()) - allRegisters_.take(AnyRegister(FramePointer)); } bool go(); diff --git a/js/src/ion/RegisterAllocator.cpp b/js/src/ion/RegisterAllocator.cpp new file mode 100644 index 000000000000..8be440033c98 --- /dev/null +++ b/js/src/ion/RegisterAllocator.cpp @@ -0,0 +1,464 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * 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 "RegisterAllocator.h" + +using namespace js; +using namespace js::ion; + +bool +AllocationIntegrityState::record() +{ + // Ignore repeated record() calls. + if (!instructions.empty()) + return true; + + if (!instructions.reserve(graph.numInstructions())) + return false; + for (size_t i = 0; i < graph.numInstructions(); i++) + instructions.infallibleAppend(InstructionInfo()); + + if (!virtualRegisters.reserve(graph.numVirtualRegisters())) + return false; + for (size_t i = 0; i < graph.numVirtualRegisters(); i++) + virtualRegisters.infallibleAppend(NULL); + + if (!blocks.reserve(graph.numBlocks())) + return false; + for (size_t i = 0; i < graph.numBlocks(); i++) { + blocks.infallibleAppend(BlockInfo()); + LBlock *block = graph.getBlock(i); + JS_ASSERT(block->mir()->id() == i); + + BlockInfo &blockInfo = blocks[i]; + if (!blockInfo.phis.reserve(block->numPhis())) + return false; + + for (size_t j = 0; j < block->numPhis(); j++) { + blockInfo.phis.infallibleAppend(InstructionInfo()); + InstructionInfo &info = blockInfo.phis[j]; + LPhi *phi = block->getPhi(j); + for (size_t k = 0; k < phi->numDefs(); k++) { + uint32 vreg = phi->getDef(k)->virtualRegister(); + virtualRegisters[vreg] = phi->getDef(k); + if (!info.outputs.append(vreg)) + return false; + } + for (size_t k = 0; k < phi->numOperands(); k++) { + if (!info.inputs.append(phi->getOperand(k)->toUse()->virtualRegister())) + return false; + } + } + + for (LInstructionIterator iter = block->begin(); iter != block->end(); iter++) { + LInstruction *ins = *iter; + InstructionInfo &info = instructions[ins->id()]; + + for (size_t k = 0; k < ins->numDefs(); k++) { + uint32 vreg = ins->getDef(k)->virtualRegister(); + virtualRegisters[vreg] = ins->getDef(k); + if (!info.outputs.append(vreg)) + return false; + } + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) { + if (!info.inputs.append(alloc->isUse() ? alloc->toUse()->virtualRegister() : UINT32_MAX)) + return false; + } + } + } + + return seen.init(); +} + +bool +AllocationIntegrityState::check(bool populateSafepoints) +{ + JS_ASSERT(!instructions.empty()); + +#ifdef DEBUG + for (size_t blockIndex = 0; blockIndex < graph.numBlocks(); blockIndex++) { + LBlock *block = graph.getBlock(blockIndex); + + // Check that all instruction inputs and outputs have been assigned an allocation. + for (LInstructionIterator iter = block->begin(); iter != block->end(); iter++) { + LInstruction *ins = *iter; + + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) + JS_ASSERT(!alloc->isUse()); + + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition *def = ins->getDef(i); + JS_ASSERT_IF(def->policy() != LDefinition::PASSTHROUGH, !def->output()->isUse()); + + if (def->output()->isRegister()) { + // The live regs for an instruction's safepoint should + // exclude the instruction's definitions. + LSafepoint *safepoint = ins->safepoint(); + JS_ASSERT_IF(safepoint, !safepoint->liveRegs().has(def->output()->toRegister())); + } + } + } + } +#endif + + // Check that the register assignment and move groups preserve the original + // semantics of the virtual registers. Each virtual register has a single + // write (owing to the SSA representation), but the allocation may move the + // written value around between registers and memory locations along + // different paths through the script. + // + // For each use of an allocation, follow the physical value which is read + // backward through the script, along all paths to the value's virtual + // register's definition. + for (size_t blockIndex = 0; blockIndex < graph.numBlocks(); blockIndex++) { + LBlock *block = graph.getBlock(blockIndex); + for (LInstructionIterator iter = block->begin(); iter != block->end(); iter++) { + LInstruction *ins = *iter; + const InstructionInfo &info = instructions[ins->id()]; + + size_t inputIndex = 0; + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) { + uint32 vreg = info.inputs[inputIndex++]; + if (vreg == UINT32_MAX) + continue; + + // Start checking at the previous instruction, in case this + // instruction reuses its input register for an output. + LInstructionReverseIterator riter = block->rbegin(ins); + riter++; + checkIntegrity(block, *riter, vreg, **alloc, populateSafepoints); + + while (!worklist.empty()) { + IntegrityItem item = worklist.back(); + worklist.popBack(); + checkIntegrity(item.block, *item.block->rbegin(), item.vreg, item.alloc, populateSafepoints); + } + } + } + } + + if (IonSpewEnabled(IonSpew_RegAlloc)) + dump(); + + return true; +} + +bool +AllocationIntegrityState::checkIntegrity(LBlock *block, LInstruction *ins, + uint32 vreg, LAllocation alloc, bool populateSafepoints) +{ + for (LInstructionReverseIterator iter(block->rbegin(ins)); iter != block->rend(); iter++) { + ins = *iter; + + // Follow values through assignments in move groups. All assignments in + // a move group are considered to happen simultaneously, so stop after + // the first matching move is found. + if (ins->isMoveGroup()) { + LMoveGroup *group = ins->toMoveGroup(); + for (int i = group->numMoves() - 1; i >= 0; i--) { + if (*group->getMove(i).to() == alloc) { + alloc = *group->getMove(i).from(); + break; + } + } + } + + const InstructionInfo &info = instructions[ins->id()]; + + // Make sure the physical location being tracked is not clobbered by + // another instruction, and that if the originating vreg definition is + // found that it is writing to the tracked location. + + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition *def = ins->getDef(i); + if (def->policy() == LDefinition::PASSTHROUGH) + continue; + if (info.outputs[i] == vreg) { + check(*def->output() == alloc, + "Found vreg definition, but tracked value does not match"); + + // Found the original definition, done scanning. + return true; + } else { + check(*def->output() != alloc, + "Tracked value clobbered by intermediate definition"); + } + } + + for (size_t i = 0; i < ins->numTemps(); i++) { + LDefinition *temp = ins->getTemp(i); + if (!temp->isBogusTemp()) { + check(*temp->output() != alloc, + "Tracked value clobbered by intermediate temporary"); + } + } + + LSafepoint *safepoint = ins->safepoint(); + if (!safepoint) + continue; + + if (alloc.isRegister()) { + AnyRegister reg = alloc.toRegister(); + if (populateSafepoints) + safepoint->addLiveRegister(reg); + else + check(safepoint->liveRegs().has(reg), "Register not marked in safepoint"); + } + + LDefinition::Type type = virtualRegisters[vreg] + ? virtualRegisters[vreg]->type() + : LDefinition::GENERAL; + + switch (type) { + case LDefinition::OBJECT: + if (populateSafepoints) + safepoint->addGcPointer(alloc); + else + check(safepoint->hasGcPointer(alloc), "GC register not marked in safepoint"); + break; +#ifdef JS_NUNBOX32 + // If a vreg for a value's components are copied in multiple places + // then the safepoint information may be incomplete and not reflect + // all copies. See SafepointWriter::writeNunboxParts. + case LDefinition::TYPE: + if (populateSafepoints) + safepoint->addNunboxType(vreg, alloc); + break; + case LDefinition::PAYLOAD: + if (populateSafepoints) + safepoint->addNunboxPayload(vreg, alloc); + break; +#else + case LDefinition::BOX: + if (populateSafepoints) + safepoint->addBoxedValue(alloc); + else + check(safepoint->hasBoxedValue(alloc), "Boxed value not marked in safepoint"); + break; +#endif + default: + break; + } + } + + // Phis are effectless, but change the vreg we are tracking. Check if there + // is one which produced this vreg. We need to follow back through the phi + // inputs as it is not guaranteed the register allocator filled in physical + // allocations for the inputs and outputs of the phis. + for (size_t i = 0; i < block->numPhis(); i++) { + InstructionInfo &info = blocks[block->mir()->id()].phis[i]; + LPhi *phi = block->getPhi(i); + if (info.outputs[0] == vreg) { + for (size_t j = 0; j < phi->numOperands(); j++) { + uint32 newvreg = info.inputs[j]; + LBlock *predecessor = graph.getBlock(block->mir()->getPredecessor(j)->id()); + if (!addPredecessor(predecessor, newvreg, alloc)) + return false; + } + return true; + } + } + + // No phi which defined the vreg we are tracking, follow back through all + // predecessors with the existing vreg. + for (size_t i = 0; i < block->mir()->numPredecessors(); i++) { + LBlock *predecessor = graph.getBlock(block->mir()->getPredecessor(i)->id()); + if (!addPredecessor(predecessor, vreg, alloc)) + return false; + } + + return true; +} + +bool +AllocationIntegrityState::addPredecessor(LBlock *block, uint32 vreg, LAllocation alloc) +{ + // There is no need to reanalyze if we have already seen this predecessor. + // We share the seen allocations across analysis of each use, as there will + // likely be common ground between different uses of the same vreg. + IntegrityItem item; + item.block = block; + item.vreg = vreg; + item.alloc = alloc; + item.index = seen.count(); + + IntegrityItemSet::AddPtr p = seen.lookupForAdd(item); + if (p) + return true; + if (!seen.add(p, item)) + return false; + + return worklist.append(item); +} + +void +AllocationIntegrityState::check(bool cond, const char *msg) +{ + if (!cond) { + if (IonSpewEnabled(IonSpew_RegAlloc)) + dump(); + printf("%s\n", msg); + JS_NOT_REACHED("Regalloc integrity failure"); + } +} + +void +AllocationIntegrityState::dump() +{ +#ifdef DEBUG + printf("Register Allocation:\n"); + + for (size_t blockIndex = 0; blockIndex < graph.numBlocks(); blockIndex++) { + LBlock *block = graph.getBlock(blockIndex); + MBasicBlock *mir = block->mir(); + + printf("\nBlock %lu", blockIndex); + for (size_t i = 0; i < mir->numSuccessors(); i++) + printf(" [successor %u]", mir->getSuccessor(i)->id()); + printf("\n"); + + for (size_t i = 0; i < block->numPhis(); i++) { + InstructionInfo &info = blocks[blockIndex].phis[i]; + LPhi *phi = block->getPhi(i); + + printf("Phi v%u <-", info.outputs[0]); + for (size_t j = 0; j < phi->numOperands(); j++) + printf(" v%u", info.inputs[j]); + printf("\n"); + } + + for (LInstructionIterator iter = block->begin(); iter != block->end(); iter++) { + LInstruction *ins = *iter; + InstructionInfo &info = instructions[ins->id()]; + + printf("[%s]", ins->opName()); + + if (ins->isMoveGroup()) { + LMoveGroup *group = ins->toMoveGroup(); + for (int i = group->numMoves() - 1; i >= 0; i--) { + printf(" ["); + LAllocation::PrintAllocation(stdout, group->getMove(i).from()); + printf(" -> "); + LAllocation::PrintAllocation(stdout, group->getMove(i).to()); + printf("]"); + } + printf("\n"); + continue; + } + + for (size_t i = 0; i < ins->numTemps(); i++) { + LDefinition *temp = ins->getTemp(i); + if (!temp->isBogusTemp()) { + printf(" [temp "); + LAllocation::PrintAllocation(stdout, temp->output()); + printf("]"); + } + } + + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition *def = ins->getDef(i); + printf(" [def v%u ", info.outputs[i]); + LAllocation::PrintAllocation(stdout, def->output()); + printf("]"); + } + + size_t index = 0; + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) { + uint32 vreg = info.inputs[index++]; + if (vreg == UINT32_MAX) + continue; + printf(" [use v%u ", vreg); + LAllocation::PrintAllocation(stdout, *alloc); + printf("]"); + } + + printf("\n"); + } + } + + printf("\nIntermediate Allocations:\n\n"); + + // Print discovered allocations at the ends of blocks, in the order they + // were discovered. + + Vector seenOrdered; + for (size_t i = 0; i < seen.count(); i++) + seenOrdered.append(IntegrityItem()); + + for (IntegrityItemSet::Enum iter(seen); !iter.empty(); iter.popFront()) { + IntegrityItem item = iter.front(); + seenOrdered[item.index] = item; + } + + for (size_t i = 0; i < seenOrdered.length(); i++) { + IntegrityItem item = seenOrdered[i]; + printf("block %u reg v%u alloc ", item.block->mir()->id(), item.vreg); + LAllocation::PrintAllocation(stdout, &item.alloc); + printf("\n"); + } + + printf("\n"); +#endif +} + +const CodePosition CodePosition::MAX(UINT_MAX); +const CodePosition CodePosition::MIN(0); + +bool +RegisterAllocator::init() +{ + if (!insData.init(lir->mir(), graph.numInstructions())) + return false; + + for (size_t i = 0; i < graph.numBlocks(); i++) { + LBlock *block = graph.getBlock(i); + for (LInstructionIterator ins = block->begin(); ins != block->end(); ins++) + insData[*ins].init(*ins, block); + for (size_t j = 0; j < block->numPhis(); j++) { + LPhi *phi = block->getPhi(j); + insData[phi].init(phi, block); + } + } + + return true; +} + +LMoveGroup * +RegisterAllocator::getInputMoveGroup(uint32 ins) +{ + InstructionData *data = &insData[ins]; + JS_ASSERT(!data->ins()->isPhi()); + JS_ASSERT(!data->ins()->isLabel()); + + if (data->inputMoves()) + return data->inputMoves(); + + LMoveGroup *moves = new LMoveGroup; + data->setInputMoves(moves); + data->block()->insertBefore(data->ins(), moves); + + return moves; +} + +LMoveGroup * +RegisterAllocator::getMoveGroupAfter(uint32 ins) +{ + InstructionData *data = &insData[ins]; + JS_ASSERT(!data->ins()->isPhi()); + + if (data->movesAfter()) + return data->movesAfter(); + + LMoveGroup *moves = new LMoveGroup; + data->setMovesAfter(moves); + + if (data->ins()->isLabel()) + data->block()->insertAfter(data->block()->getEntryMoveGroup(), moves); + else + data->block()->insertAfter(data->ins(), moves); + return moves; +} diff --git a/js/src/ion/RegisterAllocator.h b/js/src/ion/RegisterAllocator.h new file mode 100644 index 000000000000..bf82c0734e4b --- /dev/null +++ b/js/src/ion/RegisterAllocator.h @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * 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 js_ion_registerallocator_h__ +#define js_ion_registerallocator_h__ + +#include "Ion.h" +#include "MIR.h" +#include "MIRGraph.h" +#include "InlineList.h" +#include "LIR.h" +#include "Lowering.h" + +// Generic structures and functions for use by register allocators. + +namespace js { +namespace ion { + +// Structure for running a liveness analysis on a finished register allocation. +// This analysis can be used for two purposes: +// +// - Check the integrity of the allocation, i.e. that the reads and writes of +// physical values preserve the semantics of the original virtual registers. +// +// - Populate safepoints with live registers, GC thing and value data, to +// streamline the process of prototyping new allocators. +struct AllocationIntegrityState +{ + AllocationIntegrityState(LIRGraph &graph) + : graph(graph) + {} + + // Record all virtual registers in the graph. This must be called before + // register allocation, to pick up the original LUses. + bool record(); + + // Perform the liveness analysis on the graph, and assert on an invalid + // allocation. This must be called after register allocation, to pick up + // all assigned physical values. If populateSafepoints is specified, + // safepoints will be filled in with liveness information. + bool check(bool populateSafepoints); + + private: + + LIRGraph &graph; + + // For all instructions and phis in the graph, keep track of the virtual + // registers for all inputs and outputs of the nodes. These are overwritten + // in place during register allocation. This information is kept on the + // side rather than in the instructions and phis themselves to avoid + // debug-builds-only bloat in the size of the involved structures. + + struct InstructionInfo { + Vector inputs; + Vector outputs; + InstructionInfo() {} + InstructionInfo(const InstructionInfo &o) { + for (size_t i = 0; i < o.inputs.length(); i++) + inputs.append(o.inputs[i]); + for (size_t i = 0; i < o.outputs.length(); i++) + outputs.append(o.outputs[i]); + } + }; + Vector instructions; + + struct BlockInfo { + Vector phis; + BlockInfo() {} + BlockInfo(const BlockInfo &o) { + for (size_t i = 0; i < o.phis.length(); i++) + phis.append(o.phis[i]); + } + }; + Vector blocks; + + Vector virtualRegisters; + + // Describes a correspondence that should hold at the end of a block. + // The value which was written to vreg in the original LIR should be + // physically stored in alloc after the register allocation. + struct IntegrityItem + { + LBlock *block; + uint32 vreg; + LAllocation alloc; + + // Order of insertion into seen, for sorting. + uint32 index; + + typedef IntegrityItem Lookup; + static HashNumber hash(const IntegrityItem &item) { + HashNumber hash = item.alloc.hash(); + hash = JS_ROTATE_LEFT32(hash, 4) ^ item.vreg; + hash = JS_ROTATE_LEFT32(hash, 4) ^ HashNumber(item.block->mir()->id()); + return hash; + } + static bool match(const IntegrityItem &one, const IntegrityItem &two) { + return one.block == two.block + && one.vreg == two.vreg + && one.alloc == two.alloc; + } + }; + + // Items still to be processed. + Vector worklist; + + // Set of all items that have already been processed. + typedef HashSet IntegrityItemSet; + IntegrityItemSet seen; + + bool checkIntegrity(LBlock *block, LInstruction *ins, uint32 vreg, LAllocation alloc, + bool populateSafepoints); + bool addPredecessor(LBlock *block, uint32 vreg, LAllocation alloc); + + void check(bool cond, const char *msg); + void dump(); +}; + +// Represents with better-than-instruction precision a position in the +// instruction stream. +// +// An issue comes up when performing register allocation as to how to represent +// information such as "this register is only needed for the input of +// this instruction, it can be clobbered in the output". Just having ranges +// of instruction IDs is insufficiently expressive to denote all possibilities. +// This class solves this issue by associating an extra bit with the instruction +// ID which indicates whether the position is the input half or output half of +// an instruction. +class CodePosition +{ + private: + CodePosition(const uint32 &bits) + : bits_(bits) + { } + + static const unsigned int INSTRUCTION_SHIFT = 1; + static const unsigned int SUBPOSITION_MASK = 1; + uint32 bits_; + + public: + static const CodePosition MAX; + static const CodePosition MIN; + + // This is the half of the instruction this code position represents, as + // described in the huge comment above. + enum SubPosition { + INPUT, + OUTPUT + }; + + CodePosition() : bits_(0) + { } + + CodePosition(uint32 instruction, SubPosition where) { + JS_ASSERT(instruction < 0x80000000u); + JS_ASSERT(((uint32)where & SUBPOSITION_MASK) == (uint32)where); + bits_ = (instruction << INSTRUCTION_SHIFT) | (uint32)where; + } + + uint32 ins() const { + return bits_ >> INSTRUCTION_SHIFT; + } + + uint32 pos() const { + return bits_; + } + + SubPosition subpos() const { + return (SubPosition)(bits_ & SUBPOSITION_MASK); + } + + bool operator <(const CodePosition &other) const { + return bits_ < other.bits_; + } + + bool operator <=(const CodePosition &other) const { + return bits_ <= other.bits_; + } + + bool operator !=(const CodePosition &other) const { + return bits_ != other.bits_; + } + + bool operator ==(const CodePosition &other) const { + return bits_ == other.bits_; + } + + bool operator >(const CodePosition &other) const { + return bits_ > other.bits_; + } + + bool operator >=(const CodePosition &other) const { + return bits_ >= other.bits_; + } + + CodePosition previous() const { + JS_ASSERT(*this != MIN); + return CodePosition(bits_ - 1); + } + CodePosition next() const { + JS_ASSERT(*this != MAX); + return CodePosition(bits_ + 1); + } +}; + +// Structure to track moves inserted before or after an instruction. +class InstructionData +{ + LInstruction *ins_; + LBlock *block_; + LMoveGroup *inputMoves_; + LMoveGroup *movesAfter_; + + public: + void init(LInstruction *ins, LBlock *block) { + JS_ASSERT(!ins_); + JS_ASSERT(!block_); + ins_ = ins; + block_ = block; + } + LInstruction *ins() const { + return ins_; + } + LBlock *block() const { + return block_; + } + void setInputMoves(LMoveGroup *moves) { + inputMoves_ = moves; + } + LMoveGroup *inputMoves() const { + return inputMoves_; + } + void setMovesAfter(LMoveGroup *moves) { + movesAfter_ = moves; + } + LMoveGroup *movesAfter() const { + return movesAfter_; + } +}; + +// Structure to track all moves inserted next to instructions in a graph. +class InstructionDataMap +{ + InstructionData *insData_; + uint32 numIns_; + + public: + InstructionDataMap() + : insData_(NULL), + numIns_(0) + { } + + bool init(MIRGenerator *gen, uint32 numInstructions) { + insData_ = gen->allocate(numInstructions); + numIns_ = numInstructions; + if (!insData_) + return false; + memset(insData_, 0, sizeof(InstructionData) * numInstructions); + return true; + } + + InstructionData &operator[](const CodePosition &pos) { + JS_ASSERT(pos.ins() < numIns_); + return insData_[pos.ins()]; + } + InstructionData &operator[](LInstruction *ins) { + JS_ASSERT(ins->id() < numIns_); + return insData_[ins->id()]; + } + InstructionData &operator[](uint32 ins) { + JS_ASSERT(ins < numIns_); + return insData_[ins]; + } +}; + +// Common superclass for register allocators. +class RegisterAllocator +{ + protected: + // Context + MIRGenerator *mir; + LIRGenerator *lir; + LIRGraph &graph; + + // Pool of all registers that should be considered allocateable + RegisterSet allRegisters_; + + // Computed data + InstructionDataMap insData; + + public: + RegisterAllocator(MIRGenerator *mir, LIRGenerator *lir, LIRGraph &graph) + : mir(mir), + lir(lir), + graph(graph), + allRegisters_(RegisterSet::All()) + { + if (FramePointer != InvalidReg && lir->mir()->instrumentedProfiling()) + allRegisters_.take(AnyRegister(FramePointer)); + } + + protected: + bool init(); + + CodePosition outputOf(uint32 pos) { + return CodePosition(pos, CodePosition::OUTPUT); + } + CodePosition outputOf(LInstruction *ins) { + return CodePosition(ins->id(), CodePosition::OUTPUT); + } + CodePosition inputOf(uint32 pos) { + return CodePosition(pos, CodePosition::INPUT); + } + CodePosition inputOf(LInstruction *ins) { + return CodePosition(ins->id(), CodePosition::INPUT); + } + + LMoveGroup *getInputMoveGroup(uint32 ins); + LMoveGroup *getMoveGroupAfter(uint32 ins); + + LMoveGroup *getInputMoveGroup(CodePosition pos) { + return getInputMoveGroup(pos.ins()); + } + LMoveGroup *getMoveGroupAfter(CodePosition pos) { + return getMoveGroupAfter(pos.ins()); + } +}; + +} // namespace ion +} // namespace js + +#endif diff --git a/js/src/ion/Safepoints.cpp b/js/src/ion/Safepoints.cpp index d148cf2cfa55..0ac250ccefd5 100644 --- a/js/src/ion/Safepoints.cpp +++ b/js/src/ion/Safepoints.cpp @@ -226,11 +226,26 @@ SafepointWriter::writeNunboxParts(LSafepoint *safepoint) } # endif - stream_.writeUnsigned(entries.length()); + // Safepoints are permitted to have partially filled in entries for nunboxes, + // provided that only the type is live and not the payload. Omit these from + // the written safepoint. + // + // Note that partial entries typically appear when one part of a nunbox is + // stored in multiple places, in which case we will end up with incomplete + // information about all the places the value is stored. This will need to + // be fixed when the GC is permitted to move structures. + uint32 partials = safepoint->partialNunboxes(); + + stream_.writeUnsigned(entries.length() - partials); for (size_t i = 0; i < entries.length(); i++) { SafepointNunboxEntry &entry = entries[i]; + if (entry.type.isUse() || entry.payload.isUse()) { + partials--; + continue; + } + uint16 header = 0; header |= (AllocationToPartKind(entry.type) << TYPE_KIND_SHIFT); @@ -256,6 +271,8 @@ SafepointWriter::writeNunboxParts(LSafepoint *safepoint) if (payloadExtra) stream_.writeUnsigned(payloadVal); } + + JS_ASSERT(partials == 0); } #endif diff --git a/js/src/ion/StupidAllocator.cpp b/js/src/ion/StupidAllocator.cpp new file mode 100644 index 000000000000..9a83164000cc --- /dev/null +++ b/js/src/ion/StupidAllocator.cpp @@ -0,0 +1,419 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * 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 "StupidAllocator.h" + +using namespace js; +using namespace js::ion; + +static inline uint32 +DefaultStackSlot(uint32 vreg) +{ +#if JS_BITS_PER_WORD == 32 + return vreg * 2 + 2; +#else + return vreg + 1; +#endif +} + +LAllocation * +StupidAllocator::stackLocation(uint32 vreg) +{ + LDefinition *def = virtualRegisters[vreg]; + if (def->policy() == LDefinition::PRESET && def->output()->kind() == LAllocation::ARGUMENT) + return def->output(); + + return new LStackSlot(DefaultStackSlot(vreg), def->type() == LDefinition::DOUBLE); +} + +StupidAllocator::RegisterIndex +StupidAllocator::registerIndex(AnyRegister reg) +{ + for (size_t i = 0; i < registerCount; i++) { + if (reg == registers[i].reg) + return i; + } + JS_NOT_REACHED("Bad register"); + return UINT32_MAX; +} + +bool +StupidAllocator::init() +{ + if (!RegisterAllocator::init()) + return false; + + if (!virtualRegisters.reserve(graph.numVirtualRegisters())) + return false; + for (size_t i = 0; i < graph.numVirtualRegisters(); i++) + virtualRegisters.infallibleAppend(NULL); + + for (size_t i = 0; i < graph.numBlocks(); i++) { + LBlock *block = graph.getBlock(i); + for (LInstructionIterator ins = block->begin(); ins != block->end(); ins++) { + for (size_t j = 0; j < ins->numDefs(); j++) { + LDefinition *def = ins->getDef(j); + if (def->policy() != LDefinition::PASSTHROUGH) + virtualRegisters[def->virtualRegister()] = def; + } + + for (size_t j = 0; j < ins->numTemps(); j++) { + LDefinition *def = ins->getTemp(j); + if (def->isBogusTemp()) + continue; + virtualRegisters[def->virtualRegister()] = def; + } + } + for (size_t j = 0; j < block->numPhis(); j++) { + LPhi *phi = block->getPhi(j); + LDefinition *def = phi->getDef(0); + uint32 vreg = def->virtualRegister(); + + virtualRegisters[vreg] = def; + } + } + + // Assign physical registers to the tracked allocation. + { + registerCount = 0; + RegisterSet remainingRegisters(allRegisters_); + while (!remainingRegisters.empty(/* float = */ false)) + registers[registerCount++].reg = AnyRegister(remainingRegisters.takeGeneral()); + while (!remainingRegisters.empty(/* float = */ true)) + registers[registerCount++].reg = AnyRegister(remainingRegisters.takeFloat()); + JS_ASSERT(registerCount <= MAX_REGISTERS); + } + + return true; +} + +static inline bool +AllocationRequiresRegister(const LAllocation *alloc, AnyRegister reg) +{ + if (alloc->isRegister() && alloc->toRegister() == reg) + return true; + if (alloc->isUse()) { + const LUse *use = alloc->toUse(); + if (use->policy() == LUse::FIXED && AnyRegister::FromCode(use->registerCode()) == reg) + return true; + } + return false; +} + +static inline bool +RegisterIsReserved(LInstruction *ins, AnyRegister reg) +{ + // Whether reg is already reserved for an input or output of ins. + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) { + if (AllocationRequiresRegister(*alloc, reg)) + return true; + } + for (size_t i = 0; i < ins->numTemps(); i++) { + if (AllocationRequiresRegister(ins->getTemp(i)->output(), reg)) + return true; + } + for (size_t i = 0; i < ins->numDefs(); i++) { + if (AllocationRequiresRegister(ins->getDef(i)->output(), reg)) + return true; + } + return false; +} + +AnyRegister +StupidAllocator::ensureHasRegister(LInstruction *ins, uint32 vreg) +{ + // Ensure that vreg is held in a register before ins. + + // Check if the virtual register is already held in a physical register. + RegisterIndex existing = findExistingRegister(vreg); + if (existing != UINT32_MAX) { + if (RegisterIsReserved(ins, registers[existing].reg)) { + evictRegister(ins, existing); + } else { + registers[existing].age = ins->id(); + return registers[existing].reg; + } + } + + RegisterIndex best = allocateRegister(ins, vreg); + loadRegister(ins, vreg, best); + + return registers[best].reg; +} + +StupidAllocator::RegisterIndex +StupidAllocator::allocateRegister(LInstruction *ins, uint32 vreg) +{ + // Pick a register for vreg, evicting an existing register if necessary. + // Spill code will be placed before ins, and no existing allocated input + // for ins will be touched. + JS_ASSERT(ins); + + LDefinition *def = virtualRegisters[vreg]; + JS_ASSERT(def); + + RegisterIndex best = UINT32_MAX; + + for (size_t i = 0; i < registerCount; i++) { + AnyRegister reg = registers[i].reg; + + if (reg.isFloat() != (def->type() == LDefinition::DOUBLE)) + continue; + + // Skip the register if it is in use for an allocated input or output. + if (RegisterIsReserved(ins, reg)) + continue; + + if (registers[i].vreg == MISSING_ALLOCATION || + best == UINT32_MAX || + registers[best].age > registers[i].age) + { + best = i; + } + } + + evictRegister(ins, best); + return best; +} + +void +StupidAllocator::syncRegister(LInstruction *ins, RegisterIndex index) +{ + if (registers[index].dirty) { + LMoveGroup *input = getInputMoveGroup(ins->id()); + LAllocation *source = new LAllocation(registers[index].reg); + + uint32 existing = registers[index].vreg; + LAllocation *dest = stackLocation(existing); + input->addAfter(source, dest); + + registers[index].dirty = false; + } +} + +void +StupidAllocator::evictRegister(LInstruction *ins, RegisterIndex index) +{ + syncRegister(ins, index); + registers[index].set(MISSING_ALLOCATION); +} + +void +StupidAllocator::loadRegister(LInstruction *ins, uint32 vreg, RegisterIndex index) +{ + // Load a vreg from its stack location to a register. + LMoveGroup *input = getInputMoveGroup(ins->id()); + LAllocation *source = stackLocation(vreg); + LAllocation *dest = new LAllocation(registers[index].reg); + input->addAfter(source, dest); + registers[index].set(vreg, ins); +} + +StupidAllocator::RegisterIndex +StupidAllocator::findExistingRegister(uint32 vreg) +{ + for (size_t i = 0; i < registerCount; i++) { + if (registers[i].vreg == vreg) + return i; + } + return UINT32_MAX; +} + +bool +StupidAllocator::go() +{ + // This register allocator is intended to be as simple as possible, while + // still being complicated enough to share properties with more complicated + // allocators. Namely, physical registers may be used to carry virtual + // registers across LIR instructions, but not across basic blocks. + // + // This algorithm does not pay any attention to liveness. It is performed + // as a single forward pass through the basic blocks in the program. As + // virtual registers and temporaries are defined they are assigned physical + // registers, evicting existing allocations in an LRU fashion. + + // For virtual registers not carried in a register, a canonical spill + // location is used. Each vreg has a different spill location; since we do + // not track liveness we cannot determine that two vregs have disjoint + // lifetimes. Thus, the maximum stack height is the number of vregs (scaled + // by two on 32 bit platforms to allow storing double values). + graph.setLocalSlotCount(DefaultStackSlot(graph.numVirtualRegisters() - 1) + 1); + + if (!init()) + return false; + + for (size_t blockIndex = 0; blockIndex < graph.numBlocks(); blockIndex++) { + LBlock *block = graph.getBlock(blockIndex); + JS_ASSERT(block->mir()->id() == blockIndex); + + for (size_t i = 0; i < registerCount; i++) + registers[i].set(MISSING_ALLOCATION); + + for (LInstructionIterator iter = block->begin(); iter != block->end(); iter++) { + LInstruction *ins = *iter; + + if (ins == *block->rbegin()) + syncForBlockEnd(block, ins); + + allocateForInstruction(ins); + } + } + + return true; +} + +void +StupidAllocator::syncForBlockEnd(LBlock *block, LInstruction *ins) +{ + // Sync any dirty registers, and update the synced state for phi nodes at + // each successor of a block. We cannot conflate the storage for phis with + // that of their inputs, as we cannot prove the live ranges of the phi and + // its input do not overlap. The values for the two may additionally be + // different, as the phi could be for the value of the input in a previous + // loop iteration. + + for (size_t i = 0; i < registerCount; i++) + syncRegister(ins, i); + + LMoveGroup *group = NULL; + + MBasicBlock *successor = block->mir()->successorWithPhis(); + if (successor) { + uint32 position = block->mir()->positionInPhiSuccessor(); + LBlock *lirsuccessor = graph.getBlock(successor->id()); + for (size_t i = 0; i < lirsuccessor->numPhis(); i++) { + LPhi *phi = lirsuccessor->getPhi(i); + + uint32 sourcevreg = phi->getOperand(position)->toUse()->virtualRegister(); + uint32 destvreg = phi->getDef(0)->virtualRegister(); + + if (sourcevreg == destvreg) + continue; + + LAllocation *source = stackLocation(sourcevreg); + LAllocation *dest = stackLocation(destvreg); + + if (!group) { + // The moves we insert here need to happen simultaneously with + // each other, yet after any existing moves before the instruction. + LMoveGroup *input = getInputMoveGroup(ins->id()); + if (input->numMoves() == 0) { + group = input; + } else { + group = new LMoveGroup; + block->insertAfter(input, group); + } + } + + group->add(source, dest); + } + } +} + +void +StupidAllocator::allocateForInstruction(LInstruction *ins) +{ + // Sync all registers before making a call. + if (ins->isCall()) { + for (size_t i = 0; i < registerCount; i++) + syncRegister(ins, i); + } + + // Allocate for inputs which are required to be in registers. + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) { + if (!alloc->isUse()) + continue; + LUse *use = alloc->toUse(); + uint32 vreg = use->virtualRegister(); + if (use->policy() == LUse::REGISTER) { + AnyRegister reg = ensureHasRegister(ins, vreg); + alloc.replace(LAllocation(reg)); + } else if (use->policy() == LUse::FIXED) { + AnyRegister reg = AnyRegister::FromCode(use->registerCode()); + RegisterIndex index = registerIndex(reg); + if (registers[index].vreg != vreg) { + evictRegister(ins, index); + RegisterIndex existing = findExistingRegister(vreg); + if (existing != UINT32_MAX) + evictRegister(ins, existing); + loadRegister(ins, vreg, index); + } + alloc.replace(LAllocation(reg)); + } else { + // Inputs which are not required to be in a register are not + // allocated until after temps/definitions, as the latter may need + // to evict registers which hold these inputs. + } + } + + // Find registers to hold all temporaries and outputs of the instruction. + for (size_t i = 0; i < ins->numTemps(); i++) { + LDefinition *def = ins->getTemp(i); + if (!def->isBogusTemp()) + allocateForDefinition(ins, def); + } + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition *def = ins->getDef(i); + if (def->policy() != LDefinition::PASSTHROUGH) + allocateForDefinition(ins, def); + } + + // Allocate for remaining inputs which do not need to be in registers. + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) { + if (!alloc->isUse()) + continue; + LUse *use = alloc->toUse(); + uint32 vreg = use->virtualRegister(); + JS_ASSERT(use->policy() != LUse::REGISTER && use->policy() != LUse::FIXED); + + RegisterIndex index = findExistingRegister(vreg); + if (index == UINT32_MAX) { + LAllocation *stack = stackLocation(use->virtualRegister()); + alloc.replace(*stack); + } else { + registers[index].age = ins->id(); + alloc.replace(LAllocation(registers[index].reg)); + } + } + + // If this is a call, evict all registers except for those holding outputs. + if (ins->isCall()) { + for (size_t i = 0; i < registerCount; i++) { + if (!registers[i].dirty) + registers[i].set(MISSING_ALLOCATION); + } + } +} + +void +StupidAllocator::allocateForDefinition(LInstruction *ins, LDefinition *def) +{ + uint32 vreg = def->virtualRegister(); + + CodePosition from; + if ((def->output()->isRegister() && def->policy() == LDefinition::PRESET) || + def->policy() == LDefinition::MUST_REUSE_INPUT) + { + // Result will be in a specific register, spill any vreg held in + // that register before the instruction. + RegisterIndex index = + registerIndex(def->policy() == LDefinition::PRESET + ? def->output()->toRegister() + : ins->getOperand(def->getReusedInput())->toRegister()); + evictRegister(ins, index); + registers[index].set(vreg, ins, true); + def->setOutput(LAllocation(registers[index].reg)); + } else if (def->policy() == LDefinition::PRESET) { + // The result must be a stack location. + def->setOutput(*stackLocation(vreg)); + } else { + // Find a register to hold the result of the instruction. + RegisterIndex best = allocateRegister(ins, vreg); + registers[best].set(vreg, ins, true); + def->setOutput(LAllocation(registers[best].reg)); + } +} diff --git a/js/src/ion/StupidAllocator.h b/js/src/ion/StupidAllocator.h new file mode 100644 index 000000000000..d922282fc441 --- /dev/null +++ b/js/src/ion/StupidAllocator.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * 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 js_ion_stupidallocator_h__ +#define js_ion_stupidallocator_h__ + +#include "RegisterAllocator.h" + +// Simple register allocator that only carries registers within basic blocks. + +namespace js { +namespace ion { + +class StupidAllocator : public RegisterAllocator +{ + static const uint32 MAX_REGISTERS = Registers::Allocatable + FloatRegisters::Allocatable; + static const uint32 MISSING_ALLOCATION = UINT32_MAX; + + struct AllocatedRegister { + AnyRegister reg; + + // Virtual register this physical reg backs, or MISSING_ALLOCATION. + uint32 vreg; + + // id of the instruction which most recently used this register. + uint32 age; + + // Whether the physical register is not synced with the backing stack slot. + bool dirty; + + void set(uint32 vreg, LInstruction *ins = NULL, bool dirty = false) { + this->vreg = vreg; + this->age = ins ? ins->id() : 0; + this->dirty = dirty; + } + }; + + // Active allocation for the current code position. + AllocatedRegister registers[MAX_REGISTERS]; + uint32 registerCount; + + // Type indicating an index into registers. + typedef uint32 RegisterIndex; + + // Information about each virtual register. + Vector virtualRegisters; + + public: + StupidAllocator(MIRGenerator *mir, LIRGenerator *lir, LIRGraph &graph) + : RegisterAllocator(mir, lir, graph) + { + } + + bool go(); + + private: + bool init(); + + void syncForBlockEnd(LBlock *block, LInstruction *ins); + void allocateForInstruction(LInstruction *ins); + void allocateForDefinition(LInstruction *ins, LDefinition *def); + + LAllocation *stackLocation(uint32 vreg); + + RegisterIndex registerIndex(AnyRegister reg); + + AnyRegister ensureHasRegister(LInstruction *ins, uint32 vreg); + RegisterIndex allocateRegister(LInstruction *ins, uint32 vreg); + + void syncRegister(LInstruction *ins, RegisterIndex index); + void evictRegister(LInstruction *ins, RegisterIndex index); + void loadRegister(LInstruction *ins, uint32 vreg, RegisterIndex index); + + RegisterIndex findExistingRegister(uint32 vreg); +}; + +} // namespace ion +} // namespace js + +#endif diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index e3adcbf1f298..82f8b4473682 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -4850,7 +4850,9 @@ ProcessArgs(JSContext *cx, JSObject *obj_, OptionParser *op) if (const char *str = op->getStringOption("ion-regalloc")) { if (strcmp(str, "lsra") == 0) - ion::js_IonOptions.lsra = true; + ion::js_IonOptions.registerAllocator = ion::RegisterAllocator_LSRA; + else if (strcmp(str, "stupid") == 0) + ion::js_IonOptions.registerAllocator = ion::RegisterAllocator_Stupid; else return OptionFailure("ion-regalloc", str); } @@ -5079,7 +5081,8 @@ main(int argc, char **argv, char **envp) "Don't compile very large scripts (default: on, off to disable)") || !op.addStringOption('\0', "ion-regalloc", "[mode]", "Specify Ion register allocation:\n" - " lsra: Linear Scan register allocation (default)") + " lsra: Linear Scan register allocation (default)\n" + " stupid: Simple greedy register allocation") || !op.addBoolOption('\0', "ion-eager", "Always ion-compile methods") #ifdef JS_THREADSAFE || !op.addStringOption('\0', "ion-parallel-compile", "on/off", From 9704a03379dda6af2d26ba51128106a2cb478f0b Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Fri, 16 Nov 2012 19:39:44 -0800 Subject: [PATCH 039/160] Bug 813211 - Remove unneeded includes and forward declarations for threading code; r=hsivonen --- parser/htmlparser/src/nsParser.cpp | 1 - parser/htmlparser/src/nsParser.h | 2 -- 2 files changed, 3 deletions(-) diff --git a/parser/htmlparser/src/nsParser.cpp b/parser/htmlparser/src/nsParser.cpp index 8a0dde739de0..17af61912113 100644 --- a/parser/htmlparser/src/nsParser.cpp +++ b/parser/htmlparser/src/nsParser.cpp @@ -33,7 +33,6 @@ #include "nsNetUtil.h" #include "nsScriptLoader.h" #include "nsDataHashtable.h" -#include "nsIThreadPool.h" #include "nsXPCOMCIDInternal.h" #include "nsMimeTypes.h" #include "mozilla/CondVar.h" diff --git a/parser/htmlparser/src/nsParser.h b/parser/htmlparser/src/nsParser.h index 6094e0c3b0ca..901c0add4dd9 100644 --- a/parser/htmlparser/src/nsParser.h +++ b/parser/htmlparser/src/nsParser.h @@ -50,7 +50,6 @@ #include "nsITokenizer.h" #include "nsHTMLTags.h" #include "nsDTDUtils.h" -#include "nsThreadUtils.h" #include "nsIContentSink.h" #include "nsCOMArray.h" #include "nsCycleCollectionParticipant.h" @@ -59,7 +58,6 @@ class nsICharsetConverterManager; class nsIDTD; class nsScanner; -class nsIThreadPool; #ifdef _MSC_VER #pragma warning( disable : 4275 ) From 1039582660518d9a9f789d546d6ec9694cd60540 Mon Sep 17 00:00:00 2001 From: Jeff Gilbert Date: Mon, 26 Nov 2012 13:48:20 -0800 Subject: [PATCH 040/160] Backed out csets 807f2c3df974, 1637c39b4ed6, ec29e09a270e (bug 811958) for burning android --- dom/plugins/base/nsNPAPIPluginInstance.cpp | 9 +- dom/plugins/base/nsPluginInstanceOwner.cpp | 8 +- gfx/gl/GLContext.cpp | 555 +++++++++++++++++- gfx/gl/GLContext.h | 437 +++++++++++++- gfx/gl/GLContextProviderEGL.cpp | 90 ++- gfx/gl/GLContextTypes.h | 44 -- gfx/gl/GLTextureImage.cpp | 573 ------------------- gfx/gl/GLTextureImage.h | 385 ------------- gfx/gl/Makefile.in | 3 - gfx/layers/SharedTextureImage.h | 4 +- gfx/layers/basic/BasicCanvasLayer.cpp | 15 +- gfx/layers/ipc/LayersSurfaces.ipdlh | 4 +- gfx/layers/ipc/ShadowLayerUtils.h | 4 +- gfx/layers/opengl/CanvasLayerOGL.cpp | 5 +- gfx/layers/opengl/ImageLayerOGL.h | 2 +- gfx/layers/opengl/LayerManagerOGL.cpp | 89 +-- gfx/layers/opengl/LayerManagerOGL.h | 98 +++- gfx/layers/opengl/LayerManagerOGLProgram.cpp | 111 ---- gfx/layers/opengl/LayerManagerOGLProgram.h | 96 +++- gfx/layers/opengl/ReusableTileStoreOGL.cpp | 2 - widget/cocoa/nsChildView.mm | 20 +- 21 files changed, 1179 insertions(+), 1375 deletions(-) delete mode 100644 gfx/gl/GLContextTypes.h delete mode 100644 gfx/gl/GLTextureImage.cpp delete mode 100644 gfx/gl/GLTextureImage.h diff --git a/dom/plugins/base/nsNPAPIPluginInstance.cpp b/dom/plugins/base/nsNPAPIPluginInstance.cpp index 6094d5ed9518..cfce39aeb98a 100644 --- a/dom/plugins/base/nsNPAPIPluginInstance.cpp +++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp @@ -129,10 +129,7 @@ public: if (mTextureInfo.mWidth == 0 || mTextureInfo.mHeight == 0) return 0; - SharedTextureHandle handle = - sPluginContext->CreateSharedHandle(GLContext::SameProcess, - (void*)mTextureInfo.mTexture, - GLContext::TextureID); + SharedTextureHandle handle = sPluginContext->CreateSharedHandle(TextureImage::ThreadShared, (void*)mTextureInfo.mTexture, GLContext::TextureID); // We want forget about this now, so delete the texture. Assigning it to zero // ensures that we create a new one in Lock() @@ -1003,9 +1000,7 @@ SharedTextureHandle nsNPAPIPluginInstance::CreateSharedHandle() return mContentTexture->CreateSharedHandle(); } else if (mContentSurface) { EnsureGLContext(); - return sPluginContext->CreateSharedHandle(GLContext::SameProcess, - mContentSurface, - GLContext::SurfaceTexture); + return sPluginContext->CreateSharedHandle(TextureImage::ThreadShared, mContentSurface, GLContext::SurfaceTexture); } else return 0; } diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp index 55014898a03e..bf2d859833ca 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -186,7 +186,7 @@ nsPluginInstanceOwner::GetImageContainer() SharedTextureImage::Data data; data.mHandle = mInstance->CreateSharedHandle(); - data.mShareType = mozilla::gl::GLContext::SameProcess; + data.mShareType = mozilla::gl::TextureImage::ThreadShared; data.mInverted = mInstance->Inverted(); gfxRect r = GetPluginRect(); @@ -1723,10 +1723,8 @@ already_AddRefed nsPluginInstanceOwner::GetImageContainerForVide SharedTextureImage::Data data; - data.mShareType = gl::GLContext::SameProcess; - data.mHandle = mInstance->GLContext()->CreateSharedHandle(data.mShareType, - aVideoInfo->mSurfaceTexture, - gl::GLContext::SurfaceTexture); + data.mHandle = mInstance->GLContext()->CreateSharedHandle(gl::TextureImage::ThreadShared, aVideoInfo->mSurfaceTexture, gl::GLContext::SurfaceTexture); + data.mShareType = mozilla::gl::TextureImage::ThreadShared; // The logic below for Honeycomb is just a guess, but seems to work. We don't have a separate // inverted flag for video. diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 17197535c857..8f183eae5afb 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -23,8 +23,6 @@ #include "mozilla/Preferences.h" #include "mozilla/Util.h" // for DebugOnly -#include "GLTextureImage.h" - using namespace mozilla::gfx; namespace mozilla { @@ -740,19 +738,6 @@ GLContext::CreateTextureImage(const nsIntSize& aSize, return CreateBasicTextureImage(texture, aSize, aWrapMode, aContentType, this, aFlags); } -already_AddRefed -GLContext::CreateBasicTextureImage(GLuint aTexture, - const nsIntSize& aSize, - GLenum aWrapMode, - TextureImage::ContentType aContentType, - GLContext* aContext, - TextureImage::Flags aFlags) -{ - nsRefPtr teximage( - new BasicTextureImage(aTexture, aSize, aWrapMode, aContentType, aContext, aFlags)); - return teximage.forget(); -} - void GLContext::ApplyFilterToBoundTexture(gfxPattern::GraphicsFilter aFilter) { ApplyFilterToBoundTexture(LOCAL_GL_TEXTURE_2D, aFilter); @@ -770,6 +755,546 @@ void GLContext::ApplyFilterToBoundTexture(GLuint aTarget, } } +BasicTextureImage::~BasicTextureImage() +{ + GLContext *ctx = mGLContext; + if (ctx->IsDestroyed() || !ctx->IsOwningThreadCurrent()) { + ctx = ctx->GetSharedContext(); + } + + // If we have a context, then we need to delete the texture; + // if we don't have a context (either real or shared), + // then they went away when the contex was deleted, because it + // was the only one that had access to it. + if (ctx && !ctx->IsDestroyed()) { + mGLContext->MakeCurrent(); + mGLContext->fDeleteTextures(1, &mTexture); + } +} + +gfxASurface* +BasicTextureImage::BeginUpdate(nsIntRegion& aRegion) +{ + NS_ASSERTION(!mUpdateSurface, "BeginUpdate() without EndUpdate()?"); + + // determine the region the client will need to repaint + if (mGLContext->CanUploadSubTextures()) { + GetUpdateRegion(aRegion); + } else { + aRegion = nsIntRect(nsIntPoint(0, 0), mSize); + } + + mUpdateRegion = aRegion; + + nsIntRect rgnSize = mUpdateRegion.GetBounds(); + if (!nsIntRect(nsIntPoint(0, 0), mSize).Contains(rgnSize)) { + NS_ERROR("update outside of image"); + return NULL; + } + + ImageFormat format = + (GetContentType() == gfxASurface::CONTENT_COLOR) ? + gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; + mUpdateSurface = + GetSurfaceForUpdate(gfxIntSize(rgnSize.width, rgnSize.height), format); + + if (!mUpdateSurface || mUpdateSurface->CairoStatus()) { + mUpdateSurface = NULL; + return NULL; + } + + mUpdateSurface->SetDeviceOffset(gfxPoint(-rgnSize.x, -rgnSize.y)); + + return mUpdateSurface; +} + +void +BasicTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) +{ + // if the texture hasn't been initialized yet, or something important + // changed, we need to recreate our backing surface and force the + // client to paint everything + if (mTextureState != Valid) + aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); +} + +void +BasicTextureImage::EndUpdate() +{ + NS_ASSERTION(!!mUpdateSurface, "EndUpdate() without BeginUpdate()?"); + + // FIXME: this is the slow boat. Make me fast (with GLXPixmap?). + + // Undo the device offset that BeginUpdate set; doesn't much matter for us here, + // but important if we ever do anything directly with the surface. + mUpdateSurface->SetDeviceOffset(gfxPoint(0, 0)); + + bool relative = FinishedSurfaceUpdate(); + + mShaderType = + mGLContext->UploadSurfaceToTexture(mUpdateSurface, + mUpdateRegion, + mTexture, + mTextureState == Created, + mUpdateOffset, + relative); + FinishedSurfaceUpload(); + + mUpdateSurface = nullptr; + mTextureState = Valid; +} + +void +BasicTextureImage::BindTexture(GLenum aTextureUnit) +{ + mGLContext->fActiveTexture(aTextureUnit); + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); + mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); +} + +void +BasicTextureImage::ApplyFilter() +{ + mGLContext->ApplyFilterToBoundTexture(mFilter); +} + + +already_AddRefed +BasicTextureImage::GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt) +{ + return gfxPlatform::GetPlatform()-> + CreateOffscreenSurface(aSize, gfxASurface::ContentFromFormat(aFmt)); +} + +bool +BasicTextureImage::FinishedSurfaceUpdate() +{ + return false; +} + +void +BasicTextureImage::FinishedSurfaceUpload() +{ +} + +bool +BasicTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) +{ + nsIntRect bounds = aRegion.GetBounds(); + nsIntRegion region; + if (mTextureState != Valid) { + bounds = nsIntRect(0, 0, mSize.width, mSize.height); + region = nsIntRegion(bounds); + } else { + region = aRegion; + } + + mShaderType = + mGLContext->UploadSurfaceToTexture(aSurf, + region, + mTexture, + mTextureState == Created, + bounds.TopLeft() + aFrom, + false); + mTextureState = Valid; + return true; +} + +void +BasicTextureImage::Resize(const nsIntSize& aSize) +{ + NS_ASSERTION(!mUpdateSurface, "Resize() while in update?"); + + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); + + mGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D, + 0, + LOCAL_GL_RGBA, + aSize.width, + aSize.height, + 0, + LOCAL_GL_RGBA, + LOCAL_GL_UNSIGNED_BYTE, + NULL); + + mTextureState = Allocated; + mSize = aSize; +} + +TiledTextureImage::TiledTextureImage(GLContext* aGL, + nsIntSize aSize, + TextureImage::ContentType aContentType, + TextureImage::Flags aFlags) + : TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aFlags) + , mCurrentImage(0) + , mIterationCallback(nullptr) + , mInUpdate(false) + , mRows(0) + , mColumns(0) + , mGL(aGL) + , mTextureState(Created) +{ + mTileSize = (!(aFlags & TextureImage::ForceSingleTile) && mGL->WantsSmallTiles()) + ? 256 : mGL->GetMaxTextureSize(); + if (aSize != nsIntSize(0,0)) { + Resize(aSize); + } +} + +TiledTextureImage::~TiledTextureImage() +{ +} + +bool +TiledTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) +{ + nsIntRegion region; + + if (mTextureState != Valid) { + nsIntRect bounds = nsIntRect(0, 0, mSize.width, mSize.height); + region = nsIntRegion(bounds); + } else { + region = aRegion; + } + + bool result = true; + int oldCurrentImage = mCurrentImage; + BeginTileIteration(); + do { + nsIntRect tileRect = GetSrcTileRect(); + int xPos = tileRect.x; + int yPos = tileRect.y; + + nsIntRegion tileRegion; + tileRegion.And(region, tileRect); // intersect with tile + + if (tileRegion.IsEmpty()) + continue; + + if (mGL->CanUploadSubTextures()) { + tileRegion.MoveBy(-xPos, -yPos); // translate into tile local space + } else { + // If sub-textures are unsupported, expand to tile boundaries + tileRect.x = tileRect.y = 0; + tileRegion = nsIntRegion(tileRect); + } + + result &= mImages[mCurrentImage]-> + DirectUpdate(aSurf, tileRegion, aFrom + nsIntPoint(xPos, yPos)); + + if (mCurrentImage == mImages.Length() - 1) { + // We know we're done, but we still need to ensure that the callback + // gets called (e.g. to update the uploaded region). + NextTile(); + break; + } + // Override a callback cancelling iteration if the texture wasn't valid. + // We need to force the update in that situation, or we may end up + // showing invalid/out-of-date texture data. + } while (NextTile() || (mTextureState != Valid)); + mCurrentImage = oldCurrentImage; + + mShaderType = mImages[0]->GetShaderProgramType(); + mTextureState = Valid; + return result; +} + +void +TiledTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) +{ + if (mTextureState != Valid) { + // if the texture hasn't been initialized yet, or something important + // changed, we need to recreate our backing surface and force the + // client to paint everything + aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); + return; + } + + nsIntRegion newRegion; + + // We need to query each texture with the region it will be drawing and + // set aForRegion to be the combination of all of these regions + for (unsigned i = 0; i < mImages.Length(); i++) { + int xPos = (i % mColumns) * mTileSize; + int yPos = (i / mColumns) * mTileSize; + nsIntRect imageRect = nsIntRect(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); + + if (aForRegion.Intersects(imageRect)) { + // Make a copy of the region + nsIntRegion subRegion; + subRegion.And(aForRegion, imageRect); + // Translate it into tile-space + subRegion.MoveBy(-xPos, -yPos); + // Query region + mImages[i]->GetUpdateRegion(subRegion); + // Translate back + subRegion.MoveBy(xPos, yPos); + // Add to the accumulated region + newRegion.Or(newRegion, subRegion); + } + } + + aForRegion = newRegion; +} + +gfxASurface* +TiledTextureImage::BeginUpdate(nsIntRegion& aRegion) +{ + NS_ASSERTION(!mInUpdate, "nested update"); + mInUpdate = true; + + // Note, we don't call GetUpdateRegion here as if the updated region is + // fully contained in a single tile, we get to avoid iterating through + // the tiles again (and a little copying). + if (mTextureState != Valid) + { + // if the texture hasn't been initialized yet, or something important + // changed, we need to recreate our backing surface and force the + // client to paint everything + aRegion = nsIntRect(nsIntPoint(0, 0), mSize); + } + + nsIntRect bounds = aRegion.GetBounds(); + + for (unsigned i = 0; i < mImages.Length(); i++) { + int xPos = (i % mColumns) * mTileSize; + int yPos = (i / mColumns) * mTileSize; + nsIntRegion imageRegion = nsIntRegion(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); + + // a single Image can handle this update request + if (imageRegion.Contains(aRegion)) { + // adjust for tile offset + aRegion.MoveBy(-xPos, -yPos); + // forward the actual call + nsRefPtr surface = mImages[i]->BeginUpdate(aRegion); + // caller expects container space + aRegion.MoveBy(xPos, yPos); + // Correct the device offset + gfxPoint offset = surface->GetDeviceOffset(); + surface->SetDeviceOffset(gfxPoint(offset.x - xPos, + offset.y - yPos)); + // we don't have a temp surface + mUpdateSurface = nullptr; + // remember which image to EndUpdate + mCurrentImage = i; + return surface.get(); + } + } + + // Get the real updated region, taking into account the capabilities of + // each TextureImage tile + GetUpdateRegion(aRegion); + mUpdateRegion = aRegion; + bounds = aRegion.GetBounds(); + + // update covers multiple Images - create a temp surface to paint in + gfxASurface::gfxImageFormat format = + (GetContentType() == gfxASurface::CONTENT_COLOR) ? + gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; + mUpdateSurface = gfxPlatform::GetPlatform()-> + CreateOffscreenSurface(gfxIntSize(bounds.width, bounds.height), gfxASurface::ContentFromFormat(format)); + mUpdateSurface->SetDeviceOffset(gfxPoint(-bounds.x, -bounds.y)); + + return mUpdateSurface; +} + +void +TiledTextureImage::EndUpdate() +{ + NS_ASSERTION(mInUpdate, "EndUpdate not in update"); + if (!mUpdateSurface) { // update was to a single TextureImage + mImages[mCurrentImage]->EndUpdate(); + mInUpdate = false; + mTextureState = Valid; + mShaderType = mImages[mCurrentImage]->GetShaderProgramType(); + return; + } + + // upload tiles from temp surface + for (unsigned i = 0; i < mImages.Length(); i++) { + int xPos = (i % mColumns) * mTileSize; + int yPos = (i / mColumns) * mTileSize; + nsIntRect imageRect = nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize()); + + nsIntRegion subregion; + subregion.And(mUpdateRegion, imageRect); + if (subregion.IsEmpty()) + continue; + subregion.MoveBy(-xPos, -yPos); // Tile-local space + // copy tile from temp surface + gfxASurface* surf = mImages[i]->BeginUpdate(subregion); + nsRefPtr ctx = new gfxContext(surf); + gfxUtils::ClipToRegion(ctx, subregion); + ctx->SetOperator(gfxContext::OPERATOR_SOURCE); + ctx->SetSource(mUpdateSurface, gfxPoint(-xPos, -yPos)); + ctx->Paint(); + mImages[i]->EndUpdate(); + } + + mUpdateSurface = nullptr; + mInUpdate = false; + mShaderType = mImages[0]->GetShaderProgramType(); + mTextureState = Valid; +} + +void TiledTextureImage::BeginTileIteration() +{ + mCurrentImage = 0; +} + +bool TiledTextureImage::NextTile() +{ + bool continueIteration = true; + + if (mIterationCallback) + continueIteration = mIterationCallback(this, mCurrentImage, + mIterationCallbackData); + + if (mCurrentImage + 1 < mImages.Length()) { + mCurrentImage++; + return continueIteration; + } + return false; +} + +void TiledTextureImage::SetIterationCallback(TileIterationCallback aCallback, + void* aCallbackData) +{ + mIterationCallback = aCallback; + mIterationCallbackData = aCallbackData; +} + +nsIntRect TiledTextureImage::GetTileRect() +{ + nsIntRect rect = mImages[mCurrentImage]->GetTileRect(); + unsigned int xPos = (mCurrentImage % mColumns) * mTileSize; + unsigned int yPos = (mCurrentImage / mColumns) * mTileSize; + rect.MoveBy(xPos, yPos); + return rect; +} + +nsIntRect TiledTextureImage::GetSrcTileRect() +{ + nsIntRect rect = GetTileRect(); + unsigned int srcY = mFlags & NeedsYFlip + ? mSize.height - rect.height - rect.y + : rect.y; + return nsIntRect(rect.x, srcY, rect.width, rect.height); +} + +void +TiledTextureImage::BindTexture(GLenum aTextureUnit) +{ + mImages[mCurrentImage]->BindTexture(aTextureUnit); +} + +void +TiledTextureImage::ApplyFilter() +{ + mGL->ApplyFilterToBoundTexture(mFilter); +} + +/* + * Resize, trying to reuse tiles. The reuse strategy is to decide on reuse per + * column. A tile on a column is reused if it hasn't changed size, otherwise it + * is discarded/replaced. Extra tiles on a column are pruned after iterating + * each column, and extra rows are pruned after iteration over the entire image + * finishes. + */ +void TiledTextureImage::Resize(const nsIntSize& aSize) +{ + if (mSize == aSize && mTextureState != Created) { + return; + } + + // calculate rows and columns, rounding up + unsigned int columns = (aSize.width + mTileSize - 1) / mTileSize; + unsigned int rows = (aSize.height + mTileSize - 1) / mTileSize; + + // Iterate over old tile-store and insert/remove tiles as necessary + int row; + unsigned int i = 0; + for (row = 0; row < (int)rows; row++) { + // If we've gone beyond how many rows there were before, set mColumns to + // zero so that we only create new tiles. + if (row >= (int)mRows) + mColumns = 0; + + // Similarly, if we're on the last row of old tiles and the height has + // changed, discard all tiles in that row. + // This will cause the pruning of columns not to work, but we don't need + // to worry about that, as no more tiles will be reused past this point + // anyway. + if ((row == (int)mRows - 1) && (aSize.height != mSize.height)) + mColumns = 0; + + int col; + for (col = 0; col < (int)columns; col++) { + nsIntSize size( // use tilesize first, then the remainder + (col+1) * mTileSize > (unsigned int)aSize.width ? aSize.width % mTileSize : mTileSize, + (row+1) * mTileSize > (unsigned int)aSize.height ? aSize.height % mTileSize : mTileSize); + + bool replace = false; + + // Check if we can re-use old tiles. + if (col < (int)mColumns) { + // Reuse an existing tile. If the tile is an end-tile and the + // width differs, replace it instead. + if (mSize.width != aSize.width) { + if (col == (int)mColumns - 1) { + // Tile at the end of the old column, replace it with + // a new one. + replace = true; + } else if (col == (int)columns - 1) { + // Tile at the end of the new column, create a new one. + } else { + // Before the last column on both the old and new sizes, + // reuse existing tile. + i++; + continue; + } + } else { + // Width hasn't changed, reuse existing tile. + i++; + continue; + } + } + + // Create a new tile. + nsRefPtr teximg = + mGL->TileGenFunc(size, mContentType, mFlags); + if (replace) + mImages.ReplaceElementAt(i, teximg.forget()); + else + mImages.InsertElementAt(i, teximg.forget()); + i++; + } + + // Prune any unused tiles on the end of the column. + if (row < (int)mRows) { + for (col = (int)mColumns - col; col > 0; col--) { + mImages.RemoveElementAt(i); + } + } + } + + // Prune any unused tiles at the end of the store. + unsigned int length = mImages.Length(); + for (; i < length; i++) + mImages.RemoveElementAt(mImages.Length()-1); + + // Reset tile-store properties. + mRows = rows; + mColumns = columns; + mSize = aSize; + mTextureState = Allocated; + mCurrentImage = 0; +} + +uint32_t TiledTextureImage::GetTileCount() +{ + return mImages.Length(); +} GLContext::GLFormats GLContext::ChooseGLFormats(ContextFormat& aCF, ColorByteOrder aByteOrder) diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index d8a327b26d41..b109b782578c 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -32,8 +32,6 @@ #include "nsRegion.h" #include "nsAutoPtr.h" #include "nsThreadUtils.h" -#include "GLContextTypes.h" -#include "GLTextureImage.h" typedef char realGLboolean; @@ -57,6 +55,391 @@ class GLContext; typedef uintptr_t SharedTextureHandle; +enum ShaderProgramType { + RGBALayerProgramType, + RGBALayerExternalProgramType, + BGRALayerProgramType, + RGBXLayerProgramType, + BGRXLayerProgramType, + RGBARectLayerProgramType, + RGBAExternalLayerProgramType, + ColorLayerProgramType, + YCbCrLayerProgramType, + ComponentAlphaPass1ProgramType, + ComponentAlphaPass2ProgramType, + Copy2DProgramType, + Copy2DRectProgramType, + NumProgramTypes +}; + + +/** + * A TextureImage encapsulates a surface that can be drawn to by a + * Thebes gfxContext and (hopefully efficiently!) synchronized to a + * texture in the server. TextureImages are associated with one and + * only one GLContext. + * + * Implementation note: TextureImages attempt to unify two categories + * of backends + * + * (1) proxy to server-side object that can be bound to a texture; + * e.g. Pixmap on X11. + * + * (2) efficient manager of texture memory; e.g. by having clients draw + * into a scratch buffer which is then uploaded with + * glTexSubImage2D(). + */ +class TextureImage +{ + NS_INLINE_DECL_REFCOUNTING(TextureImage) +public: + enum TextureState + { + Created, // Texture created, but has not had glTexImage called to initialize it. + Allocated, // Texture memory exists, but contents are invalid. + Valid // Texture fully ready to use. + }; + + enum Flags { + NoFlags = 0x0, + UseNearestFilter = 0x1, + NeedsYFlip = 0x2, + ForceSingleTile = 0x4 + }; + + enum TextureShareType { + ThreadShared = 0x0, + ProcessShared = 0x1 + }; + + typedef gfxASurface::gfxContentType ContentType; + + virtual ~TextureImage() {} + + /** + * Returns a gfxASurface for updating |aRegion| of the client's + * image if successul, NULL if not. |aRegion|'s bounds must fit + * within Size(); its coordinate space (if any) is ignored. If + * the update begins successfully, the returned gfxASurface is + * owned by this. Otherwise, NULL is returned. + * + * |aRegion| is an inout param: the returned region is what the + * client must repaint. Category (1) regions above can + * efficiently handle repaints to "scattered" regions, while (2) + * can only efficiently handle repaints to rects. + * + * Painting the returned surface outside of |aRegion| results + * in undefined behavior. + * + * BeginUpdate() calls cannot be "nested", and each successful + * BeginUpdate() must be followed by exactly one EndUpdate() (see + * below). Failure to do so can leave this in a possibly + * inconsistent state. Unsuccessful BeginUpdate()s must not be + * followed by EndUpdate(). + */ + virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion) = 0; + /** + * Retrieves the region that will require updating, given a + * region that needs to be updated. This can be used for + * making decisions about updating before calling BeginUpdate(). + * + * |aRegion| is an inout param. + */ + virtual void GetUpdateRegion(nsIntRegion& aForRegion) { + } + /** + * Finish the active update and synchronize with the server, if + * necessary. + * + * BeginUpdate() must have been called exactly once before + * EndUpdate(). + */ + virtual void EndUpdate() = 0; + + /** + * The Image may contain several textures for different regions (tiles). + * These functions iterate over each sub texture image tile. + */ + virtual void BeginTileIteration() { + } + + virtual bool NextTile() { + return false; + } + + // Function prototype for a tile iteration callback. Returning false will + // cause iteration to be interrupted (i.e. the corresponding NextTile call + // will return false). + typedef bool (* TileIterationCallback)(TextureImage* aImage, + int aTileNumber, + void* aCallbackData); + + // Sets a callback to be called every time NextTile is called. + virtual void SetIterationCallback(TileIterationCallback aCallback, + void* aCallbackData) { + } + + virtual nsIntRect GetTileRect() { + return nsIntRect(nsIntPoint(0,0), mSize); + } + + virtual GLuint GetTextureID() = 0; + + virtual uint32_t GetTileCount() { + return 1; + } + + /** + * Set this TextureImage's size, and ensure a texture has been + * allocated. Must not be called between BeginUpdate and EndUpdate. + * After a resize, the contents are undefined. + * + * If this isn't implemented by a subclass, it will just perform + * a dummy BeginUpdate/EndUpdate pair. + */ + virtual void Resize(const nsIntSize& aSize) { + mSize = aSize; + nsIntRegion r(nsIntRect(0, 0, aSize.width, aSize.height)); + BeginUpdate(r); + EndUpdate(); + } + + /** + * Mark this texture as having valid contents. Call this after modifying + * the texture contents externally. + */ + virtual void MarkValid() {} + + /** + * aSurf - the source surface to update from + * aRegion - the region in this image to update + * aFrom - offset in the source to update from + */ + virtual bool DirectUpdate(gfxASurface *aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)) = 0; + + virtual void BindTexture(GLenum aTextureUnit) = 0; + virtual void ReleaseTexture() {} + + void BindTextureAndApplyFilter(GLenum aTextureUnit) { + BindTexture(aTextureUnit); + ApplyFilter(); + } + + class ScopedBindTexture + { + public: + ScopedBindTexture(TextureImage *aTexture, GLenum aTextureUnit) : + mTexture(aTexture) + { + if (mTexture) { + mTexture->BindTexture(aTextureUnit); + } + } + + ~ScopedBindTexture() + { + if (mTexture) { + mTexture->ReleaseTexture(); + } + } + + protected: + TextureImage *mTexture; + }; + + class ScopedBindTextureAndApplyFilter + : public ScopedBindTexture + { + public: + ScopedBindTextureAndApplyFilter(TextureImage *aTexture, GLenum aTextureUnit) : + ScopedBindTexture(aTexture, aTextureUnit) + { + if (mTexture) { + mTexture->ApplyFilter(); + } + } + }; + + /** + * Returns the shader program type that should be used to render + * this texture. Only valid after a matching BeginUpdate/EndUpdate + * pair have been called. + */ + virtual ShaderProgramType GetShaderProgramType() + { + return mShaderType; + } + + /** Can be called safely at any time. */ + + /** + * If this TextureImage has a permanent gfxASurface backing, + * return it. Otherwise return NULL. + */ + virtual already_AddRefed GetBackingSurface() + { return NULL; } + + const nsIntSize& GetSize() const { return mSize; } + ContentType GetContentType() const { return mContentType; } + virtual bool InUpdate() const = 0; + GLenum GetWrapMode() const { return mWrapMode; } + + void SetFilter(gfxPattern::GraphicsFilter aFilter) { mFilter = aFilter; } + + /** + * Applies this TextureImage's filter, assuming that its texture is + * the currently bound texture. + */ + virtual void ApplyFilter() = 0; + +protected: + friend class GLContext; + + /** + * After the ctor, the TextureImage is invalid. Implementations + * must allocate resources successfully before returning the new + * TextureImage from GLContext::CreateTextureImage(). That is, + * clients must not be given partially-constructed TextureImages. + */ + TextureImage(const nsIntSize& aSize, + GLenum aWrapMode, ContentType aContentType, + Flags aFlags = NoFlags) + : mSize(aSize) + , mWrapMode(aWrapMode) + , mContentType(aContentType) + , mFilter(gfxPattern::FILTER_GOOD) + , mFlags(aFlags) + {} + + virtual nsIntRect GetSrcTileRect() { + return nsIntRect(nsIntPoint(0,0), mSize); + } + + nsIntSize mSize; + GLenum mWrapMode; + ContentType mContentType; + ShaderProgramType mShaderType; + gfxPattern::GraphicsFilter mFilter; + Flags mFlags; +}; + +/** + * BasicTextureImage is the baseline TextureImage implementation --- + * it updates its texture by allocating a scratch buffer for the + * client to draw into, then using glTexSubImage2D() to upload the new + * pixels. Platforms must provide the code to create a new surface + * into which the updated pixels will be drawn, and the code to + * convert the update surface's pixels into an image on which we can + * glTexSubImage2D(). + */ +class BasicTextureImage + : public TextureImage +{ +public: + typedef gfxASurface::gfxImageFormat ImageFormat; + virtual ~BasicTextureImage(); + + BasicTextureImage(GLuint aTexture, + const nsIntSize& aSize, + GLenum aWrapMode, + ContentType aContentType, + GLContext* aContext, + TextureImage::Flags aFlags = TextureImage::NoFlags) + : TextureImage(aSize, aWrapMode, aContentType, aFlags) + , mTexture(aTexture) + , mTextureState(Created) + , mGLContext(aContext) + , mUpdateOffset(0, 0) + {} + + virtual void BindTexture(GLenum aTextureUnit); + + virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); + virtual void GetUpdateRegion(nsIntRegion& aForRegion); + virtual void EndUpdate(); + virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); + virtual GLuint GetTextureID() { return mTexture; } + // Returns a surface to draw into + virtual already_AddRefed + GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt); + + virtual void MarkValid() { mTextureState = Valid; } + + // Call when drawing into the update surface is complete. + // Returns true if textures should be upload with a relative + // offset - See UploadSurfaceToTexture. + virtual bool FinishedSurfaceUpdate(); + + // Call after surface data has been uploaded to a texture. + virtual void FinishedSurfaceUpload(); + + virtual bool InUpdate() const { return !!mUpdateSurface; } + + virtual void Resize(const nsIntSize& aSize); + + virtual void ApplyFilter(); +protected: + + GLuint mTexture; + TextureState mTextureState; + GLContext* mGLContext; + nsRefPtr mUpdateSurface; + nsIntRegion mUpdateRegion; + + // The offset into the update surface at which the update rect is located. + nsIntPoint mUpdateOffset; +}; + +/** + * A container class that complements many sub TextureImages into a big TextureImage. + * Aims to behave just like the real thing. + */ + +class TiledTextureImage + : public TextureImage +{ +public: + TiledTextureImage(GLContext* aGL, nsIntSize aSize, + TextureImage::ContentType, TextureImage::Flags aFlags = TextureImage::NoFlags); + ~TiledTextureImage(); + void DumpDiv(); + virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); + virtual void GetUpdateRegion(nsIntRegion& aForRegion); + virtual void EndUpdate(); + virtual void Resize(const nsIntSize& aSize); + virtual uint32_t GetTileCount(); + virtual void BeginTileIteration(); + virtual bool NextTile(); + virtual void SetIterationCallback(TileIterationCallback aCallback, + void* aCallbackData); + virtual nsIntRect GetTileRect(); + virtual GLuint GetTextureID() { + return mImages[mCurrentImage]->GetTextureID(); + } + virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); + virtual bool InUpdate() const { return mInUpdate; } + virtual void BindTexture(GLenum); + virtual void ApplyFilter(); + +protected: + virtual nsIntRect GetSrcTileRect(); + + unsigned int mCurrentImage; + TileIterationCallback mIterationCallback; + void* mIterationCallbackData; + nsTArray< nsRefPtr > mImages; + bool mInUpdate; + nsIntSize mSize; + unsigned int mTileSize; + unsigned int mRows, mColumns; + GLContext* mGL; + // A temporary surface to faciliate cross-tile updates. + nsRefPtr mUpdateSurface; + // The region of update requested + nsIntRegion mUpdateRegion; + TextureState mTextureState; +}; + struct THEBES_API ContextFormat { static const ContextFormat BasicRGBA32Format; @@ -546,12 +929,6 @@ public: return IsExtensionSupported(EXT_framebuffer_blit) || IsExtensionSupported(ANGLE_framebuffer_blit); } - - enum SharedTextureShareType { - SameProcess = 0, - CrossProcess - }; - enum SharedTextureBufferType { TextureID #ifdef MOZ_WIDGET_ANDROID @@ -562,26 +939,23 @@ public: /** * Create new shared GLContext content handle, must be released by ReleaseSharedHandle. */ - virtual SharedTextureHandle CreateSharedHandle(SharedTextureShareType shareType) - { return 0; } + virtual SharedTextureHandle CreateSharedHandle(TextureImage::TextureShareType aType) { return 0; } /* * Create a new shared GLContext content handle, using the passed buffer as a source. * Must be released by ReleaseSharedHandle. UpdateSharedHandle will have no effect * on handles created with this method, as the caller owns the source (the passed buffer) * and is responsible for updating it accordingly. */ - virtual SharedTextureHandle CreateSharedHandle(SharedTextureShareType shareType, - void* buffer, - SharedTextureBufferType bufferType) - { return 0; } + virtual SharedTextureHandle CreateSharedHandle(TextureImage::TextureShareType aType, + void* aBuffer, + SharedTextureBufferType aBufferType) { return 0; } /** * Publish GLContext content to intermediate buffer attached to shared handle. * Shared handle content is ready to be used after call returns, and no need extra Flush/Finish are required. * GLContext must be current before this call */ - virtual void UpdateSharedHandle(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle) - { } + virtual void UpdateSharedHandle(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle) { } /** * - It is better to call ReleaseSharedHandle before original GLContext destroyed, * otherwise warning will be thrown on attempt to destroy Texture associated with SharedHandle, depends on backend implementation. @@ -595,9 +969,8 @@ public: * SharedHandle (currently EGLImage) does not require GLContext because it is EGL call, and can be destroyed * at any time, unless EGLImage have siblings (which are not expected with current API). */ - virtual void ReleaseSharedHandle(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle) - { } + virtual void ReleaseSharedHandle(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle) { } typedef struct { @@ -610,24 +983,21 @@ public: * Returns information necessary for rendering a shared handle. * These values change depending on what sharing mechanism is in use */ - virtual bool GetSharedHandleDetails(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle, - SharedHandleDetails& details) - { return false; } + virtual bool GetSharedHandleDetails(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle, + SharedHandleDetails& aDetails) { return false; } /** * Attach Shared GL Handle to GL_TEXTURE_2D target * GLContext must be current before this call */ - virtual bool AttachSharedHandle(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle) - { return false; } + virtual bool AttachSharedHandle(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle) { return false; } /** * Detach Shared GL Handle from GL_TEXTURE_2D target */ - virtual void DetachSharedHandle(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle) - { } + virtual void DetachSharedHandle(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle) { return; } private: GLuint mUserBoundDrawFBO; @@ -1566,7 +1936,12 @@ protected: GLenum aWrapMode, TextureImage::ContentType aContentType, GLContext* aContext, - TextureImage::Flags aFlags = TextureImage::NoFlags); + TextureImage::Flags aFlags = TextureImage::NoFlags) + { + nsRefPtr teximage( + new BasicTextureImage(aTexture, aSize, aWrapMode, aContentType, aContext, aFlags)); + return teximage.forget(); + } bool IsOffscreenSizeAllowed(const gfxIntSize& aSize) const { int32_t biggerDimension = NS_MAX(aSize.width, aSize.height); diff --git a/gfx/gl/GLContextProviderEGL.cpp b/gfx/gl/GLContextProviderEGL.cpp index 0f7b7cbc1efd..e080cb10159b 100644 --- a/gfx/gl/GLContextProviderEGL.cpp +++ b/gfx/gl/GLContextProviderEGL.cpp @@ -646,19 +646,19 @@ public: return sEGLLibrary.HasKHRLockSurface(); } - virtual SharedTextureHandle CreateSharedHandle(SharedTextureShareType shareType); - virtual SharedTextureHandle CreateSharedHandle(SharedTextureShareType shareType, - void* buffer, - SharedTextureBufferType bufferType); - virtual void UpdateSharedHandle(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle); - virtual void ReleaseSharedHandle(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle); - virtual bool GetSharedHandleDetails(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle, - SharedHandleDetails& details); - virtual bool AttachSharedHandle(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle); + virtual SharedTextureHandle CreateSharedHandle(TextureImage::TextureShareType aType); + virtual SharedTextureHandle CreateSharedHandle(TextureImage::TextureShareType aType, + void* aBuffer, + SharedTextureBufferType aBufferType); + virtual void UpdateSharedHandle(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle); + virtual void ReleaseSharedHandle(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle); + virtual bool GetSharedHandleDetails(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle, + SharedHandleDetails& aDetails); + virtual bool AttachSharedHandle(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle); protected: friend class GLContextProviderEGL; @@ -854,15 +854,15 @@ private: }; void -GLContextEGL::UpdateSharedHandle(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle) +GLContextEGL::UpdateSharedHandle(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle) { - if (shareType != SameProcess) { + if (aType != TextureImage::ThreadShared) { NS_ERROR("Implementation not available for this sharing type"); return; } - SharedTextureHandleWrapper* wrapper = reinterpret_cast(sharedHandle); + SharedTextureHandleWrapper* wrapper = reinterpret_cast(aSharedHandle); NS_ASSERTION(wrapper->Type() == SharedHandleType::Image, "Expected EGLImage shared handle"); NS_ASSERTION(mShareWithEGLImage, "EGLImage not supported or disabled in runtime"); @@ -895,9 +895,9 @@ GLContextEGL::UpdateSharedHandle(SharedTextureShareType shareType, } SharedTextureHandle -GLContextEGL::CreateSharedHandle(SharedTextureShareType shareType) +GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType) { - if (shareType != SameProcess) + if (aType != TextureImage::ThreadShared) return 0; if (!mShareWithEGLImage) @@ -914,7 +914,7 @@ GLContextEGL::CreateSharedHandle(SharedTextureShareType shareType) if (!ok) { NS_ERROR("EGLImage creation for EGLTextureWrapper failed"); - ReleaseSharedHandle(shareType, (SharedTextureHandle)tex); + ReleaseSharedHandle(aType, (SharedTextureHandle)tex); return 0; } @@ -923,16 +923,16 @@ GLContextEGL::CreateSharedHandle(SharedTextureShareType shareType) } SharedTextureHandle -GLContextEGL::CreateSharedHandle(SharedTextureShareType shareType, - void* buffer, - SharedTextureBufferType bufferType) +GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType, + void* aBuffer, + SharedTextureBufferType aBufferType) { // Both EGLImage and SurfaceTexture only support ThreadShared currently, but // it's possible to make SurfaceTexture work across processes. We should do that. - if (shareType != SameProcess) + if (aType != TextureImage::ThreadShared) return 0; - switch (bufferType) { + switch (aBufferType) { #ifdef MOZ_WIDGET_ANDROID case SharedTextureBufferType::SurfaceTexture: if (!IsExtensionSupported(GLContext::OES_EGL_image_external)) { @@ -940,13 +940,13 @@ GLContextEGL::CreateSharedHandle(SharedTextureShareType shareType, return 0; } - return (SharedTextureHandle) new SurfaceTextureWrapper(reinterpret_cast(buffer)); + return (SharedTextureHandle) new SurfaceTextureWrapper(reinterpret_cast(aBuffer)); #endif case SharedTextureBufferType::TextureID: { if (!mShareWithEGLImage) return 0; - GLuint texture = (uintptr_t)buffer; + GLuint texture = (uintptr_t)aBuffer; EGLTextureWrapper* tex = new EGLTextureWrapper(); if (!tex->CreateEGLImage(this, texture)) { NS_ERROR("EGLImage creation for EGLTextureWrapper failed"); @@ -962,15 +962,15 @@ GLContextEGL::CreateSharedHandle(SharedTextureShareType shareType, } } -void GLContextEGL::ReleaseSharedHandle(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle) +void GLContextEGL::ReleaseSharedHandle(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle) { - if (shareType != SameProcess) { + if (aType != TextureImage::ThreadShared) { NS_ERROR("Implementation not available for this sharing type"); return; } - SharedTextureHandleWrapper* wrapper = reinterpret_cast(sharedHandle); + SharedTextureHandleWrapper* wrapper = reinterpret_cast(aSharedHandle); switch (wrapper->Type()) { #ifdef MOZ_WIDGET_ANDROID @@ -982,25 +982,24 @@ void GLContextEGL::ReleaseSharedHandle(SharedTextureShareType shareType, case SharedHandleType::Image: { NS_ASSERTION(mShareWithEGLImage, "EGLImage not supported or disabled in runtime"); - EGLTextureWrapper* wrap = (EGLTextureWrapper*)sharedHandle; + EGLTextureWrapper* wrap = (EGLTextureWrapper*)aSharedHandle; delete wrap; break; } default: NS_ERROR("Unknown shared handle type"); - return; } } -bool GLContextEGL::GetSharedHandleDetails(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle, - SharedHandleDetails& details) +bool GLContextEGL::GetSharedHandleDetails(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle, + SharedHandleDetails& aDetails) { - if (shareType != SameProcess) + if (aType != TextureImage::ThreadShared) return false; - SharedTextureHandleWrapper* wrapper = reinterpret_cast(sharedHandle); + SharedTextureHandleWrapper* wrapper = reinterpret_cast(aSharedHandle); switch (wrapper->Type()) { #ifdef MOZ_WIDGET_ANDROID @@ -1015,8 +1014,8 @@ bool GLContextEGL::GetSharedHandleDetails(SharedTextureShareType shareType, #endif case SharedHandleType::Image: - details.mTarget = LOCAL_GL_TEXTURE_2D; - details.mProgramType = RGBALayerProgramType; + aDetails.mTarget = LOCAL_GL_TEXTURE_2D; + aDetails.mProgramType = RGBALayerProgramType; break; default: @@ -1027,13 +1026,13 @@ bool GLContextEGL::GetSharedHandleDetails(SharedTextureShareType shareType, return true; } -bool GLContextEGL::AttachSharedHandle(SharedTextureShareType shareType, - SharedTextureHandle sharedHandle) +bool GLContextEGL::AttachSharedHandle(TextureImage::TextureShareType aType, + SharedTextureHandle aSharedHandle) { - if (shareType != SameProcess) + if (aType != TextureImage::ThreadShared) return false; - SharedTextureHandleWrapper* wrapper = reinterpret_cast(sharedHandle); + SharedTextureHandleWrapper* wrapper = reinterpret_cast(aSharedHandle); switch (wrapper->Type()) { #ifdef MOZ_WIDGET_ANDROID @@ -1058,7 +1057,7 @@ bool GLContextEGL::AttachSharedHandle(SharedTextureShareType shareType, case SharedHandleType::Image: { NS_ASSERTION(mShareWithEGLImage, "EGLImage not supported or disabled in runtime"); - EGLTextureWrapper* wrap = (EGLTextureWrapper*)sharedHandle; + EGLTextureWrapper* wrap = (EGLTextureWrapper*)aSharedHandle; wrap->WaitSync(); fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_2D, wrap->GetEGLImage()); break; @@ -1072,7 +1071,6 @@ bool GLContextEGL::AttachSharedHandle(SharedTextureShareType shareType, return true; } - bool GLContextEGL::BindTex2DOffscreen(GLContext *aOffscreen) { diff --git a/gfx/gl/GLContextTypes.h b/gfx/gl/GLContextTypes.h deleted file mode 100644 index e369b07a6361..000000000000 --- a/gfx/gl/GLContextTypes.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * GLContextStuff.h - * - * Created on: Nov 13, 2012 - * Author: jgilbert - */ - -#ifndef GLCONTEXTSTUFF_H_ -#define GLCONTEXTSTUFF_H_ - -/** - * We don't include GLDefs.h here since we don't want to drag in all defines - * in for all our users. - */ -typedef unsigned int GLenum; -typedef unsigned int GLbitfield; -typedef unsigned int GLuint; -typedef int GLint; -typedef int GLsizei; - -namespace mozilla { -namespace gl { - -enum ShaderProgramType { - RGBALayerProgramType, - RGBALayerExternalProgramType, - BGRALayerProgramType, - RGBXLayerProgramType, - BGRXLayerProgramType, - RGBARectLayerProgramType, - RGBAExternalLayerProgramType, - ColorLayerProgramType, - YCbCrLayerProgramType, - ComponentAlphaPass1ProgramType, - ComponentAlphaPass2ProgramType, - Copy2DProgramType, - Copy2DRectProgramType, - NumProgramTypes -}; - -} // namespace gl -} // namespace mozilla - -#endif /* GLCONTEXTSTUFF_H_ */ diff --git a/gfx/gl/GLTextureImage.cpp b/gfx/gl/GLTextureImage.cpp deleted file mode 100644 index fbfef04fa4e6..000000000000 --- a/gfx/gl/GLTextureImage.cpp +++ /dev/null @@ -1,573 +0,0 @@ -/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */ -/* 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 "GLTextureImage.h" -#include "GLContext.h" -#include "gfxContext.h" -#include "gfxPlatform.h" -#include "gfxUtils.h" - -using namespace mozilla::gl; - -already_AddRefed -TextureImage::Create(GLContext* gl, - const nsIntSize& size, - TextureImage::ContentType contentType, - GLenum wrapMode, - TextureImage::Flags flags) -{ - return gl->CreateTextureImage(size, contentType, wrapMode, flags); -} - -BasicTextureImage::~BasicTextureImage() -{ - GLContext *ctx = mGLContext; - if (ctx->IsDestroyed() || !ctx->IsOwningThreadCurrent()) { - ctx = ctx->GetSharedContext(); - } - - // If we have a context, then we need to delete the texture; - // if we don't have a context (either real or shared), - // then they went away when the contex was deleted, because it - // was the only one that had access to it. - if (ctx && !ctx->IsDestroyed()) { - mGLContext->MakeCurrent(); - mGLContext->fDeleteTextures(1, &mTexture); - } -} - -gfxASurface* -BasicTextureImage::BeginUpdate(nsIntRegion& aRegion) -{ - NS_ASSERTION(!mUpdateSurface, "BeginUpdate() without EndUpdate()?"); - - // determine the region the client will need to repaint - if (mGLContext->CanUploadSubTextures()) { - GetUpdateRegion(aRegion); - } else { - aRegion = nsIntRect(nsIntPoint(0, 0), mSize); - } - - mUpdateRegion = aRegion; - - nsIntRect rgnSize = mUpdateRegion.GetBounds(); - if (!nsIntRect(nsIntPoint(0, 0), mSize).Contains(rgnSize)) { - NS_ERROR("update outside of image"); - return NULL; - } - - ImageFormat format = - (GetContentType() == gfxASurface::CONTENT_COLOR) ? - gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; - mUpdateSurface = - GetSurfaceForUpdate(gfxIntSize(rgnSize.width, rgnSize.height), format); - - if (!mUpdateSurface || mUpdateSurface->CairoStatus()) { - mUpdateSurface = NULL; - return NULL; - } - - mUpdateSurface->SetDeviceOffset(gfxPoint(-rgnSize.x, -rgnSize.y)); - - return mUpdateSurface; -} - -void -BasicTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) -{ - // if the texture hasn't been initialized yet, or something important - // changed, we need to recreate our backing surface and force the - // client to paint everything - if (mTextureState != Valid) - aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); -} - -void -BasicTextureImage::EndUpdate() -{ - NS_ASSERTION(!!mUpdateSurface, "EndUpdate() without BeginUpdate()?"); - - // FIXME: this is the slow boat. Make me fast (with GLXPixmap?). - - // Undo the device offset that BeginUpdate set; doesn't much matter for us here, - // but important if we ever do anything directly with the surface. - mUpdateSurface->SetDeviceOffset(gfxPoint(0, 0)); - - bool relative = FinishedSurfaceUpdate(); - - mShaderType = - mGLContext->UploadSurfaceToTexture(mUpdateSurface, - mUpdateRegion, - mTexture, - mTextureState == Created, - mUpdateOffset, - relative); - FinishedSurfaceUpload(); - - mUpdateSurface = nullptr; - mTextureState = Valid; -} - -void -BasicTextureImage::BindTexture(GLenum aTextureUnit) -{ - mGLContext->fActiveTexture(aTextureUnit); - mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); - mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); -} - -void -BasicTextureImage::ApplyFilter() -{ - mGLContext->ApplyFilterToBoundTexture(mFilter); -} - - -already_AddRefed -BasicTextureImage::GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt) -{ - return gfxPlatform::GetPlatform()-> - CreateOffscreenSurface(aSize, gfxASurface::ContentFromFormat(aFmt)); -} - -bool -BasicTextureImage::FinishedSurfaceUpdate() -{ - return false; -} - -void -BasicTextureImage::FinishedSurfaceUpload() -{ -} - -bool -BasicTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) -{ - nsIntRect bounds = aRegion.GetBounds(); - nsIntRegion region; - if (mTextureState != Valid) { - bounds = nsIntRect(0, 0, mSize.width, mSize.height); - region = nsIntRegion(bounds); - } else { - region = aRegion; - } - - mShaderType = - mGLContext->UploadSurfaceToTexture(aSurf, - region, - mTexture, - mTextureState == Created, - bounds.TopLeft() + aFrom, - false); - mTextureState = Valid; - return true; -} - -void -BasicTextureImage::Resize(const nsIntSize& aSize) -{ - NS_ASSERTION(!mUpdateSurface, "Resize() while in update?"); - - mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); - - mGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D, - 0, - LOCAL_GL_RGBA, - aSize.width, - aSize.height, - 0, - LOCAL_GL_RGBA, - LOCAL_GL_UNSIGNED_BYTE, - NULL); - - mTextureState = Allocated; - mSize = aSize; -} - -TiledTextureImage::TiledTextureImage(GLContext* aGL, - nsIntSize aSize, - TextureImage::ContentType aContentType, - TextureImage::Flags aFlags) - : TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aFlags) - , mCurrentImage(0) - , mIterationCallback(nullptr) - , mInUpdate(false) - , mRows(0) - , mColumns(0) - , mGL(aGL) - , mTextureState(Created) -{ - mTileSize = (!(aFlags & TextureImage::ForceSingleTile) && mGL->WantsSmallTiles()) - ? 256 : mGL->GetMaxTextureSize(); - if (aSize != nsIntSize(0,0)) { - Resize(aSize); - } -} - -TiledTextureImage::~TiledTextureImage() -{ -} - -bool -TiledTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) -{ - nsIntRegion region; - - if (mTextureState != Valid) { - nsIntRect bounds = nsIntRect(0, 0, mSize.width, mSize.height); - region = nsIntRegion(bounds); - } else { - region = aRegion; - } - - bool result = true; - int oldCurrentImage = mCurrentImage; - BeginTileIteration(); - do { - nsIntRect tileRect = GetSrcTileRect(); - int xPos = tileRect.x; - int yPos = tileRect.y; - - nsIntRegion tileRegion; - tileRegion.And(region, tileRect); // intersect with tile - - if (tileRegion.IsEmpty()) - continue; - - if (mGL->CanUploadSubTextures()) { - tileRegion.MoveBy(-xPos, -yPos); // translate into tile local space - } else { - // If sub-textures are unsupported, expand to tile boundaries - tileRect.x = tileRect.y = 0; - tileRegion = nsIntRegion(tileRect); - } - - result &= mImages[mCurrentImage]-> - DirectUpdate(aSurf, tileRegion, aFrom + nsIntPoint(xPos, yPos)); - - if (mCurrentImage == mImages.Length() - 1) { - // We know we're done, but we still need to ensure that the callback - // gets called (e.g. to update the uploaded region). - NextTile(); - break; - } - // Override a callback cancelling iteration if the texture wasn't valid. - // We need to force the update in that situation, or we may end up - // showing invalid/out-of-date texture data. - } while (NextTile() || (mTextureState != Valid)); - mCurrentImage = oldCurrentImage; - - mShaderType = mImages[0]->GetShaderProgramType(); - mTextureState = Valid; - return result; -} - -void -TiledTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) -{ - if (mTextureState != Valid) { - // if the texture hasn't been initialized yet, or something important - // changed, we need to recreate our backing surface and force the - // client to paint everything - aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); - return; - } - - nsIntRegion newRegion; - - // We need to query each texture with the region it will be drawing and - // set aForRegion to be the combination of all of these regions - for (unsigned i = 0; i < mImages.Length(); i++) { - int xPos = (i % mColumns) * mTileSize; - int yPos = (i / mColumns) * mTileSize; - nsIntRect imageRect = nsIntRect(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); - - if (aForRegion.Intersects(imageRect)) { - // Make a copy of the region - nsIntRegion subRegion; - subRegion.And(aForRegion, imageRect); - // Translate it into tile-space - subRegion.MoveBy(-xPos, -yPos); - // Query region - mImages[i]->GetUpdateRegion(subRegion); - // Translate back - subRegion.MoveBy(xPos, yPos); - // Add to the accumulated region - newRegion.Or(newRegion, subRegion); - } - } - - aForRegion = newRegion; -} - -gfxASurface* -TiledTextureImage::BeginUpdate(nsIntRegion& aRegion) -{ - NS_ASSERTION(!mInUpdate, "nested update"); - mInUpdate = true; - - // Note, we don't call GetUpdateRegion here as if the updated region is - // fully contained in a single tile, we get to avoid iterating through - // the tiles again (and a little copying). - if (mTextureState != Valid) - { - // if the texture hasn't been initialized yet, or something important - // changed, we need to recreate our backing surface and force the - // client to paint everything - aRegion = nsIntRect(nsIntPoint(0, 0), mSize); - } - - nsIntRect bounds = aRegion.GetBounds(); - - for (unsigned i = 0; i < mImages.Length(); i++) { - int xPos = (i % mColumns) * mTileSize; - int yPos = (i / mColumns) * mTileSize; - nsIntRegion imageRegion = nsIntRegion(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); - - // a single Image can handle this update request - if (imageRegion.Contains(aRegion)) { - // adjust for tile offset - aRegion.MoveBy(-xPos, -yPos); - // forward the actual call - nsRefPtr surface = mImages[i]->BeginUpdate(aRegion); - // caller expects container space - aRegion.MoveBy(xPos, yPos); - // Correct the device offset - gfxPoint offset = surface->GetDeviceOffset(); - surface->SetDeviceOffset(gfxPoint(offset.x - xPos, - offset.y - yPos)); - // we don't have a temp surface - mUpdateSurface = nullptr; - // remember which image to EndUpdate - mCurrentImage = i; - return surface.get(); - } - } - - // Get the real updated region, taking into account the capabilities of - // each TextureImage tile - GetUpdateRegion(aRegion); - mUpdateRegion = aRegion; - bounds = aRegion.GetBounds(); - - // update covers multiple Images - create a temp surface to paint in - gfxASurface::gfxImageFormat format = - (GetContentType() == gfxASurface::CONTENT_COLOR) ? - gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; - mUpdateSurface = gfxPlatform::GetPlatform()-> - CreateOffscreenSurface(gfxIntSize(bounds.width, bounds.height), gfxASurface::ContentFromFormat(format)); - mUpdateSurface->SetDeviceOffset(gfxPoint(-bounds.x, -bounds.y)); - - return mUpdateSurface; -} - -void -TiledTextureImage::EndUpdate() -{ - NS_ASSERTION(mInUpdate, "EndUpdate not in update"); - if (!mUpdateSurface) { // update was to a single TextureImage - mImages[mCurrentImage]->EndUpdate(); - mInUpdate = false; - mTextureState = Valid; - mShaderType = mImages[mCurrentImage]->GetShaderProgramType(); - return; - } - - // upload tiles from temp surface - for (unsigned i = 0; i < mImages.Length(); i++) { - int xPos = (i % mColumns) * mTileSize; - int yPos = (i / mColumns) * mTileSize; - nsIntRect imageRect = nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize()); - - nsIntRegion subregion; - subregion.And(mUpdateRegion, imageRect); - if (subregion.IsEmpty()) - continue; - subregion.MoveBy(-xPos, -yPos); // Tile-local space - // copy tile from temp surface - gfxASurface* surf = mImages[i]->BeginUpdate(subregion); - nsRefPtr ctx = new gfxContext(surf); - gfxUtils::ClipToRegion(ctx, subregion); - ctx->SetOperator(gfxContext::OPERATOR_SOURCE); - ctx->SetSource(mUpdateSurface, gfxPoint(-xPos, -yPos)); - ctx->Paint(); - mImages[i]->EndUpdate(); - } - - mUpdateSurface = nullptr; - mInUpdate = false; - mShaderType = mImages[0]->GetShaderProgramType(); - mTextureState = Valid; -} - -void TiledTextureImage::BeginTileIteration() -{ - mCurrentImage = 0; -} - -bool TiledTextureImage::NextTile() -{ - bool continueIteration = true; - - if (mIterationCallback) - continueIteration = mIterationCallback(this, mCurrentImage, - mIterationCallbackData); - - if (mCurrentImage + 1 < mImages.Length()) { - mCurrentImage++; - return continueIteration; - } - return false; -} - -void TiledTextureImage::SetIterationCallback(TileIterationCallback aCallback, - void* aCallbackData) -{ - mIterationCallback = aCallback; - mIterationCallbackData = aCallbackData; -} - -nsIntRect TiledTextureImage::GetTileRect() -{ - nsIntRect rect = mImages[mCurrentImage]->GetTileRect(); - unsigned int xPos = (mCurrentImage % mColumns) * mTileSize; - unsigned int yPos = (mCurrentImage / mColumns) * mTileSize; - rect.MoveBy(xPos, yPos); - return rect; -} - -nsIntRect TiledTextureImage::GetSrcTileRect() -{ - nsIntRect rect = GetTileRect(); - unsigned int srcY = mFlags & NeedsYFlip - ? mSize.height - rect.height - rect.y - : rect.y; - return nsIntRect(rect.x, srcY, rect.width, rect.height); -} - -void -TiledTextureImage::BindTexture(GLenum aTextureUnit) -{ - mImages[mCurrentImage]->BindTexture(aTextureUnit); -} - -void -TiledTextureImage::ApplyFilter() -{ - mGL->ApplyFilterToBoundTexture(mFilter); -} - -/* - * Resize, trying to reuse tiles. The reuse strategy is to decide on reuse per - * column. A tile on a column is reused if it hasn't changed size, otherwise it - * is discarded/replaced. Extra tiles on a column are pruned after iterating - * each column, and extra rows are pruned after iteration over the entire image - * finishes. - */ -void TiledTextureImage::Resize(const nsIntSize& aSize) -{ - if (mSize == aSize && mTextureState != Created) { - return; - } - - // calculate rows and columns, rounding up - unsigned int columns = (aSize.width + mTileSize - 1) / mTileSize; - unsigned int rows = (aSize.height + mTileSize - 1) / mTileSize; - - // Iterate over old tile-store and insert/remove tiles as necessary - int row; - unsigned int i = 0; - for (row = 0; row < (int)rows; row++) { - // If we've gone beyond how many rows there were before, set mColumns to - // zero so that we only create new tiles. - if (row >= (int)mRows) - mColumns = 0; - - // Similarly, if we're on the last row of old tiles and the height has - // changed, discard all tiles in that row. - // This will cause the pruning of columns not to work, but we don't need - // to worry about that, as no more tiles will be reused past this point - // anyway. - if ((row == (int)mRows - 1) && (aSize.height != mSize.height)) - mColumns = 0; - - int col; - for (col = 0; col < (int)columns; col++) { - nsIntSize size( // use tilesize first, then the remainder - (col+1) * mTileSize > (unsigned int)aSize.width ? aSize.width % mTileSize : mTileSize, - (row+1) * mTileSize > (unsigned int)aSize.height ? aSize.height % mTileSize : mTileSize); - - bool replace = false; - - // Check if we can re-use old tiles. - if (col < (int)mColumns) { - // Reuse an existing tile. If the tile is an end-tile and the - // width differs, replace it instead. - if (mSize.width != aSize.width) { - if (col == (int)mColumns - 1) { - // Tile at the end of the old column, replace it with - // a new one. - replace = true; - } else if (col == (int)columns - 1) { - // Tile at the end of the new column, create a new one. - } else { - // Before the last column on both the old and new sizes, - // reuse existing tile. - i++; - continue; - } - } else { - // Width hasn't changed, reuse existing tile. - i++; - continue; - } - } - - // Create a new tile. - nsRefPtr teximg = - mGL->TileGenFunc(size, mContentType, mFlags); - if (replace) - mImages.ReplaceElementAt(i, teximg.forget()); - else - mImages.InsertElementAt(i, teximg.forget()); - i++; - } - - // Prune any unused tiles on the end of the column. - if (row < (int)mRows) { - for (col = (int)mColumns - col; col > 0; col--) { - mImages.RemoveElementAt(i); - } - } - } - - // Prune any unused tiles at the end of the store. - unsigned int length = mImages.Length(); - for (; i < length; i++) - mImages.RemoveElementAt(mImages.Length()-1); - - // Reset tile-store properties. - mRows = rows; - mColumns = columns; - mSize = aSize; - mTextureState = Allocated; - mCurrentImage = 0; -} - -uint32_t TiledTextureImage::GetTileCount() -{ - return mImages.Length(); -} - -TextureImage::ScopedBindTexture::ScopedBindTexture(TextureImage* aTexture, - GLenum aTextureUnit) - : mTexture(aTexture) -{ - if (mTexture) { - MOZ_ASSERT(aTextureUnit >= LOCAL_GL_TEXTURE0); - mTexture->BindTexture(aTextureUnit); - } -} diff --git a/gfx/gl/GLTextureImage.h b/gfx/gl/GLTextureImage.h deleted file mode 100644 index 4217778fb0ef..000000000000 --- a/gfx/gl/GLTextureImage.h +++ /dev/null @@ -1,385 +0,0 @@ -/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */ -/* 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 GLTEXTUREIMAGE_H_ -#define GLTEXTUREIMAGE_H_ - -#include "nsAutoPtr.h" -#include "nsRegion.h" -#include "gfxASurface.h" -#include "GLContextTypes.h" -#include "gfxPattern.h" - -namespace mozilla { -namespace gl { -class GLContext; - -/** - * A TextureImage encapsulates a surface that can be drawn to by a - * Thebes gfxContext and (hopefully efficiently!) synchronized to a - * texture in the server. TextureImages are associated with one and - * only one GLContext. - * - * Implementation note: TextureImages attempt to unify two categories - * of backends - * - * (1) proxy to server-side object that can be bound to a texture; - * e.g. Pixmap on X11. - * - * (2) efficient manager of texture memory; e.g. by having clients draw - * into a scratch buffer which is then uploaded with - * glTexSubImage2D(). - */ -class TextureImage -{ - NS_INLINE_DECL_REFCOUNTING(TextureImage) -public: - enum TextureState - { - Created, // Texture created, but has not had glTexImage called to initialize it. - Allocated, // Texture memory exists, but contents are invalid. - Valid // Texture fully ready to use. - }; - - enum Flags { - NoFlags = 0x0, - UseNearestFilter = 0x1, - NeedsYFlip = 0x2, - ForceSingleTile = 0x4 - }; - - typedef gfxASurface::gfxContentType ContentType; - - static already_AddRefed Create( - GLContext* gl, - const nsIntSize& aSize, - TextureImage::ContentType aContentType, - GLenum aWrapMode, - TextureImage::Flags aFlags = TextureImage::NoFlags); - - virtual ~TextureImage() {} - - /** - * Returns a gfxASurface for updating |aRegion| of the client's - * image if successul, NULL if not. |aRegion|'s bounds must fit - * within Size(); its coordinate space (if any) is ignored. If - * the update begins successfully, the returned gfxASurface is - * owned by this. Otherwise, NULL is returned. - * - * |aRegion| is an inout param: the returned region is what the - * client must repaint. Category (1) regions above can - * efficiently handle repaints to "scattered" regions, while (2) - * can only efficiently handle repaints to rects. - * - * Painting the returned surface outside of |aRegion| results - * in undefined behavior. - * - * BeginUpdate() calls cannot be "nested", and each successful - * BeginUpdate() must be followed by exactly one EndUpdate() (see - * below). Failure to do so can leave this in a possibly - * inconsistent state. Unsuccessful BeginUpdate()s must not be - * followed by EndUpdate(). - */ - virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion) = 0; - /** - * Retrieves the region that will require updating, given a - * region that needs to be updated. This can be used for - * making decisions about updating before calling BeginUpdate(). - * - * |aRegion| is an inout param. - */ - virtual void GetUpdateRegion(nsIntRegion& aForRegion) { - } - /** - * Finish the active update and synchronize with the server, if - * necessary. - * - * BeginUpdate() must have been called exactly once before - * EndUpdate(). - */ - virtual void EndUpdate() = 0; - - /** - * The Image may contain several textures for different regions (tiles). - * These functions iterate over each sub texture image tile. - */ - virtual void BeginTileIteration() { - } - - virtual bool NextTile() { - return false; - } - - // Function prototype for a tile iteration callback. Returning false will - // cause iteration to be interrupted (i.e. the corresponding NextTile call - // will return false). - typedef bool (* TileIterationCallback)(TextureImage* aImage, - int aTileNumber, - void* aCallbackData); - - // Sets a callback to be called every time NextTile is called. - virtual void SetIterationCallback(TileIterationCallback aCallback, - void* aCallbackData) { - } - - virtual nsIntRect GetTileRect() { - return nsIntRect(nsIntPoint(0,0), mSize); - } - - virtual GLuint GetTextureID() = 0; - - virtual uint32_t GetTileCount() { - return 1; - } - - /** - * Set this TextureImage's size, and ensure a texture has been - * allocated. Must not be called between BeginUpdate and EndUpdate. - * After a resize, the contents are undefined. - * - * If this isn't implemented by a subclass, it will just perform - * a dummy BeginUpdate/EndUpdate pair. - */ - virtual void Resize(const nsIntSize& aSize) { - mSize = aSize; - nsIntRegion r(nsIntRect(0, 0, aSize.width, aSize.height)); - BeginUpdate(r); - EndUpdate(); - } - - /** - * Mark this texture as having valid contents. Call this after modifying - * the texture contents externally. - */ - virtual void MarkValid() {} - - /** - * aSurf - the source surface to update from - * aRegion - the region in this image to update - * aFrom - offset in the source to update from - */ - virtual bool DirectUpdate(gfxASurface *aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)) = 0; - - virtual void BindTexture(GLenum aTextureUnit) = 0; - virtual void ReleaseTexture() {} - - void BindTextureAndApplyFilter(GLenum aTextureUnit) { - BindTexture(aTextureUnit); - ApplyFilter(); - } - - class ScopedBindTexture - { - public: - ScopedBindTexture(TextureImage *aTexture, GLenum aTextureUnit); - - ~ScopedBindTexture() - { - if (mTexture) { - mTexture->ReleaseTexture(); - } - } - - protected: - TextureImage *mTexture; - }; - - class ScopedBindTextureAndApplyFilter - : public ScopedBindTexture - { - public: - ScopedBindTextureAndApplyFilter(TextureImage *aTexture, GLenum aTextureUnit) : - ScopedBindTexture(aTexture, aTextureUnit) - { - if (mTexture) { - mTexture->ApplyFilter(); - } - } - }; - - /** - * Returns the shader program type that should be used to render - * this texture. Only valid after a matching BeginUpdate/EndUpdate - * pair have been called. - */ - virtual ShaderProgramType GetShaderProgramType() - { - return mShaderType; - } - - /** Can be called safely at any time. */ - - /** - * If this TextureImage has a permanent gfxASurface backing, - * return it. Otherwise return NULL. - */ - virtual already_AddRefed GetBackingSurface() - { return NULL; } - - const nsIntSize& GetSize() const { return mSize; } - ContentType GetContentType() const { return mContentType; } - virtual bool InUpdate() const = 0; - GLenum GetWrapMode() const { return mWrapMode; } - - void SetFilter(gfxPattern::GraphicsFilter aFilter) { mFilter = aFilter; } - - /** - * Applies this TextureImage's filter, assuming that its texture is - * the currently bound texture. - */ - virtual void ApplyFilter() = 0; - -protected: - friend class GLContext; - - /** - * After the ctor, the TextureImage is invalid. Implementations - * must allocate resources successfully before returning the new - * TextureImage from GLContext::CreateTextureImage(). That is, - * clients must not be given partially-constructed TextureImages. - */ - TextureImage(const nsIntSize& aSize, - GLenum aWrapMode, ContentType aContentType, - Flags aFlags = NoFlags) - : mSize(aSize) - , mWrapMode(aWrapMode) - , mContentType(aContentType) - , mFilter(gfxPattern::FILTER_GOOD) - , mFlags(aFlags) - {} - - virtual nsIntRect GetSrcTileRect() { - return nsIntRect(nsIntPoint(0,0), mSize); - } - - nsIntSize mSize; - GLenum mWrapMode; - ContentType mContentType; - ShaderProgramType mShaderType; - gfxPattern::GraphicsFilter mFilter; - Flags mFlags; -}; - -/** - * BasicTextureImage is the baseline TextureImage implementation --- - * it updates its texture by allocating a scratch buffer for the - * client to draw into, then using glTexSubImage2D() to upload the new - * pixels. Platforms must provide the code to create a new surface - * into which the updated pixels will be drawn, and the code to - * convert the update surface's pixels into an image on which we can - * glTexSubImage2D(). - */ -class BasicTextureImage - : public TextureImage -{ -public: - typedef gfxASurface::gfxImageFormat ImageFormat; - virtual ~BasicTextureImage(); - - BasicTextureImage(GLuint aTexture, - const nsIntSize& aSize, - GLenum aWrapMode, - ContentType aContentType, - GLContext* aContext, - TextureImage::Flags aFlags = TextureImage::NoFlags) - : TextureImage(aSize, aWrapMode, aContentType, aFlags) - , mTexture(aTexture) - , mTextureState(Created) - , mGLContext(aContext) - , mUpdateOffset(0, 0) - {} - - virtual void BindTexture(GLenum aTextureUnit); - - virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); - virtual void GetUpdateRegion(nsIntRegion& aForRegion); - virtual void EndUpdate(); - virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); - virtual GLuint GetTextureID() { return mTexture; } - // Returns a surface to draw into - virtual already_AddRefed - GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt); - - virtual void MarkValid() { mTextureState = Valid; } - - // Call when drawing into the update surface is complete. - // Returns true if textures should be upload with a relative - // offset - See UploadSurfaceToTexture. - virtual bool FinishedSurfaceUpdate(); - - // Call after surface data has been uploaded to a texture. - virtual void FinishedSurfaceUpload(); - - virtual bool InUpdate() const { return !!mUpdateSurface; } - - virtual void Resize(const nsIntSize& aSize); - - virtual void ApplyFilter(); -protected: - - GLuint mTexture; - TextureState mTextureState; - GLContext* mGLContext; - nsRefPtr mUpdateSurface; - nsIntRegion mUpdateRegion; - - // The offset into the update surface at which the update rect is located. - nsIntPoint mUpdateOffset; -}; - -/** - * A container class that complements many sub TextureImages into a big TextureImage. - * Aims to behave just like the real thing. - */ - -class TiledTextureImage - : public TextureImage -{ -public: - TiledTextureImage(GLContext* aGL, nsIntSize aSize, - TextureImage::ContentType, TextureImage::Flags aFlags = TextureImage::NoFlags); - ~TiledTextureImage(); - void DumpDiv(); - virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); - virtual void GetUpdateRegion(nsIntRegion& aForRegion); - virtual void EndUpdate(); - virtual void Resize(const nsIntSize& aSize); - virtual uint32_t GetTileCount(); - virtual void BeginTileIteration(); - virtual bool NextTile(); - virtual void SetIterationCallback(TileIterationCallback aCallback, - void* aCallbackData); - virtual nsIntRect GetTileRect(); - virtual GLuint GetTextureID() { - return mImages[mCurrentImage]->GetTextureID(); - } - virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); - virtual bool InUpdate() const { return mInUpdate; } - virtual void BindTexture(GLenum); - virtual void ApplyFilter(); - -protected: - virtual nsIntRect GetSrcTileRect(); - - unsigned int mCurrentImage; - TileIterationCallback mIterationCallback; - void* mIterationCallbackData; - nsTArray< nsRefPtr > mImages; - bool mInUpdate; - nsIntSize mSize; - unsigned int mTileSize; - unsigned int mRows, mColumns; - GLContext* mGL; - // A temporary surface to faciliate cross-tile updates. - nsRefPtr mUpdateSurface; - // The region of update requested - nsIntRegion mUpdateRegion; - TextureState mTextureState; -}; - -} // namespace gl -} // namespace mozilla - -#endif /* GLTEXTUREIMAGE_H_ */ diff --git a/gfx/gl/Makefile.in b/gfx/gl/Makefile.in index 8f1cde8fe9de..4d8928ee8f8a 100644 --- a/gfx/gl/Makefile.in +++ b/gfx/gl/Makefile.in @@ -18,13 +18,11 @@ FAIL_ON_WARNINGS = 1 EXPORTS = \ GLDefs.h \ GLContext.h \ - GLContextTypes.h \ GLContextSymbols.h \ GLContextProvider.h \ GLContextProviderImpl.h \ GLLibraryLoader.h \ ForceDiscreteGPUHelperCGL.h \ - GLTextureImage.h \ $(NULL) ifdef MOZ_X11 @@ -49,7 +47,6 @@ CPPSRCS = \ GLContext.cpp \ GLContextUtils.cpp \ GLLibraryLoader.cpp \ - GLTextureImage.cpp \ $(NULL) GL_PROVIDER = Null diff --git a/gfx/layers/SharedTextureImage.h b/gfx/layers/SharedTextureImage.h index 2a42154cfcf6..3f0732023954 100644 --- a/gfx/layers/SharedTextureImage.h +++ b/gfx/layers/SharedTextureImage.h @@ -20,7 +20,7 @@ class THEBES_API SharedTextureImage : public Image { public: struct Data { gl::SharedTextureHandle mHandle; - gl::GLContext::SharedTextureShareType mShareType; + gl::TextureImage::TextureShareType mShareType; gfxIntSize mSize; bool mInverted; }; @@ -41,4 +41,4 @@ private: } // layers } // mozilla -#endif // GFX_SHAREDTEXTUREIMAGE_H +#endif // GFX_SHAREDTEXTUREIMAGE_H \ No newline at end of file diff --git a/gfx/layers/basic/BasicCanvasLayer.cpp b/gfx/layers/basic/BasicCanvasLayer.cpp index e028468f29b1..fdc133d38747 100644 --- a/gfx/layers/basic/BasicCanvasLayer.cpp +++ b/gfx/layers/basic/BasicCanvasLayer.cpp @@ -387,25 +387,24 @@ BasicShadowableCanvasLayer::Paint(gfxContext* aContext, Layer* aMaskLayer) if (mGLContext && !mForceReadback && - BasicManager()->GetParentBackendType() == mozilla::layers::LAYERS_OPENGL) - { - GLContext::SharedTextureShareType shareType; + BasicManager()->GetParentBackendType() == mozilla::layers::LAYERS_OPENGL) { + TextureImage::TextureShareType flags; // if process type is default, then it is single-process (non-e10s) if (XRE_GetProcessType() == GeckoProcessType_Default) - shareType = GLContext::SameProcess; + flags = TextureImage::ThreadShared; else - shareType = GLContext::CrossProcess; + flags = TextureImage::ProcessShared; SharedTextureHandle handle = GetSharedBackBufferHandle(); if (!handle) { - handle = mGLContext->CreateSharedHandle(shareType); + handle = mGLContext->CreateSharedHandle(flags); if (handle) { - mBackBuffer = SharedTextureDescriptor(shareType, handle, mBounds.Size(), false); + mBackBuffer = SharedTextureDescriptor(flags, handle, mBounds.Size(), false); } } if (handle) { mGLContext->MakeCurrent(); - mGLContext->UpdateSharedHandle(shareType, handle); + mGLContext->UpdateSharedHandle(flags, handle); // call Painted() to reset our dirty 'bit' Painted(); FireDidTransactionCallback(); diff --git a/gfx/layers/ipc/LayersSurfaces.ipdlh b/gfx/layers/ipc/LayersSurfaces.ipdlh index 2d8e763f05b8..39db98123782 100644 --- a/gfx/layers/ipc/LayersSurfaces.ipdlh +++ b/gfx/layers/ipc/LayersSurfaces.ipdlh @@ -22,7 +22,7 @@ using mozilla::layers::SurfaceDescriptorX11; using mozilla::null_t; using mozilla::WindowsHandle; using mozilla::gl::SharedTextureHandle; -using mozilla::gl::GLContext::SharedTextureShareType; +using mozilla::gl::TextureImage::TextureShareType; namespace mozilla { namespace layers { @@ -37,7 +37,7 @@ struct SurfaceDescriptorD3D10 { }; struct SharedTextureDescriptor { - SharedTextureShareType shareType; + TextureShareType shareType; SharedTextureHandle handle; nsIntSize size; bool inverted; diff --git a/gfx/layers/ipc/ShadowLayerUtils.h b/gfx/layers/ipc/ShadowLayerUtils.h index b7eaa1d4d191..4d66cef624bd 100644 --- a/gfx/layers/ipc/ShadowLayerUtils.h +++ b/gfx/layers/ipc/ShadowLayerUtils.h @@ -52,9 +52,9 @@ struct ParamTraits { #endif // !defined(MOZ_HAVE_XSURFACEDESCRIPTORX11) template<> -struct ParamTraits +struct ParamTraits { - typedef mozilla::gl::GLContext::SharedTextureShareType paramType; + typedef mozilla::gl::TextureImage::TextureShareType paramType; static void Write(Message* msg, const paramType& param) { diff --git a/gfx/layers/opengl/CanvasLayerOGL.cpp b/gfx/layers/opengl/CanvasLayerOGL.cpp index 6616fb9fde05..3bd9e578b40e 100644 --- a/gfx/layers/opengl/CanvasLayerOGL.cpp +++ b/gfx/layers/opengl/CanvasLayerOGL.cpp @@ -420,10 +420,7 @@ ShadowCanvasLayerOGL::Swap(const CanvasSurface& aNewFront, } else if (IsValidSharedTexDescriptor(aNewFront)) { MakeTextureIfNeeded(gl(), mTexture); if (!IsValidSharedTexDescriptor(mFrontBufferDescriptor)) { - mFrontBufferDescriptor = SharedTextureDescriptor(GLContext::SameProcess, - 0, - nsIntSize(0, 0), - false); + mFrontBufferDescriptor = SharedTextureDescriptor(TextureImage::ThreadShared, 0, nsIntSize(0, 0), false); } *aNewBack = mFrontBufferDescriptor; mFrontBufferDescriptor = aNewFront; diff --git a/gfx/layers/opengl/ImageLayerOGL.h b/gfx/layers/opengl/ImageLayerOGL.h index 5995c12ef7c9..5db8b6e9c897 100644 --- a/gfx/layers/opengl/ImageLayerOGL.h +++ b/gfx/layers/opengl/ImageLayerOGL.h @@ -194,7 +194,7 @@ private: // For SharedTextureHandle gl::SharedTextureHandle mSharedHandle; - gl::GLContext::SharedTextureShareType mShareType; + gl::TextureImage::TextureShareType mShareType; bool mInverted; GLuint mTexture; diff --git a/gfx/layers/opengl/LayerManagerOGL.cpp b/gfx/layers/opengl/LayerManagerOGL.cpp index 932150e4482e..7f794318f349 100644 --- a/gfx/layers/opengl/LayerManagerOGL.cpp +++ b/gfx/layers/opengl/LayerManagerOGL.cpp @@ -3,13 +3,13 @@ * 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 "LayerManagerOGL.h" - #include "mozilla/layers/PLayers.h" /* This must occur *after* layers/PLayers.h to avoid typedefs conflicts. */ #include "mozilla/Util.h" + #include "Composer2D.h" +#include "LayerManagerOGL.h" #include "ThebesLayerOGL.h" #include "ContainerLayerOGL.h" #include "ImageLayerOGL.h" @@ -52,91 +52,6 @@ using namespace mozilla::gl; int ShaderProgramOGL::sCurrentProgramKey = 0; #endif -bool -LayerManagerOGL::Initialize(bool force) -{ - return Initialize(CreateContext(), force); -} - -int32_t -LayerManagerOGL::GetMaxTextureSize() const -{ - return mGLContext->GetMaxTextureSize(); -} - -void -LayerManagerOGL::MakeCurrent(bool aForce) -{ - if (mDestroyed) { - NS_WARNING("Call on destroyed layer manager"); - return; - } - mGLContext->MakeCurrent(aForce); -} - -void* -LayerManagerOGL::GetNSOpenGLContext() const -{ - return gl()->GetNativeData(GLContext::NativeGLContext); -} - - -void -LayerManagerOGL::BindQuadVBO() { - mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); -} - -void -LayerManagerOGL::QuadVBOVerticesAttrib(GLuint aAttribIndex) { - mGLContext->fVertexAttribPointer(aAttribIndex, 2, - LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, - (GLvoid*) QuadVBOVertexOffset()); -} - -void -LayerManagerOGL::QuadVBOTexCoordsAttrib(GLuint aAttribIndex) { - mGLContext->fVertexAttribPointer(aAttribIndex, 2, - LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, - (GLvoid*) QuadVBOTexCoordOffset()); -} - -void -LayerManagerOGL::QuadVBOFlippedTexCoordsAttrib(GLuint aAttribIndex) { - mGLContext->fVertexAttribPointer(aAttribIndex, 2, - LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, - (GLvoid*) QuadVBOFlippedTexCoordOffset()); -} - -// Super common - -void -LayerManagerOGL::BindAndDrawQuad(GLuint aVertAttribIndex, - GLuint aTexCoordAttribIndex, - bool aFlipped) -{ - BindQuadVBO(); - QuadVBOVerticesAttrib(aVertAttribIndex); - - if (aTexCoordAttribIndex != GLuint(-1)) { - if (aFlipped) - QuadVBOFlippedTexCoordsAttrib(aTexCoordAttribIndex); - else - QuadVBOTexCoordsAttrib(aTexCoordAttribIndex); - - mGLContext->fEnableVertexAttribArray(aTexCoordAttribIndex); - } - - mGLContext->fEnableVertexAttribArray(aVertAttribIndex); - - mGLContext->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4); - - mGLContext->fDisableVertexAttribArray(aVertAttribIndex); - - if (aTexCoordAttribIndex != GLuint(-1)) { - mGLContext->fDisableVertexAttribArray(aTexCoordAttribIndex); - } -} - static const double kFpsWindowMs = 250.0; static const size_t kNumFrameTimeStamps = 16; struct FPSCounter { diff --git a/gfx/layers/opengl/LayerManagerOGL.h b/gfx/layers/opengl/LayerManagerOGL.h index 78059d36a8c9..623e009e0e64 100644 --- a/gfx/layers/opengl/LayerManagerOGL.h +++ b/gfx/layers/opengl/LayerManagerOGL.h @@ -9,23 +9,31 @@ #include "LayerManagerOGLProgram.h" #include "mozilla/layers/ShadowLayers.h" + #include "mozilla/TimeStamp.h" #ifdef XP_WIN #include #endif +/** + * We don't include GLDefs.h here since we don't want to drag in all defines + * in for all our users. + */ +typedef unsigned int GLenum; +typedef unsigned int GLbitfield; +typedef unsigned int GLuint; +typedef int GLint; +typedef int GLsizei; + #define BUFFER_OFFSET(i) ((char *)NULL + (i)) #include "gfxContext.h" #include "gfx3DMatrix.h" #include "nsIWidget.h" -#include "GLContextTypes.h" +#include "GLContext.h" namespace mozilla { -namespace gl { -class GLContext; -} namespace layers { class Composer2D; @@ -63,7 +71,9 @@ public: * * \return True is initialization was succesful, false when it was not. */ - bool Initialize(bool force = false); + bool Initialize(bool force = false) { + return Initialize(CreateContext(), force); + } bool Initialize(nsRefPtr aContext, bool force = false); @@ -100,14 +110,18 @@ public: virtual void SetRoot(Layer* aLayer) { mRoot = aLayer; } - virtual bool CanUseCanvasLayerForSize(const gfxIntSize &aSize) { - if (!mGLContext) - return false; - int32_t maxSize = GetMaxTextureSize(); - return aSize <= gfxIntSize(maxSize, maxSize); + virtual bool CanUseCanvasLayerForSize(const gfxIntSize &aSize) + { + if (!mGLContext) + return false; + int32_t maxSize = mGLContext->GetMaxTextureSize(); + return aSize <= gfxIntSize(maxSize, maxSize); } - virtual int32_t GetMaxTextureSize() const; + virtual int32_t GetMaxTextureSize() const + { + return mGLContext->GetMaxTextureSize(); + } virtual already_AddRefed CreateThebesLayer(); @@ -137,7 +151,13 @@ public: /** * Helper methods. */ - void MakeCurrent(bool aForce = false); + void MakeCurrent(bool aForce = false) { + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return; + } + mGLContext->MakeCurrent(aForce); + } ShaderProgramOGL* GetBasicLayerProgram(bool aOpaque, bool aIsRGB, MaskType aMask = MaskNone) @@ -183,9 +203,6 @@ public: GLContext* gl() const { return mGLContext; } - // |NSOpenGLContext*|: - void* GetNSOpenGLContext() const; - DrawThebesLayerCallback GetThebesLayerCallback() const { return mThebesLayerCallback; } @@ -237,15 +254,56 @@ public: GLintptr QuadVBOTexCoordOffset() { return sizeof(float)*4*2; } GLintptr QuadVBOFlippedTexCoordOffset() { return sizeof(float)*8*2; } - void BindQuadVBO(); - void QuadVBOVerticesAttrib(GLuint aAttribIndex); - void QuadVBOTexCoordsAttrib(GLuint aAttribIndex); - void QuadVBOFlippedTexCoordsAttrib(GLuint aAttribIndex); + void BindQuadVBO() { + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); + } + + void QuadVBOVerticesAttrib(GLuint aAttribIndex) { + mGLContext->fVertexAttribPointer(aAttribIndex, 2, + LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, + (GLvoid*) QuadVBOVertexOffset()); + } + + void QuadVBOTexCoordsAttrib(GLuint aAttribIndex) { + mGLContext->fVertexAttribPointer(aAttribIndex, 2, + LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, + (GLvoid*) QuadVBOTexCoordOffset()); + } + + void QuadVBOFlippedTexCoordsAttrib(GLuint aAttribIndex) { + mGLContext->fVertexAttribPointer(aAttribIndex, 2, + LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, + (GLvoid*) QuadVBOFlippedTexCoordOffset()); + } // Super common + void BindAndDrawQuad(GLuint aVertAttribIndex, GLuint aTexCoordAttribIndex, - bool aFlipped = false); + bool aFlipped = false) + { + BindQuadVBO(); + QuadVBOVerticesAttrib(aVertAttribIndex); + + if (aTexCoordAttribIndex != GLuint(-1)) { + if (aFlipped) + QuadVBOFlippedTexCoordsAttrib(aTexCoordAttribIndex); + else + QuadVBOTexCoordsAttrib(aTexCoordAttribIndex); + + mGLContext->fEnableVertexAttribArray(aTexCoordAttribIndex); + } + + mGLContext->fEnableVertexAttribArray(aVertAttribIndex); + + mGLContext->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4); + + mGLContext->fDisableVertexAttribArray(aVertAttribIndex); + + if (aTexCoordAttribIndex != GLuint(-1)) { + mGLContext->fDisableVertexAttribArray(aTexCoordAttribIndex); + } + } void BindAndDrawQuad(ShaderProgramOGL *aProg, bool aFlipped = false) diff --git a/gfx/layers/opengl/LayerManagerOGLProgram.cpp b/gfx/layers/opengl/LayerManagerOGLProgram.cpp index 3631e831da50..155b28870693 100644 --- a/gfx/layers/opengl/LayerManagerOGLProgram.cpp +++ b/gfx/layers/opengl/LayerManagerOGLProgram.cpp @@ -3,16 +3,9 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "LayerManagerOGLProgram.h" - -#ifdef GLCONTEXT_H_ -#error GLContext.h should not have been included here. -#endif - #include "LayerManagerOGLShaders.h" #include "LayerManagerOGL.h" -#include "GLContext.h" - namespace mozilla { namespace layers { @@ -234,28 +227,6 @@ ProgramProfileOGL::GetProfileFor(gl::ShaderProgramType aType, const char* const ShaderProgramOGL::VertexCoordAttrib = "aVertexCoord"; const char* const ShaderProgramOGL::TexCoordAttrib = "aTexCoord"; -ShaderProgramOGL::ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile) - : mIsProjectionMatrixStale(false) - , mGL(aGL) - , mProgram(0) - , mProfile(aProfile) - , mProgramState(STATE_NEW) -{} - -ShaderProgramOGL::~ShaderProgramOGL() -{ - if (mProgram <= 0) { - return; - } - - nsRefPtr ctx = mGL->GetSharedContext(); - if (!ctx) { - ctx = mGL; - } - ctx->MakeCurrent(); - ctx->fDeleteProgram(mProgram); -} - bool ShaderProgramOGL::Initialize() { @@ -423,87 +394,5 @@ ShaderProgramOGL::LoadMask(Layer* aMaskLayer) return true; } -void -ShaderProgramOGL::Activate() -{ - if (mProgramState == STATE_NEW) { - if (!Initialize()) { - NS_WARNING("Shader could not be initialised"); - return; - } - } - NS_ASSERTION(HasInitialized(), "Attempting to activate a program that's not in use!"); - mGL->fUseProgram(mProgram); -#if CHECK_CURRENT_PROGRAM - mGL->SetUserData(&sCurrentProgramKey, this); -#endif - // check and set the projection matrix - if (mIsProjectionMatrixStale) { - SetProjectionMatrix(mProjectionMatrix); - } -} - - -void -ShaderProgramOGL::SetUniform(GLint aLocation, float aFloatValue) -{ - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - mGL->fUniform1f(aLocation, aFloatValue); -} - -void -ShaderProgramOGL::SetUniform(GLint aLocation, const gfxRGBA& aColor) -{ - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - mGL->fUniform4f(aLocation, float(aColor.r), float(aColor.g), float(aColor.b), float(aColor.a)); -} - -void -ShaderProgramOGL::SetUniform(GLint aLocation, int aLength, float *aFloatValues) -{ - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - if (aLength == 1) { - mGL->fUniform1fv(aLocation, 1, aFloatValues); - } else if (aLength == 2) { - mGL->fUniform2fv(aLocation, 1, aFloatValues); - } else if (aLength == 3) { - mGL->fUniform3fv(aLocation, 1, aFloatValues); - } else if (aLength == 4) { - mGL->fUniform4fv(aLocation, 1, aFloatValues); - } else { - NS_NOTREACHED("Bogus aLength param"); - } -} - -void -ShaderProgramOGL::SetUniform(GLint aLocation, GLint aIntValue) -{ - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - mGL->fUniform1i(aLocation, aIntValue); -} - -void -ShaderProgramOGL::SetMatrixUniform(GLint aLocation, const gfx3DMatrix& aMatrix) -{ - SetMatrixUniform(aLocation, &aMatrix._11); -} - -void -ShaderProgramOGL::SetMatrixUniform(GLint aLocation, const float *aFloatValues) -{ - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - mGL->fUniformMatrix4fv(aLocation, 1, false, aFloatValues); -} - } /* layers */ } /* mozilla */ diff --git a/gfx/layers/opengl/LayerManagerOGLProgram.h b/gfx/layers/opengl/LayerManagerOGLProgram.h index f6ed2b7a64bb..ac4a2151f9bb 100644 --- a/gfx/layers/opengl/LayerManagerOGLProgram.h +++ b/gfx/layers/opengl/LayerManagerOGLProgram.h @@ -10,16 +10,11 @@ #include "prenv.h" -#include "nsAutoPtr.h" #include "nsString.h" -#include "GLContextTypes.h" +#include "GLContext.h" #include "gfx3DMatrix.h" -#include "gfxColor.h" namespace mozilla { -namespace gl { -class GLContext; -} namespace layers { class Layer; @@ -145,16 +140,45 @@ class ShaderProgramOGL public: typedef mozilla::gl::GLContext GLContext; - ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile); + ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile) : + mIsProjectionMatrixStale(false), mGL(aGL), mProgram(0), + mProfile(aProfile), mProgramState(STATE_NEW) { } - ~ShaderProgramOGL(); + ~ShaderProgramOGL() { + if (mProgram <= 0) { + return; + } + + nsRefPtr ctx = mGL->GetSharedContext(); + if (!ctx) { + ctx = mGL; + } + ctx->MakeCurrent(); + ctx->fDeleteProgram(mProgram); + } bool HasInitialized() { NS_ASSERTION(mProgramState != STATE_OK || mProgram > 0, "Inconsistent program state"); return mProgramState == STATE_OK; } - void Activate(); + void Activate() { + if (mProgramState == STATE_NEW) { + if (!Initialize()) { + NS_WARNING("Shader could not be initialised"); + return; + } + } + NS_ASSERTION(HasInitialized(), "Attempting to activate a program that's not in use!"); + mGL->fUseProgram(mProgram); +#if CHECK_CURRENT_PROGRAM + mGL->SetUserData(&sCurrentProgramKey, this); +#endif + // check and set the projection matrix + if (mIsProjectionMatrixStale) { + SetProjectionMatrix(mProjectionMatrix); + } + } bool Initialize(); @@ -305,12 +329,54 @@ protected: static int sCurrentProgramKey; #endif - void SetUniform(GLint aLocation, float aFloatValue); - void SetUniform(GLint aLocation, const gfxRGBA& aColor); - void SetUniform(GLint aLocation, int aLength, float *aFloatValues); - void SetUniform(GLint aLocation, GLint aIntValue); - void SetMatrixUniform(GLint aLocation, const gfx3DMatrix& aMatrix); - void SetMatrixUniform(GLint aLocation, const float *aFloatValues); + void SetUniform(GLint aLocation, float aFloatValue) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + mGL->fUniform1f(aLocation, aFloatValue); + } + + void SetUniform(GLint aLocation, const gfxRGBA& aColor) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + mGL->fUniform4f(aLocation, float(aColor.r), float(aColor.g), float(aColor.b), float(aColor.a)); + } + + void SetUniform(GLint aLocation, int aLength, float *aFloatValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + if (aLength == 1) { + mGL->fUniform1fv(aLocation, 1, aFloatValues); + } else if (aLength == 2) { + mGL->fUniform2fv(aLocation, 1, aFloatValues); + } else if (aLength == 3) { + mGL->fUniform3fv(aLocation, 1, aFloatValues); + } else if (aLength == 4) { + mGL->fUniform4fv(aLocation, 1, aFloatValues); + } else { + NS_NOTREACHED("Bogus aLength param"); + } + } + + void SetUniform(GLint aLocation, GLint aIntValue) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + mGL->fUniform1i(aLocation, aIntValue); + } + + void SetMatrixUniform(GLint aLocation, const gfx3DMatrix& aMatrix) { + SetMatrixUniform(aLocation, &aMatrix._11); + } + + void SetMatrixUniform(GLint aLocation, const float *aFloatValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + mGL->fUniformMatrix4fv(aLocation, 1, false, aFloatValues); + } }; diff --git a/gfx/layers/opengl/ReusableTileStoreOGL.cpp b/gfx/layers/opengl/ReusableTileStoreOGL.cpp index 6c52f74c060f..63e4d531785e 100644 --- a/gfx/layers/opengl/ReusableTileStoreOGL.cpp +++ b/gfx/layers/opengl/ReusableTileStoreOGL.cpp @@ -4,8 +4,6 @@ #include "ReusableTileStoreOGL.h" -#include "GLContext.h" - namespace mozilla { namespace layers { diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index 403183554a59..e7ba09c2b627 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -51,7 +51,7 @@ #include "nsRegion.h" #include "Layers.h" #include "LayerManagerOGL.h" -#include "GLTextureImage.h" +#include "GLContext.h" #include "mozilla/layers/CompositorCocoaWidgetHelper.h" #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" @@ -67,10 +67,6 @@ #include "nsIDOMWheelEvent.h" -#ifdef GLCONTEXT_H_ -#error GLContext.h should not have been included here. -#endif - using namespace mozilla; using namespace mozilla::layers; using namespace mozilla::gl; @@ -1696,7 +1692,8 @@ nsChildView::CreateCompositor() LayerManagerOGL *manager = static_cast(compositor::GetLayerManager(mCompositorParent)); - NSOpenGLContext *glContext = (NSOpenGLContext *)manager->GetNSOpenGLContext(); + NSOpenGLContext *glContext = + (NSOpenGLContext *) manager->gl()->GetNativeData(GLContext::NativeGLContext); [(ChildView *)mView setGLContext:glContext]; [(ChildView *)mView setUsingOMTCompositor:true]; @@ -1759,11 +1756,10 @@ nsChildView::DrawWindowOverlay(LayerManager* aManager, nsIntRect aRect) } if (!mResizerImage) { - mResizerImage = TextureImage::Create(manager->gl(), - nsIntSize(15, 15), - gfxASurface::CONTENT_COLOR_ALPHA, - LOCAL_GL_CLAMP_TO_EDGE, - TextureImage::UseNearestFilter); + mResizerImage = manager->gl()->CreateTextureImage(nsIntSize(15, 15), + gfxASurface::CONTENT_COLOR_ALPHA, + LOCAL_GL_CLAMP_TO_EDGE, + TextureImage::UseNearestFilter); // Creation of texture images can fail. if (!mResizerImage) @@ -2461,7 +2457,7 @@ NSEvent* gLastDragMouseDownEvent = nil; LayerManagerOGL *manager = static_cast(layerManager); manager->SetClippingRegion(region); - glContext = (NSOpenGLContext *)manager->GetNSOpenGLContext(); + glContext = (NSOpenGLContext *)manager->gl()->GetNativeData(mozilla::gl::GLContext::NativeGLContext); if (!mGLContext) { [self setGLContext:glContext]; From 6d96cbc37a5d0882f4822791e344e9dac2ce894b Mon Sep 17 00:00:00 2001 From: David Keeler Date: Tue, 20 Nov 2012 16:58:22 -0800 Subject: [PATCH 041/160] bug 812562 - click-to-play: reshow notification for blocklisted plugins r=jaws --- browser/base/content/browser-plugins.js | 4 - browser/base/content/test/Makefile.in | 3 + .../test/blockPluginVulnerableNoUpdate.xml | 11 +++ .../test/blockPluginVulnerableUpdatable.xml | 11 +++ .../base/content/test/browser_bug812562.js | 81 +++++++++++++++++++ .../test/browser_pluginnotification.js | 64 +++------------ browser/base/content/test/head.js | 21 +++++ 7 files changed, 138 insertions(+), 57 deletions(-) create mode 100644 browser/base/content/test/blockPluginVulnerableNoUpdate.xml create mode 100644 browser/base/content/test/blockPluginVulnerableUpdatable.xml create mode 100644 browser/base/content/test/browser_bug812562.js diff --git a/browser/base/content/browser-plugins.js b/browser/base/content/browser-plugins.js index 0c3e760a27d0..0c5c0dc56ce8 100644 --- a/browser/base/content/browser-plugins.js +++ b/browser/base/content/browser-plugins.js @@ -416,11 +416,7 @@ var gPluginHandler = { }, reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() { - if (!Services.prefs.getBoolPref("plugins.click_to_play")) - return; - let browser = gBrowser.selectedBrowser; - let pluginsPermission = Services.perms.testPermission(browser.currentURI, "plugins"); if (pluginsPermission == Ci.nsIPermissionManager.DENY_ACTION) return; diff --git a/browser/base/content/test/Makefile.in b/browser/base/content/test/Makefile.in index a60f5334c7cb..c8735d595d29 100644 --- a/browser/base/content/test/Makefile.in +++ b/browser/base/content/test/Makefile.in @@ -269,6 +269,9 @@ _BROWSER_FILES = \ browser_tabDrop.js \ browser_lastAccessedTab.js \ browser_bug734076.js \ + browser_bug812562.js \ + blockPluginVulnerableUpdatable.xml \ + blockPluginVulnerableNoUpdate.xml \ browser_social.js \ browser_social_toolbar.js \ browser_social_shareButton.js \ diff --git a/browser/base/content/test/blockPluginVulnerableNoUpdate.xml b/browser/base/content/test/blockPluginVulnerableNoUpdate.xml new file mode 100644 index 000000000000..bf8545afee9a --- /dev/null +++ b/browser/base/content/test/blockPluginVulnerableNoUpdate.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/browser/base/content/test/blockPluginVulnerableUpdatable.xml b/browser/base/content/test/blockPluginVulnerableUpdatable.xml new file mode 100644 index 000000000000..5545162b1b07 --- /dev/null +++ b/browser/base/content/test/blockPluginVulnerableUpdatable.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/browser/base/content/test/browser_bug812562.js b/browser/base/content/test/browser_bug812562.js new file mode 100644 index 000000000000..a81bde3c5b29 --- /dev/null +++ b/browser/base/content/test/browser_bug812562.js @@ -0,0 +1,81 @@ +var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; +var gNextTest = null; + +Components.utils.import("resource://gre/modules/Services.jsm"); + +function test() { + waitForExplicitFinish(); + registerCleanupFunction(function() { + Services.prefs.clearUserPref("plugins.click_to_play"); + }); + + Services.prefs.setBoolPref("plugins.click_to_play", false); + var newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + gTestBrowser.addEventListener("load", pageLoad, true); + setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml", + function() { + prepareTest(testPart1, gHttpTestRoot + "plugin_test.html"); + }); +} + +function finishTest() { + gTestBrowser.removeEventListener("load", pageLoad, true); + gBrowser.removeCurrentTab(); + window.focus(); + resetBlocklist(finish); +} + +function pageLoad(aEvent) { + // The plugin events are async dispatched and can come after the load event + // This just allows the events to fire before we then go on to test the states + if (gNextTest != null) + executeSoon(gNextTest); +} + +function prepareTest(nextTest, url) { + gNextTest = nextTest; + gTestBrowser.contentWindow.location = url; +} + +// Tests that the going back will reshow the notification for click-to-play +// blocklisted plugins (part 1/4) +function testPart1() { + var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(popupNotification, "test part 1: Should have a click-to-play notification"); + var plugin = gTestBrowser.contentDocument.getElementById("test"); + var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "test part 1: plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE"); + ok(!objLoadingContent.activated, "test part 1: plugin should not be activated"); + + prepareTest(testPart2, "about:blank"); +} + +function testPart2() { + var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(!popupNotification, "test part 2: Should not have a click-to-play notification"); + var plugin = gTestBrowser.contentDocument.getElementById("test"); + ok(!plugin, "test part 2: Should not have a plugin in this page"); + + Services.obs.addObserver(testPart3, "PopupNotifications-updateNotShowing", false); + gTestBrowser.contentWindow.history.back(); +} + +function testPart3() { + Services.obs.removeObserver(testPart3, "PopupNotifications-updateNotShowing", false); + var condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + waitForCondition(condition, testPart4, "test part 3: waited too long for click-to-play-plugin notification"); +} + +function testPart4() { + var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(popupNotification, "test part 4: Should have a click-to-play notification"); + var plugin = gTestBrowser.contentDocument.getElementById("test"); + var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "test part 4: plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE"); + ok(!objLoadingContent.activated, "test part 4: plugin should not be activated"); + + finishTest(); +} diff --git a/browser/base/content/test/browser_pluginnotification.js b/browser/base/content/test/browser_pluginnotification.js index 6f3a37c98e50..1692218532cc 100644 --- a/browser/base/content/test/browser_pluginnotification.js +++ b/browser/base/content/test/browser_pluginnotification.js @@ -527,53 +527,10 @@ function test17() { var missingNotification = PopupNotifications.getNotification("missing-plugins", gTestBrowser); ok(!missingNotification, "Test 17, Should not have a missing plugin notification"); - registerFakeBlocklistService(Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE); - prepareTest(test18a, gHttpTestRoot + "plugin_test.html"); -} - -const Cr = Components.results; -const Cm = Components.manager; -const Cc = Components.classes; -const gReg = Cm.QueryInterface(Ci.nsIComponentRegistrar); -const gRealBlocklistServiceCID = Cc["@mozilla.org/extensions/blocklist;1"]; -const gFakeBlocklistServiceCID = Components.ID("{614b68a0-3c53-4ec0-8146-28cc1e25f8a1}"); -var gFactory = null; - -function registerFakeBlocklistService(blockState) { - - var BlocklistService = { - getPluginBlocklistState: function(plugin, appVersion, toolkitVersion) { - return blockState; - }, - - classID: gFakeBlocklistServiceCID, - QueryInterface: XPCOMUtils.generateQI([Ci.nsIBlocklistService]) - }; - - gFactory = { - createInstance: function(outer, iid) { - if (outer != null) - throw Cr.NS_ERROR_NO_AGGREGATION; - return BlocklistService.QueryInterface(iid); - } - }; - - gReg.registerFactory(gFakeBlocklistServiceCID, - "Fake Blocklist Service", - "@mozilla.org/extensions/blocklist;1", - gFactory); -} - -function unregisterFakeBlocklistService() { - if (gFactory != null ) { - gReg.unregisterFactory(gFakeBlocklistServiceCID, gFactory); - gFactory = null; - // This should restore the original blocklist service: - gReg.registerFactory(gRealBlocklistServiceCID, - "Blocklist Service", - "@mozilla.org/extensions/blocklist;1", - null); - } + setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml", + function() { + prepareTest(test18a, gHttpTestRoot + "plugin_test.html"); + }); } // Tests a vulnerable, updatable plugin @@ -613,9 +570,10 @@ function test18b() { var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox"); ok(overlay.style.visibility != "hidden", "Test 18b, Plugin overlay should exist, not be hidden"); - unregisterFakeBlocklistService(); - registerFakeBlocklistService(Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE); - prepareTest(test18c, gHttpTestRoot + "plugin_test.html"); + setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableNoUpdate.xml", + function() { + prepareTest(test18c, gHttpTestRoot + "plugin_test.html"); + }); } // Tests a vulnerable plugin with no update @@ -658,10 +616,10 @@ function test18e() { var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); ok(objLoadingContent.activated, "Test 18e, Plugin should be activated"); - unregisterFakeBlocklistService(); Services.perms.removeAll(); - - prepareTest(test19a, gTestRoot + "plugin_test.html"); + resetBlocklist(function () { + prepareTest(test19a, gTestRoot + "plugin_test.html"); + }); } // Tests that clicking the icon of the overlay activates the plugin diff --git a/browser/base/content/test/head.js b/browser/base/content/test/head.js index 9ec29374b092..727aac2fa68b 100644 --- a/browser/base/content/test/head.js +++ b/browser/base/content/test/head.js @@ -201,6 +201,27 @@ function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) { runNextTest(); } +function updateBlocklist(aCallback) { + var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"] + .getService(Ci.nsITimerCallback); + var observer = function() { + aCallback(); + Services.obs.removeObserver(observer, "blocklist-updated"); + }; + Services.obs.addObserver(observer, "blocklist-updated", false); + blocklistNotifier.notify(null); +} + +function setAndUpdateBlocklist(aURL, aCallback) { + Services.prefs.setCharPref("extensions.blocklist.url", aURL); + updateBlocklist(aCallback); +} + +function resetBlocklist(aCallback) { + Services.prefs.clearUserPref("extensions.blocklist.url"); + updateBlocklist(aCallback); +} + function whenNewWindowLoaded(aOptions, aCallback) { let win = OpenBrowserWindow(aOptions); win.addEventListener("load", function onLoad() { From 58eba6e6b719e8ddb801c7855f14b379b75af9c8 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Mon, 26 Nov 2012 09:52:19 -0800 Subject: [PATCH 042/160] Bug 564815 - Tests for window.devicePixelRatio --- dom/tests/mochitest/general/test_innerScreen.xul | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dom/tests/mochitest/general/test_innerScreen.xul b/dom/tests/mochitest/general/test_innerScreen.xul index 907a5a9d31bb..e94ebd3eb086 100644 --- a/dom/tests/mochitest/general/test_innerScreen.xul +++ b/dom/tests/mochitest/general/test_innerScreen.xul @@ -38,6 +38,9 @@ function doTests() var domWindowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIDOMWindowUtils); var devPxPerCSSPx = domWindowUtils.screenPixelsPerCSSPixel; + + is(window.devicePixelRatio, devPxPerCSSPx, "window.devicePixelRatio"); + var windowBO = document.documentElement.boxObject; isRounded(window.mozInnerScreenX*devPxPerCSSPx, windowBO.screenX, "window screen X"); @@ -64,6 +67,8 @@ function doTests() is(frameDomWindowUtils.screenPixelsPerCSSPixel, 2*devPxPerCSSPx, "frame screen pixels per CSS pixel"); + is(f.contentWindow.devicePixelRatio, 2*devPxPerCSSPx, "frame devicePixelRatio"); + isRounded(f.contentWindow.mozInnerScreenX*2, window.mozInnerScreenX + fBounds.left, "zoomed frame screen X"); From ce71c8bd00b7fcba86fbadd9032e1a98692378db Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Mon, 26 Nov 2012 13:08:00 -0500 Subject: [PATCH 043/160] Bug 815001 - Pass in the privacy information for the view sourced document to the web browser persist service when using an external view source program; r=jdm --- .../viewsource/content/viewSourceUtils.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/toolkit/components/viewsource/content/viewSourceUtils.js b/toolkit/components/viewsource/content/viewSourceUtils.js index 56d09afd9212..91e1ede5a7ba 100644 --- a/toolkit/components/viewsource/content/viewSourceUtils.js +++ b/toolkit/components/viewsource/content/viewSourceUtils.js @@ -111,14 +111,6 @@ var gViewSourceUtils = { var file = this.getTemporaryFile(uri, aDocument, contentType); this.viewSourceProgressListener.file = file; - var webBrowserPersist = Components - .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] - .createInstance(this.mnsIWebBrowserPersist); - // the default setting is to not decode. we need to decode. - webBrowserPersist.persistFlags = this.mnsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES; - webBrowserPersist.progressListener = this.viewSourceProgressListener; - webBrowserPersist.saveURI(uri, null, null, null, null, file); - let fromPrivateWindow = false; if (aDocument) { try { @@ -132,6 +124,14 @@ var gViewSourceUtils = { } } + var webBrowserPersist = Components + .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(this.mnsIWebBrowserPersist); + // the default setting is to not decode. we need to decode. + webBrowserPersist.persistFlags = this.mnsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES; + webBrowserPersist.progressListener = this.viewSourceProgressListener; + webBrowserPersist.savePrivacyAwareURI(uri, null, null, null, null, file, fromPrivateWindow); + let helperService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"] .getService(Components.interfaces.nsPIExternalAppLauncher); if (fromPrivateWindow) { From 340359b404b0d1ddb2c02845b4988cc9e4ed3b99 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Mon, 26 Nov 2012 19:13:53 +0100 Subject: [PATCH 044/160] Bug 813277 - Unable to long-tap hold on the home-screen to invoke the wallpaper menu r=smaug --- content/events/src/nsEventStateManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/content/events/src/nsEventStateManager.cpp b/content/events/src/nsEventStateManager.cpp index 277032396de1..2239f722f004 100644 --- a/content/events/src/nsEventStateManager.cpp +++ b/content/events/src/nsEventStateManager.cpp @@ -1927,6 +1927,9 @@ nsEventStateManager::FireContextClick() } } + nsIDocument* doc = mGestureDownContent->GetCurrentDoc(); + nsAutoHandlingUserInputStatePusher userInpStatePusher(true, &event, doc); + // dispatch to DOM nsEventDispatcher::Dispatch(mGestureDownContent, mPresContext, &event, nullptr, &status); From 3ab6be9a62e0b4ad417ac031e50101cd90fb39b4 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Thu, 22 Nov 2012 15:50:06 -0500 Subject: [PATCH 045/160] Bug 814513 - Enable browser_save_link-perwindowpb.js. r=ehsan --- browser/base/content/test/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/base/content/test/Makefile.in b/browser/base/content/test/Makefile.in index 268359380db2..a60f5334c7cb 100644 --- a/browser/base/content/test/Makefile.in +++ b/browser/base/content/test/Makefile.in @@ -304,7 +304,7 @@ ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING _BROWSER_FILES += \ browser_bug763468_perwindowpb.js \ browser_private_browsing_window.js \ - $(filter disabled-until-bug-722850, browser_save_link-perwindowpb.js) \ + browser_save_link-perwindowpb.js \ $(NULL) else _BROWSER_FILES += \ From e69205c689e7877c066c76898c180b26abfa2b67 Mon Sep 17 00:00:00 2001 From: Suhas Nandakumar Date: Mon, 26 Nov 2012 08:38:14 -0800 Subject: [PATCH 046/160] Bug 814734 - Fixed Log format string mismatches r=ekr --- .../src/media-conduit/AudioConduit.cpp | 20 +++++++++---------- .../src/media-conduit/VideoConduit.cpp | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp index 3a5c6cc21ec9..75deb9ca5851 100644 --- a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp @@ -265,7 +265,7 @@ WebrtcAudioConduit::ConfigureRecvMediaCodecs( { if( mPtrVoEBase->LastError() == VE_CANNOT_STOP_PLAYOUT) { - CSFLogDebug(logTag, "%s Stop-Playout Failed %d", mPtrVoEBase->LastError()); + CSFLogDebug(logTag, "%s Stop-Playout Failed %d", __FUNCTION__, mPtrVoEBase->LastError()); return kMediaConduitPlayoutError; } } @@ -327,7 +327,7 @@ WebrtcAudioConduit::ConfigureRecvMediaCodecs( if(mPtrVoEBase->StartReceive(mChannel) == -1) { error = mPtrVoEBase->LastError(); - CSFLogError(logTag , "StartReceive Failed %d ",error); + CSFLogError(logTag , "%s StartReceive Failed %d ",__FUNCTION__, error); if(error == VE_RECV_SOCKET_ERROR) { return kMediaConduitSocketError; @@ -338,7 +338,7 @@ WebrtcAudioConduit::ConfigureRecvMediaCodecs( if(mPtrVoEBase->StartPlayout(mChannel) == -1) { - CSFLogError(logTag, "Starting playout Failed"); + CSFLogError(logTag, "%s Starting playout Failed", __FUNCTION__); return kMediaConduitPlayoutError; } @@ -397,7 +397,7 @@ WebrtcAudioConduit::SendAudioFrame(const int16_t audio_data[], capture_delay) == -1) { int error = mPtrVoEBase->LastError(); - CSFLogError(logTag, "Inserting audio data Failed %d", error); + CSFLogError(logTag, "%s Inserting audio data Failed %d", __FUNCTION__, error); if(error == VE_RUNTIME_REC_ERROR) { return kMediaConduitRecordingError; @@ -459,7 +459,7 @@ WebrtcAudioConduit::GetAudioFrame(int16_t speechData[], lengthSamples) == -1) { int error = mPtrVoEBase->LastError(); - CSFLogError(logTag, "Getting audio data Failed %d", error); + CSFLogError(logTag, "%s Getting audio data Failed %d", __FUNCTION__, error); if(error == VE_RUNTIME_PLAY_ERROR) { return kMediaConduitPlayoutError; @@ -483,7 +483,7 @@ WebrtcAudioConduit::ReceivedRTPPacket(const void *data, int len) if(mPtrVoENetwork->ReceivedRTPPacket(mChannel,data,len) == -1) { int error = mPtrVoEBase->LastError(); - CSFLogError(logTag, "%s RTP Processing Error %d ", error); + CSFLogError(logTag, "%s RTP Processing Error %d", __FUNCTION__, error); if(error == VE_RTP_RTCP_MODULE_ERROR) { return kMediaConduitRTPRTCPModuleError; @@ -492,7 +492,7 @@ WebrtcAudioConduit::ReceivedRTPPacket(const void *data, int len) } } else { //engine not receiving - CSFLogError(logTag, "ReceivedRTPPacket: Engine Error"); + CSFLogError(logTag, "%s ReceivedRTPPacket: Engine Error", __FUNCTION__); return kMediaConduitSessionNotInited; } //good here @@ -502,14 +502,14 @@ WebrtcAudioConduit::ReceivedRTPPacket(const void *data, int len) MediaConduitErrorCode WebrtcAudioConduit::ReceivedRTCPPacket(const void *data, int len) { - CSFLogDebug(logTag, "%s : channel %d ",__FUNCTION__, mChannel); + CSFLogDebug(logTag, "%s : channel %d",__FUNCTION__, mChannel); if(mEngineReceiving) { if(mPtrVoENetwork->ReceivedRTCPPacket(mChannel, data, len) == -1) { int error = mPtrVoEBase->LastError(); - CSFLogError(logTag, "%s RTCP Processing Error %d ", error); + CSFLogError(logTag, "%s RTCP Processing Error %d", __FUNCTION__, error); if(error == VE_RTP_RTCP_MODULE_ERROR) { return kMediaConduitRTPRTCPModuleError; @@ -518,7 +518,7 @@ WebrtcAudioConduit::ReceivedRTCPPacket(const void *data, int len) } } else { //engine not running - CSFLogError(logTag, "ReceivedRTPPacket: Engine Error"); + CSFLogError(logTag, "%s ReceivedRTPPacket: Engine Error", __FUNCTION__); return kMediaConduitSessionNotInited; } //good here diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp index 5e2f9a84e1de..225c983cb394 100644 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -133,7 +133,7 @@ MediaConduitErrorCode WebrtcVideoConduit::Init() } - CSFLogDebug(logTag, "%sEngine Created: Init'ng the interfaces ",__FUNCTION__); + CSFLogDebug(logTag, "%s Engine Created: Init'ng the interfaces ",__FUNCTION__); if(mPtrViEBase->Init() == -1) { @@ -184,7 +184,7 @@ MediaConduitErrorCode WebrtcVideoConduit::Init() } - CSFLogError(logTag, "Initialization Done"); + CSFLogError(logTag, "%s Initialization Done", __FUNCTION__); return kMediaConduitNoError; } From 901b5336db107f667fad159f8a276ae789ded386 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Mon, 26 Nov 2012 14:04:07 -0500 Subject: [PATCH 047/160] Bug 815229 - Make search completion test for private browsing test all autocomplete entries. r=ehsan --- browser/components/search/test/browser_private_search.js | 2 +- .../search/test/browser_private_search_perwindowpb.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/components/search/test/browser_private_search.js b/browser/components/search/test/browser_private_search.js index 6ef21950dec3..baf5d0b432e8 100644 --- a/browser/components/search/test/browser_private_search.js +++ b/browser/components/search/test/browser_private_search.js @@ -22,7 +22,7 @@ function doPrivateTest(searchBar) { popup.addEventListener("popupshowing", function showing() { let entries = getMenuEntries(searchBar); for (var i = 0; i < entries.length; i++) - isnot(entries[0], "private test", "shouldn't see private autocomplete entries"); + isnot(entries[i], "private test", "shouldn't see private autocomplete entries"); popup.removeEventListener("popupshowing", showing, false); searchBar.textbox.toggleHistoryPopup(); diff --git a/browser/components/search/test/browser_private_search_perwindowpb.js b/browser/components/search/test/browser_private_search_perwindowpb.js index 2c62df91b8ca..71e01b6c5b4a 100644 --- a/browser/components/search/test/browser_private_search_perwindowpb.js +++ b/browser/components/search/test/browser_private_search_perwindowpb.js @@ -89,7 +89,7 @@ function checkSearchPopup(aWin, aCallback) { let entries = getMenuEntries(searchBar); for (let i = 0; i < entries.length; i++) { - isnot(entries[0], "private test", + isnot(entries[i], "private test", "shouldn't see private autocomplete entries"); } From ed24acb94f13658d383c85c5689a1fe3a042d323 Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Fri, 23 Nov 2012 11:13:39 -0800 Subject: [PATCH 048/160] Bug 814738 - Modernize Traverse for nsTObserverArray. r=smaug --- accessible/src/base/nsAccessiblePivot.cpp | 17 +---------------- content/events/src/nsEventListenerManager.cpp | 15 ++++++++++----- dom/base/nsGlobalWindow.cpp | 14 ++++++++++---- xpcom/glue/nsTObserverArray.h | 8 ++++---- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/accessible/src/base/nsAccessiblePivot.cpp b/accessible/src/base/nsAccessiblePivot.cpp index 5a44ca43b8eb..254a68d7ddbf 100644 --- a/accessible/src/base/nsAccessiblePivot.cpp +++ b/accessible/src/base/nsAccessiblePivot.cpp @@ -54,22 +54,7 @@ nsAccessiblePivot::nsAccessiblePivot(Accessible* aRoot) : //////////////////////////////////////////////////////////////////////////////// // nsISupports -NS_IMPL_CYCLE_COLLECTION_CLASS(nsAccessiblePivot) - -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAccessiblePivot) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPosition) - uint32_t i, length = tmp->mObservers.Length(); - for (i = 0; i < length; ++i) { - cb.NoteXPCOMChild(tmp->mObservers.ElementAt(i)); - } -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END - -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAccessiblePivot) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mPosition) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers) -NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_3(nsAccessiblePivot, mRoot, mPosition, mObservers) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccessiblePivot) NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivot) diff --git a/content/events/src/nsEventListenerManager.cpp b/content/events/src/nsEventListenerManager.cpp index 7477adca4866..6dda2b5dcba1 100644 --- a/content/events/src/nsEventListenerManager.cpp +++ b/content/events/src/nsEventListenerManager.cpp @@ -150,12 +150,17 @@ NS_IMPL_CYCLE_COLLECTION_NATIVE_CLASS(nsEventListenerManager) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsEventListenerManager, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsEventListenerManager, Release) +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + nsListenerStruct& aField, + const char* aName, + unsigned aFlags) +{ + CycleCollectionNoteChild(aCallback, aField.mListener.get(), aName, aFlags); +} + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsEventListenerManager) - uint32_t count = tmp->mListeners.Length(); - for (uint32_t i = 0; i < count; i++) { - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mListeners[i] mListener"); - cb.NoteXPCOMChild(tmp->mListeners.ElementAt(i).mListener.get()); - } + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsEventListenerManager) diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index a7a077195e54..b1a53b15cfe2 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1210,6 +1210,15 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGlobalWindow) return tmp->IsBlackForCC(); NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + IdleObserverHolder& aField, + const char* aName, + unsigned aFlags) +{ + CycleCollectionNoteChild(aCallback, aField.mIdleObserver.get(), aName, aFlags); +} + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow) if (MOZ_UNLIKELY(cb.WantDebugInfo())) { char name[512]; @@ -1259,10 +1268,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents) - for (uint32_t i = 0; i < tmp->mIdleObservers.Length(); i++) { - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mIdleObservers[i].nsIIdleObserverPtr"); - cb.NoteXPCOMChild(tmp->mIdleObservers.ElementAt(i).mIdleObserver.get()); - } + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow) diff --git a/xpcom/glue/nsTObserverArray.h b/xpcom/glue/nsTObserverArray.h index 67c50e201c0e..f377ad094d71 100644 --- a/xpcom/glue/nsTObserverArray.h +++ b/xpcom/glue/nsTObserverArray.h @@ -370,17 +370,17 @@ class nsTObserverArray : public nsAutoTObserverArray { } }; -template +template inline void -ImplCycleCollectionUnlink(nsTObserverArray& aField) +ImplCycleCollectionUnlink(nsAutoTObserverArray& aField) { aField.Clear(); } -template +template inline void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, - nsTObserverArray& aField, + nsAutoTObserverArray& aField, const char* aName, uint32_t aFlags = 0) { From ead425b3e03994eabd7c8cb25e61420bc6f5d95c Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Fri, 23 Nov 2012 11:27:06 -0800 Subject: [PATCH 049/160] Bug 814453 - Eliminate NS_IMPL_CYCLE_COLLECTION_UNLINK_NATIVE_0 and NS_IMPL_CYCLE_COLLECTION_TRACE_NATIVE_BEGIN. r=smaug --- content/base/src/nsNodeInfoManager.cpp | 2 +- content/media/webaudio/AudioContext.cpp | 6 +----- content/media/webaudio/AudioListener.cpp | 5 ++--- content/media/webaudio/AudioParam.cpp | 4 +--- content/xul/content/src/nsXULElement.cpp | 2 +- dom/base/nsGlobalWindow.cpp | 2 +- xpcom/glue/nsCycleCollectionParticipant.h | 19 ++----------------- 7 files changed, 9 insertions(+), 31 deletions(-) diff --git a/content/base/src/nsNodeInfoManager.cpp b/content/base/src/nsNodeInfoManager.cpp index 9b0631d39fdd..ac3d430256e4 100644 --- a/content/base/src/nsNodeInfoManager.cpp +++ b/content/base/src/nsNodeInfoManager.cpp @@ -131,7 +131,7 @@ nsNodeInfoManager::~nsNodeInfoManager() NS_IMPL_CYCLE_COLLECTION_NATIVE_CLASS(nsNodeInfoManager) -NS_IMPL_CYCLE_COLLECTION_UNLINK_NATIVE_0(nsNodeInfoManager) +NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsNodeInfoManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNodeInfoManager) if (tmp->mDocument && nsCCUncollectableMarker::InGeneration(cb, diff --git a/content/media/webaudio/AudioContext.cpp b/content/media/webaudio/AudioContext.cpp index c7ed39ecd218..ce589933c691 100644 --- a/content/media/webaudio/AudioContext.cpp +++ b/content/media/webaudio/AudioContext.cpp @@ -32,14 +32,10 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDestination) - // Cannot use NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR since AudioListener - // does not inherit from nsISupports. NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListener) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END -NS_IMPL_CYCLE_COLLECTION_TRACE_NATIVE_BEGIN(AudioContext) - NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER -NS_IMPL_CYCLE_COLLECTION_TRACE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(AudioContext) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioContext, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioContext, Release) diff --git a/content/media/webaudio/AudioListener.cpp b/content/media/webaudio/AudioListener.cpp index ef07e766cea0..408901d0bb2c 100644 --- a/content/media/webaudio/AudioListener.cpp +++ b/content/media/webaudio/AudioListener.cpp @@ -25,9 +25,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioListener) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END -NS_IMPL_CYCLE_COLLECTION_TRACE_NATIVE_BEGIN(AudioListener) - NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER -NS_IMPL_CYCLE_COLLECTION_TRACE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(AudioListener) + NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioListener, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioListener, Release) diff --git a/content/media/webaudio/AudioParam.cpp b/content/media/webaudio/AudioParam.cpp index f7070f126d5d..b760dce04841 100644 --- a/content/media/webaudio/AudioParam.cpp +++ b/content/media/webaudio/AudioParam.cpp @@ -22,9 +22,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioParam) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END -NS_IMPL_CYCLE_COLLECTION_TRACE_NATIVE_BEGIN(AudioParam) - NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER -NS_IMPL_CYCLE_COLLECTION_TRACE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(AudioParam) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioParam, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioParam, Release) diff --git a/content/xul/content/src/nsXULElement.cpp b/content/xul/content/src/nsXULElement.cpp index e38d56a8d4ba..2410fac5b1d9 100644 --- a/content/xul/content/src/nsXULElement.cpp +++ b/content/xul/content/src/nsXULElement.cpp @@ -1855,7 +1855,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeNode) } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END -NS_IMPL_CYCLE_COLLECTION_TRACE_NATIVE_BEGIN(nsXULPrototypeNode) +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXULPrototypeNode) if (tmp->mType == nsXULPrototypeNode::eType_Script) { nsXULPrototypeScript *script = static_cast(tmp); diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index b1a53b15cfe2..0c2533ae640b 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -487,7 +487,7 @@ nsTimeout::~nsTimeout() } NS_IMPL_CYCLE_COLLECTION_LEGACY_NATIVE_CLASS(nsTimeout) -NS_IMPL_CYCLE_COLLECTION_UNLINK_NATIVE_0(nsTimeout) +NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsTimeout) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTimeout) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal) diff --git a/xpcom/glue/nsCycleCollectionParticipant.h b/xpcom/glue/nsCycleCollectionParticipant.h index 42aa397244aa..af1a0545daf8 100644 --- a/xpcom/glue/nsCycleCollectionParticipant.h +++ b/xpcom/glue/nsCycleCollectionParticipant.h @@ -403,20 +403,8 @@ T* DowncastCCParticipant(void *p) } #define NS_IMPL_CYCLE_COLLECTION_UNLINK_0(_class) \ - NS_METHOD \ - NS_CYCLE_COLLECTION_CLASSNAME(_class)::UnlinkImpl(void *p) \ - { \ - MOZ_ASSERT(CheckForRightISupports(static_cast(p)), \ - "not the nsISupports pointer we expect"); \ - return NS_OK; \ - } - -#define NS_IMPL_CYCLE_COLLECTION_UNLINK_NATIVE_0(_class) \ - NS_METHOD \ - NS_CYCLE_COLLECTION_CLASSNAME(_class)::UnlinkImpl(void *p) \ - { \ - return NS_OK; \ - } + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END /////////////////////////////////////////////////////////////////////////////// @@ -483,9 +471,6 @@ T* DowncastCCParticipant(void *p) aCallback, \ aClosure); -#define NS_IMPL_CYCLE_COLLECTION_TRACE_NATIVE_BEGIN(_class) \ - NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) - #define NS_IMPL_CYCLE_COLLECTION_TRACE_JS_CALLBACK(_object, _name) \ if (_object) \ aCallback(_object, _name, aClosure); From db826ecfb96ae765a9f463ac75201283884131cb Mon Sep 17 00:00:00 2001 From: Sriram Ramasubramanian Date: Wed, 21 Nov 2012 11:53:25 -0800 Subject: [PATCH 050/160] Bug 792373: XHDPI and XXHDPI icons for Fennec. [r=mfinkle] --- mobile/android/base/Makefile.in | 14 ++++++++++++-- .../branding/aurora/content/fennec_144x144.png | Bin 0 -> 33141 bytes .../branding/aurora/content/fennec_96x96.png | Bin 0 -> 17360 bytes .../branding/beta/content/fennec_144x144.png | Bin 0 -> 28774 bytes .../branding/beta/content/fennec_96x96.png | Bin 0 -> 16011 bytes .../branding/nightly/content/fennec_144x144.png | Bin 0 -> 29988 bytes .../branding/nightly/content/fennec_96x96.png | Bin 0 -> 15997 bytes .../official/content/fennec_144x144.png | Bin 0 -> 30481 bytes .../branding/official/content/fennec_96x96.png | Bin 0 -> 16471 bytes .../unofficial/content/fennec_144x144.png | Bin 0 -> 19935 bytes .../unofficial/content/fennec_96x96.png | Bin 0 -> 11976 bytes 11 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 mobile/android/branding/aurora/content/fennec_144x144.png create mode 100644 mobile/android/branding/aurora/content/fennec_96x96.png create mode 100644 mobile/android/branding/beta/content/fennec_144x144.png create mode 100644 mobile/android/branding/beta/content/fennec_96x96.png create mode 100644 mobile/android/branding/nightly/content/fennec_144x144.png create mode 100644 mobile/android/branding/nightly/content/fennec_96x96.png create mode 100644 mobile/android/branding/official/content/fennec_144x144.png create mode 100644 mobile/android/branding/official/content/fennec_96x96.png create mode 100644 mobile/android/branding/unofficial/content/fennec_144x144.png create mode 100644 mobile/android/branding/unofficial/content/fennec_96x96.png diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 33a01cea9b71..b34b6c2ef2c2 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -305,6 +305,8 @@ MOZ_ANDROID_SHARED_ACCOUNT_TYPE = "$(ANDROID_PACKAGE_NAME)_sync" ifeq ($(MOZ_APP_NAME),fennec) ICON_PATH = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_48x48.png ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_72x72.png +ICON_PATH_XHDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_96x96.png +ICON_PATH_XXHDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_144x144.png # we released these builds to the public with shared IDs and need to keep them ifeq (org.mozilla.firefox,$(ANDROID_PACKAGE_NAME)) @@ -1131,6 +1133,14 @@ res/drawable-hdpi/icon.png: $(MOZ_APP_ICON) $(NSINSTALL) -D res/drawable-hdpi cp $(ICON_PATH_HDPI) $@ +res/drawable-xhdpi/icon.png: $(MOZ_APP_ICON) + $(NSINSTALL) -D res/drawable-xhdpi + cp $(ICON_PATH_XHDPI) $@ + +res/drawable-xxhdpi/icon.png: $(MOZ_APP_ICON) + $(NSINSTALL) -D res/drawable-xxhdpi + cp $(ICON_PATH_XXHDPI) $@ + RES_DRAWABLE = $(addprefix res/drawable/,$(notdir $(MOZ_ANDROID_DRAWABLES))) $(RES_DRAWABLE): $(addprefix $(topsrcdir)/,$(MOZ_ANDROID_DRAWABLES)) $(NSINSTALL) -D res/drawable @@ -1164,10 +1174,10 @@ $(RESOURCES): $(RES_DIRS) $(subst res/,$(srcdir)/resources/,$(RESOURCES)) $(NSINSTALL) $(subst res/,$(srcdir)/resources/,$@) $(dir $@) -R.java: $(MOZ_APP_ICON) $(RESOURCES) $(RES_DRAWABLE) $(RES_BRANDING_DRAWABLE_MDPI) $(RES_BRANDING_DRAWABLE_HDPI) $(RES_BRANDING_DRAWABLE_XHDPI) $(PP_RES_XML) res/values/defaults.xml res/drawable/icon.png res/drawable-hdpi/icon.png res/values/strings.xml AndroidManifest.xml FORCE +R.java: $(MOZ_APP_ICON) $(RESOURCES) $(RES_DRAWABLE) $(RES_BRANDING_DRAWABLE_MDPI) $(RES_BRANDING_DRAWABLE_HDPI) $(RES_BRANDING_DRAWABLE_XHDPI) $(PP_RES_XML) res/values/defaults.xml res/drawable/icon.png res/drawable-hdpi/icon.png res/drawable-xhdpi/icon.png res/drawable-xxhdpi/icon.png res/values/strings.xml AndroidManifest.xml FORCE $(AAPT) package -f -M AndroidManifest.xml -I $(ANDROID_SDK)/android.jar -S res -J . --custom-package org.mozilla.gecko -gecko.ap_: AndroidManifest.xml res/drawable/icon.png res/drawable-hdpi/icon.png $(RESOURCES) $(RES_DRAWABLE) $(RES_BRANDING_DRAWABLE_MDPI) $(RES_BRANDING_DRAWABLE_HDPI) $(RES_BRANDING_DRAWABLE_XHDPI) $(PP_RES_XML) res/values/defaults.xml res/values/strings.xml FORCE +gecko.ap_: AndroidManifest.xml res/drawable/icon.png res/drawable-hdpi/icon.png res/drawable-xhdpi/icon.png res/drawable-xxhdpi/icon.png $(RESOURCES) $(RES_DRAWABLE) $(RES_BRANDING_DRAWABLE_MDPI) $(RES_BRANDING_DRAWABLE_HDPI) $(RES_BRANDING_DRAWABLE_XHDPI) $(PP_RES_XML) res/values/defaults.xml res/values/strings.xml FORCE $(AAPT) package -f -M AndroidManifest.xml -I $(ANDROID_SDK)/android.jar -S res -F $@ libs:: classes.dex package-name.txt diff --git a/mobile/android/branding/aurora/content/fennec_144x144.png b/mobile/android/branding/aurora/content/fennec_144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..1f7da46624173b82bc7e21e14e30be447cf255aa GIT binary patch literal 33141 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

s~vz@uG{s@B?0kBwrg)MmD9sBHAlY(lR_IjlPzq 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 qccRl|TCjA!` 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

d8u#Ti3jlM|at-gL(EeYet~}`vzF&JZ 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 Wvt-(^7yWZQTuMw{v{u+K`2PSieS@|D literal 0 HcmV?d00001 diff --git a/mobile/android/branding/nightly/content/fennec_144x144.png b/mobile/android/branding/nightly/content/fennec_144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..c48c696784f313082a7e81fe1d9d519138036408 GIT binary patch literal 29988 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�b+o2ptKCwX^X+!W&xd^o~? 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 literal 0 HcmV?d00001 diff --git a/mobile/android/branding/official/content/fennec_96x96.png b/mobile/android/branding/official/content/fennec_96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..fabf8bf998ec50adf15aae09ba91b55d92ed9a86 GIT binary patch literal 16471 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 literal 0 HcmV?d00001 diff --git a/mobile/android/branding/unofficial/content/fennec_144x144.png b/mobile/android/branding/unofficial/content/fennec_144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..91974a1966fe43a54ff7a3d6243a1ab24e17903c GIT binary patch literal 19935 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 Rg0TPq002ovPDHLkV1hrMhMfQa literal 0 HcmV?d00001 diff --git a/mobile/android/branding/unofficial/content/fennec_96x96.png b/mobile/android/branding/unofficial/content/fennec_96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..27e348b0d4af9aee666a42dc80bb682a11f17d7a GIT binary patch literal 11976 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 literal 0 HcmV?d00001 From 65a2d009d3c022a239dd971543bdc930fe673f02 Mon Sep 17 00:00:00 2001 From: Jeff Gilbert Date: Mon, 26 Nov 2012 14:23:27 -0800 Subject: [PATCH 051/160] Bug 811958 - Pull GLContext out of Cocoa stuff - r=bgirard --- gfx/gl/GLContext.h | 19 +--- gfx/gl/GLContextTypes.h | 42 ++++++++ gfx/gl/Makefile.in | 1 + gfx/layers/opengl/LayerManagerOGL.cpp | 88 ++++++++++++++- gfx/layers/opengl/LayerManagerOGL.h | 98 ++++------------- gfx/layers/opengl/LayerManagerOGLProgram.cpp | 107 +++++++++++++++++++ gfx/layers/opengl/LayerManagerOGLProgram.h | 96 +++-------------- gfx/layers/opengl/ReusableTileStoreOGL.cpp | 2 + widget/cocoa/nsChildView.mm | 6 +- 9 files changed, 277 insertions(+), 182 deletions(-) create mode 100644 gfx/gl/GLContextTypes.h diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index b109b782578c..38b26722f416 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -32,6 +32,7 @@ #include "nsRegion.h" #include "nsAutoPtr.h" #include "nsThreadUtils.h" +#include "GLContextTypes.h" typedef char realGLboolean; @@ -55,24 +56,6 @@ class GLContext; typedef uintptr_t SharedTextureHandle; -enum ShaderProgramType { - RGBALayerProgramType, - RGBALayerExternalProgramType, - BGRALayerProgramType, - RGBXLayerProgramType, - BGRXLayerProgramType, - RGBARectLayerProgramType, - RGBAExternalLayerProgramType, - ColorLayerProgramType, - YCbCrLayerProgramType, - ComponentAlphaPass1ProgramType, - ComponentAlphaPass2ProgramType, - Copy2DProgramType, - Copy2DRectProgramType, - NumProgramTypes -}; - - /** * A TextureImage encapsulates a surface that can be drawn to by a * Thebes gfxContext and (hopefully efficiently!) synchronized to a diff --git a/gfx/gl/GLContextTypes.h b/gfx/gl/GLContextTypes.h new file mode 100644 index 000000000000..948c67933a2a --- /dev/null +++ b/gfx/gl/GLContextTypes.h @@ -0,0 +1,42 @@ +/* -*- 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/. */ + +#ifndef GLCONTEXTSTUFF_H_ +#define GLCONTEXTSTUFF_H_ + +/** + * We don't include GLDefs.h here since we don't want to drag in all defines + * in for all our users. + */ +typedef unsigned int GLenum; +typedef unsigned int GLbitfield; +typedef unsigned int GLuint; +typedef int GLint; +typedef int GLsizei; + +namespace mozilla { +namespace gl { + +enum ShaderProgramType { + RGBALayerProgramType, + RGBALayerExternalProgramType, + BGRALayerProgramType, + RGBXLayerProgramType, + BGRXLayerProgramType, + RGBARectLayerProgramType, + RGBAExternalLayerProgramType, + ColorLayerProgramType, + YCbCrLayerProgramType, + ComponentAlphaPass1ProgramType, + ComponentAlphaPass2ProgramType, + Copy2DProgramType, + Copy2DRectProgramType, + NumProgramTypes +}; + +} // namespace gl +} // namespace mozilla + +#endif /* GLCONTEXTSTUFF_H_ */ diff --git a/gfx/gl/Makefile.in b/gfx/gl/Makefile.in index 4d8928ee8f8a..e3729a278007 100644 --- a/gfx/gl/Makefile.in +++ b/gfx/gl/Makefile.in @@ -18,6 +18,7 @@ FAIL_ON_WARNINGS = 1 EXPORTS = \ GLDefs.h \ GLContext.h \ + GLContextTypes.h \ GLContextSymbols.h \ GLContextProvider.h \ GLContextProviderImpl.h \ diff --git a/gfx/layers/opengl/LayerManagerOGL.cpp b/gfx/layers/opengl/LayerManagerOGL.cpp index 7f794318f349..21afd011e224 100644 --- a/gfx/layers/opengl/LayerManagerOGL.cpp +++ b/gfx/layers/opengl/LayerManagerOGL.cpp @@ -3,13 +3,14 @@ * 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 "LayerManagerOGL.h" + #include "mozilla/layers/PLayers.h" /* This must occur *after* layers/PLayers.h to avoid typedefs conflicts. */ #include "mozilla/Util.h" #include "Composer2D.h" -#include "LayerManagerOGL.h" #include "ThebesLayerOGL.h" #include "ContainerLayerOGL.h" #include "ImageLayerOGL.h" @@ -52,6 +53,91 @@ using namespace mozilla::gl; int ShaderProgramOGL::sCurrentProgramKey = 0; #endif +bool +LayerManagerOGL::Initialize(bool force) +{ + return Initialize(CreateContext(), force); +} + +int32_t +LayerManagerOGL::GetMaxTextureSize() const +{ + return mGLContext->GetMaxTextureSize(); +} + +void +LayerManagerOGL::MakeCurrent(bool aForce) +{ + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return; + } + mGLContext->MakeCurrent(aForce); +} + +void* +LayerManagerOGL::GetNSOpenGLContext() const +{ + return gl()->GetNativeData(GLContext::NativeGLContext); +} + + +void +LayerManagerOGL::BindQuadVBO() { + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); +} + +void +LayerManagerOGL::QuadVBOVerticesAttrib(GLuint aAttribIndex) { + mGLContext->fVertexAttribPointer(aAttribIndex, 2, + LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, + (GLvoid*) QuadVBOVertexOffset()); +} + +void +LayerManagerOGL::QuadVBOTexCoordsAttrib(GLuint aAttribIndex) { + mGLContext->fVertexAttribPointer(aAttribIndex, 2, + LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, + (GLvoid*) QuadVBOTexCoordOffset()); +} + +void +LayerManagerOGL::QuadVBOFlippedTexCoordsAttrib(GLuint aAttribIndex) { + mGLContext->fVertexAttribPointer(aAttribIndex, 2, + LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, + (GLvoid*) QuadVBOFlippedTexCoordOffset()); +} + +// Super common + +void +LayerManagerOGL::BindAndDrawQuad(GLuint aVertAttribIndex, + GLuint aTexCoordAttribIndex, + bool aFlipped) +{ + BindQuadVBO(); + QuadVBOVerticesAttrib(aVertAttribIndex); + + if (aTexCoordAttribIndex != GLuint(-1)) { + if (aFlipped) + QuadVBOFlippedTexCoordsAttrib(aTexCoordAttribIndex); + else + QuadVBOTexCoordsAttrib(aTexCoordAttribIndex); + + mGLContext->fEnableVertexAttribArray(aTexCoordAttribIndex); + } + + mGLContext->fEnableVertexAttribArray(aVertAttribIndex); + + mGLContext->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4); + + mGLContext->fDisableVertexAttribArray(aVertAttribIndex); + + if (aTexCoordAttribIndex != GLuint(-1)) { + mGLContext->fDisableVertexAttribArray(aTexCoordAttribIndex); + } +} + static const double kFpsWindowMs = 250.0; static const size_t kNumFrameTimeStamps = 16; struct FPSCounter { diff --git a/gfx/layers/opengl/LayerManagerOGL.h b/gfx/layers/opengl/LayerManagerOGL.h index 623e009e0e64..78059d36a8c9 100644 --- a/gfx/layers/opengl/LayerManagerOGL.h +++ b/gfx/layers/opengl/LayerManagerOGL.h @@ -9,31 +9,23 @@ #include "LayerManagerOGLProgram.h" #include "mozilla/layers/ShadowLayers.h" - #include "mozilla/TimeStamp.h" #ifdef XP_WIN #include #endif -/** - * We don't include GLDefs.h here since we don't want to drag in all defines - * in for all our users. - */ -typedef unsigned int GLenum; -typedef unsigned int GLbitfield; -typedef unsigned int GLuint; -typedef int GLint; -typedef int GLsizei; - #define BUFFER_OFFSET(i) ((char *)NULL + (i)) #include "gfxContext.h" #include "gfx3DMatrix.h" #include "nsIWidget.h" -#include "GLContext.h" +#include "GLContextTypes.h" namespace mozilla { +namespace gl { +class GLContext; +} namespace layers { class Composer2D; @@ -71,9 +63,7 @@ public: * * \return True is initialization was succesful, false when it was not. */ - bool Initialize(bool force = false) { - return Initialize(CreateContext(), force); - } + bool Initialize(bool force = false); bool Initialize(nsRefPtr aContext, bool force = false); @@ -110,18 +100,14 @@ public: virtual void SetRoot(Layer* aLayer) { mRoot = aLayer; } - virtual bool CanUseCanvasLayerForSize(const gfxIntSize &aSize) - { - if (!mGLContext) - return false; - int32_t maxSize = mGLContext->GetMaxTextureSize(); - return aSize <= gfxIntSize(maxSize, maxSize); + virtual bool CanUseCanvasLayerForSize(const gfxIntSize &aSize) { + if (!mGLContext) + return false; + int32_t maxSize = GetMaxTextureSize(); + return aSize <= gfxIntSize(maxSize, maxSize); } - virtual int32_t GetMaxTextureSize() const - { - return mGLContext->GetMaxTextureSize(); - } + virtual int32_t GetMaxTextureSize() const; virtual already_AddRefed CreateThebesLayer(); @@ -151,13 +137,7 @@ public: /** * Helper methods. */ - void MakeCurrent(bool aForce = false) { - if (mDestroyed) { - NS_WARNING("Call on destroyed layer manager"); - return; - } - mGLContext->MakeCurrent(aForce); - } + void MakeCurrent(bool aForce = false); ShaderProgramOGL* GetBasicLayerProgram(bool aOpaque, bool aIsRGB, MaskType aMask = MaskNone) @@ -203,6 +183,9 @@ public: GLContext* gl() const { return mGLContext; } + // |NSOpenGLContext*|: + void* GetNSOpenGLContext() const; + DrawThebesLayerCallback GetThebesLayerCallback() const { return mThebesLayerCallback; } @@ -254,56 +237,15 @@ public: GLintptr QuadVBOTexCoordOffset() { return sizeof(float)*4*2; } GLintptr QuadVBOFlippedTexCoordOffset() { return sizeof(float)*8*2; } - void BindQuadVBO() { - mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); - } - - void QuadVBOVerticesAttrib(GLuint aAttribIndex) { - mGLContext->fVertexAttribPointer(aAttribIndex, 2, - LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, - (GLvoid*) QuadVBOVertexOffset()); - } - - void QuadVBOTexCoordsAttrib(GLuint aAttribIndex) { - mGLContext->fVertexAttribPointer(aAttribIndex, 2, - LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, - (GLvoid*) QuadVBOTexCoordOffset()); - } - - void QuadVBOFlippedTexCoordsAttrib(GLuint aAttribIndex) { - mGLContext->fVertexAttribPointer(aAttribIndex, 2, - LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, - (GLvoid*) QuadVBOFlippedTexCoordOffset()); - } + void BindQuadVBO(); + void QuadVBOVerticesAttrib(GLuint aAttribIndex); + void QuadVBOTexCoordsAttrib(GLuint aAttribIndex); + void QuadVBOFlippedTexCoordsAttrib(GLuint aAttribIndex); // Super common - void BindAndDrawQuad(GLuint aVertAttribIndex, GLuint aTexCoordAttribIndex, - bool aFlipped = false) - { - BindQuadVBO(); - QuadVBOVerticesAttrib(aVertAttribIndex); - - if (aTexCoordAttribIndex != GLuint(-1)) { - if (aFlipped) - QuadVBOFlippedTexCoordsAttrib(aTexCoordAttribIndex); - else - QuadVBOTexCoordsAttrib(aTexCoordAttribIndex); - - mGLContext->fEnableVertexAttribArray(aTexCoordAttribIndex); - } - - mGLContext->fEnableVertexAttribArray(aVertAttribIndex); - - mGLContext->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4); - - mGLContext->fDisableVertexAttribArray(aVertAttribIndex); - - if (aTexCoordAttribIndex != GLuint(-1)) { - mGLContext->fDisableVertexAttribArray(aTexCoordAttribIndex); - } - } + bool aFlipped = false); void BindAndDrawQuad(ShaderProgramOGL *aProg, bool aFlipped = false) diff --git a/gfx/layers/opengl/LayerManagerOGLProgram.cpp b/gfx/layers/opengl/LayerManagerOGLProgram.cpp index 155b28870693..be62af9fe1d8 100644 --- a/gfx/layers/opengl/LayerManagerOGLProgram.cpp +++ b/gfx/layers/opengl/LayerManagerOGLProgram.cpp @@ -3,9 +3,12 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "LayerManagerOGLProgram.h" + #include "LayerManagerOGLShaders.h" #include "LayerManagerOGL.h" +#include "GLContext.h" + namespace mozilla { namespace layers { @@ -227,6 +230,28 @@ ProgramProfileOGL::GetProfileFor(gl::ShaderProgramType aType, const char* const ShaderProgramOGL::VertexCoordAttrib = "aVertexCoord"; const char* const ShaderProgramOGL::TexCoordAttrib = "aTexCoord"; +ShaderProgramOGL::ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile) + : mIsProjectionMatrixStale(false) + , mGL(aGL) + , mProgram(0) + , mProfile(aProfile) + , mProgramState(STATE_NEW) +{} + +ShaderProgramOGL::~ShaderProgramOGL() +{ + if (mProgram <= 0) { + return; + } + + nsRefPtr ctx = mGL->GetSharedContext(); + if (!ctx) { + ctx = mGL; + } + ctx->MakeCurrent(); + ctx->fDeleteProgram(mProgram); +} + bool ShaderProgramOGL::Initialize() { @@ -394,5 +419,87 @@ ShaderProgramOGL::LoadMask(Layer* aMaskLayer) return true; } +void +ShaderProgramOGL::Activate() +{ + if (mProgramState == STATE_NEW) { + if (!Initialize()) { + NS_WARNING("Shader could not be initialised"); + return; + } + } + NS_ASSERTION(HasInitialized(), "Attempting to activate a program that's not in use!"); + mGL->fUseProgram(mProgram); +#if CHECK_CURRENT_PROGRAM + mGL->SetUserData(&sCurrentProgramKey, this); +#endif + // check and set the projection matrix + if (mIsProjectionMatrixStale) { + SetProjectionMatrix(mProjectionMatrix); + } +} + + +void +ShaderProgramOGL::SetUniform(GLint aLocation, float aFloatValue) +{ + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + mGL->fUniform1f(aLocation, aFloatValue); +} + +void +ShaderProgramOGL::SetUniform(GLint aLocation, const gfxRGBA& aColor) +{ + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + mGL->fUniform4f(aLocation, float(aColor.r), float(aColor.g), float(aColor.b), float(aColor.a)); +} + +void +ShaderProgramOGL::SetUniform(GLint aLocation, int aLength, float *aFloatValues) +{ + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + if (aLength == 1) { + mGL->fUniform1fv(aLocation, 1, aFloatValues); + } else if (aLength == 2) { + mGL->fUniform2fv(aLocation, 1, aFloatValues); + } else if (aLength == 3) { + mGL->fUniform3fv(aLocation, 1, aFloatValues); + } else if (aLength == 4) { + mGL->fUniform4fv(aLocation, 1, aFloatValues); + } else { + NS_NOTREACHED("Bogus aLength param"); + } +} + +void +ShaderProgramOGL::SetUniform(GLint aLocation, GLint aIntValue) +{ + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + mGL->fUniform1i(aLocation, aIntValue); +} + +void +ShaderProgramOGL::SetMatrixUniform(GLint aLocation, const gfx3DMatrix& aMatrix) +{ + SetMatrixUniform(aLocation, &aMatrix._11); +} + +void +ShaderProgramOGL::SetMatrixUniform(GLint aLocation, const float *aFloatValues) +{ + ASSERT_THIS_PROGRAM; + NS_ASSERTION(aLocation >= 0, "Invalid location"); + + mGL->fUniformMatrix4fv(aLocation, 1, false, aFloatValues); +} + } /* layers */ } /* mozilla */ diff --git a/gfx/layers/opengl/LayerManagerOGLProgram.h b/gfx/layers/opengl/LayerManagerOGLProgram.h index ac4a2151f9bb..f6ed2b7a64bb 100644 --- a/gfx/layers/opengl/LayerManagerOGLProgram.h +++ b/gfx/layers/opengl/LayerManagerOGLProgram.h @@ -10,11 +10,16 @@ #include "prenv.h" +#include "nsAutoPtr.h" #include "nsString.h" -#include "GLContext.h" +#include "GLContextTypes.h" #include "gfx3DMatrix.h" +#include "gfxColor.h" namespace mozilla { +namespace gl { +class GLContext; +} namespace layers { class Layer; @@ -140,45 +145,16 @@ class ShaderProgramOGL public: typedef mozilla::gl::GLContext GLContext; - ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile) : - mIsProjectionMatrixStale(false), mGL(aGL), mProgram(0), - mProfile(aProfile), mProgramState(STATE_NEW) { } + ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile); - ~ShaderProgramOGL() { - if (mProgram <= 0) { - return; - } - - nsRefPtr ctx = mGL->GetSharedContext(); - if (!ctx) { - ctx = mGL; - } - ctx->MakeCurrent(); - ctx->fDeleteProgram(mProgram); - } + ~ShaderProgramOGL(); bool HasInitialized() { NS_ASSERTION(mProgramState != STATE_OK || mProgram > 0, "Inconsistent program state"); return mProgramState == STATE_OK; } - void Activate() { - if (mProgramState == STATE_NEW) { - if (!Initialize()) { - NS_WARNING("Shader could not be initialised"); - return; - } - } - NS_ASSERTION(HasInitialized(), "Attempting to activate a program that's not in use!"); - mGL->fUseProgram(mProgram); -#if CHECK_CURRENT_PROGRAM - mGL->SetUserData(&sCurrentProgramKey, this); -#endif - // check and set the projection matrix - if (mIsProjectionMatrixStale) { - SetProjectionMatrix(mProjectionMatrix); - } - } + void Activate(); bool Initialize(); @@ -329,54 +305,12 @@ protected: static int sCurrentProgramKey; #endif - void SetUniform(GLint aLocation, float aFloatValue) { - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - mGL->fUniform1f(aLocation, aFloatValue); - } - - void SetUniform(GLint aLocation, const gfxRGBA& aColor) { - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - mGL->fUniform4f(aLocation, float(aColor.r), float(aColor.g), float(aColor.b), float(aColor.a)); - } - - void SetUniform(GLint aLocation, int aLength, float *aFloatValues) { - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - if (aLength == 1) { - mGL->fUniform1fv(aLocation, 1, aFloatValues); - } else if (aLength == 2) { - mGL->fUniform2fv(aLocation, 1, aFloatValues); - } else if (aLength == 3) { - mGL->fUniform3fv(aLocation, 1, aFloatValues); - } else if (aLength == 4) { - mGL->fUniform4fv(aLocation, 1, aFloatValues); - } else { - NS_NOTREACHED("Bogus aLength param"); - } - } - - void SetUniform(GLint aLocation, GLint aIntValue) { - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - mGL->fUniform1i(aLocation, aIntValue); - } - - void SetMatrixUniform(GLint aLocation, const gfx3DMatrix& aMatrix) { - SetMatrixUniform(aLocation, &aMatrix._11); - } - - void SetMatrixUniform(GLint aLocation, const float *aFloatValues) { - ASSERT_THIS_PROGRAM; - NS_ASSERTION(aLocation >= 0, "Invalid location"); - - mGL->fUniformMatrix4fv(aLocation, 1, false, aFloatValues); - } + void SetUniform(GLint aLocation, float aFloatValue); + void SetUniform(GLint aLocation, const gfxRGBA& aColor); + void SetUniform(GLint aLocation, int aLength, float *aFloatValues); + void SetUniform(GLint aLocation, GLint aIntValue); + void SetMatrixUniform(GLint aLocation, const gfx3DMatrix& aMatrix); + void SetMatrixUniform(GLint aLocation, const float *aFloatValues); }; diff --git a/gfx/layers/opengl/ReusableTileStoreOGL.cpp b/gfx/layers/opengl/ReusableTileStoreOGL.cpp index 63e4d531785e..6c52f74c060f 100644 --- a/gfx/layers/opengl/ReusableTileStoreOGL.cpp +++ b/gfx/layers/opengl/ReusableTileStoreOGL.cpp @@ -4,6 +4,8 @@ #include "ReusableTileStoreOGL.h" +#include "GLContext.h" + namespace mozilla { namespace layers { diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index e7ba09c2b627..0d6f08572e48 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -51,7 +51,6 @@ #include "nsRegion.h" #include "Layers.h" #include "LayerManagerOGL.h" -#include "GLContext.h" #include "mozilla/layers/CompositorCocoaWidgetHelper.h" #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" @@ -1692,8 +1691,7 @@ nsChildView::CreateCompositor() LayerManagerOGL *manager = static_cast(compositor::GetLayerManager(mCompositorParent)); - NSOpenGLContext *glContext = - (NSOpenGLContext *) manager->gl()->GetNativeData(GLContext::NativeGLContext); + NSOpenGLContext *glContext = (NSOpenGLContext *)manager->GetNSOpenGLContext(); [(ChildView *)mView setGLContext:glContext]; [(ChildView *)mView setUsingOMTCompositor:true]; @@ -2457,7 +2455,7 @@ NSEvent* gLastDragMouseDownEvent = nil; LayerManagerOGL *manager = static_cast(layerManager); manager->SetClippingRegion(region); - glContext = (NSOpenGLContext *)manager->gl()->GetNativeData(mozilla::gl::GLContext::NativeGLContext); + glContext = (NSOpenGLContext *)manager->GetNSOpenGLContext(); if (!mGLContext) { [self setGLContext:glContext]; From 601bff1c6fdab564891adbba3504c67a4f39aeba Mon Sep 17 00:00:00 2001 From: Jeff Gilbert Date: Mon, 26 Nov 2012 14:23:27 -0800 Subject: [PATCH 052/160] Bug 811958 - Fix and move ShateType type - r=bgirard --- dom/plugins/base/nsNPAPIPluginInstance.cpp | 9 +- dom/plugins/base/nsPluginInstanceOwner.cpp | 8 +- gfx/gl/GLContext.h | 48 ++++++----- gfx/gl/GLContextProviderEGL.cpp | 95 +++++++++++----------- gfx/layers/SharedTextureImage.h | 4 +- gfx/layers/basic/BasicCanvasLayer.cpp | 15 ++-- gfx/layers/ipc/LayersSurfaces.ipdlh | 4 +- gfx/layers/ipc/ShadowLayerUtils.h | 4 +- gfx/layers/opengl/CanvasLayerOGL.cpp | 5 +- gfx/layers/opengl/ImageLayerOGL.h | 2 +- 10 files changed, 107 insertions(+), 87 deletions(-) diff --git a/dom/plugins/base/nsNPAPIPluginInstance.cpp b/dom/plugins/base/nsNPAPIPluginInstance.cpp index cfce39aeb98a..6094d5ed9518 100644 --- a/dom/plugins/base/nsNPAPIPluginInstance.cpp +++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp @@ -129,7 +129,10 @@ public: if (mTextureInfo.mWidth == 0 || mTextureInfo.mHeight == 0) return 0; - SharedTextureHandle handle = sPluginContext->CreateSharedHandle(TextureImage::ThreadShared, (void*)mTextureInfo.mTexture, GLContext::TextureID); + SharedTextureHandle handle = + sPluginContext->CreateSharedHandle(GLContext::SameProcess, + (void*)mTextureInfo.mTexture, + GLContext::TextureID); // We want forget about this now, so delete the texture. Assigning it to zero // ensures that we create a new one in Lock() @@ -1000,7 +1003,9 @@ SharedTextureHandle nsNPAPIPluginInstance::CreateSharedHandle() return mContentTexture->CreateSharedHandle(); } else if (mContentSurface) { EnsureGLContext(); - return sPluginContext->CreateSharedHandle(TextureImage::ThreadShared, mContentSurface, GLContext::SurfaceTexture); + return sPluginContext->CreateSharedHandle(GLContext::SameProcess, + mContentSurface, + GLContext::SurfaceTexture); } else return 0; } diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp index bf2d859833ca..55014898a03e 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -186,7 +186,7 @@ nsPluginInstanceOwner::GetImageContainer() SharedTextureImage::Data data; data.mHandle = mInstance->CreateSharedHandle(); - data.mShareType = mozilla::gl::TextureImage::ThreadShared; + data.mShareType = mozilla::gl::GLContext::SameProcess; data.mInverted = mInstance->Inverted(); gfxRect r = GetPluginRect(); @@ -1723,8 +1723,10 @@ already_AddRefed nsPluginInstanceOwner::GetImageContainerForVide SharedTextureImage::Data data; - data.mHandle = mInstance->GLContext()->CreateSharedHandle(gl::TextureImage::ThreadShared, aVideoInfo->mSurfaceTexture, gl::GLContext::SurfaceTexture); - data.mShareType = mozilla::gl::TextureImage::ThreadShared; + data.mShareType = gl::GLContext::SameProcess; + data.mHandle = mInstance->GLContext()->CreateSharedHandle(data.mShareType, + aVideoInfo->mSurfaceTexture, + gl::GLContext::SurfaceTexture); // The logic below for Honeycomb is just a guess, but seems to work. We don't have a separate // inverted flag for video. diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index 38b26722f416..5188d4411157 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -90,11 +90,6 @@ public: ForceSingleTile = 0x4 }; - enum TextureShareType { - ThreadShared = 0x0, - ProcessShared = 0x1 - }; - typedef gfxASurface::gfxContentType ContentType; virtual ~TextureImage() {} @@ -912,6 +907,12 @@ public: return IsExtensionSupported(EXT_framebuffer_blit) || IsExtensionSupported(ANGLE_framebuffer_blit); } + + enum SharedTextureShareType { + SameProcess = 0, + CrossProcess + }; + enum SharedTextureBufferType { TextureID #ifdef MOZ_WIDGET_ANDROID @@ -922,23 +923,26 @@ public: /** * Create new shared GLContext content handle, must be released by ReleaseSharedHandle. */ - virtual SharedTextureHandle CreateSharedHandle(TextureImage::TextureShareType aType) { return 0; } + virtual SharedTextureHandle CreateSharedHandle(SharedTextureShareType shareType) + { return 0; } /* * Create a new shared GLContext content handle, using the passed buffer as a source. * Must be released by ReleaseSharedHandle. UpdateSharedHandle will have no effect * on handles created with this method, as the caller owns the source (the passed buffer) * and is responsible for updating it accordingly. */ - virtual SharedTextureHandle CreateSharedHandle(TextureImage::TextureShareType aType, - void* aBuffer, - SharedTextureBufferType aBufferType) { return 0; } + virtual SharedTextureHandle CreateSharedHandle(SharedTextureShareType shareType, + void* buffer, + SharedTextureBufferType bufferType) + { return 0; } /** * Publish GLContext content to intermediate buffer attached to shared handle. * Shared handle content is ready to be used after call returns, and no need extra Flush/Finish are required. * GLContext must be current before this call */ - virtual void UpdateSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) { } + virtual void UpdateSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) + { } /** * - It is better to call ReleaseSharedHandle before original GLContext destroyed, * otherwise warning will be thrown on attempt to destroy Texture associated with SharedHandle, depends on backend implementation. @@ -952,8 +956,9 @@ public: * SharedHandle (currently EGLImage) does not require GLContext because it is EGL call, and can be destroyed * at any time, unless EGLImage have siblings (which are not expected with current API). */ - virtual void ReleaseSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) { } + virtual void ReleaseSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) + { } typedef struct { @@ -966,21 +971,24 @@ public: * Returns information necessary for rendering a shared handle. * These values change depending on what sharing mechanism is in use */ - virtual bool GetSharedHandleDetails(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle, - SharedHandleDetails& aDetails) { return false; } + virtual bool GetSharedHandleDetails(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle, + SharedHandleDetails& details) + { return false; } /** * Attach Shared GL Handle to GL_TEXTURE_2D target * GLContext must be current before this call */ - virtual bool AttachSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) { return false; } + virtual bool AttachSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) + { return false; } /** * Detach Shared GL Handle from GL_TEXTURE_2D target */ - virtual void DetachSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) { return; } + virtual void DetachSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) + { } private: GLuint mUserBoundDrawFBO; diff --git a/gfx/gl/GLContextProviderEGL.cpp b/gfx/gl/GLContextProviderEGL.cpp index e080cb10159b..29ba4d74707c 100644 --- a/gfx/gl/GLContextProviderEGL.cpp +++ b/gfx/gl/GLContextProviderEGL.cpp @@ -646,19 +646,19 @@ public: return sEGLLibrary.HasKHRLockSurface(); } - virtual SharedTextureHandle CreateSharedHandle(TextureImage::TextureShareType aType); - virtual SharedTextureHandle CreateSharedHandle(TextureImage::TextureShareType aType, - void* aBuffer, - SharedTextureBufferType aBufferType); - virtual void UpdateSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle); - virtual void ReleaseSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle); - virtual bool GetSharedHandleDetails(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle, - SharedHandleDetails& aDetails); - virtual bool AttachSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle); + virtual SharedTextureHandle CreateSharedHandle(SharedTextureShareType shareType); + virtual SharedTextureHandle CreateSharedHandle(SharedTextureShareType shareType, + void* buffer, + SharedTextureBufferType bufferType); + virtual void UpdateSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle); + virtual void ReleaseSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle); + virtual bool GetSharedHandleDetails(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle, + SharedHandleDetails& details); + virtual bool AttachSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle); protected: friend class GLContextProviderEGL; @@ -854,15 +854,15 @@ private: }; void -GLContextEGL::UpdateSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) +GLContextEGL::UpdateSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) { - if (aType != TextureImage::ThreadShared) { + if (shareType != SameProcess) { NS_ERROR("Implementation not available for this sharing type"); return; } - SharedTextureHandleWrapper* wrapper = reinterpret_cast(aSharedHandle); + SharedTextureHandleWrapper* wrapper = reinterpret_cast(sharedHandle); NS_ASSERTION(wrapper->Type() == SharedHandleType::Image, "Expected EGLImage shared handle"); NS_ASSERTION(mShareWithEGLImage, "EGLImage not supported or disabled in runtime"); @@ -895,9 +895,9 @@ GLContextEGL::UpdateSharedHandle(TextureImage::TextureShareType aType, } SharedTextureHandle -GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType) +GLContextEGL::CreateSharedHandle(SharedTextureShareType shareType) { - if (aType != TextureImage::ThreadShared) + if (shareType != SameProcess) return 0; if (!mShareWithEGLImage) @@ -914,7 +914,7 @@ GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType) if (!ok) { NS_ERROR("EGLImage creation for EGLTextureWrapper failed"); - ReleaseSharedHandle(aType, (SharedTextureHandle)tex); + ReleaseSharedHandle(shareType, (SharedTextureHandle)tex); return 0; } @@ -923,16 +923,16 @@ GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType) } SharedTextureHandle -GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType, - void* aBuffer, - SharedTextureBufferType aBufferType) +GLContextEGL::CreateSharedHandle(SharedTextureShareType shareType, + void* buffer, + SharedTextureBufferType bufferType) { // Both EGLImage and SurfaceTexture only support ThreadShared currently, but // it's possible to make SurfaceTexture work across processes. We should do that. - if (aType != TextureImage::ThreadShared) + if (shareType != SameProcess) return 0; - switch (aBufferType) { + switch (bufferType) { #ifdef MOZ_WIDGET_ANDROID case SharedTextureBufferType::SurfaceTexture: if (!IsExtensionSupported(GLContext::OES_EGL_image_external)) { @@ -940,13 +940,13 @@ GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType, return 0; } - return (SharedTextureHandle) new SurfaceTextureWrapper(reinterpret_cast(aBuffer)); + return (SharedTextureHandle) new SurfaceTextureWrapper(reinterpret_cast(buffer)); #endif case SharedTextureBufferType::TextureID: { if (!mShareWithEGLImage) return 0; - GLuint texture = (uintptr_t)aBuffer; + GLuint texture = (uintptr_t)buffer; EGLTextureWrapper* tex = new EGLTextureWrapper(); if (!tex->CreateEGLImage(this, texture)) { NS_ERROR("EGLImage creation for EGLTextureWrapper failed"); @@ -962,15 +962,15 @@ GLContextEGL::CreateSharedHandle(TextureImage::TextureShareType aType, } } -void GLContextEGL::ReleaseSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) +void GLContextEGL::ReleaseSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) { - if (aType != TextureImage::ThreadShared) { + if (shareType != SameProcess) { NS_ERROR("Implementation not available for this sharing type"); return; } - SharedTextureHandleWrapper* wrapper = reinterpret_cast(aSharedHandle); + SharedTextureHandleWrapper* wrapper = reinterpret_cast(sharedHandle); switch (wrapper->Type()) { #ifdef MOZ_WIDGET_ANDROID @@ -982,40 +982,41 @@ void GLContextEGL::ReleaseSharedHandle(TextureImage::TextureShareType aType, case SharedHandleType::Image: { NS_ASSERTION(mShareWithEGLImage, "EGLImage not supported or disabled in runtime"); - EGLTextureWrapper* wrap = (EGLTextureWrapper*)aSharedHandle; + EGLTextureWrapper* wrap = (EGLTextureWrapper*)sharedHandle; delete wrap; break; } default: NS_ERROR("Unknown shared handle type"); + return; } } -bool GLContextEGL::GetSharedHandleDetails(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle, - SharedHandleDetails& aDetails) +bool GLContextEGL::GetSharedHandleDetails(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle, + SharedHandleDetails& details) { - if (aType != TextureImage::ThreadShared) + if (shareType != SameProcess) return false; - SharedTextureHandleWrapper* wrapper = reinterpret_cast(aSharedHandle); + SharedTextureHandleWrapper* wrapper = reinterpret_cast(sharedHandle); switch (wrapper->Type()) { #ifdef MOZ_WIDGET_ANDROID case SharedHandleType::SurfaceTexture: { SurfaceTextureWrapper* surfaceWrapper = reinterpret_cast(wrapper); - aDetails.mTarget = LOCAL_GL_TEXTURE_EXTERNAL; - aDetails.mProgramType = RGBALayerExternalProgramType; - surfaceWrapper->SurfaceTexture()->GetTransformMatrix(aDetails.mTextureTransform); + details.mTarget = LOCAL_GL_TEXTURE_EXTERNAL; + details.mProgramType = RGBALayerExternalProgramType; + surfaceWrapper->SurfaceTexture()->GetTransformMatrix(details.mTextureTransform); break; } #endif case SharedHandleType::Image: - aDetails.mTarget = LOCAL_GL_TEXTURE_2D; - aDetails.mProgramType = RGBALayerProgramType; + details.mTarget = LOCAL_GL_TEXTURE_2D; + details.mProgramType = RGBALayerProgramType; break; default: @@ -1026,13 +1027,13 @@ bool GLContextEGL::GetSharedHandleDetails(TextureImage::TextureShareType aType, return true; } -bool GLContextEGL::AttachSharedHandle(TextureImage::TextureShareType aType, - SharedTextureHandle aSharedHandle) +bool GLContextEGL::AttachSharedHandle(SharedTextureShareType shareType, + SharedTextureHandle sharedHandle) { - if (aType != TextureImage::ThreadShared) + if (shareType != SameProcess) return false; - SharedTextureHandleWrapper* wrapper = reinterpret_cast(aSharedHandle); + SharedTextureHandleWrapper* wrapper = reinterpret_cast(sharedHandle); switch (wrapper->Type()) { #ifdef MOZ_WIDGET_ANDROID @@ -1057,7 +1058,7 @@ bool GLContextEGL::AttachSharedHandle(TextureImage::TextureShareType aType, case SharedHandleType::Image: { NS_ASSERTION(mShareWithEGLImage, "EGLImage not supported or disabled in runtime"); - EGLTextureWrapper* wrap = (EGLTextureWrapper*)aSharedHandle; + EGLTextureWrapper* wrap = (EGLTextureWrapper*)sharedHandle; wrap->WaitSync(); fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_2D, wrap->GetEGLImage()); break; diff --git a/gfx/layers/SharedTextureImage.h b/gfx/layers/SharedTextureImage.h index 3f0732023954..2a42154cfcf6 100644 --- a/gfx/layers/SharedTextureImage.h +++ b/gfx/layers/SharedTextureImage.h @@ -20,7 +20,7 @@ class THEBES_API SharedTextureImage : public Image { public: struct Data { gl::SharedTextureHandle mHandle; - gl::TextureImage::TextureShareType mShareType; + gl::GLContext::SharedTextureShareType mShareType; gfxIntSize mSize; bool mInverted; }; @@ -41,4 +41,4 @@ private: } // layers } // mozilla -#endif // GFX_SHAREDTEXTUREIMAGE_H \ No newline at end of file +#endif // GFX_SHAREDTEXTUREIMAGE_H diff --git a/gfx/layers/basic/BasicCanvasLayer.cpp b/gfx/layers/basic/BasicCanvasLayer.cpp index fdc133d38747..e028468f29b1 100644 --- a/gfx/layers/basic/BasicCanvasLayer.cpp +++ b/gfx/layers/basic/BasicCanvasLayer.cpp @@ -387,24 +387,25 @@ BasicShadowableCanvasLayer::Paint(gfxContext* aContext, Layer* aMaskLayer) if (mGLContext && !mForceReadback && - BasicManager()->GetParentBackendType() == mozilla::layers::LAYERS_OPENGL) { - TextureImage::TextureShareType flags; + BasicManager()->GetParentBackendType() == mozilla::layers::LAYERS_OPENGL) + { + GLContext::SharedTextureShareType shareType; // if process type is default, then it is single-process (non-e10s) if (XRE_GetProcessType() == GeckoProcessType_Default) - flags = TextureImage::ThreadShared; + shareType = GLContext::SameProcess; else - flags = TextureImage::ProcessShared; + shareType = GLContext::CrossProcess; SharedTextureHandle handle = GetSharedBackBufferHandle(); if (!handle) { - handle = mGLContext->CreateSharedHandle(flags); + handle = mGLContext->CreateSharedHandle(shareType); if (handle) { - mBackBuffer = SharedTextureDescriptor(flags, handle, mBounds.Size(), false); + mBackBuffer = SharedTextureDescriptor(shareType, handle, mBounds.Size(), false); } } if (handle) { mGLContext->MakeCurrent(); - mGLContext->UpdateSharedHandle(flags, handle); + mGLContext->UpdateSharedHandle(shareType, handle); // call Painted() to reset our dirty 'bit' Painted(); FireDidTransactionCallback(); diff --git a/gfx/layers/ipc/LayersSurfaces.ipdlh b/gfx/layers/ipc/LayersSurfaces.ipdlh index 39db98123782..2d8e763f05b8 100644 --- a/gfx/layers/ipc/LayersSurfaces.ipdlh +++ b/gfx/layers/ipc/LayersSurfaces.ipdlh @@ -22,7 +22,7 @@ using mozilla::layers::SurfaceDescriptorX11; using mozilla::null_t; using mozilla::WindowsHandle; using mozilla::gl::SharedTextureHandle; -using mozilla::gl::TextureImage::TextureShareType; +using mozilla::gl::GLContext::SharedTextureShareType; namespace mozilla { namespace layers { @@ -37,7 +37,7 @@ struct SurfaceDescriptorD3D10 { }; struct SharedTextureDescriptor { - TextureShareType shareType; + SharedTextureShareType shareType; SharedTextureHandle handle; nsIntSize size; bool inverted; diff --git a/gfx/layers/ipc/ShadowLayerUtils.h b/gfx/layers/ipc/ShadowLayerUtils.h index 4d66cef624bd..b7eaa1d4d191 100644 --- a/gfx/layers/ipc/ShadowLayerUtils.h +++ b/gfx/layers/ipc/ShadowLayerUtils.h @@ -52,9 +52,9 @@ struct ParamTraits { #endif // !defined(MOZ_HAVE_XSURFACEDESCRIPTORX11) template<> -struct ParamTraits +struct ParamTraits { - typedef mozilla::gl::TextureImage::TextureShareType paramType; + typedef mozilla::gl::GLContext::SharedTextureShareType paramType; static void Write(Message* msg, const paramType& param) { diff --git a/gfx/layers/opengl/CanvasLayerOGL.cpp b/gfx/layers/opengl/CanvasLayerOGL.cpp index 3bd9e578b40e..6616fb9fde05 100644 --- a/gfx/layers/opengl/CanvasLayerOGL.cpp +++ b/gfx/layers/opengl/CanvasLayerOGL.cpp @@ -420,7 +420,10 @@ ShadowCanvasLayerOGL::Swap(const CanvasSurface& aNewFront, } else if (IsValidSharedTexDescriptor(aNewFront)) { MakeTextureIfNeeded(gl(), mTexture); if (!IsValidSharedTexDescriptor(mFrontBufferDescriptor)) { - mFrontBufferDescriptor = SharedTextureDescriptor(TextureImage::ThreadShared, 0, nsIntSize(0, 0), false); + mFrontBufferDescriptor = SharedTextureDescriptor(GLContext::SameProcess, + 0, + nsIntSize(0, 0), + false); } *aNewBack = mFrontBufferDescriptor; mFrontBufferDescriptor = aNewFront; diff --git a/gfx/layers/opengl/ImageLayerOGL.h b/gfx/layers/opengl/ImageLayerOGL.h index 5db8b6e9c897..5995c12ef7c9 100644 --- a/gfx/layers/opengl/ImageLayerOGL.h +++ b/gfx/layers/opengl/ImageLayerOGL.h @@ -194,7 +194,7 @@ private: // For SharedTextureHandle gl::SharedTextureHandle mSharedHandle; - gl::TextureImage::TextureShareType mShareType; + gl::GLContext::SharedTextureShareType mShareType; bool mInverted; GLuint mTexture; From a9a0fc4866dc76e7debaf71d43c2927d86ba0461 Mon Sep 17 00:00:00 2001 From: Jeff Gilbert Date: Mon, 26 Nov 2012 14:23:27 -0800 Subject: [PATCH 053/160] Bug 811958 - Move TextureImage into its own files - r=bgirard --- gfx/gl/GLContext.cpp | 555 +------------------------------ gfx/gl/GLContext.h | 370 +-------------------- gfx/gl/GLContextProviderEGL.cpp | 1 + gfx/gl/GLTextureImage.cpp | 573 ++++++++++++++++++++++++++++++++ gfx/gl/GLTextureImage.h | 385 +++++++++++++++++++++ gfx/gl/Makefile.in | 2 + widget/cocoa/nsChildView.mm | 10 +- 7 files changed, 984 insertions(+), 912 deletions(-) create mode 100644 gfx/gl/GLTextureImage.cpp create mode 100644 gfx/gl/GLTextureImage.h diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 8f183eae5afb..17197535c857 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -23,6 +23,8 @@ #include "mozilla/Preferences.h" #include "mozilla/Util.h" // for DebugOnly +#include "GLTextureImage.h" + using namespace mozilla::gfx; namespace mozilla { @@ -738,6 +740,19 @@ GLContext::CreateTextureImage(const nsIntSize& aSize, return CreateBasicTextureImage(texture, aSize, aWrapMode, aContentType, this, aFlags); } +already_AddRefed +GLContext::CreateBasicTextureImage(GLuint aTexture, + const nsIntSize& aSize, + GLenum aWrapMode, + TextureImage::ContentType aContentType, + GLContext* aContext, + TextureImage::Flags aFlags) +{ + nsRefPtr teximage( + new BasicTextureImage(aTexture, aSize, aWrapMode, aContentType, aContext, aFlags)); + return teximage.forget(); +} + void GLContext::ApplyFilterToBoundTexture(gfxPattern::GraphicsFilter aFilter) { ApplyFilterToBoundTexture(LOCAL_GL_TEXTURE_2D, aFilter); @@ -755,546 +770,6 @@ void GLContext::ApplyFilterToBoundTexture(GLuint aTarget, } } -BasicTextureImage::~BasicTextureImage() -{ - GLContext *ctx = mGLContext; - if (ctx->IsDestroyed() || !ctx->IsOwningThreadCurrent()) { - ctx = ctx->GetSharedContext(); - } - - // If we have a context, then we need to delete the texture; - // if we don't have a context (either real or shared), - // then they went away when the contex was deleted, because it - // was the only one that had access to it. - if (ctx && !ctx->IsDestroyed()) { - mGLContext->MakeCurrent(); - mGLContext->fDeleteTextures(1, &mTexture); - } -} - -gfxASurface* -BasicTextureImage::BeginUpdate(nsIntRegion& aRegion) -{ - NS_ASSERTION(!mUpdateSurface, "BeginUpdate() without EndUpdate()?"); - - // determine the region the client will need to repaint - if (mGLContext->CanUploadSubTextures()) { - GetUpdateRegion(aRegion); - } else { - aRegion = nsIntRect(nsIntPoint(0, 0), mSize); - } - - mUpdateRegion = aRegion; - - nsIntRect rgnSize = mUpdateRegion.GetBounds(); - if (!nsIntRect(nsIntPoint(0, 0), mSize).Contains(rgnSize)) { - NS_ERROR("update outside of image"); - return NULL; - } - - ImageFormat format = - (GetContentType() == gfxASurface::CONTENT_COLOR) ? - gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; - mUpdateSurface = - GetSurfaceForUpdate(gfxIntSize(rgnSize.width, rgnSize.height), format); - - if (!mUpdateSurface || mUpdateSurface->CairoStatus()) { - mUpdateSurface = NULL; - return NULL; - } - - mUpdateSurface->SetDeviceOffset(gfxPoint(-rgnSize.x, -rgnSize.y)); - - return mUpdateSurface; -} - -void -BasicTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) -{ - // if the texture hasn't been initialized yet, or something important - // changed, we need to recreate our backing surface and force the - // client to paint everything - if (mTextureState != Valid) - aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); -} - -void -BasicTextureImage::EndUpdate() -{ - NS_ASSERTION(!!mUpdateSurface, "EndUpdate() without BeginUpdate()?"); - - // FIXME: this is the slow boat. Make me fast (with GLXPixmap?). - - // Undo the device offset that BeginUpdate set; doesn't much matter for us here, - // but important if we ever do anything directly with the surface. - mUpdateSurface->SetDeviceOffset(gfxPoint(0, 0)); - - bool relative = FinishedSurfaceUpdate(); - - mShaderType = - mGLContext->UploadSurfaceToTexture(mUpdateSurface, - mUpdateRegion, - mTexture, - mTextureState == Created, - mUpdateOffset, - relative); - FinishedSurfaceUpload(); - - mUpdateSurface = nullptr; - mTextureState = Valid; -} - -void -BasicTextureImage::BindTexture(GLenum aTextureUnit) -{ - mGLContext->fActiveTexture(aTextureUnit); - mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); - mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); -} - -void -BasicTextureImage::ApplyFilter() -{ - mGLContext->ApplyFilterToBoundTexture(mFilter); -} - - -already_AddRefed -BasicTextureImage::GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt) -{ - return gfxPlatform::GetPlatform()-> - CreateOffscreenSurface(aSize, gfxASurface::ContentFromFormat(aFmt)); -} - -bool -BasicTextureImage::FinishedSurfaceUpdate() -{ - return false; -} - -void -BasicTextureImage::FinishedSurfaceUpload() -{ -} - -bool -BasicTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) -{ - nsIntRect bounds = aRegion.GetBounds(); - nsIntRegion region; - if (mTextureState != Valid) { - bounds = nsIntRect(0, 0, mSize.width, mSize.height); - region = nsIntRegion(bounds); - } else { - region = aRegion; - } - - mShaderType = - mGLContext->UploadSurfaceToTexture(aSurf, - region, - mTexture, - mTextureState == Created, - bounds.TopLeft() + aFrom, - false); - mTextureState = Valid; - return true; -} - -void -BasicTextureImage::Resize(const nsIntSize& aSize) -{ - NS_ASSERTION(!mUpdateSurface, "Resize() while in update?"); - - mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); - - mGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D, - 0, - LOCAL_GL_RGBA, - aSize.width, - aSize.height, - 0, - LOCAL_GL_RGBA, - LOCAL_GL_UNSIGNED_BYTE, - NULL); - - mTextureState = Allocated; - mSize = aSize; -} - -TiledTextureImage::TiledTextureImage(GLContext* aGL, - nsIntSize aSize, - TextureImage::ContentType aContentType, - TextureImage::Flags aFlags) - : TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aFlags) - , mCurrentImage(0) - , mIterationCallback(nullptr) - , mInUpdate(false) - , mRows(0) - , mColumns(0) - , mGL(aGL) - , mTextureState(Created) -{ - mTileSize = (!(aFlags & TextureImage::ForceSingleTile) && mGL->WantsSmallTiles()) - ? 256 : mGL->GetMaxTextureSize(); - if (aSize != nsIntSize(0,0)) { - Resize(aSize); - } -} - -TiledTextureImage::~TiledTextureImage() -{ -} - -bool -TiledTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) -{ - nsIntRegion region; - - if (mTextureState != Valid) { - nsIntRect bounds = nsIntRect(0, 0, mSize.width, mSize.height); - region = nsIntRegion(bounds); - } else { - region = aRegion; - } - - bool result = true; - int oldCurrentImage = mCurrentImage; - BeginTileIteration(); - do { - nsIntRect tileRect = GetSrcTileRect(); - int xPos = tileRect.x; - int yPos = tileRect.y; - - nsIntRegion tileRegion; - tileRegion.And(region, tileRect); // intersect with tile - - if (tileRegion.IsEmpty()) - continue; - - if (mGL->CanUploadSubTextures()) { - tileRegion.MoveBy(-xPos, -yPos); // translate into tile local space - } else { - // If sub-textures are unsupported, expand to tile boundaries - tileRect.x = tileRect.y = 0; - tileRegion = nsIntRegion(tileRect); - } - - result &= mImages[mCurrentImage]-> - DirectUpdate(aSurf, tileRegion, aFrom + nsIntPoint(xPos, yPos)); - - if (mCurrentImage == mImages.Length() - 1) { - // We know we're done, but we still need to ensure that the callback - // gets called (e.g. to update the uploaded region). - NextTile(); - break; - } - // Override a callback cancelling iteration if the texture wasn't valid. - // We need to force the update in that situation, or we may end up - // showing invalid/out-of-date texture data. - } while (NextTile() || (mTextureState != Valid)); - mCurrentImage = oldCurrentImage; - - mShaderType = mImages[0]->GetShaderProgramType(); - mTextureState = Valid; - return result; -} - -void -TiledTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) -{ - if (mTextureState != Valid) { - // if the texture hasn't been initialized yet, or something important - // changed, we need to recreate our backing surface and force the - // client to paint everything - aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); - return; - } - - nsIntRegion newRegion; - - // We need to query each texture with the region it will be drawing and - // set aForRegion to be the combination of all of these regions - for (unsigned i = 0; i < mImages.Length(); i++) { - int xPos = (i % mColumns) * mTileSize; - int yPos = (i / mColumns) * mTileSize; - nsIntRect imageRect = nsIntRect(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); - - if (aForRegion.Intersects(imageRect)) { - // Make a copy of the region - nsIntRegion subRegion; - subRegion.And(aForRegion, imageRect); - // Translate it into tile-space - subRegion.MoveBy(-xPos, -yPos); - // Query region - mImages[i]->GetUpdateRegion(subRegion); - // Translate back - subRegion.MoveBy(xPos, yPos); - // Add to the accumulated region - newRegion.Or(newRegion, subRegion); - } - } - - aForRegion = newRegion; -} - -gfxASurface* -TiledTextureImage::BeginUpdate(nsIntRegion& aRegion) -{ - NS_ASSERTION(!mInUpdate, "nested update"); - mInUpdate = true; - - // Note, we don't call GetUpdateRegion here as if the updated region is - // fully contained in a single tile, we get to avoid iterating through - // the tiles again (and a little copying). - if (mTextureState != Valid) - { - // if the texture hasn't been initialized yet, or something important - // changed, we need to recreate our backing surface and force the - // client to paint everything - aRegion = nsIntRect(nsIntPoint(0, 0), mSize); - } - - nsIntRect bounds = aRegion.GetBounds(); - - for (unsigned i = 0; i < mImages.Length(); i++) { - int xPos = (i % mColumns) * mTileSize; - int yPos = (i / mColumns) * mTileSize; - nsIntRegion imageRegion = nsIntRegion(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); - - // a single Image can handle this update request - if (imageRegion.Contains(aRegion)) { - // adjust for tile offset - aRegion.MoveBy(-xPos, -yPos); - // forward the actual call - nsRefPtr surface = mImages[i]->BeginUpdate(aRegion); - // caller expects container space - aRegion.MoveBy(xPos, yPos); - // Correct the device offset - gfxPoint offset = surface->GetDeviceOffset(); - surface->SetDeviceOffset(gfxPoint(offset.x - xPos, - offset.y - yPos)); - // we don't have a temp surface - mUpdateSurface = nullptr; - // remember which image to EndUpdate - mCurrentImage = i; - return surface.get(); - } - } - - // Get the real updated region, taking into account the capabilities of - // each TextureImage tile - GetUpdateRegion(aRegion); - mUpdateRegion = aRegion; - bounds = aRegion.GetBounds(); - - // update covers multiple Images - create a temp surface to paint in - gfxASurface::gfxImageFormat format = - (GetContentType() == gfxASurface::CONTENT_COLOR) ? - gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; - mUpdateSurface = gfxPlatform::GetPlatform()-> - CreateOffscreenSurface(gfxIntSize(bounds.width, bounds.height), gfxASurface::ContentFromFormat(format)); - mUpdateSurface->SetDeviceOffset(gfxPoint(-bounds.x, -bounds.y)); - - return mUpdateSurface; -} - -void -TiledTextureImage::EndUpdate() -{ - NS_ASSERTION(mInUpdate, "EndUpdate not in update"); - if (!mUpdateSurface) { // update was to a single TextureImage - mImages[mCurrentImage]->EndUpdate(); - mInUpdate = false; - mTextureState = Valid; - mShaderType = mImages[mCurrentImage]->GetShaderProgramType(); - return; - } - - // upload tiles from temp surface - for (unsigned i = 0; i < mImages.Length(); i++) { - int xPos = (i % mColumns) * mTileSize; - int yPos = (i / mColumns) * mTileSize; - nsIntRect imageRect = nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize()); - - nsIntRegion subregion; - subregion.And(mUpdateRegion, imageRect); - if (subregion.IsEmpty()) - continue; - subregion.MoveBy(-xPos, -yPos); // Tile-local space - // copy tile from temp surface - gfxASurface* surf = mImages[i]->BeginUpdate(subregion); - nsRefPtr ctx = new gfxContext(surf); - gfxUtils::ClipToRegion(ctx, subregion); - ctx->SetOperator(gfxContext::OPERATOR_SOURCE); - ctx->SetSource(mUpdateSurface, gfxPoint(-xPos, -yPos)); - ctx->Paint(); - mImages[i]->EndUpdate(); - } - - mUpdateSurface = nullptr; - mInUpdate = false; - mShaderType = mImages[0]->GetShaderProgramType(); - mTextureState = Valid; -} - -void TiledTextureImage::BeginTileIteration() -{ - mCurrentImage = 0; -} - -bool TiledTextureImage::NextTile() -{ - bool continueIteration = true; - - if (mIterationCallback) - continueIteration = mIterationCallback(this, mCurrentImage, - mIterationCallbackData); - - if (mCurrentImage + 1 < mImages.Length()) { - mCurrentImage++; - return continueIteration; - } - return false; -} - -void TiledTextureImage::SetIterationCallback(TileIterationCallback aCallback, - void* aCallbackData) -{ - mIterationCallback = aCallback; - mIterationCallbackData = aCallbackData; -} - -nsIntRect TiledTextureImage::GetTileRect() -{ - nsIntRect rect = mImages[mCurrentImage]->GetTileRect(); - unsigned int xPos = (mCurrentImage % mColumns) * mTileSize; - unsigned int yPos = (mCurrentImage / mColumns) * mTileSize; - rect.MoveBy(xPos, yPos); - return rect; -} - -nsIntRect TiledTextureImage::GetSrcTileRect() -{ - nsIntRect rect = GetTileRect(); - unsigned int srcY = mFlags & NeedsYFlip - ? mSize.height - rect.height - rect.y - : rect.y; - return nsIntRect(rect.x, srcY, rect.width, rect.height); -} - -void -TiledTextureImage::BindTexture(GLenum aTextureUnit) -{ - mImages[mCurrentImage]->BindTexture(aTextureUnit); -} - -void -TiledTextureImage::ApplyFilter() -{ - mGL->ApplyFilterToBoundTexture(mFilter); -} - -/* - * Resize, trying to reuse tiles. The reuse strategy is to decide on reuse per - * column. A tile on a column is reused if it hasn't changed size, otherwise it - * is discarded/replaced. Extra tiles on a column are pruned after iterating - * each column, and extra rows are pruned after iteration over the entire image - * finishes. - */ -void TiledTextureImage::Resize(const nsIntSize& aSize) -{ - if (mSize == aSize && mTextureState != Created) { - return; - } - - // calculate rows and columns, rounding up - unsigned int columns = (aSize.width + mTileSize - 1) / mTileSize; - unsigned int rows = (aSize.height + mTileSize - 1) / mTileSize; - - // Iterate over old tile-store and insert/remove tiles as necessary - int row; - unsigned int i = 0; - for (row = 0; row < (int)rows; row++) { - // If we've gone beyond how many rows there were before, set mColumns to - // zero so that we only create new tiles. - if (row >= (int)mRows) - mColumns = 0; - - // Similarly, if we're on the last row of old tiles and the height has - // changed, discard all tiles in that row. - // This will cause the pruning of columns not to work, but we don't need - // to worry about that, as no more tiles will be reused past this point - // anyway. - if ((row == (int)mRows - 1) && (aSize.height != mSize.height)) - mColumns = 0; - - int col; - for (col = 0; col < (int)columns; col++) { - nsIntSize size( // use tilesize first, then the remainder - (col+1) * mTileSize > (unsigned int)aSize.width ? aSize.width % mTileSize : mTileSize, - (row+1) * mTileSize > (unsigned int)aSize.height ? aSize.height % mTileSize : mTileSize); - - bool replace = false; - - // Check if we can re-use old tiles. - if (col < (int)mColumns) { - // Reuse an existing tile. If the tile is an end-tile and the - // width differs, replace it instead. - if (mSize.width != aSize.width) { - if (col == (int)mColumns - 1) { - // Tile at the end of the old column, replace it with - // a new one. - replace = true; - } else if (col == (int)columns - 1) { - // Tile at the end of the new column, create a new one. - } else { - // Before the last column on both the old and new sizes, - // reuse existing tile. - i++; - continue; - } - } else { - // Width hasn't changed, reuse existing tile. - i++; - continue; - } - } - - // Create a new tile. - nsRefPtr teximg = - mGL->TileGenFunc(size, mContentType, mFlags); - if (replace) - mImages.ReplaceElementAt(i, teximg.forget()); - else - mImages.InsertElementAt(i, teximg.forget()); - i++; - } - - // Prune any unused tiles on the end of the column. - if (row < (int)mRows) { - for (col = (int)mColumns - col; col > 0; col--) { - mImages.RemoveElementAt(i); - } - } - } - - // Prune any unused tiles at the end of the store. - unsigned int length = mImages.Length(); - for (; i < length; i++) - mImages.RemoveElementAt(mImages.Length()-1); - - // Reset tile-store properties. - mRows = rows; - mColumns = columns; - mSize = aSize; - mTextureState = Allocated; - mCurrentImage = 0; -} - -uint32_t TiledTextureImage::GetTileCount() -{ - return mImages.Length(); -} GLContext::GLFormats GLContext::ChooseGLFormats(ContextFormat& aCF, ColorByteOrder aByteOrder) diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index 5188d4411157..d8a327b26d41 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -33,6 +33,7 @@ #include "nsAutoPtr.h" #include "nsThreadUtils.h" #include "GLContextTypes.h" +#include "GLTextureImage.h" typedef char realGLboolean; @@ -56,368 +57,6 @@ class GLContext; typedef uintptr_t SharedTextureHandle; -/** - * A TextureImage encapsulates a surface that can be drawn to by a - * Thebes gfxContext and (hopefully efficiently!) synchronized to a - * texture in the server. TextureImages are associated with one and - * only one GLContext. - * - * Implementation note: TextureImages attempt to unify two categories - * of backends - * - * (1) proxy to server-side object that can be bound to a texture; - * e.g. Pixmap on X11. - * - * (2) efficient manager of texture memory; e.g. by having clients draw - * into a scratch buffer which is then uploaded with - * glTexSubImage2D(). - */ -class TextureImage -{ - NS_INLINE_DECL_REFCOUNTING(TextureImage) -public: - enum TextureState - { - Created, // Texture created, but has not had glTexImage called to initialize it. - Allocated, // Texture memory exists, but contents are invalid. - Valid // Texture fully ready to use. - }; - - enum Flags { - NoFlags = 0x0, - UseNearestFilter = 0x1, - NeedsYFlip = 0x2, - ForceSingleTile = 0x4 - }; - - typedef gfxASurface::gfxContentType ContentType; - - virtual ~TextureImage() {} - - /** - * Returns a gfxASurface for updating |aRegion| of the client's - * image if successul, NULL if not. |aRegion|'s bounds must fit - * within Size(); its coordinate space (if any) is ignored. If - * the update begins successfully, the returned gfxASurface is - * owned by this. Otherwise, NULL is returned. - * - * |aRegion| is an inout param: the returned region is what the - * client must repaint. Category (1) regions above can - * efficiently handle repaints to "scattered" regions, while (2) - * can only efficiently handle repaints to rects. - * - * Painting the returned surface outside of |aRegion| results - * in undefined behavior. - * - * BeginUpdate() calls cannot be "nested", and each successful - * BeginUpdate() must be followed by exactly one EndUpdate() (see - * below). Failure to do so can leave this in a possibly - * inconsistent state. Unsuccessful BeginUpdate()s must not be - * followed by EndUpdate(). - */ - virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion) = 0; - /** - * Retrieves the region that will require updating, given a - * region that needs to be updated. This can be used for - * making decisions about updating before calling BeginUpdate(). - * - * |aRegion| is an inout param. - */ - virtual void GetUpdateRegion(nsIntRegion& aForRegion) { - } - /** - * Finish the active update and synchronize with the server, if - * necessary. - * - * BeginUpdate() must have been called exactly once before - * EndUpdate(). - */ - virtual void EndUpdate() = 0; - - /** - * The Image may contain several textures for different regions (tiles). - * These functions iterate over each sub texture image tile. - */ - virtual void BeginTileIteration() { - } - - virtual bool NextTile() { - return false; - } - - // Function prototype for a tile iteration callback. Returning false will - // cause iteration to be interrupted (i.e. the corresponding NextTile call - // will return false). - typedef bool (* TileIterationCallback)(TextureImage* aImage, - int aTileNumber, - void* aCallbackData); - - // Sets a callback to be called every time NextTile is called. - virtual void SetIterationCallback(TileIterationCallback aCallback, - void* aCallbackData) { - } - - virtual nsIntRect GetTileRect() { - return nsIntRect(nsIntPoint(0,0), mSize); - } - - virtual GLuint GetTextureID() = 0; - - virtual uint32_t GetTileCount() { - return 1; - } - - /** - * Set this TextureImage's size, and ensure a texture has been - * allocated. Must not be called between BeginUpdate and EndUpdate. - * After a resize, the contents are undefined. - * - * If this isn't implemented by a subclass, it will just perform - * a dummy BeginUpdate/EndUpdate pair. - */ - virtual void Resize(const nsIntSize& aSize) { - mSize = aSize; - nsIntRegion r(nsIntRect(0, 0, aSize.width, aSize.height)); - BeginUpdate(r); - EndUpdate(); - } - - /** - * Mark this texture as having valid contents. Call this after modifying - * the texture contents externally. - */ - virtual void MarkValid() {} - - /** - * aSurf - the source surface to update from - * aRegion - the region in this image to update - * aFrom - offset in the source to update from - */ - virtual bool DirectUpdate(gfxASurface *aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)) = 0; - - virtual void BindTexture(GLenum aTextureUnit) = 0; - virtual void ReleaseTexture() {} - - void BindTextureAndApplyFilter(GLenum aTextureUnit) { - BindTexture(aTextureUnit); - ApplyFilter(); - } - - class ScopedBindTexture - { - public: - ScopedBindTexture(TextureImage *aTexture, GLenum aTextureUnit) : - mTexture(aTexture) - { - if (mTexture) { - mTexture->BindTexture(aTextureUnit); - } - } - - ~ScopedBindTexture() - { - if (mTexture) { - mTexture->ReleaseTexture(); - } - } - - protected: - TextureImage *mTexture; - }; - - class ScopedBindTextureAndApplyFilter - : public ScopedBindTexture - { - public: - ScopedBindTextureAndApplyFilter(TextureImage *aTexture, GLenum aTextureUnit) : - ScopedBindTexture(aTexture, aTextureUnit) - { - if (mTexture) { - mTexture->ApplyFilter(); - } - } - }; - - /** - * Returns the shader program type that should be used to render - * this texture. Only valid after a matching BeginUpdate/EndUpdate - * pair have been called. - */ - virtual ShaderProgramType GetShaderProgramType() - { - return mShaderType; - } - - /** Can be called safely at any time. */ - - /** - * If this TextureImage has a permanent gfxASurface backing, - * return it. Otherwise return NULL. - */ - virtual already_AddRefed GetBackingSurface() - { return NULL; } - - const nsIntSize& GetSize() const { return mSize; } - ContentType GetContentType() const { return mContentType; } - virtual bool InUpdate() const = 0; - GLenum GetWrapMode() const { return mWrapMode; } - - void SetFilter(gfxPattern::GraphicsFilter aFilter) { mFilter = aFilter; } - - /** - * Applies this TextureImage's filter, assuming that its texture is - * the currently bound texture. - */ - virtual void ApplyFilter() = 0; - -protected: - friend class GLContext; - - /** - * After the ctor, the TextureImage is invalid. Implementations - * must allocate resources successfully before returning the new - * TextureImage from GLContext::CreateTextureImage(). That is, - * clients must not be given partially-constructed TextureImages. - */ - TextureImage(const nsIntSize& aSize, - GLenum aWrapMode, ContentType aContentType, - Flags aFlags = NoFlags) - : mSize(aSize) - , mWrapMode(aWrapMode) - , mContentType(aContentType) - , mFilter(gfxPattern::FILTER_GOOD) - , mFlags(aFlags) - {} - - virtual nsIntRect GetSrcTileRect() { - return nsIntRect(nsIntPoint(0,0), mSize); - } - - nsIntSize mSize; - GLenum mWrapMode; - ContentType mContentType; - ShaderProgramType mShaderType; - gfxPattern::GraphicsFilter mFilter; - Flags mFlags; -}; - -/** - * BasicTextureImage is the baseline TextureImage implementation --- - * it updates its texture by allocating a scratch buffer for the - * client to draw into, then using glTexSubImage2D() to upload the new - * pixels. Platforms must provide the code to create a new surface - * into which the updated pixels will be drawn, and the code to - * convert the update surface's pixels into an image on which we can - * glTexSubImage2D(). - */ -class BasicTextureImage - : public TextureImage -{ -public: - typedef gfxASurface::gfxImageFormat ImageFormat; - virtual ~BasicTextureImage(); - - BasicTextureImage(GLuint aTexture, - const nsIntSize& aSize, - GLenum aWrapMode, - ContentType aContentType, - GLContext* aContext, - TextureImage::Flags aFlags = TextureImage::NoFlags) - : TextureImage(aSize, aWrapMode, aContentType, aFlags) - , mTexture(aTexture) - , mTextureState(Created) - , mGLContext(aContext) - , mUpdateOffset(0, 0) - {} - - virtual void BindTexture(GLenum aTextureUnit); - - virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); - virtual void GetUpdateRegion(nsIntRegion& aForRegion); - virtual void EndUpdate(); - virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); - virtual GLuint GetTextureID() { return mTexture; } - // Returns a surface to draw into - virtual already_AddRefed - GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt); - - virtual void MarkValid() { mTextureState = Valid; } - - // Call when drawing into the update surface is complete. - // Returns true if textures should be upload with a relative - // offset - See UploadSurfaceToTexture. - virtual bool FinishedSurfaceUpdate(); - - // Call after surface data has been uploaded to a texture. - virtual void FinishedSurfaceUpload(); - - virtual bool InUpdate() const { return !!mUpdateSurface; } - - virtual void Resize(const nsIntSize& aSize); - - virtual void ApplyFilter(); -protected: - - GLuint mTexture; - TextureState mTextureState; - GLContext* mGLContext; - nsRefPtr mUpdateSurface; - nsIntRegion mUpdateRegion; - - // The offset into the update surface at which the update rect is located. - nsIntPoint mUpdateOffset; -}; - -/** - * A container class that complements many sub TextureImages into a big TextureImage. - * Aims to behave just like the real thing. - */ - -class TiledTextureImage - : public TextureImage -{ -public: - TiledTextureImage(GLContext* aGL, nsIntSize aSize, - TextureImage::ContentType, TextureImage::Flags aFlags = TextureImage::NoFlags); - ~TiledTextureImage(); - void DumpDiv(); - virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); - virtual void GetUpdateRegion(nsIntRegion& aForRegion); - virtual void EndUpdate(); - virtual void Resize(const nsIntSize& aSize); - virtual uint32_t GetTileCount(); - virtual void BeginTileIteration(); - virtual bool NextTile(); - virtual void SetIterationCallback(TileIterationCallback aCallback, - void* aCallbackData); - virtual nsIntRect GetTileRect(); - virtual GLuint GetTextureID() { - return mImages[mCurrentImage]->GetTextureID(); - } - virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); - virtual bool InUpdate() const { return mInUpdate; } - virtual void BindTexture(GLenum); - virtual void ApplyFilter(); - -protected: - virtual nsIntRect GetSrcTileRect(); - - unsigned int mCurrentImage; - TileIterationCallback mIterationCallback; - void* mIterationCallbackData; - nsTArray< nsRefPtr > mImages; - bool mInUpdate; - nsIntSize mSize; - unsigned int mTileSize; - unsigned int mRows, mColumns; - GLContext* mGL; - // A temporary surface to faciliate cross-tile updates. - nsRefPtr mUpdateSurface; - // The region of update requested - nsIntRegion mUpdateRegion; - TextureState mTextureState; -}; - struct THEBES_API ContextFormat { static const ContextFormat BasicRGBA32Format; @@ -1927,12 +1566,7 @@ protected: GLenum aWrapMode, TextureImage::ContentType aContentType, GLContext* aContext, - TextureImage::Flags aFlags = TextureImage::NoFlags) - { - nsRefPtr teximage( - new BasicTextureImage(aTexture, aSize, aWrapMode, aContentType, aContext, aFlags)); - return teximage.forget(); - } + TextureImage::Flags aFlags = TextureImage::NoFlags); bool IsOffscreenSizeAllowed(const gfxIntSize& aSize) const { int32_t biggerDimension = NS_MAX(aSize.width, aSize.height); diff --git a/gfx/gl/GLContextProviderEGL.cpp b/gfx/gl/GLContextProviderEGL.cpp index 29ba4d74707c..34d64afb6e25 100644 --- a/gfx/gl/GLContextProviderEGL.cpp +++ b/gfx/gl/GLContextProviderEGL.cpp @@ -1072,6 +1072,7 @@ bool GLContextEGL::AttachSharedHandle(SharedTextureShareType shareType, return true; } + bool GLContextEGL::BindTex2DOffscreen(GLContext *aOffscreen) { diff --git a/gfx/gl/GLTextureImage.cpp b/gfx/gl/GLTextureImage.cpp new file mode 100644 index 000000000000..fbfef04fa4e6 --- /dev/null +++ b/gfx/gl/GLTextureImage.cpp @@ -0,0 +1,573 @@ +/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */ +/* 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 "GLTextureImage.h" +#include "GLContext.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" + +using namespace mozilla::gl; + +already_AddRefed +TextureImage::Create(GLContext* gl, + const nsIntSize& size, + TextureImage::ContentType contentType, + GLenum wrapMode, + TextureImage::Flags flags) +{ + return gl->CreateTextureImage(size, contentType, wrapMode, flags); +} + +BasicTextureImage::~BasicTextureImage() +{ + GLContext *ctx = mGLContext; + if (ctx->IsDestroyed() || !ctx->IsOwningThreadCurrent()) { + ctx = ctx->GetSharedContext(); + } + + // If we have a context, then we need to delete the texture; + // if we don't have a context (either real or shared), + // then they went away when the contex was deleted, because it + // was the only one that had access to it. + if (ctx && !ctx->IsDestroyed()) { + mGLContext->MakeCurrent(); + mGLContext->fDeleteTextures(1, &mTexture); + } +} + +gfxASurface* +BasicTextureImage::BeginUpdate(nsIntRegion& aRegion) +{ + NS_ASSERTION(!mUpdateSurface, "BeginUpdate() without EndUpdate()?"); + + // determine the region the client will need to repaint + if (mGLContext->CanUploadSubTextures()) { + GetUpdateRegion(aRegion); + } else { + aRegion = nsIntRect(nsIntPoint(0, 0), mSize); + } + + mUpdateRegion = aRegion; + + nsIntRect rgnSize = mUpdateRegion.GetBounds(); + if (!nsIntRect(nsIntPoint(0, 0), mSize).Contains(rgnSize)) { + NS_ERROR("update outside of image"); + return NULL; + } + + ImageFormat format = + (GetContentType() == gfxASurface::CONTENT_COLOR) ? + gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; + mUpdateSurface = + GetSurfaceForUpdate(gfxIntSize(rgnSize.width, rgnSize.height), format); + + if (!mUpdateSurface || mUpdateSurface->CairoStatus()) { + mUpdateSurface = NULL; + return NULL; + } + + mUpdateSurface->SetDeviceOffset(gfxPoint(-rgnSize.x, -rgnSize.y)); + + return mUpdateSurface; +} + +void +BasicTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) +{ + // if the texture hasn't been initialized yet, or something important + // changed, we need to recreate our backing surface and force the + // client to paint everything + if (mTextureState != Valid) + aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); +} + +void +BasicTextureImage::EndUpdate() +{ + NS_ASSERTION(!!mUpdateSurface, "EndUpdate() without BeginUpdate()?"); + + // FIXME: this is the slow boat. Make me fast (with GLXPixmap?). + + // Undo the device offset that BeginUpdate set; doesn't much matter for us here, + // but important if we ever do anything directly with the surface. + mUpdateSurface->SetDeviceOffset(gfxPoint(0, 0)); + + bool relative = FinishedSurfaceUpdate(); + + mShaderType = + mGLContext->UploadSurfaceToTexture(mUpdateSurface, + mUpdateRegion, + mTexture, + mTextureState == Created, + mUpdateOffset, + relative); + FinishedSurfaceUpload(); + + mUpdateSurface = nullptr; + mTextureState = Valid; +} + +void +BasicTextureImage::BindTexture(GLenum aTextureUnit) +{ + mGLContext->fActiveTexture(aTextureUnit); + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); + mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); +} + +void +BasicTextureImage::ApplyFilter() +{ + mGLContext->ApplyFilterToBoundTexture(mFilter); +} + + +already_AddRefed +BasicTextureImage::GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt) +{ + return gfxPlatform::GetPlatform()-> + CreateOffscreenSurface(aSize, gfxASurface::ContentFromFormat(aFmt)); +} + +bool +BasicTextureImage::FinishedSurfaceUpdate() +{ + return false; +} + +void +BasicTextureImage::FinishedSurfaceUpload() +{ +} + +bool +BasicTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) +{ + nsIntRect bounds = aRegion.GetBounds(); + nsIntRegion region; + if (mTextureState != Valid) { + bounds = nsIntRect(0, 0, mSize.width, mSize.height); + region = nsIntRegion(bounds); + } else { + region = aRegion; + } + + mShaderType = + mGLContext->UploadSurfaceToTexture(aSurf, + region, + mTexture, + mTextureState == Created, + bounds.TopLeft() + aFrom, + false); + mTextureState = Valid; + return true; +} + +void +BasicTextureImage::Resize(const nsIntSize& aSize) +{ + NS_ASSERTION(!mUpdateSurface, "Resize() while in update?"); + + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); + + mGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D, + 0, + LOCAL_GL_RGBA, + aSize.width, + aSize.height, + 0, + LOCAL_GL_RGBA, + LOCAL_GL_UNSIGNED_BYTE, + NULL); + + mTextureState = Allocated; + mSize = aSize; +} + +TiledTextureImage::TiledTextureImage(GLContext* aGL, + nsIntSize aSize, + TextureImage::ContentType aContentType, + TextureImage::Flags aFlags) + : TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aFlags) + , mCurrentImage(0) + , mIterationCallback(nullptr) + , mInUpdate(false) + , mRows(0) + , mColumns(0) + , mGL(aGL) + , mTextureState(Created) +{ + mTileSize = (!(aFlags & TextureImage::ForceSingleTile) && mGL->WantsSmallTiles()) + ? 256 : mGL->GetMaxTextureSize(); + if (aSize != nsIntSize(0,0)) { + Resize(aSize); + } +} + +TiledTextureImage::~TiledTextureImage() +{ +} + +bool +TiledTextureImage::DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom /* = nsIntPoint(0, 0) */) +{ + nsIntRegion region; + + if (mTextureState != Valid) { + nsIntRect bounds = nsIntRect(0, 0, mSize.width, mSize.height); + region = nsIntRegion(bounds); + } else { + region = aRegion; + } + + bool result = true; + int oldCurrentImage = mCurrentImage; + BeginTileIteration(); + do { + nsIntRect tileRect = GetSrcTileRect(); + int xPos = tileRect.x; + int yPos = tileRect.y; + + nsIntRegion tileRegion; + tileRegion.And(region, tileRect); // intersect with tile + + if (tileRegion.IsEmpty()) + continue; + + if (mGL->CanUploadSubTextures()) { + tileRegion.MoveBy(-xPos, -yPos); // translate into tile local space + } else { + // If sub-textures are unsupported, expand to tile boundaries + tileRect.x = tileRect.y = 0; + tileRegion = nsIntRegion(tileRect); + } + + result &= mImages[mCurrentImage]-> + DirectUpdate(aSurf, tileRegion, aFrom + nsIntPoint(xPos, yPos)); + + if (mCurrentImage == mImages.Length() - 1) { + // We know we're done, but we still need to ensure that the callback + // gets called (e.g. to update the uploaded region). + NextTile(); + break; + } + // Override a callback cancelling iteration if the texture wasn't valid. + // We need to force the update in that situation, or we may end up + // showing invalid/out-of-date texture data. + } while (NextTile() || (mTextureState != Valid)); + mCurrentImage = oldCurrentImage; + + mShaderType = mImages[0]->GetShaderProgramType(); + mTextureState = Valid; + return result; +} + +void +TiledTextureImage::GetUpdateRegion(nsIntRegion& aForRegion) +{ + if (mTextureState != Valid) { + // if the texture hasn't been initialized yet, or something important + // changed, we need to recreate our backing surface and force the + // client to paint everything + aForRegion = nsIntRect(nsIntPoint(0, 0), mSize); + return; + } + + nsIntRegion newRegion; + + // We need to query each texture with the region it will be drawing and + // set aForRegion to be the combination of all of these regions + for (unsigned i = 0; i < mImages.Length(); i++) { + int xPos = (i % mColumns) * mTileSize; + int yPos = (i / mColumns) * mTileSize; + nsIntRect imageRect = nsIntRect(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); + + if (aForRegion.Intersects(imageRect)) { + // Make a copy of the region + nsIntRegion subRegion; + subRegion.And(aForRegion, imageRect); + // Translate it into tile-space + subRegion.MoveBy(-xPos, -yPos); + // Query region + mImages[i]->GetUpdateRegion(subRegion); + // Translate back + subRegion.MoveBy(xPos, yPos); + // Add to the accumulated region + newRegion.Or(newRegion, subRegion); + } + } + + aForRegion = newRegion; +} + +gfxASurface* +TiledTextureImage::BeginUpdate(nsIntRegion& aRegion) +{ + NS_ASSERTION(!mInUpdate, "nested update"); + mInUpdate = true; + + // Note, we don't call GetUpdateRegion here as if the updated region is + // fully contained in a single tile, we get to avoid iterating through + // the tiles again (and a little copying). + if (mTextureState != Valid) + { + // if the texture hasn't been initialized yet, or something important + // changed, we need to recreate our backing surface and force the + // client to paint everything + aRegion = nsIntRect(nsIntPoint(0, 0), mSize); + } + + nsIntRect bounds = aRegion.GetBounds(); + + for (unsigned i = 0; i < mImages.Length(); i++) { + int xPos = (i % mColumns) * mTileSize; + int yPos = (i / mColumns) * mTileSize; + nsIntRegion imageRegion = nsIntRegion(nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize())); + + // a single Image can handle this update request + if (imageRegion.Contains(aRegion)) { + // adjust for tile offset + aRegion.MoveBy(-xPos, -yPos); + // forward the actual call + nsRefPtr surface = mImages[i]->BeginUpdate(aRegion); + // caller expects container space + aRegion.MoveBy(xPos, yPos); + // Correct the device offset + gfxPoint offset = surface->GetDeviceOffset(); + surface->SetDeviceOffset(gfxPoint(offset.x - xPos, + offset.y - yPos)); + // we don't have a temp surface + mUpdateSurface = nullptr; + // remember which image to EndUpdate + mCurrentImage = i; + return surface.get(); + } + } + + // Get the real updated region, taking into account the capabilities of + // each TextureImage tile + GetUpdateRegion(aRegion); + mUpdateRegion = aRegion; + bounds = aRegion.GetBounds(); + + // update covers multiple Images - create a temp surface to paint in + gfxASurface::gfxImageFormat format = + (GetContentType() == gfxASurface::CONTENT_COLOR) ? + gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; + mUpdateSurface = gfxPlatform::GetPlatform()-> + CreateOffscreenSurface(gfxIntSize(bounds.width, bounds.height), gfxASurface::ContentFromFormat(format)); + mUpdateSurface->SetDeviceOffset(gfxPoint(-bounds.x, -bounds.y)); + + return mUpdateSurface; +} + +void +TiledTextureImage::EndUpdate() +{ + NS_ASSERTION(mInUpdate, "EndUpdate not in update"); + if (!mUpdateSurface) { // update was to a single TextureImage + mImages[mCurrentImage]->EndUpdate(); + mInUpdate = false; + mTextureState = Valid; + mShaderType = mImages[mCurrentImage]->GetShaderProgramType(); + return; + } + + // upload tiles from temp surface + for (unsigned i = 0; i < mImages.Length(); i++) { + int xPos = (i % mColumns) * mTileSize; + int yPos = (i / mColumns) * mTileSize; + nsIntRect imageRect = nsIntRect(nsIntPoint(xPos,yPos), mImages[i]->GetSize()); + + nsIntRegion subregion; + subregion.And(mUpdateRegion, imageRect); + if (subregion.IsEmpty()) + continue; + subregion.MoveBy(-xPos, -yPos); // Tile-local space + // copy tile from temp surface + gfxASurface* surf = mImages[i]->BeginUpdate(subregion); + nsRefPtr ctx = new gfxContext(surf); + gfxUtils::ClipToRegion(ctx, subregion); + ctx->SetOperator(gfxContext::OPERATOR_SOURCE); + ctx->SetSource(mUpdateSurface, gfxPoint(-xPos, -yPos)); + ctx->Paint(); + mImages[i]->EndUpdate(); + } + + mUpdateSurface = nullptr; + mInUpdate = false; + mShaderType = mImages[0]->GetShaderProgramType(); + mTextureState = Valid; +} + +void TiledTextureImage::BeginTileIteration() +{ + mCurrentImage = 0; +} + +bool TiledTextureImage::NextTile() +{ + bool continueIteration = true; + + if (mIterationCallback) + continueIteration = mIterationCallback(this, mCurrentImage, + mIterationCallbackData); + + if (mCurrentImage + 1 < mImages.Length()) { + mCurrentImage++; + return continueIteration; + } + return false; +} + +void TiledTextureImage::SetIterationCallback(TileIterationCallback aCallback, + void* aCallbackData) +{ + mIterationCallback = aCallback; + mIterationCallbackData = aCallbackData; +} + +nsIntRect TiledTextureImage::GetTileRect() +{ + nsIntRect rect = mImages[mCurrentImage]->GetTileRect(); + unsigned int xPos = (mCurrentImage % mColumns) * mTileSize; + unsigned int yPos = (mCurrentImage / mColumns) * mTileSize; + rect.MoveBy(xPos, yPos); + return rect; +} + +nsIntRect TiledTextureImage::GetSrcTileRect() +{ + nsIntRect rect = GetTileRect(); + unsigned int srcY = mFlags & NeedsYFlip + ? mSize.height - rect.height - rect.y + : rect.y; + return nsIntRect(rect.x, srcY, rect.width, rect.height); +} + +void +TiledTextureImage::BindTexture(GLenum aTextureUnit) +{ + mImages[mCurrentImage]->BindTexture(aTextureUnit); +} + +void +TiledTextureImage::ApplyFilter() +{ + mGL->ApplyFilterToBoundTexture(mFilter); +} + +/* + * Resize, trying to reuse tiles. The reuse strategy is to decide on reuse per + * column. A tile on a column is reused if it hasn't changed size, otherwise it + * is discarded/replaced. Extra tiles on a column are pruned after iterating + * each column, and extra rows are pruned after iteration over the entire image + * finishes. + */ +void TiledTextureImage::Resize(const nsIntSize& aSize) +{ + if (mSize == aSize && mTextureState != Created) { + return; + } + + // calculate rows and columns, rounding up + unsigned int columns = (aSize.width + mTileSize - 1) / mTileSize; + unsigned int rows = (aSize.height + mTileSize - 1) / mTileSize; + + // Iterate over old tile-store and insert/remove tiles as necessary + int row; + unsigned int i = 0; + for (row = 0; row < (int)rows; row++) { + // If we've gone beyond how many rows there were before, set mColumns to + // zero so that we only create new tiles. + if (row >= (int)mRows) + mColumns = 0; + + // Similarly, if we're on the last row of old tiles and the height has + // changed, discard all tiles in that row. + // This will cause the pruning of columns not to work, but we don't need + // to worry about that, as no more tiles will be reused past this point + // anyway. + if ((row == (int)mRows - 1) && (aSize.height != mSize.height)) + mColumns = 0; + + int col; + for (col = 0; col < (int)columns; col++) { + nsIntSize size( // use tilesize first, then the remainder + (col+1) * mTileSize > (unsigned int)aSize.width ? aSize.width % mTileSize : mTileSize, + (row+1) * mTileSize > (unsigned int)aSize.height ? aSize.height % mTileSize : mTileSize); + + bool replace = false; + + // Check if we can re-use old tiles. + if (col < (int)mColumns) { + // Reuse an existing tile. If the tile is an end-tile and the + // width differs, replace it instead. + if (mSize.width != aSize.width) { + if (col == (int)mColumns - 1) { + // Tile at the end of the old column, replace it with + // a new one. + replace = true; + } else if (col == (int)columns - 1) { + // Tile at the end of the new column, create a new one. + } else { + // Before the last column on both the old and new sizes, + // reuse existing tile. + i++; + continue; + } + } else { + // Width hasn't changed, reuse existing tile. + i++; + continue; + } + } + + // Create a new tile. + nsRefPtr teximg = + mGL->TileGenFunc(size, mContentType, mFlags); + if (replace) + mImages.ReplaceElementAt(i, teximg.forget()); + else + mImages.InsertElementAt(i, teximg.forget()); + i++; + } + + // Prune any unused tiles on the end of the column. + if (row < (int)mRows) { + for (col = (int)mColumns - col; col > 0; col--) { + mImages.RemoveElementAt(i); + } + } + } + + // Prune any unused tiles at the end of the store. + unsigned int length = mImages.Length(); + for (; i < length; i++) + mImages.RemoveElementAt(mImages.Length()-1); + + // Reset tile-store properties. + mRows = rows; + mColumns = columns; + mSize = aSize; + mTextureState = Allocated; + mCurrentImage = 0; +} + +uint32_t TiledTextureImage::GetTileCount() +{ + return mImages.Length(); +} + +TextureImage::ScopedBindTexture::ScopedBindTexture(TextureImage* aTexture, + GLenum aTextureUnit) + : mTexture(aTexture) +{ + if (mTexture) { + MOZ_ASSERT(aTextureUnit >= LOCAL_GL_TEXTURE0); + mTexture->BindTexture(aTextureUnit); + } +} diff --git a/gfx/gl/GLTextureImage.h b/gfx/gl/GLTextureImage.h new file mode 100644 index 000000000000..4217778fb0ef --- /dev/null +++ b/gfx/gl/GLTextureImage.h @@ -0,0 +1,385 @@ +/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */ +/* 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 GLTEXTUREIMAGE_H_ +#define GLTEXTUREIMAGE_H_ + +#include "nsAutoPtr.h" +#include "nsRegion.h" +#include "gfxASurface.h" +#include "GLContextTypes.h" +#include "gfxPattern.h" + +namespace mozilla { +namespace gl { +class GLContext; + +/** + * A TextureImage encapsulates a surface that can be drawn to by a + * Thebes gfxContext and (hopefully efficiently!) synchronized to a + * texture in the server. TextureImages are associated with one and + * only one GLContext. + * + * Implementation note: TextureImages attempt to unify two categories + * of backends + * + * (1) proxy to server-side object that can be bound to a texture; + * e.g. Pixmap on X11. + * + * (2) efficient manager of texture memory; e.g. by having clients draw + * into a scratch buffer which is then uploaded with + * glTexSubImage2D(). + */ +class TextureImage +{ + NS_INLINE_DECL_REFCOUNTING(TextureImage) +public: + enum TextureState + { + Created, // Texture created, but has not had glTexImage called to initialize it. + Allocated, // Texture memory exists, but contents are invalid. + Valid // Texture fully ready to use. + }; + + enum Flags { + NoFlags = 0x0, + UseNearestFilter = 0x1, + NeedsYFlip = 0x2, + ForceSingleTile = 0x4 + }; + + typedef gfxASurface::gfxContentType ContentType; + + static already_AddRefed Create( + GLContext* gl, + const nsIntSize& aSize, + TextureImage::ContentType aContentType, + GLenum aWrapMode, + TextureImage::Flags aFlags = TextureImage::NoFlags); + + virtual ~TextureImage() {} + + /** + * Returns a gfxASurface for updating |aRegion| of the client's + * image if successul, NULL if not. |aRegion|'s bounds must fit + * within Size(); its coordinate space (if any) is ignored. If + * the update begins successfully, the returned gfxASurface is + * owned by this. Otherwise, NULL is returned. + * + * |aRegion| is an inout param: the returned region is what the + * client must repaint. Category (1) regions above can + * efficiently handle repaints to "scattered" regions, while (2) + * can only efficiently handle repaints to rects. + * + * Painting the returned surface outside of |aRegion| results + * in undefined behavior. + * + * BeginUpdate() calls cannot be "nested", and each successful + * BeginUpdate() must be followed by exactly one EndUpdate() (see + * below). Failure to do so can leave this in a possibly + * inconsistent state. Unsuccessful BeginUpdate()s must not be + * followed by EndUpdate(). + */ + virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion) = 0; + /** + * Retrieves the region that will require updating, given a + * region that needs to be updated. This can be used for + * making decisions about updating before calling BeginUpdate(). + * + * |aRegion| is an inout param. + */ + virtual void GetUpdateRegion(nsIntRegion& aForRegion) { + } + /** + * Finish the active update and synchronize with the server, if + * necessary. + * + * BeginUpdate() must have been called exactly once before + * EndUpdate(). + */ + virtual void EndUpdate() = 0; + + /** + * The Image may contain several textures for different regions (tiles). + * These functions iterate over each sub texture image tile. + */ + virtual void BeginTileIteration() { + } + + virtual bool NextTile() { + return false; + } + + // Function prototype for a tile iteration callback. Returning false will + // cause iteration to be interrupted (i.e. the corresponding NextTile call + // will return false). + typedef bool (* TileIterationCallback)(TextureImage* aImage, + int aTileNumber, + void* aCallbackData); + + // Sets a callback to be called every time NextTile is called. + virtual void SetIterationCallback(TileIterationCallback aCallback, + void* aCallbackData) { + } + + virtual nsIntRect GetTileRect() { + return nsIntRect(nsIntPoint(0,0), mSize); + } + + virtual GLuint GetTextureID() = 0; + + virtual uint32_t GetTileCount() { + return 1; + } + + /** + * Set this TextureImage's size, and ensure a texture has been + * allocated. Must not be called between BeginUpdate and EndUpdate. + * After a resize, the contents are undefined. + * + * If this isn't implemented by a subclass, it will just perform + * a dummy BeginUpdate/EndUpdate pair. + */ + virtual void Resize(const nsIntSize& aSize) { + mSize = aSize; + nsIntRegion r(nsIntRect(0, 0, aSize.width, aSize.height)); + BeginUpdate(r); + EndUpdate(); + } + + /** + * Mark this texture as having valid contents. Call this after modifying + * the texture contents externally. + */ + virtual void MarkValid() {} + + /** + * aSurf - the source surface to update from + * aRegion - the region in this image to update + * aFrom - offset in the source to update from + */ + virtual bool DirectUpdate(gfxASurface *aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)) = 0; + + virtual void BindTexture(GLenum aTextureUnit) = 0; + virtual void ReleaseTexture() {} + + void BindTextureAndApplyFilter(GLenum aTextureUnit) { + BindTexture(aTextureUnit); + ApplyFilter(); + } + + class ScopedBindTexture + { + public: + ScopedBindTexture(TextureImage *aTexture, GLenum aTextureUnit); + + ~ScopedBindTexture() + { + if (mTexture) { + mTexture->ReleaseTexture(); + } + } + + protected: + TextureImage *mTexture; + }; + + class ScopedBindTextureAndApplyFilter + : public ScopedBindTexture + { + public: + ScopedBindTextureAndApplyFilter(TextureImage *aTexture, GLenum aTextureUnit) : + ScopedBindTexture(aTexture, aTextureUnit) + { + if (mTexture) { + mTexture->ApplyFilter(); + } + } + }; + + /** + * Returns the shader program type that should be used to render + * this texture. Only valid after a matching BeginUpdate/EndUpdate + * pair have been called. + */ + virtual ShaderProgramType GetShaderProgramType() + { + return mShaderType; + } + + /** Can be called safely at any time. */ + + /** + * If this TextureImage has a permanent gfxASurface backing, + * return it. Otherwise return NULL. + */ + virtual already_AddRefed GetBackingSurface() + { return NULL; } + + const nsIntSize& GetSize() const { return mSize; } + ContentType GetContentType() const { return mContentType; } + virtual bool InUpdate() const = 0; + GLenum GetWrapMode() const { return mWrapMode; } + + void SetFilter(gfxPattern::GraphicsFilter aFilter) { mFilter = aFilter; } + + /** + * Applies this TextureImage's filter, assuming that its texture is + * the currently bound texture. + */ + virtual void ApplyFilter() = 0; + +protected: + friend class GLContext; + + /** + * After the ctor, the TextureImage is invalid. Implementations + * must allocate resources successfully before returning the new + * TextureImage from GLContext::CreateTextureImage(). That is, + * clients must not be given partially-constructed TextureImages. + */ + TextureImage(const nsIntSize& aSize, + GLenum aWrapMode, ContentType aContentType, + Flags aFlags = NoFlags) + : mSize(aSize) + , mWrapMode(aWrapMode) + , mContentType(aContentType) + , mFilter(gfxPattern::FILTER_GOOD) + , mFlags(aFlags) + {} + + virtual nsIntRect GetSrcTileRect() { + return nsIntRect(nsIntPoint(0,0), mSize); + } + + nsIntSize mSize; + GLenum mWrapMode; + ContentType mContentType; + ShaderProgramType mShaderType; + gfxPattern::GraphicsFilter mFilter; + Flags mFlags; +}; + +/** + * BasicTextureImage is the baseline TextureImage implementation --- + * it updates its texture by allocating a scratch buffer for the + * client to draw into, then using glTexSubImage2D() to upload the new + * pixels. Platforms must provide the code to create a new surface + * into which the updated pixels will be drawn, and the code to + * convert the update surface's pixels into an image on which we can + * glTexSubImage2D(). + */ +class BasicTextureImage + : public TextureImage +{ +public: + typedef gfxASurface::gfxImageFormat ImageFormat; + virtual ~BasicTextureImage(); + + BasicTextureImage(GLuint aTexture, + const nsIntSize& aSize, + GLenum aWrapMode, + ContentType aContentType, + GLContext* aContext, + TextureImage::Flags aFlags = TextureImage::NoFlags) + : TextureImage(aSize, aWrapMode, aContentType, aFlags) + , mTexture(aTexture) + , mTextureState(Created) + , mGLContext(aContext) + , mUpdateOffset(0, 0) + {} + + virtual void BindTexture(GLenum aTextureUnit); + + virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); + virtual void GetUpdateRegion(nsIntRegion& aForRegion); + virtual void EndUpdate(); + virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); + virtual GLuint GetTextureID() { return mTexture; } + // Returns a surface to draw into + virtual already_AddRefed + GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt); + + virtual void MarkValid() { mTextureState = Valid; } + + // Call when drawing into the update surface is complete. + // Returns true if textures should be upload with a relative + // offset - See UploadSurfaceToTexture. + virtual bool FinishedSurfaceUpdate(); + + // Call after surface data has been uploaded to a texture. + virtual void FinishedSurfaceUpload(); + + virtual bool InUpdate() const { return !!mUpdateSurface; } + + virtual void Resize(const nsIntSize& aSize); + + virtual void ApplyFilter(); +protected: + + GLuint mTexture; + TextureState mTextureState; + GLContext* mGLContext; + nsRefPtr mUpdateSurface; + nsIntRegion mUpdateRegion; + + // The offset into the update surface at which the update rect is located. + nsIntPoint mUpdateOffset; +}; + +/** + * A container class that complements many sub TextureImages into a big TextureImage. + * Aims to behave just like the real thing. + */ + +class TiledTextureImage + : public TextureImage +{ +public: + TiledTextureImage(GLContext* aGL, nsIntSize aSize, + TextureImage::ContentType, TextureImage::Flags aFlags = TextureImage::NoFlags); + ~TiledTextureImage(); + void DumpDiv(); + virtual gfxASurface* BeginUpdate(nsIntRegion& aRegion); + virtual void GetUpdateRegion(nsIntRegion& aForRegion); + virtual void EndUpdate(); + virtual void Resize(const nsIntSize& aSize); + virtual uint32_t GetTileCount(); + virtual void BeginTileIteration(); + virtual bool NextTile(); + virtual void SetIterationCallback(TileIterationCallback aCallback, + void* aCallbackData); + virtual nsIntRect GetTileRect(); + virtual GLuint GetTextureID() { + return mImages[mCurrentImage]->GetTextureID(); + } + virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); + virtual bool InUpdate() const { return mInUpdate; } + virtual void BindTexture(GLenum); + virtual void ApplyFilter(); + +protected: + virtual nsIntRect GetSrcTileRect(); + + unsigned int mCurrentImage; + TileIterationCallback mIterationCallback; + void* mIterationCallbackData; + nsTArray< nsRefPtr > mImages; + bool mInUpdate; + nsIntSize mSize; + unsigned int mTileSize; + unsigned int mRows, mColumns; + GLContext* mGL; + // A temporary surface to faciliate cross-tile updates. + nsRefPtr mUpdateSurface; + // The region of update requested + nsIntRegion mUpdateRegion; + TextureState mTextureState; +}; + +} // namespace gl +} // namespace mozilla + +#endif /* GLTEXTUREIMAGE_H_ */ diff --git a/gfx/gl/Makefile.in b/gfx/gl/Makefile.in index e3729a278007..8f1cde8fe9de 100644 --- a/gfx/gl/Makefile.in +++ b/gfx/gl/Makefile.in @@ -24,6 +24,7 @@ EXPORTS = \ GLContextProviderImpl.h \ GLLibraryLoader.h \ ForceDiscreteGPUHelperCGL.h \ + GLTextureImage.h \ $(NULL) ifdef MOZ_X11 @@ -48,6 +49,7 @@ CPPSRCS = \ GLContext.cpp \ GLContextUtils.cpp \ GLLibraryLoader.cpp \ + GLTextureImage.cpp \ $(NULL) GL_PROVIDER = Null diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index 0d6f08572e48..7eeb314933b4 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -51,6 +51,7 @@ #include "nsRegion.h" #include "Layers.h" #include "LayerManagerOGL.h" +#include "GLTextureImage.h" #include "mozilla/layers/CompositorCocoaWidgetHelper.h" #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" @@ -1754,10 +1755,11 @@ nsChildView::DrawWindowOverlay(LayerManager* aManager, nsIntRect aRect) } if (!mResizerImage) { - mResizerImage = manager->gl()->CreateTextureImage(nsIntSize(15, 15), - gfxASurface::CONTENT_COLOR_ALPHA, - LOCAL_GL_CLAMP_TO_EDGE, - TextureImage::UseNearestFilter); + mResizerImage = TextureImage::Create(manager->gl(), + nsIntSize(15, 15), + gfxASurface::CONTENT_COLOR_ALPHA, + LOCAL_GL_CLAMP_TO_EDGE, + TextureImage::UseNearestFilter); // Creation of texture images can fail. if (!mResizerImage) From 38a35ea5a73ec280207934b709d4b9e0e161d2c4 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Mon, 26 Nov 2012 14:41:55 -0800 Subject: [PATCH 054/160] Bug 811784: Account for subscripts when figuring out what object to stick properties on. r=mrbkap --- js/xpconnect/loader/mozJSComponentLoader.cpp | 10 +++ js/xpconnect/loader/mozJSComponentLoader.h | 2 + js/xpconnect/loader/mozJSSubScriptLoader.cpp | 69 +++++++++++++++----- js/xpconnect/loader/mozJSSubScriptLoader.h | 3 +- 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/js/xpconnect/loader/mozJSComponentLoader.cpp b/js/xpconnect/loader/mozJSComponentLoader.cpp index f05442fea00f..fcbe40320a9d 100644 --- a/js/xpconnect/loader/mozJSComponentLoader.cpp +++ b/js/xpconnect/loader/mozJSComponentLoader.cpp @@ -625,6 +625,16 @@ mozJSComponentLoader::FindTargetObject(JSContext* aCx, return NS_OK; } +void +mozJSComponentLoader::NoteSubScript(JSScript* aScript, JSObject* aThisObject) +{ + if (!mInitialized && NS_FAILED(ReallyInit())) { + MOZ_NOT_REACHED(); + } + + mThisObjects.Put(aScript, aThisObject); +} + // Some stack based classes for cleaning up on early return #ifdef HAVE_PR_MEMMAP class FileAutoCloser diff --git a/js/xpconnect/loader/mozJSComponentLoader.h b/js/xpconnect/loader/mozJSComponentLoader.h index d8bdcd1af425..61dbc461c3c7 100644 --- a/js/xpconnect/loader/mozJSComponentLoader.h +++ b/js/xpconnect/loader/mozJSComponentLoader.h @@ -54,6 +54,8 @@ class mozJSComponentLoader : public mozilla::ModuleLoader, static mozJSComponentLoader* Get() { return sSelf; } + void NoteSubScript(JSScript* aScript, JSObject* aThisObject); + protected: static mozJSComponentLoader* sSelf; diff --git a/js/xpconnect/loader/mozJSSubScriptLoader.cpp b/js/xpconnect/loader/mozJSSubScriptLoader.cpp index 999302a41f0c..3969036d093b 100644 --- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp +++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp @@ -31,6 +31,7 @@ #include "mozilla/scache/StartupCache.h" #include "mozilla/scache/StartupCacheUtils.h" +#include "mozilla/Preferences.h" using namespace mozilla::scache; @@ -77,12 +78,16 @@ nsresult mozJSSubScriptLoader::ReadScript(nsIURI *uri, JSContext *cx, JSObject *target_obj, const nsAString& charset, const char *uriStr, nsIIOService *serv, nsIPrincipal *principal, - JSScript **scriptp) + bool reuseGlobal, JSScript **scriptp, + JSFunction **functionp) { nsCOMPtr chan; nsCOMPtr instream; JSErrorReporter er; + *scriptp = nullptr; + *functionp = nullptr; + nsresult rv; // Instead of calling NS_OpenURI, we create the channel ourselves and call // SetContentType, to avoid expensive MIME type lookups (bug 632490). @@ -130,13 +135,27 @@ mozJSSubScriptLoader::ReadScript(nsIURI *uri, JSContext *cx, JSObject *target_ob return ReportError(cx, LOAD_ERROR_BADCHARSET); } - *scriptp = JS::Compile(cx, target_obj_root, options, - reinterpret_cast(script.get()), script.Length()); + if (!reuseGlobal) { + *scriptp = JS::Compile(cx, target_obj_root, options, + reinterpret_cast(script.get()), + script.Length()); + } else { + *functionp = JS::CompileFunction(cx, target_obj_root, options, + nullptr, 0, nullptr, + reinterpret_cast(script.get()), + script.Length()); + } } else { // We only use LAZY_SOURCE when no special encoding is specified because // the lazy source loader doesn't know the encoding. - options.setSourcePolicy(JS::CompileOptions::LAZY_SOURCE); - *scriptp = JS::Compile(cx, target_obj_root, options, buf.get(), len); + if (!reuseGlobal) { + options.setSourcePolicy(JS::CompileOptions::LAZY_SOURCE); + *scriptp = JS::Compile(cx, target_obj_root, options, buf.get(), len); + } else { + *functionp = JS::CompileFunction(cx, target_obj_root, options, + nullptr, 0, nullptr, buf.get(), + len); + } } /* repent for our evil deeds */ @@ -180,16 +199,20 @@ mozJSSubScriptLoader::LoadSubScript(const nsAString& url, JSAutoRequest ar(cx); JSObject* targetObj; - if (!JS_ValueToObject(cx, target, &targetObj)) + mozJSComponentLoader* loader = mozJSComponentLoader::Get(); + rv = loader->FindTargetObject(cx, &targetObj); + NS_ENSURE_SUCCESS(rv, rv); + + bool reusingGlobal = !JS_IsGlobalObject(targetObj); + + // We base reusingGlobal off of what the loader told us, but we may not + // actually be using that object. + JSObject* passedObj; + if (!JS_ValueToObject(cx, target, &passedObj)) return NS_ERROR_ILLEGAL_VALUE; - - if (!targetObj) { - // If the user didn't provide an object to eval onto, find one. - mozJSComponentLoader* loader = mozJSComponentLoader::Get(); - rv = loader->FindTargetObject(cx, &targetObj); - NS_ENSURE_SUCCESS(rv, rv); - } + if (passedObj) + targetObj = passedObj; // Remember an object out of the calling compartment so that we // can properly wrap the result later. @@ -274,20 +297,32 @@ mozJSSubScriptLoader::LoadSubScript(const nsAString& url, cachePath.AppendPrintf("jssubloader/%d", version); PathifyURI(uri, cachePath); + JSFunction* function = nullptr; script = nullptr; if (cache) rv = ReadCachedScript(cache, cachePath, cx, mSystemPrincipal, &script); if (!script) { rv = ReadScript(uri, cx, targetObj, charset, static_cast(uriStr.get()), serv, - principal, &script); - writeScript = true; + principal, reusingGlobal, &script, &function); + writeScript = !!script; } - if (NS_FAILED(rv) || !script) + if (NS_FAILED(rv) || (!script && !function)) return rv; - bool ok = JS_ExecuteScriptVersion(cx, targetObj, script, retval, version); + if (function) { + script = JS_GetFunctionScript(cx, function); + } + + loader->NoteSubScript(script, targetObj); + + bool ok = false; + if (function) { + ok = JS_CallFunction(cx, targetObj, function, 0, nullptr, retval); + } else { + ok = JS_ExecuteScriptVersion(cx, targetObj, script, retval, version); + } if (ok) { JSAutoCompartment rac(cx, result_obj); diff --git a/js/xpconnect/loader/mozJSSubScriptLoader.h b/js/xpconnect/loader/mozJSSubScriptLoader.h index 4d7b7b5a33ab..ab384df9e5d8 100644 --- a/js/xpconnect/loader/mozJSSubScriptLoader.h +++ b/js/xpconnect/loader/mozJSSubScriptLoader.h @@ -33,7 +33,8 @@ private: nsresult ReadScript(nsIURI *uri, JSContext *cx, JSObject *target_obj, const nsAString& charset, const char *uriStr, nsIIOService *serv, nsIPrincipal *principal, - JSScript **scriptp); + bool reuseGlobal, JSScript **scriptp, + JSFunction **functionp); nsCOMPtr mSystemPrincipal; }; From 81804a45748a222855aab4158ef609182acbf031 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Mon, 26 Nov 2012 14:41:59 -0800 Subject: [PATCH 055/160] Bug 814102: Make the 'this' object used when compartment sharing a FakeBackstagePass. r=mrbkap --- js/xpconnect/loader/mozJSComponentLoader.cpp | 21 ++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/js/xpconnect/loader/mozJSComponentLoader.cpp b/js/xpconnect/loader/mozJSComponentLoader.cpp index fcbe40320a9d..12bfc1e7e8d0 100644 --- a/js/xpconnect/loader/mozJSComponentLoader.cpp +++ b/js/xpconnect/loader/mozJSComponentLoader.cpp @@ -72,6 +72,22 @@ using namespace mozilla; using namespace mozilla::scache; using namespace xpc; +// This JSClass exists to trick silly code that expects toString()ing the +// global in a component scope to return something with "BackstagePass" in it +// to continue working. +static JSClass kFakeBackstagePassJSClass = +{ + "FakeBackstagePass", + 0, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_StrictPropertyStub, + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub +}; + static const char kJSRuntimeServiceContractID[] = "@mozilla.org/js/xpc/RuntimeService;1"; static const char kXPConnectServiceContractID[] = "@mozilla.org/js/xpc/XPConnect;1"; static const char kObserverServiceContractID[] = "@mozilla.org/observer-service;1"; @@ -472,7 +488,7 @@ mozJSComponentLoader::LoadModule(FileLocation &aFile) ModuleEntry* mod; if (mModules.Get(spec, &mod)) - return mod; + return mod; nsAutoPtr entry(new ModuleEntry); @@ -717,7 +733,8 @@ mozJSComponentLoader::PrepareObjectForLocation(JSCLContextHelper& aCx, if (aReuseLoaderGlobal) { // If we're reusing the loader global, we don't actually use the // global, but rather we use a different object as the 'this' object. - JSObject* newObj = JS_NewObject(aCx, nullptr, nullptr, nullptr); + JSObject* newObj = JS_NewObject(aCx, &kFakeBackstagePassJSClass, + nullptr, nullptr); NS_ENSURE_TRUE(newObj, nullptr); obj = newObj; From 6cb2fce1f04a4ae4974bb9fb9db45ec7fd1bae5c Mon Sep 17 00:00:00 2001 From: Brian Nicholson Date: Mon, 26 Nov 2012 14:48:11 -0800 Subject: [PATCH 056/160] Bug 775142 - Use placeholder format strings for localization. r=mfinkle,Pike --- .../android/base/locales/en-US/android_strings.dtd | 13 +++++-------- mobile/android/base/strings.xml.in | 9 +++++++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index d137e0bd8254..745e9f31142c 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -56,9 +56,9 @@ - - + @@ -156,8 +156,6 @@ size. --> - @@ -240,10 +238,9 @@ just addresses the organization to follow, e.g. "This site is run by " --> from Android"> - - + + diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index b76a71ee5e72..5972c13054d5 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -8,7 +8,12 @@ #includesubst @BRANDPATH@ #includesubst @STRINGSPATH@ #includesubst @SYNCSTRINGSPATH@ + + + + ]> + #includesubst @BOOKMARKSPATH@ @MOZ_APP_DISPLAYNAME@ @@ -129,7 +134,7 @@ &new_tab; &new_tab_opened; &one_tab; - &num_tabs; + &num_tabs2; &addons; &downloads; &apps; @@ -243,6 +248,6 @@ &updater_apply_select2; - &suggestions_prompt; + &suggestions_prompt2; From a200a3bb380269615bf4d92870196f8ac800fe3f Mon Sep 17 00:00:00 2001 From: Wes Johnston Date: Mon, 26 Nov 2012 14:57:11 -0800 Subject: [PATCH 057/160] Bug 813677 - Only send about:home telemetry pings when showing about:home. r=mfinkle --- mobile/android/base/BrowserApp.java | 11 ++++++++--- mobile/android/base/GeckoApp.java | 2 -- mobile/android/base/Telemetry.java | 6 ++++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index 8600cae01cdd..b561cf0b3121 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -58,6 +58,7 @@ abstract public class BrowserApp extends GeckoApp public static BrowserToolbar mBrowserToolbar; private AboutHomeContent mAboutHomeContent; private Boolean mAboutHomeShowing = null; + protected Telemetry.Timer mAboutHomeStartupTimer = null; private static final int ADDON_MENU_OFFSET = 1000; private class MenuItemInfo { @@ -202,6 +203,8 @@ abstract public class BrowserApp extends GeckoApp @Override public void onCreate(Bundle savedInstanceState) { + mAboutHomeStartupTimer = new Telemetry.Timer("FENNEC_STARTUP_TIME_ABOUTHOME"); + super.onCreate(savedInstanceState); LinearLayout actionBar = (LinearLayout) getActionBarLayout(); @@ -271,10 +274,12 @@ abstract public class BrowserApp extends GeckoApp Tab tab = Tabs.getInstance().loadUrl("about:home", Tabs.LOADURL_NEW_TAB); } else { hideAboutHome(); + mAboutHomeStartupTimer.cancel(); } } else { int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED; Tabs.getInstance().loadUrl(uri, flags); + mAboutHomeStartupTimer.cancel(); } } @@ -670,9 +675,9 @@ abstract public class BrowserApp extends GeckoApp } }); mAboutHomeContent.setLoadCompleteCallback(new AboutHomeContent.VoidCallback() { - public void callback() { - mAboutHomeStartupTimer.stop(); - } + public void callback() { + mAboutHomeStartupTimer.stop(); + } }); } else { mAboutHomeContent.update(EnumSet.of(AboutHomeContent.UpdateFlags.TOP_SITES, diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index d9a344b631a5..5ab49fd438b8 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -190,7 +190,6 @@ abstract public class GeckoApp protected int mRestoreMode = RESTORE_NONE; protected boolean mInitialized = false; - protected Telemetry.Timer mAboutHomeStartupTimer; private Telemetry.Timer mJavaUiStartupTimer; private Telemetry.Timer mGeckoReadyStartupTimer; @@ -1470,7 +1469,6 @@ abstract public class GeckoApp // The clock starts...now. Better hurry! mJavaUiStartupTimer = new Telemetry.Timer("FENNEC_STARTUP_TIME_JAVAUI"); - mAboutHomeStartupTimer = new Telemetry.Timer("FENNEC_STARTUP_TIME_ABOUTHOME"); mGeckoReadyStartupTimer = new Telemetry.Timer("FENNEC_STARTUP_TIME_GECKOREADY"); ((GeckoApplication)getApplication()).initialize(); diff --git a/mobile/android/base/Telemetry.java b/mobile/android/base/Telemetry.java index 9245fd8301fa..256f418f61a1 100644 --- a/mobile/android/base/Telemetry.java +++ b/mobile/android/base/Telemetry.java @@ -27,8 +27,6 @@ public class Telemetry { GeckoEvent event = GeckoEvent.createBroadcastEvent("Telemetry:Add", jsonData.toString()); GeckoAppShell.sendEventToGecko(event); - - Log.v(LOGTAG, "Sending telemetry: " + jsonData.toString()); } catch (JSONException e) { Log.e(LOGTAG, "JSON exception: ", e); } @@ -45,6 +43,10 @@ public class Telemetry { mHasFinished = false; } + public void cancel() { + mHasFinished = true; + } + public void stop() { // Only the first stop counts. if (mHasFinished) { From 3f615fa66094be06254cff74c65f0698c0a612a1 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Thu, 22 Nov 2012 18:33:33 +1300 Subject: [PATCH 058/160] Bug 810592 - Make nsSubDocumentFrames that are actively scrolling build their own layer. r=roc --- content/test/reftest/bug559996-ref.html | 11 ++++++++++- content/test/reftest/bug559996.html | 11 ++++++++++- layout/generic/nsSubDocumentFrame.cpp | 5 ++++- .../reftests/scrolling/iframe-border-radius-ref.html | 8 +++++--- layout/reftests/scrolling/iframe-border-radius.html | 2 +- 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/content/test/reftest/bug559996-ref.html b/content/test/reftest/bug559996-ref.html index 4133bf0c6f24..05178cbf8d6e 100644 --- a/content/test/reftest/bug559996-ref.html +++ b/content/test/reftest/bug559996-ref.html @@ -1,7 +1,16 @@ - + + diff --git a/content/test/reftest/bug559996.html b/content/test/reftest/bug559996.html index 766bb7bfdbe8..e6dda8df9119 100644 --- a/content/test/reftest/bug559996.html +++ b/content/test/reftest/bug559996.html @@ -1,7 +1,16 @@ - + + diff --git a/layout/generic/nsSubDocumentFrame.cpp b/layout/generic/nsSubDocumentFrame.cpp index 66fcd6a98710..51c8a2acab5a 100644 --- a/layout/generic/nsSubDocumentFrame.cpp +++ b/layout/generic/nsSubDocumentFrame.cpp @@ -414,7 +414,10 @@ nsSubDocumentFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, childItems.AppendToTop(zoomItem); } - if (!addedLayer && presContext->IsRootContentDocument()) { + nsIScrollableFrame *sf = presShell->GetRootScrollFrameAsScrollable(); + if (!addedLayer && + (presContext->IsRootContentDocument() || + (sf && sf->IsScrollingActive()))) { // We always want top level content documents to be in their own layer. nsDisplayOwnLayer* layerItem = new (aBuilder) nsDisplayOwnLayer( aBuilder, subdocRootFrame ? subdocRootFrame : this, diff --git a/layout/reftests/scrolling/iframe-border-radius-ref.html b/layout/reftests/scrolling/iframe-border-radius-ref.html index c2f8ae9f8a33..de5f611c1798 100644 --- a/layout/reftests/scrolling/iframe-border-radius-ref.html +++ b/layout/reftests/scrolling/iframe-border-radius-ref.html @@ -1,13 +1,15 @@ - - - diff --git a/layout/reftests/scrolling/iframe-border-radius.html b/layout/reftests/scrolling/iframe-border-radius.html index d6c00a635473..1901bc5dc43f 100644 --- a/layout/reftests/scrolling/iframe-border-radius.html +++ b/layout/reftests/scrolling/iframe-border-radius.html @@ -1,7 +1,7 @@ - + + +

+

+
+
+ + + +
+
+
+

&availability;

+
    +
    +
    +

    &connecting;

    +
    +
    + + diff --git a/browser/devtools/framework/gDevTools.jsm b/browser/devtools/framework/gDevTools.jsm new file mode 100644 index 000000000000..6c22ebdf3847 --- /dev/null +++ b/browser/devtools/framework/gDevTools.jsm @@ -0,0 +1,543 @@ +/* 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/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = [ "gDevTools", "DevTools", "DevToolsXULCommands" ]; + +const Cu = Components.utils; +const Ci = Components.interfaces; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource:///modules/devtools/EventEmitter.jsm"); +Cu.import("resource:///modules/devtools/ToolDefinitions.jsm"); +Cu.import("resource:///modules/devtools/Toolbox.jsm"); +Cu.import("resource:///modules/devtools/Target.jsm"); + +const FORBIDDEN_IDS = new Set("toolbox", ""); + +/** + * DevTools is a class that represents a set of developer tools, it holds a + * set of tools and keeps track of open toolboxes in the browser. + */ +this.DevTools = function DevTools() { + this._tools = new Map(); + this._toolboxes = new Map(); + + // destroy() is an observer's handler so we need to preserve context. + this.destroy = this.destroy.bind(this); + + this._trackedBrowserWindows = new Set(); + + // Bind _updateMenuCheckbox() to preserve context. + this._updateMenuCheckbox = this._updateMenuCheckbox.bind(this); + + new EventEmitter(this); + + Services.obs.addObserver(this.destroy, "quit-application", false); + + /** + * Register the set of default tools + */ + for (let definition of defaultTools) { + this.registerTool(definition); + } +} + +DevTools.prototype = { + /** + * Register a new developer tool. + * + * A definition is a light object that holds different information about a + * developer tool. This object is not supposed to have any operational code. + * See it as a "manifest". + * The only actual code lives in the build() function, which will be used to + * start an instance of this tool. + * + * Each toolDefinition has the following properties: + * - id: Unique identifier for this tool (string|required) + * - killswitch: Property name to allow us to turn this tool on/off globally + * (string|required) (TODO: default to devtools.{id}.enabled?) + * - icon: URL pointing to a graphic which will be used as the src for an + * 16x16 img tag (string|required) + * - url: URL pointing to a XUL/XHTML document containing the user interface + * (string|required) + * - label: Localized name for the tool to be displayed to the user + * (string|required) + * - build: Function that takes an iframe, which has been populated with the + * markup from |url|, and also the toolbox containing the panel. + * And returns an instance of ToolPanel (function|required) + */ + registerTool: function DT_registerTool(toolDefinition) { + let toolId = toolDefinition.id; + + if (!toolId || FORBIDDEN_IDS.has(toolId)) { + throw new Error("Invalid definition.id"); + } + + toolDefinition.killswitch = toolDefinition.killswitch || + "devtools." + toolId + ".enabled"; + this._tools.set(toolId, toolDefinition); + + this._addToolToWindows(toolDefinition); + + this.emit("tool-registered", toolId); + }, + + /** + * Removes all tools that match the given |toolId| + * Needed so that add-ons can remove themselves when they are deactivated + * + * @param {string} toolId + * id of the tool to unregister + */ + unregisterTool: function DT_unregisterTool(toolId) { + this._tools.delete(toolId); + + this._removeToolFromWindows(toolId); + + this.emit("tool-unregistered", toolId); + }, + + /** + * Allow ToolBoxes to get at the list of tools that they should populate + * themselves with. + * + * @return {Map} tools + * A map of the the tool definitions registered in this instance + */ + getToolDefinitions: function DT_getToolDefinitions() { + let tools = new Map(); + + for (let [key, value] of this._tools) { + let enabled; + + try { + enabled = Services.prefs.getBoolPref(value.killswitch); + } catch(e) { + enabled = true; + } + + if (enabled) { + tools.set(key, value); + } + } + return tools; + }, + + /** + * Create a toolbox to debug |target| using a window displayed in |hostType| + * (optionally with |defaultToolId| opened) + * + * @param {Target} target + * The target the toolbox will debug + * @param {Toolbox.HostType} hostType + * The type of host (bottom, top, side) + * @param {string} defaultToolId + * The id of the initial tool to show + * + * @return {Toolbox} toolbox + * The toolbox that was opened + */ + openToolbox: function DT_openToolbox(target, hostType, defaultToolId) { + if (this._toolboxes.has(target)) { + // only allow one toolbox per target + return this._toolboxes.get(target); + } + + let tb = new Toolbox(target, hostType, defaultToolId); + + this._toolboxes.set(target, tb); + tb.once("destroyed", function() { + this._toolboxes.delete(target); + this._updateMenuCheckbox(); + this.emit("toolbox-destroyed", target); + }.bind(this)); + + tb.once("ready", function() { + this.emit("toolbox-ready", tb); + this._updateMenuCheckbox(); + }.bind(this)); + + tb.open(); + + return tb; + }, + + /** + * Close the toolbox for a given target + */ + closeToolbox: function DT_closeToolbox(target) { + let toolbox = this._toolboxes.get(target); + if (toolbox == null) { + return; + } + toolbox.destroy(); + }, + + /** + * Open the toolbox for a specific target (not tab). + * FIXME: We should probably merge this function and openToolbox + * + * @param {Target} target + * The target that the toolbox should be debugging + * @param {String} toolId + * The id of the tool to open + * + * @return {Toolbox} toolbox + * The toolbox that has been opened + */ + openToolboxForTab: function DT_openToolboxForTab(target, toolId) { + let tb = this.getToolboxForTarget(target); + + if (tb) { + tb.selectTool(toolId); + } else { + tb = this.openToolbox(target, null, toolId); + } + return tb; + }, + + /** + * This function is for the benefit of command#Tools:DevToolbox in + * browser/base/content/browser-sets.inc and should not be used outside + * of there + */ + toggleToolboxCommand: function(gBrowser, toolId=null) { + let target = TargetFactory.forTab(gBrowser.selectedTab); + this.toggleToolboxForTarget(target, toolId); + }, + + /** + * Toggle a toolbox for the given target. + * + * @param {Target} target + * The target the toolbox is debugging + * @param {string} toolId + * The id of the tool to show in the toolbox, if it's to be opened. + */ + toggleToolboxForTarget: function DT_toggleToolboxForTarget(target, toolId) { + let tb = this.getToolboxForTarget(target); + + if (tb /* FIXME: && tool is showing */ ) { + tb.destroy(); + } else { + this.openToolboxForTab(target, toolId); + } + }, + + /** + * Return the toolbox for a given target. + * + * @param {object} target + * Target value e.g. the target that owns this toolbox + * + * @return {Toolbox} toolbox + * The toobox that is debugging the given target + */ + getToolboxForTarget: function DT_getToolboxForTarget(target) { + return this._toolboxes.get(target); + }, + + /** + * Return a tool panel for a given tool and target. + * + * @param {String} toolId + * The id of the tool to open. + * @param {object} target + * The toolbox's target. + * + * @return {ToolPanel} panel + * Panel for the tool with the toolid + */ + getPanelForTarget: function DT_getPanelForTarget(toolId, target) { + let toolbox = this.getToolboxForTarget(target); + if (!toolbox) { + return undefined; + } + return toolbox.getToolPanels().get(toolId); + }, + + /** + * Add this DevTools's presence to a browser window's document + * + * @param {XULDocument} doc + * The document to which menuitems and handlers are to be added + */ + registerBrowserWindow: function DT_registerBrowserWindow(win) { + this._trackedBrowserWindows.add(win); + this._addAllToolsToMenu(win.document); + + let tabContainer = win.document.getElementById("tabbrowser-tabs") + tabContainer.addEventListener("TabSelect", this._updateMenuCheckbox, false); + }, + + /** + * Add the menuitem for a tool to all open browser windows. + * + * @param {object} toolDefinition + * properties of the tool to add + */ + _addToolToWindows: function DT_addToolToWindows(toolDefinition) { + for (let win of this._trackedBrowserWindows) { + this._addToolToMenu(toolDefinition, win.document); + } + }, + + /** + * Add all tools to the developer tools menu of a window. + * + * @param {XULDocument} doc + * The document to which the tool items are to be added. + */ + _addAllToolsToMenu: function DT_addAllToolsToMenu(doc) { + let fragCommands = doc.createDocumentFragment(); + let fragKeys = doc.createDocumentFragment(); + let fragBroadcasters = doc.createDocumentFragment(); + let fragAppMenuItems = doc.createDocumentFragment(); + let fragMenuItems = doc.createDocumentFragment(); + + for (let [key, toolDefinition] of this._tools) { + let frags = this._addToolToMenu(toolDefinition, doc, true); + + if (!frags) { + return; + } + + let [cmd, key, bc, appmenuitem, menuitem] = frags; + + fragCommands.appendChild(cmd); + if (key) { + fragKeys.appendChild(key); + } + fragBroadcasters.appendChild(bc); + fragAppMenuItems.appendChild(appmenuitem); + fragMenuItems.appendChild(menuitem); + } + + let mcs = doc.getElementById("mainCommandSet"); + mcs.appendChild(fragCommands); + + let mks = doc.getElementById("mainKeyset"); + mks.appendChild(fragKeys); + + let mbs = doc.getElementById("mainBroadcasterSet"); + mbs.appendChild(fragBroadcasters); + + let amp = doc.getElementById("appmenu_webDeveloper_popup"); + if (amp) { + let amps = doc.getElementById("appmenu_devtools_separator"); + amp.insertBefore(fragAppMenuItems, amps); + } + + let mp = doc.getElementById("menuWebDeveloperPopup"); + let mps = doc.getElementById("menu_devtools_separator"); + mp.insertBefore(fragMenuItems, mps); + }, + + /** + * Add a menu entry for a tool definition + * + * @param {string} toolDefinition + * Tool definition of the tool to add a menu entry. + * @param {XULDocument} doc + * The document to which the tool menu item is to be added. + * @param {Boolean} [noAppend] + * Return an array of elements instead of appending them to the + * document. Default is false. + */ + _addToolToMenu: function DT_addToolToMenu(toolDefinition, doc, noAppend) { + let id = toolDefinition.id; + + // Prevent multiple entries for the same tool. + if (doc.getElementById("Tools:" + id)) { + return; + } + + let cmd = doc.createElement("command"); + cmd.id = "Tools:" + id; + cmd.setAttribute("oncommand", + 'gDevTools.toggleToolboxCommand(gBrowser, "' + id + '");'); + + let key = null; + if (toolDefinition.key) { + key = doc.createElement("key"); + key.id = "key_" + id; + + if (toolDefinition.key.startsWith("VK_")) { + key.setAttribute("keycode", toolDefinition.key); + } else { + key.setAttribute("key", toolDefinition.key); + } + + key.setAttribute("oncommand", + 'gDevTools.toggleToolboxCommand(gBrowser, "' + id + '");'); + key.setAttribute("modifiers", toolDefinition.modifiers); + } + + let bc = doc.createElement("broadcaster"); + bc.id = "devtoolsMenuBroadcaster_" + id; + bc.setAttribute("label", toolDefinition.label); + bc.setAttribute("command", "Tools:" + id); + + if (key) { + bc.setAttribute("key", "key_" + id); + } + + let appmenuitem = doc.createElement("menuitem"); + appmenuitem.id = "appmenuitem_" + id; + appmenuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id); + + let menuitem = doc.createElement("menuitem"); + menuitem.id = "menuitem_" + id; + menuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id); + + if (toolDefinition.accesskey) { + menuitem.setAttribute("accesskey", toolDefinition.accesskey); + } + + if (noAppend) { + return [cmd, key, bc, appmenuitem, menuitem]; + } else { + let mcs = doc.getElementById("mainCommandSet"); + mcs.appendChild(cmd); + + if (key) { + let mks = doc.getElementById("mainKeyset"); + mks.appendChild(key); + } + + let mbs = doc.getElementById("mainBroadcasterSet"); + mbs.appendChild(bc); + + let amp = doc.getElementById("appmenu_webDeveloper_popup"); + if (amp) { + let amps = doc.getElementById("appmenu_devtools_separator"); + amp.insertBefore(appmenuitem, amps); + } + + let mp = doc.getElementById("menuWebDeveloperPopup"); + let mps = doc.getElementById("menu_devtools_separator"); + mp.insertBefore(menuitem, mps); + } + }, + + /** + * Update the "Toggle Toolbox" checkbox in the developer tools menu. This is + * called when a toolbox is created or destroyed. + */ + _updateMenuCheckbox: function DT_updateMenuCheckbox() { + for (let win of this._trackedBrowserWindows) { + + let hasToolbox = false; + if (TargetFactory.isKnownTab(win.gBrowser.selectedTab)) { + let target = TargetFactory.forTab(win.gBrowser.selectedTab); + if (this._toolboxes.has(target)) { + hasToolbox = true; + } + } + + let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox"); + if (hasToolbox) { + broadcaster.setAttribute("checked", "true"); + } else { + broadcaster.removeAttribute("checked"); + } + } + }, + + /** + * Remove the menuitem for a tool to all open browser windows. + * + * @param {object} toolId + * id of the tool to remove + */ + _removeToolFromWindows: function DT_removeToolFromWindows(toolId) { + for (let win of this._trackedBrowserWindows) { + this._removeToolFromMenu(toolId, win.document); + } + }, + + /** + * Remove a tool's menuitem from a window + * + * @param {string} toolId + * Id of the tool to add a menu entry for + * @param {XULDocument} doc + * The document to which the tool menu item is to be removed from + */ + _removeToolFromMenu: function DT_removeToolFromMenu(toolId, doc) { + let command = doc.getElementById("Tools:" + toolId); + command.parentNode.removeChild(command); + + let key = doc.getElementById("key_" + toolId); + if (key) { + key.parentNode.removeChild(key); + } + + let bc = doc.getElementById("devtoolsMenuBroadcaster_" + toolId); + bc.parentNode.removeChild(bc); + + /* + // FIXME: item is null in testing. This is the only place to use + // "appmenu_devToolbar" + toolId, so it seems clear that this is wrong + let item = doc.getElementById("appmenu_devToolbar" + toolId); + item.parentNode.removeChild(item); + */ + }, + + /** + * Called on browser unload to remove menu entries, toolboxes and event + * listeners from the closed browser window. + * + * @param {XULWindow} win + * The window containing the menu entry + */ + forgetBrowserWindow: function DT_forgetBrowserWindow(win) { + if (!this._tools) { + return; + } + + this._trackedBrowserWindows.delete(win); + + // Destroy toolboxes for closed window + for (let [target, toolbox] of this._toolboxes) { + if (toolbox.frame.ownerDocument.defaultView == win) { + toolbox.destroy(); + } + } + + let tabContainer = win.document.getElementById("tabbrowser-tabs") + tabContainer.removeEventListener("TabSelect", + this._updateMenuCheckbox, false); + }, + + /** + * All browser windows have been closed, tidy up remaining objects. + */ + destroy: function() { + Services.obs.removeObserver(this.destroy, "quit-application"); + + delete this._trackedBrowserWindows; + delete this._tools; + delete this._toolboxes; + }, +}; + +/** + * gDevTools is a singleton that controls the Firefox Developer Tools. + * + * It is an instance of a DevTools class that holds a set of tools. It has the + * same lifetime as the browser. + */ +this.gDevTools = new DevTools(); + +/** + * DevToolsXULCommands exposes methods used by browser's s. + */ +this.DevToolsXULCommands = { + openConnectScreen: function(gBrowser) { + gBrowser.selectedTab = gBrowser.addTab("chrome://browser/content/devtools/connect.xhtml"); + }, +} diff --git a/browser/devtools/framework/test/Makefile.in b/browser/devtools/framework/test/Makefile.in new file mode 100644 index 000000000000..618e044c4929 --- /dev/null +++ b/browser/devtools/framework/test/Makefile.in @@ -0,0 +1,26 @@ +# 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/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +relativesrcdir = @relativesrcdir@ + +include $(DEPTH)/config/autoconf.mk + +MOCHITEST_BROWSER_FILES = \ + head.js \ + browser_devtools_api.js \ + browser_new_activation_workflow.js \ + browser_toolbox_dynamic_registration.js \ + browser_toolbox_hosts.js \ + browser_toolbox_ready.js \ + browser_toolbox_select_event.js \ + browser_target_events.js \ + browser_toolbox_tool_ready.js \ + browser_toolbox_sidebar.js \ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/browser/devtools/framework/test/browser_devtools_api.js b/browser/devtools/framework/test/browser_devtools_api.js new file mode 100644 index 000000000000..205a26808ab5 --- /dev/null +++ b/browser/devtools/framework/test/browser_devtools_api.js @@ -0,0 +1,137 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests devtools API + +const Cu = Components.utils; +const toolId = "test-tool"; + +let tempScope = {}; +Cu.import("resource:///modules/devtools/EventEmitter.jsm", tempScope); +let EventEmitter = tempScope.EventEmitter; +Cu.import("resource:///modules/devtools/Target.jsm", tempScope); +let TargetFactory = tempScope.TargetFactory; + +function test() { + addTab("about:blank", function(aBrowser, aTab) { + runTests(aTab); + }); +} + +function runTests(aTab) { + let toolDefinition = { + id: toolId, + isTargetSupported: function() true, + killswitch: "devtools.test-tool.enabled", + url: "about:blank", + label: "someLabel", + build: function(iframeWindow, toolbox) { + let panel = new DevToolPanel(iframeWindow, toolbox); + return panel; + }, + }; + + ok(gDevTools, "gDevTools exists"); + is(gDevTools.getToolDefinitions().has(toolId), false, + "The tool is not registered"); + + gDevTools.registerTool(toolDefinition); + is(gDevTools.getToolDefinitions().has(toolId), true, + "The tool is registered"); + + let target = TargetFactory.forTab(gBrowser.selectedTab); + + function onNewToolbox(event, toolboxFromEvent) { + let toolBoxes = gDevTools._toolboxes; + let target = TargetFactory.forTab(gBrowser.selectedTab); + let tb = toolBoxes.get(target); + is(toolboxFromEvent, tb, "'toolbox-ready' event fired. Correct toolbox value."); + is(tb.target, target, "toolbox target is correct"); + is(tb._host.hostTab, gBrowser.selectedTab, "toolbox host is correct"); + gDevTools.once(toolId + "-ready", continueTests); + } + + function onToolboxClosed(event, targetFromEvent) { + is(targetFromEvent, target, "'toolbox-destroyed' event fired. Correct tab value."); + finishUp(); + } + + + gDevTools.once("toolbox-ready", onNewToolbox); + gDevTools.once("toolbox-destroyed", onToolboxClosed); + + executeSoon(function() { + gDevTools.openToolbox(target, "bottom", toolId); + }); +} + +function continueTests(event, toolbox, panel) { + let target = TargetFactory.forTab(gBrowser.selectedTab); + is(toolbox, gDevTools._toolboxes.get(target), "{toolId}-ready event received, with correct toolbox value"); + is(panel, toolbox.getToolPanels().get(toolId), "panel value is correct"); + + is(toolbox.currentToolId, toolId, "toolbox _currentToolId is correct"); + + let toolDefinitions = gDevTools.getToolDefinitions(); + is(toolDefinitions.has(toolId), true, "The tool is in gDevTools"); + + let toolDefinition = toolDefinitions.get(toolId); + is(toolDefinition.id, toolId, "toolDefinition id is correct"); + + gDevTools.unregisterTool(toolId); + is(gDevTools.getToolDefinitions().has(toolId), false, + "The tool is no longer registered"); + + toolbox.destroy(); +} + +function finishUp() { + tempScope = null; + gBrowser.removeCurrentTab(); + finish(); +} + +/** +* When a Toolbox is started it creates a DevToolPanel for each of the tools +* by calling toolDefinition.build(). The returned object should +* at least implement these functions. They will be used by the ToolBox. +* +* There may be no benefit in doing this as an abstract type, but if nothing +* else gives us a place to write documentation. +*/ +function DevToolPanel(iframeWindow, toolbox) { + new EventEmitter(this); + + this._toolbox = toolbox; + + /*let doc = iframeWindow.document + let label = doc.createElement("label"); + let textNode = doc.createTextNode("Some Tool"); + + label.appendChild(textNode); + doc.body.appendChild(label);*/ + + executeSoon(function() { + this.setReady(); + }.bind(this)); +} + +DevToolPanel.prototype = { + get target() this._toolbox.target, + + get toolbox() this._toolbox, + + get isReady() this._isReady, + + _isReady: false, + + setReady: function() { + this._isReady = true; + this.emit("ready"); + }, + + destroy: function DTI_destroy() + { + + }, +}; diff --git a/browser/devtools/framework/test/browser_new_activation_workflow.js b/browser/devtools/framework/test/browser_new_activation_workflow.js new file mode 100644 index 000000000000..242b725dcc5b --- /dev/null +++ b/browser/devtools/framework/test/browser_new_activation_workflow.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests devtools API + +const Cu = Components.utils; + +let toolbox; + +let tempScope = {}; +Cu.import("resource:///modules/devtools/Target.jsm", tempScope); +let TargetFactory = tempScope.TargetFactory; + +function test() { + addTab("about:blank", function(aBrowser, aTab) { + loadWebConsole(aTab); + }); +} + +function loadWebConsole(aTab) { + ok(gDevTools, "gDevTools exists"); + + let target = TargetFactory.forTab(gBrowser.selectedTab); + toolbox = gDevTools.openToolbox(target, "bottom", "webconsole"); + toolbox.once("webconsole-ready", checkToolLoading); +} + +function checkToolLoading() { + is(toolbox.currentToolId, "webconsole", "The web console is selected"); + selectAndCheckById("jsdebugger"); + selectAndCheckById("styleeditor"); + testToggle(); +} + +function selectAndCheckById(id) { + let doc = toolbox.frame.contentDocument; + + toolbox.selectTool(id); + let tab = doc.getElementById("toolbox-tab-" + id); + is(tab.selected, true, "The " + id + " tab is selected"); +} + +function testToggle() { + toolbox.once("destroyed", function() { + let target = TargetFactory.forTab(gBrowser.selectedTab); + toolbox = gDevTools.openToolbox(target, "bottom", "styleeditor"); + toolbox.once("styleeditor-ready", checkStyleEditorLoaded); + }.bind(this)); + + let target = TargetFactory.forTab(gBrowser.selectedTab); + gDevTools.toggleToolboxForTarget(target); +} + +function checkStyleEditorLoaded() { + is(toolbox.currentToolId, "styleeditor", "The style editor is selected"); + finishUp(); +} + +function finishUp() { + toolbox.destroy(); + toolbox = null; + gBrowser.removeCurrentTab(); + finish(); +} diff --git a/browser/devtools/framework/test/browser_target_events.js b/browser/devtools/framework/test/browser_target_events.js new file mode 100644 index 000000000000..56496fea2d64 --- /dev/null +++ b/browser/devtools/framework/test/browser_target_events.js @@ -0,0 +1,58 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var tempScope = {}; +Cu.import("resource:///modules/devtools/Target.jsm", tempScope); +var TargetFactory = tempScope.TargetFactory; + +var target; + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", onLoad, true); +} + +function onLoad(evt) { + gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true); + + target = TargetFactory.forTab(gBrowser.selectedTab); + + is(target.tab, gBrowser.selectedTab, "Target linked to the right tab."); + + target.once("hidden", onHidden); + gBrowser.selectedTab = gBrowser.addTab(); +} + +function onHidden() { + ok(true, "Hidden event received"); + target.once("visible", onVisible); + gBrowser.removeCurrentTab(); +} + +function onVisible() { + ok(true, "Visible event received"); + target.once("will-navigate", onWillNavigate); + gBrowser.contentWindow.location = "data:text/html,test navigation"; +} + +function onWillNavigate(event, request) { + ok(true, "will-navigate event received"); + target.once("navigate", onNavigate); +} + +function onNavigate() { + ok(true, "navigate event received"); + target.once("close", onClose); + gBrowser.removeCurrentTab(); +} + +function onClose() { + ok(true, "close event received"); + + target = null; + finish(); +} diff --git a/browser/devtools/framework/test/browser_toolbox_dynamic_registration.js b/browser/devtools/framework/test/browser_toolbox_dynamic_registration.js new file mode 100644 index 000000000000..848dbef8aac0 --- /dev/null +++ b/browser/devtools/framework/test/browser_toolbox_dynamic_registration.js @@ -0,0 +1,112 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let toolbox; + +let temp = {}; +Cu.import("resource:///modules/devtools/Target.jsm", temp); +let TargetFactory = temp.TargetFactory; + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) { + gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true); + openToolbox(); + }, true); + + content.location = "data:text/html,test for dynamically registering and unregistering tools"; +} + +function openToolbox() +{ + let target = TargetFactory.forTab(gBrowser.selectedTab); + toolbox = gDevTools.openToolbox(target); + + toolbox.once("ready", testRegister); +} + + +function testRegister() +{ + gDevTools.once("tool-registered", toolRegistered); + + gDevTools.registerTool({ + id: "test-tool", + label: "Test Tool", + isTargetSupported: function() true, + build: function() {} + }); +} + +function toolRegistered(event, toolId) +{ + is(toolId, "test-tool", "tool-registered event handler sent tool id"); + + ok(gDevTools.getToolDefinitions().has(toolId), "tool added to map"); + + // test that it appeared in the UI + let doc = toolbox.frame.contentDocument; + let tab = doc.getElementById("toolbox-tab-" + toolId); + ok(tab, "new tool's tab exists in toolbox UI"); + + let panel = doc.getElementById("toolbox-panel-" + toolId); + ok(panel, "new tool's panel exists in toolbox UI"); + + for (let win of getAllBrowserWindows()) { + let command = win.document.getElementById("Tools:" + toolId); + ok(command, "command for new tool added to every browser window"); + } + + // then unregister it + testUnregister(); +} + +function getAllBrowserWindows() { + let wins = []; + let enumerator = Services.wm.getEnumerator("navigator:browser"); + while (enumerator.hasMoreElements()) { + wins.push(enumerator.getNext()); + } + return wins; +} + +function testUnregister() +{ + gDevTools.once("tool-unregistered", toolUnregistered); + + gDevTools.unregisterTool("test-tool"); +} + +function toolUnregistered(event, toolId) +{ + is(toolId, "test-tool", "tool-unregistered event handler sent tool id"); + + ok(!gDevTools.getToolDefinitions().has(toolId), "tool removed from map"); + + // test that it disappeared from the UI + let doc = toolbox.frame.contentDocument; + let tab = doc.getElementById("toolbox-tab-" + toolId); + ok(!tab, "tool's tab was removed from the toolbox UI"); + + let panel = doc.getElementById("toolbox-panel-" + toolId); + ok(!panel, "tool's panel was removed from toolbox UI"); + + for (let win of getAllBrowserWindows()) { + let command = win.document.getElementById("Tools:" + toolId); + ok(!command, "command removed from every browser window"); + } + + cleanup(); +} + +function cleanup() +{ + toolbox.destroy(); + toolbox = null; + gBrowser.removeCurrentTab(); + finish(); +} diff --git a/browser/devtools/framework/test/browser_toolbox_hosts.js b/browser/devtools/framework/test/browser_toolbox_hosts.js new file mode 100644 index 000000000000..5edbad6a3278 --- /dev/null +++ b/browser/devtools/framework/test/browser_toolbox_hosts.js @@ -0,0 +1,135 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let temp = {} +Cu.import("resource:///modules/devtools/gDevTools.jsm", temp); +let DevTools = temp.DevTools; + +Cu.import("resource:///modules/devtools/Toolbox.jsm", temp); +let Toolbox = temp.Toolbox; + +Cu.import("resource:///modules/devtools/Target.jsm", temp); +let TargetFactory = temp.TargetFactory; + +let toolbox; + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) { + gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true); + openToolbox(testBottomHost); + }, true); + + content.location = "data:text/html,test for opening toolbox in different hosts"; +} + +function openToolbox(callback) +{ + let target = TargetFactory.forTab(gBrowser.selectedTab); + gDevTools.toggleToolboxForTarget(target); + + toolbox = gDevTools.getToolboxForTarget(target); + toolbox.once("ready", callback); +} + +function testBottomHost() +{ + checkHostType(Toolbox.HostType.BOTTOM); + + // test UI presence + let iframe = document.getElementById("devtools-toolbox-bottom-iframe"); + ok(iframe, "toolbox bottom iframe exists"); + + checkToolboxLoaded(iframe); + + toolbox.once("host-changed", testSidebarHost); + toolbox.hostType = Toolbox.HostType.SIDE; +} + +function testSidebarHost() +{ + checkHostType(Toolbox.HostType.SIDE); + + // test UI presence + let bottom = document.getElementById("devtools-toolbox-bottom-iframe"); + ok(!bottom, "toolbox bottom iframe doesn't exist"); + + let iframe = document.getElementById("devtools-toolbox-side-iframe"); + ok(iframe, "toolbox side iframe exists"); + + checkToolboxLoaded(iframe); + + toolbox.once("host-changed", testWindowHost); + toolbox.hostType = Toolbox.HostType.WINDOW; +} + +function testWindowHost() +{ + checkHostType(Toolbox.HostType.WINDOW); + + let sidebar = document.getElementById("devtools-toolbox-side-iframe"); + ok(!sidebar, "toolbox sidebar iframe doesn't exist"); + + let win = Services.wm.getMostRecentWindow("devtools:toolbox"); + ok(win, "toolbox separate window exists"); + + let iframe = win.document.getElementById("toolbox-iframe"); + checkToolboxLoaded(iframe); + + testToolSelect(); +} + +function testToolSelect() +{ + // make sure we can load a tool after switching hosts + toolbox.once("inspector-ready", testDestroy); + toolbox.selectTool("inspector"); +} + +function testDestroy() +{ + toolbox.once("destroyed", function() { + openToolbox(testRememberHost); + }); + + toolbox.destroy(); +} + +function testRememberHost() +{ + // last host was the window - make sure it's the same when re-opening + is(toolbox.hostType, Toolbox.HostType.WINDOW, "host remembered"); + + let win = Services.wm.getMostRecentWindow("devtools:toolbox"); + ok(win, "toolbox separate window exists"); + + cleanup(); +} + +function checkHostType(hostType) +{ + is(toolbox.hostType, hostType, "host type is " + hostType); + + let pref = Services.prefs.getCharPref("devtools.toolbox.host"); + is(pref, hostType, "host pref is " + hostType); +} + +function checkToolboxLoaded(iframe) +{ + let tabs = iframe.contentDocument.getElementById("toolbox-tabs"); + ok(tabs, "toolbox UI has been loaded into iframe"); +} + +function cleanup() +{ + Services.prefs.setCharPref("devtools.toolbox.host", Toolbox.HostType.BOTTOM); + + toolbox.destroy(); + DevTools = Toolbox = toolbox = null; + gBrowser.removeCurrentTab(); + finish(); +} diff --git a/browser/devtools/framework/test/browser_toolbox_ready.js b/browser/devtools/framework/test/browser_toolbox_ready.js new file mode 100644 index 000000000000..cbf6d89efe22 --- /dev/null +++ b/browser/devtools/framework/test/browser_toolbox_ready.js @@ -0,0 +1,56 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let tempScope = {}; +Cu.import("resource:///modules/devtools/Target.jsm", tempScope); +let TargetFactory = tempScope.TargetFactory; + +let toolbox; + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) { + gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true); + openToolbox(); + }, true); + + content.location = "data:text/html,test for dynamically registering and unregistering tools"; +} + +function openToolbox() +{ + let target = TargetFactory.forTab(gBrowser.selectedTab); + gDevTools.toggleToolboxForTarget(target); + + toolbox = gDevTools.getToolboxForTarget(target); + + ok(!toolbox.isReady, "toolbox isReady isn't set yet"); + + try { + toolbox.selectTool("webconsole"); + ok(false, "Should throw when selectTool() called before toolbox is ready"); + } + catch(error) { + is(error.message, "Can't select tool, wait for toolbox 'ready' event") + } + + toolbox.once("ready", testReady); +} + +function testReady() +{ + ok(toolbox.isReady, "toolbox isReady is set"); + cleanup(); +} + +function cleanup() +{ + toolbox.destroy(); + toolbox = null; + gBrowser.removeCurrentTab(); + finish(); +} diff --git a/browser/devtools/framework/test/browser_toolbox_select_event.js b/browser/devtools/framework/test/browser_toolbox_select_event.js new file mode 100644 index 000000000000..191bd80896f9 --- /dev/null +++ b/browser/devtools/framework/test/browser_toolbox_select_event.js @@ -0,0 +1,97 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +let toolbox; + +function test() { + addTab("about:blank", function() { + let target = TargetFactory.forTab(gBrowser.selectedTab); + toolbox = gDevTools.openToolbox(target, "bottom", "webconsole"); + toolbox.once("ready", testSelect); + }); +} + +let called = { + inspector: false, + webconsole: false, + styleeditor: false, + //jsdebugger: false, +} + +function testSelect() { + info("Toolbox fired a `ready` event"); + + toolbox.on("select", selectCB); + + toolbox.selectTool("inspector"); + toolbox.selectTool("webconsole"); + toolbox.selectTool("styleeditor"); + //toolbox.selectTool("jsdebugger"); +} + +function selectCB(event, id) { + called[id] = true; + info("toolbox-select event from " + id); + + for (let tool in called) { + if (!called[tool]) { + return; + } + } + + ok(true, "All the tools fired a 'select event'"); + toolbox.off("select", selectCB); + + reselect(); +} + +function reselect() { + for (let tool in called) { + called[tool] = false; + } + + toolbox.once("inspector-selected", function() { + tidyUpIfAllCalled("inspector"); + }); + + toolbox.once("webconsole-selected", function() { + tidyUpIfAllCalled("webconsole"); + }); + + /* + toolbox.once("jsdebugger-selected", function() { + tidyUpIfAllCalled("jsdebugger"); + }); + */ + + toolbox.once("styleeditor-selected", function() { + tidyUpIfAllCalled("styleeditor"); + }); + + toolbox.selectTool("inspector"); + toolbox.selectTool("webconsole"); + toolbox.selectTool("styleeditor"); + //toolbox.selectTool("jsdebugger"); +} + +function tidyUpIfAllCalled(id) { + called[id] = true; + info("select event from " + id); + + for (let tool in called) { + if (!called[tool]) { + return; + } + } + + ok(true, "All the tools fired a {id}-selected event"); + tidyUp(); +} + +function tidyUp() { + toolbox.destroy(); + gBrowser.removeCurrentTab(); + + toolbox = null; + finish(); +} diff --git a/browser/devtools/framework/test/browser_toolbox_sidebar.js b/browser/devtools/framework/test/browser_toolbox_sidebar.js new file mode 100644 index 000000000000..b7c17b04f1c5 --- /dev/null +++ b/browser/devtools/framework/test/browser_toolbox_sidebar.js @@ -0,0 +1,132 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() { + const Cu = Components.utils; + let tempScope = {}; + Cu.import("resource:///modules/devtools/gDevTools.jsm", tempScope); + Cu.import("resource:///modules/devtools/Target.jsm", tempScope); + Cu.import("resource:///modules/devtools/Sidebar.jsm", tempScope); + let {TargetFactory: TargetFactory, gDevTools: gDevTools, ToolSidebar: ToolSidebar} = tempScope; + + const toolURL = "data:text/xml;charset=utf8," + + "" + + "" + + "foo" + + "" + + "" + + ""; + + const tab1URL = "data:text/html;charset=utf8,1

    1

    "; + const tab2URL = "data:text/html;charset=utf8,2

    2

    "; + const tab3URL = "data:text/html;charset=utf8,3

    3

    "; + + let panelDoc; + + let registeredTabs = {}; + let readyTabs = {}; + + let toolDefinition = { + id: "fakeTool4242", + killswitch: "devtools.fakeTool4242.enabled", + url: toolURL, + label: "FAKE TOOL!!!", + isTargetSupported: function() true, + build: function(iframeWindow, toolbox) { + let panel = { + target: toolbox.target, + toolbox: toolbox, + isReady: true, + destroy: function(){}, + panelDoc: iframeWindow.document, + } + return panel; + }, + }; + + gDevTools.registerTool(toolDefinition); + + addTab("about:blank", function(aBrowser, aTab) { + let target = TargetFactory.forTab(gBrowser.selectedTab); + let toolbox = gDevTools.openToolbox(target, "bottom", "fakeTool4242"); + toolbox.once("fakeTool4242-ready", function(event, panel) { + ok(true, "Tool open"); + + let tabbox = panel.panelDoc.getElementById("sidebar"); + panel.sidebar = new ToolSidebar(tabbox, panel, true); + + panel.sidebar.on("new-tab-registered", function(event, id) { + registeredTabs[id] = true; + }); + + panel.sidebar.once("tab1-ready", function(event) { + readyTabs.tab1 = true; + if (readyTabs.tab1 && readyTabs.tab2 && readyTabs.tab3) { + allTabsReady(panel); + } + }); + + panel.sidebar.once("tab2-ready", function(event) { + readyTabs.tab2 = true; + if (readyTabs.tab1 && readyTabs.tab2 && readyTabs.tab3) { + allTabsReady(panel); + } + }); + + panel.sidebar.once("tab3-ready", function(event) { + readyTabs.tab3 = true; + if (readyTabs.tab1 && readyTabs.tab2 && readyTabs.tab3) { + allTabsReady(panel); + } + }); + + panel.sidebar.addTab("tab1", tab1URL, true); + panel.sidebar.addTab("tab2", tab2URL); + panel.sidebar.addTab("tab3", tab3URL); + + panel.sidebar.show(); + }); + }); + + function allTabsReady(panel) { + ok(registeredTabs.tab1, "tab1 registered"); + ok(registeredTabs.tab2, "tab2 registered"); + ok(registeredTabs.tab3, "tab3 registered"); + ok(readyTabs.tab1, "tab1 ready"); + ok(readyTabs.tab2, "tab2 ready"); + ok(readyTabs.tab3, "tab3 ready"); + + let tabs = panel.sidebar._tabbox.querySelectorAll("tab"); + let panels = panel.sidebar._tabbox.querySelectorAll("tabpanel"); + let label = 1; + for (let tab of tabs) { + is(tab.getAttribute("label"), label++, "Tab has the right title"); + } + is(label, 4, "Found the right amount of tabs."); + is(panel.sidebar._tabbox.selectedPanel, panels[0], "First tab is selected"); + ok(panel.sidebar.getCurrentTabID(), "tab1", "getCurrentTabID() is correct"); + + panel.sidebar.once("tab1-unselected", function() { + ok(true, "received 'unselected' event"); + panel.sidebar.once("tab2-selected", function() { + ok(true, "received 'selected' event"); + panel.sidebar.hide(); + is(panel.sidebar._tabbox.getAttribute("hidden"), "true", "Sidebar hidden"); + is(panel.sidebar.getWindowForTab("tab1").location.href, tab1URL, "Window is accessible"); + finishUp(panel); + }); + }); + + panel.sidebar.select("tab2"); + } + + function finishUp(panel) { + panel.sidebar.destroy(); + gDevTools.unregisterTool(toolDefinition.id); + + executeSoon(function() { + gBrowser.removeCurrentTab(); + finish(); + }); + } +} diff --git a/browser/devtools/framework/test/browser_toolbox_tool_ready.js b/browser/devtools/framework/test/browser_toolbox_tool_ready.js new file mode 100644 index 000000000000..98a537f03ca6 --- /dev/null +++ b/browser/devtools/framework/test/browser_toolbox_tool_ready.js @@ -0,0 +1,72 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let temp = []; +Cu.import("resource:///modules/devtools/Target.jsm", temp); +let TargetFactory = temp.TargetFactory; + +function test() { + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) { + gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true); + openAllTools(); + }, true); + + function openAllTools() { + let target = TargetFactory.forTab(gBrowser.selectedTab); + + let tools = gDevTools.getToolDefinitions(); + let expectedCallbacksCount = tools.size; + + let firstTool = null; + // we transform the map to a [id, eventHasBeenFiredYet] map + for (let [id] of tools) { + if (!firstTool) + firstTool = id; + tools.set(id, false); + } + + let toolbox = gDevTools.openToolbox(target, undefined, firstTool); + + // We add the event listeners + for (let [toolId] of tools) { + let id = toolId; + info("Registering listener for " + id); + tools.set(id, false); + toolbox.on(id + "-ready", function(event, panel) { + expectedCallbacksCount--; + info("Got event " + event); + is(toolbox.getToolPanels().get(id), panel, "Got the right tool panel for " + id); + tools.set(id, true); + if (expectedCallbacksCount == 0) { + // "executeSoon" because we want to let a chance + // to falsy code to fire unexpected ready events. + executeSoon(theEnd); + } + if (expectedCallbacksCount < 0) { + ok(false, "we are receiving too many events"); + } + }); + } + + toolbox.once("ready", function() { + // We open all the + for (let [id] of tools) { + if (id != firstTool) { + toolbox.selectTool(id); + } + } + }); + + function theEnd() { + for (let [id, called] of tools) { + ok(called, "Tool " + id + " has fired its ready event"); + } + toolbox.destroy(); + gBrowser.removeCurrentTab(); + finish(); + } + } +} diff --git a/browser/devtools/framework/test/head.js b/browser/devtools/framework/test/head.js new file mode 100644 index 000000000000..796a7946c2d0 --- /dev/null +++ b/browser/devtools/framework/test/head.js @@ -0,0 +1,36 @@ +/* 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/. */ + +let tempScope = {}; +Components.utils.import("resource:///modules/devtools/Target.jsm", tempScope); +let TargetFactory = tempScope.TargetFactory; +Components.utils.import("resource:///modules/devtools/Console.jsm", tempScope); +let console = tempScope.console; + +/** + * Open a new tab at a URL and call a callback on load + */ +function addTab(aURL, aCallback) +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + content.location = aURL; + + let tab = gBrowser.selectedTab; + let browser = gBrowser.getBrowserForTab(tab); + + function onTabLoad() { + browser.removeEventListener("load", onTabLoad, true); + aCallback(browser, tab, browser.contentDocument); + } + + browser.addEventListener("load", onTabLoad, true); +} + +registerCleanupFunction(function tearDown() { + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } +}); diff --git a/browser/devtools/framework/toolbox-window.xul b/browser/devtools/framework/toolbox-window.xul new file mode 100644 index 000000000000..774747b531d9 --- /dev/null +++ b/browser/devtools/framework/toolbox-window.xul @@ -0,0 +1,33 @@ + + + + %toolboxDTD; +]> + + + + + + + + + + + + + + + diff --git a/browser/devtools/framework/toolbox.css b/browser/devtools/framework/toolbox.css new file mode 100644 index 000000000000..57f18006c237 --- /dev/null +++ b/browser/devtools/framework/toolbox.css @@ -0,0 +1,10 @@ +/* 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/. */ + +.devtools-tab > .radio-check, +.devtools-tab > .radio-check-box1, +.devtools-tab > .radio-spacer-box { + display: none; +} + diff --git a/browser/devtools/framework/toolbox.xul b/browser/devtools/framework/toolbox.xul new file mode 100644 index 000000000000..744278c12d3e --- /dev/null +++ b/browser/devtools/framework/toolbox.xul @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/devtools/highlighter/CmdInspect.jsm b/browser/devtools/highlighter/CmdInspect.jsm deleted file mode 100644 index c352c96cb285..000000000000 --- a/browser/devtools/highlighter/CmdInspect.jsm +++ /dev/null @@ -1,29 +0,0 @@ -/* 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/. */ - -const { classes: Cc, interfaces: Ci, utils: Cu } = Components; -this.EXPORTED_SYMBOLS = [ ]; - -Cu.import("resource:///modules/devtools/gcli.jsm"); - -/** - * 'inspect' command - */ -gcli.addCommand({ - name: "inspect", - description: gcli.lookup("inspectDesc"), - manual: gcli.lookup("inspectManual"), - params: [ - { - name: "selector", - type: "node", - description: gcli.lookup("inspectNodeDesc"), - manual: gcli.lookup("inspectNodeManual") - } - ], - exec: function Command_inspect(args, context) { - let document = context.environment.chromeDocument; - document.defaultView.InspectorUI.openInspectorUI(args.selector); - } -}); diff --git a/browser/devtools/highlighter/inspector.jsm b/browser/devtools/highlighter/inspector.jsm deleted file mode 100644 index 2ffbaf77cec5..000000000000 --- a/browser/devtools/highlighter/inspector.jsm +++ /dev/null @@ -1,2414 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ -/* 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/. */ - -const Cc = Components.classes; -const Cu = Components.utils; -const Ci = Components.interfaces; -const Cr = Components.results; - -this.EXPORTED_SYMBOLS = ["InspectorUI"]; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource:///modules/devtools/MarkupView.jsm"); -Cu.import("resource:///modules/highlighter.jsm"); -Cu.import("resource:///modules/devtools/LayoutView.jsm"); -Cu.import("resource:///modules/devtools/LayoutHelpers.jsm"); -Cu.import("resource:///modules/devtools/EventEmitter.jsm"); -Cu.import("resource:///modules/devtools/DOMHelpers.jsm"); - -// Inspector notifications dispatched through the nsIObserverService. -const INSPECTOR_NOTIFICATIONS = { - // Fires once the Inspector completes the initialization and opens up on - // screen. - OPENED: "inspector-opened", - - // Fires once the Inspector is closed. - CLOSED: "inspector-closed", - - // Fires once the Inspector is destroyed. Not fired on tab switch. - DESTROYED: "inspector-destroyed", - - // Fires when the Inspector is reopened after tab-switch. - STATE_RESTORED: "inspector-state-restored", - - // Fires when the Tree Panel is opened and initialized. - TREEPANELREADY: "inspector-treepanel-ready", - - // Event notifications for the attribute-value editor - EDITOR_OPENED: "inspector-editor-opened", - EDITOR_CLOSED: "inspector-editor-closed", - EDITOR_SAVED: "inspector-editor-saved", -}; - -const PSEUDO_CLASSES = [":hover", ":active", ":focus"]; - -// Timer, in milliseconds, between change events fired by -// things like resize events. -const LAYOUT_CHANGE_TIMER = 250; - -/** - * Represents an open instance of the Inspector for a tab. - * This is the object handed out to sidebars and other API consumers. - * - * Right now it's a thin layer over InspectorUI, but we will - * start moving per-tab state into this object soon, eventually - * replacing the per-winID InspectorStore objects. - * - * The lifetime of this object is also not yet correct. This object - * is currently destroyed when the inspector is torn down, either by user - * closing the inspector or by user switching the tab. This should - * only be destroyed when user closes the inspector. - */ -function Inspector(aIUI) -{ - this._IUI = aIUI; - this._winID = aIUI.winID; - this._browser = aIUI.browser; - this._eventEmitter = new EventEmitter(); - - this._browser.addEventListener("resize", this, true); - - this._markupButton = this._IUI.chromeDoc.getElementById("inspector-treepanel-toolbutton"); - - if (Services.prefs.getBoolPref("devtools.inspector.htmlPanelOpen")) { - this.openMarkup(); - } else { - this.closeMarkup(); - } - -} - -Inspector.prototype = { - /** - * True if the highlighter is locked on a node. - */ - get locked() { - return !this._IUI.inspecting; - }, - - /** - * The currently selected node in the highlighter. - */ - get selection() { - return this._IUI.selection; - }, - - /** - * Indicate that a tool has modified the state of the page. Used to - * decide whether to show the "are you sure you want to navigate" - * notification. - */ - markDirty: function Inspector_markDirty() - { - this._IUI.isDirty = true; - }, - - /** - * The chrome window the inspector lives in. - */ - get chromeWindow() { - return this._IUI.chromeWin; - }, - - /** - * Notify the inspector that the current selection has changed. - * - * @param string aContext - * An string that will be passed to the change event. Allows - * a tool to recognize when it sent a change notification itself - * to avoid unnecessary refresh. - */ - change: function Inspector_change(aContext) - { - this._cancelLayoutChange(); - this._IUI.nodeChanged(aContext); - }, - - /** - * Returns true if a given sidebar panel is currently visible. - * @param string aPanelName - * The panel name as registered with registerSidebar - */ - isPanelVisible: function Inspector_isPanelVisible(aPanelName) - { - return this._IUI.sidebar.visible && - this._IUI.sidebar.activePanel === aPanelName; - }, - - /** - * Called by the InspectorUI when the inspector is being destroyed. - */ - _destroy: function Inspector__destroy() - { - this._cancelLayoutChange(); - this._destroyMarkup(); - this._browser.removeEventListener("resize", this, true); - delete this._IUI; - delete this._eventEmitter; - }, - - /** - * Event handler for DOM events. - * - * @param DOMEvent aEvent - */ - handleEvent: function Inspector_handleEvent(aEvent) - { - switch(aEvent.type) { - case "resize": - this._scheduleLayoutChange(); - } - }, - - /** - * Schedule a low-priority change event for things like paint - * and resize. - */ - _scheduleLayoutChange: function Inspector_scheduleLayoutChange() - { - if (this._timer) { - return null; - } - this._timer = this._IUI.win.setTimeout(function() { - this.change("layout"); - }.bind(this), LAYOUT_CHANGE_TIMER); - }, - - /** - * Cancel a pending low-priority change event if any is - * scheduled. - */ - _cancelLayoutChange: function Inspector_cancelLayoutChange() - { - if (this._timer) { - this._IUI.win.clearTimeout(this._timer); - delete this._timer; - } - }, - - toggleMarkup: function Inspector_toggleMarkup() - { - if (this._markupFrame) { - this.closeMarkup(); - Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", false); - } else { - this.openMarkup(true); - Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", true); - } - }, - - /** - * XXX: The sidebar has an object that exists and is manipulated - * separately from its actual loading. So the public api for - * the sidebar looks like: - * - * if (inspector.sidebar.visible) { inspector.sidebar.close() } - * - * whereas the markup API looks more like - * - * if (inspector.markupOpen) { inspector.closeMarkup() } - * - * Maybe we should add an InspectorMarkup object that presents - * the public api for the markup panel? - */ - get markupOpen() { - return this._markupOpen; - }, - - openMarkup: function Inspector_openMarkup(aFocus) - { - this._markupButton.setAttribute("checked", "true"); - this._markupOpen = true; - if (!this._markupFrame) { - this._initMarkup(aFocus); - } - }, - - closeMarkup: function Inspector_closeMarkup() - { - this._markupButton.removeAttribute("checked"); - this._markupOpen = false; - this._destroyMarkup(); - }, - - _initMarkup: function Inspector_initMarkupPane(aFocus) - { - let doc = this._IUI.chromeDoc; - - this._markupBox = doc.createElement("vbox"); - try { - this._markupBox.height = - Services.prefs.getIntPref("devtools.inspector.htmlHeight"); - } catch(e) { - this._markupBox.height = 112; - } - this._markupBox.minHeight = 64; - - this._markupSplitter = doc.createElement("splitter"); - this._markupSplitter.className = "devtools-horizontal-splitter"; - - let container = doc.getElementById("appcontent"); - container.appendChild(this._markupSplitter); - container.appendChild(this._markupBox); - - // create tool iframe - this._markupFrame = doc.createElement("iframe"); - this._markupFrame.setAttribute("flex", "1"); - this._markupFrame.setAttribute("tooltip", "aHTMLTooltip"); - this._markupFrame.setAttribute("context", "inspector-node-popup"); - - // This is needed to enable tooltips inside the iframe document. - this._boundMarkupFrameLoad = function Inspector_initMarkupPanel_onload() { - if (aFocus) { - this._markupFrame.contentWindow.focus(); - } - this._onMarkupFrameLoad(); - }.bind(this); - this._markupFrame.addEventListener("load", this._boundMarkupFrameLoad, true); - - this._markupSplitter.setAttribute("hidden", true); - this._markupBox.setAttribute("hidden", true); - this._markupBox.appendChild(this._markupFrame); - this._markupFrame.setAttribute("src", "chrome://browser/content/devtools/markup-view.xhtml"); - }, - - _onMarkupFrameLoad: function Inspector__onMarkupFrameLoad() - { - this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true); - delete this._boundMarkupFrameLoad; - - this._markupSplitter.removeAttribute("hidden"); - this._markupBox.removeAttribute("hidden"); - - this.markup = new MarkupView(this, this._markupFrame); - this.emit("markuploaded"); - }, - - _destroyMarkup: function Inspector__destroyMarkup() - { - if (this._boundMarkupFrameLoad) { - this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true); - delete this._boundMarkupFrameLoad; - } - - if (this.markup) { - this.markup.destroy(); - delete this.markup; - } - - if (this._markupFrame) { - delete this._markupFrame; - } - - if (this._markupBox) { - Services.prefs.setIntPref("devtools.inspector.htmlHeight", this._markupBox.height); - this._markupBox.parentNode.removeChild(this._markupBox); - delete this._markupBox; - } - - if (this._markupSplitter) { - this._markupSplitter.parentNode.removeChild(this._markupSplitter); - delete this._markupSplitter; - } - }, - - /** - * Called by InspectorUI after a tab switch, when the - * inspector is no longer the active tab. - */ - _freeze: function Inspector__freeze() - { - if (this._markupBox) { - this._markupSplitter.setAttribute("hidden", true); - this._markupBox.setAttribute("hidden", true); - } - this._cancelLayoutChange(); - this._browser.removeEventListener("resize", this, true); - this._frozen = true; - }, - - /** - * Called by InspectorUI after a tab switch when the - * inspector is back to being the active tab. - */ - _thaw: function Inspector__thaw() - { - if (!this._frozen) { - return; - } - - if (this._markupOpen && !this._boundMarkupFrameLoad) { - this._markupSplitter.removeAttribute("hidden"); - this._markupBox.removeAttribute("hidden"); - } - this._browser.addEventListener("resize", this, true); - delete this._frozen; - }, - - /// Forward the events related calls to the event emitter. - - /** - * Connect a listener to this object. - * - * @param string aEvent - * The event name to which we're connecting. - * @param function aListener - * Called when the event is fired. - */ - on: function Inspector_on(aEvent, aListener) - { - this._eventEmitter.on(aEvent, aListener); - }, - - /** - * Listen for the next time an event is fired. - * - * @param string aEvent - * The event name to which we're connecting. - * @param function aListener - * Called when the event is fired. Will be called at most one time. - */ - once: function Inspector_once(aEvent, aListener) - { - this._eventEmitter.once(aEvent, aListener); - }, - - /** - * Remove a previously-registered event listener. Works for events - * registered with either on or once. - * - * @param string aEvent - * The event name whose listener we're disconnecting. - * @param function aListener - * The listener to remove. - */ - off: function Inspector_removeListener(aEvent, aListener) - { - this._eventEmitter.off(aEvent, aListener); - }, - - /** - * Emit an event on the inspector. All arguments to this method will - * be sent to listner functions. - */ - emit: function Inspector_emit() - { - this._eventEmitter.emit.apply(this._eventEmitter, arguments); - } -} - -/////////////////////////////////////////////////////////////////////////// -//// InspectorUI - -/** - * Main controller class for the Inspector. - * - * @constructor - * @param nsIDOMWindow aWindow - * The chrome window for which the Inspector instance is created. - */ -this.InspectorUI = function InspectorUI(aWindow) -{ - // Let style inspector tools register themselves. - let tmp = {}; - Cu.import("resource:///modules/devtools/StyleInspector.jsm", tmp); - - this.chromeWin = aWindow; - this.chromeDoc = aWindow.document; - this.tabbrowser = aWindow.gBrowser; - this.tools = {}; - this.toolEvents = {}; - this.store = new InspectorStore(); - this.INSPECTOR_NOTIFICATIONS = INSPECTOR_NOTIFICATIONS; - this.buildButtonsTooltip(); -} - -InspectorUI.prototype = { - browser: null, - tools: null, - toolEvents: null, - inspecting: false, - ruleViewEnabled: true, - isDirty: false, - store: null, - - _currentInspector: null, - _sidebar: null, - - /** - * The Inspector object for the current tab. - */ - get currentInspector() this._currentInspector, - - /** - * The InspectorStyleSidebar for the current tab. - */ - get sidebar() this._sidebar, - - /** - * Toggle the inspector interface elements on or off. - * - * @param aEvent - * The event that requested the UI change. Toolbar button or menu. - */ - toggleInspectorUI: function IUI_toggleInspectorUI(aEvent) - { - if (this.isInspectorOpen) { - this.closeInspectorUI(); - } else { - this.openInspectorUI(); - } - }, - - /** - * Add a tooltip to the Inspect and Markup buttons. - * The tooltips include the related keyboard shortcut. - */ - buildButtonsTooltip: function IUI_buildButtonsTooltip() - { - let keysbundle = Services.strings.createBundle("chrome://global-platform/locale/platformKeys.properties"); - let separator = keysbundle.GetStringFromName("MODIFIER_SEPARATOR"); - - let button, tooltip; - - // Inspect Button - the shortcut string is built from the element - - let key = this.chromeDoc.getElementById("key_inspect"); - - if (key) { - let modifiersAttr = key.getAttribute("modifiers"); - - let combo = []; - - if (modifiersAttr.match("accel")) -#ifdef XP_MACOSX - combo.push(keysbundle.GetStringFromName("VK_META")); -#else - combo.push(keysbundle.GetStringFromName("VK_CONTROL")); -#endif - if (modifiersAttr.match("shift")) - combo.push(keysbundle.GetStringFromName("VK_SHIFT")); - if (modifiersAttr.match("alt")) - combo.push(keysbundle.GetStringFromName("VK_ALT")); - if (modifiersAttr.match("ctrl")) - combo.push(keysbundle.GetStringFromName("VK_CONTROL")); - if (modifiersAttr.match("meta")) - combo.push(keysbundle.GetStringFromName("VK_META")); - - combo.push(key.getAttribute("key")); - - tooltip = this.strings.formatStringFromName("inspectButtonWithShortcutKey.tooltip", - [combo.join(separator)], 1); - } else { - tooltip = this.strings.GetStringFromName("inspectButton.tooltip"); - } - - button = this.chromeDoc.getElementById("inspector-inspect-toolbutton"); - button.setAttribute("tooltiptext", tooltip); - - // Markup Button - the shortcut string is built from the accesskey attribute - - button = this.chromeDoc.getElementById("inspector-treepanel-toolbutton"); -#ifdef XP_MACOSX - // On Mac, no accesskey - tooltip = this.strings.GetStringFromName("markupButton.tooltip"); -#else - let altString = keysbundle.GetStringFromName("VK_ALT"); - let accesskey = button.getAttribute("accesskey"); - let shortcut = altString + separator + accesskey; - tooltip = this.strings.formatStringFromName("markupButton.tooltipWithAccesskey", - [shortcut], 1); -#endif - button.setAttribute("tooltiptext", tooltip); - - }, - - /** - * Toggle the status of the inspector, starting or stopping it. Invoked - * from the toolbar's Inspect button. - */ - toggleInspection: function IUI_toggleInspection() - { - if (!this.isInspectorOpen) { - this.openInspectorUI(); - return; - } - - if (this.inspecting) { - this.stopInspecting(); - } else { - this.startInspecting(); - } - }, - - /** - * Show or hide the sidebar. Called from the Styling button on the - * highlighter toolbar. - */ - toggleSidebar: function IUI_toggleSidebar() - { - if (!this.sidebar.visible) { - this.sidebar.show(); - } else { - this.sidebar.hide(); - } - }, - - /** - * Toggle the TreePanel. - */ - toggleHTMLPanel: function IUI_toggleHTMLPanel() - { - this.currentInspector.toggleMarkup(); - }, - - /** - * Is the inspector UI open? Simply check if the toolbar is visible or not. - * - * @returns boolean - */ - get isInspectorOpen() - { - return !!(this.toolbar && !this.toolbar.hidden && this.highlighter); - }, - - /** - * Return the default selection element for the inspected document. - */ - get defaultSelection() - { - let doc = this.win.document; - return doc.documentElement ? doc.documentElement.lastElementChild : null; - }, - - /** - * Open inspector UI and HTML tree. Add listeners for document scrolling, - * resize, tabContainer.TabSelect and others. If a node is provided, then - * start inspecting it. - * - * @param [optional] aNode - * The node to inspect. - */ - openInspectorUI: function IUI_openInspectorUI(aNode) - { - // InspectorUI is already up and running. Lock a node if asked (via context). - if (this.isInspectorOpen) { - if (aNode) { - this.inspectNode(aNode); - this.stopInspecting(); - } - return; - } - - // Observer used to inspect the specified element from content after the - // inspector UI has been opened (via the content context menu). - function inspectObserver(aElement) { - Services.obs.removeObserver(boundInspectObserver, - INSPECTOR_NOTIFICATIONS.OPENED, - false); - this.inspectNode(aElement); - this.stopInspecting(); - }; - - var boundInspectObserver = inspectObserver.bind(this, aNode); - - if (aNode) { - // Add the observer to inspect the node after initialization finishes. - Services.obs.addObserver(boundInspectObserver, - INSPECTOR_NOTIFICATIONS.OPENED, - false); - } - // Start initialization. - this.browser = this.tabbrowser.selectedBrowser; - this.win = this.browser.contentWindow; - this.winID = this.getWindowID(this.win); - this.toolbar = this.chromeDoc.getElementById("inspector-toolbar"); - this.inspectCommand = this.chromeDoc.getElementById("Inspector:Inspect"); - - // Update menus: - this.inspectorUICommand = this.chromeDoc.getElementById("Tools:Inspect"); - this.inspectorUICommand.setAttribute("checked", "true"); - - this.chromeWin.Tilt.setup(); - - this.toolbar.hidden = false; - - // initialize the HTML Breadcrumbs - this.breadcrumbs = new HTMLBreadcrumbs(this); - - this.isDirty = false; - - this.progressListener = new InspectorProgressListener(this); - - this.chromeWin.addEventListener("keypress", this, false); - - // initialize the highlighter - this.highlighter = new Highlighter(this.chromeWin); - - this.initializeStore(); - - this._sidebar = new InspectorStyleSidebar({ - document: this.chromeDoc, - inspector: this._currentInspector, - }); - - // Fade out the highlighter when needed - let deck = this.chromeDoc.getElementById("devtools-sidebar-deck"); - deck.addEventListener("mouseenter", this, true); - deck.addEventListener("mouseleave", this, true); - - // Create UI for any sidebars registered with - // InspectorUI.registerSidebar() - for each (let tool in InspectorUI._registeredSidebars) { - this._sidebar.addTool(tool); - } - - this.setupNavigationKeys(); - this.highlighterReady(); - - // Focus the first focusable element in the toolbar - this.chromeDoc.commandDispatcher.advanceFocusIntoSubtree(this.toolbar); - - // If nothing is focused in the toolbar, it means that the focus manager - // is limited to some specific elements and has moved the focus somewhere else. - // So in this case, we want to focus the content window. - // See: https://developer.mozilla.org/en/XUL_Tutorial/Focus_and_Selection#Platform_Specific_Behaviors - if (!this.toolbar.querySelector(":-moz-focusring")) { - this.win.focus(); - } - - }, - - /** - * Initialize the InspectorStore. - */ - initializeStore: function IUI_initializeStore() - { - // First time opened, add the TabSelect listener - if (this.store.isEmpty()) { - this.tabbrowser.tabContainer.addEventListener("TabSelect", this, false); - } - - // Has this windowID been inspected before? - if (this.store.hasID(this.winID)) { - this._currentInspector = this.store.getInspector(this.winID); - this._currentInspector._thaw(); - let selectedNode = this.currentInspector._selectedNode; - if (selectedNode) { - this.inspectNode(selectedNode); - } - this.isDirty = this.currentInspector._isDirty; - } else { - // First time inspecting, set state to no selection + live inspection. - let inspector = new Inspector(this); - this.store.addInspector(this.winID, inspector); - inspector._selectedNode = null; - inspector._inspecting = true; - inspector._isDirty = this.isDirty; - - inspector._htmlPanelOpen = - Services.prefs.getBoolPref("devtools.inspector.htmlPanelOpen"); - - inspector._sidebarOpen = - Services.prefs.getBoolPref("devtools.inspector.sidebarOpen"); - - inspector._activeSidebar = - Services.prefs.getCharPref("devtools.inspector.activeSidebar"); - - this.win.addEventListener("pagehide", this, true); - - this._currentInspector = inspector; - } - }, - - /** - * Browse nodes according to the breadcrumbs layout, only for some specific - * elements of the UI. - */ - setupNavigationKeys: function IUI_setupNavigationKeys() - { - // UI elements that are arrow keys sensitive: - // - the Inspector toolbar. - - this.onKeypress = this.onKeypress.bind(this); - - this.toolbar.addEventListener("keypress", this.onKeypress, true); - }, - - /** - * Remove the event listeners for the arrowkeys. - */ - removeNavigationKeys: function IUI_removeNavigationKeys() - { - this.toolbar.removeEventListener("keypress", this.onKeypress, true); - }, - - /** - * Close inspector UI and associated panels. Unhighlight and stop inspecting. - * Remove event listeners for document scrolling, resize, - * tabContainer.TabSelect and others. - * - * @param boolean aKeepInspector - * Tells if you want the inspector associated to the current tab/window to - * be cleared or not. Set this to true to save the inspector, or false - * to destroy it. - */ - closeInspectorUI: function IUI_closeInspectorUI(aKeepInspector) - { - if (this.closing || !this.win || !this.browser) { - return; - } - - let winId = new String(this.winID); // retain this to notify observers. - - this.closing = true; - this.toolbar.hidden = true; - - this.removeNavigationKeys(); - - this.progressListener.destroy(); - delete this.progressListener; - - if (!aKeepInspector) { - this.win.removeEventListener("pagehide", this, true); - this.clearPseudoClassLocks(); - } else { - // Update the inspector before closing. - if (this.selection) { - this.currentInspector._selectedNode = this.selection; - } - this.currentInspector._inspecting = this.inspecting; - this.currentInspector._isDirty = this.isDirty; - } - - if (this.store.isEmpty()) { - this.tabbrowser.tabContainer.removeEventListener("TabSelect", this, false); - } - - this.chromeWin.removeEventListener("keypress", this, false); - - this.stopInspecting(); - - // close the sidebar - if (this._sidebar) { - this._sidebar.destroy(); - this._sidebar = null; - } - - let deck = this.chromeDoc.getElementById("devtools-sidebar-deck"); - deck.removeEventListener("mouseenter", this, true); - deck.removeEventListener("mouseleave", this, true); - - this.highlighter.destroy(); - this.highlighter = null; - - if (this.breadcrumbs) { - this.breadcrumbs.destroy(); - this.breadcrumbs = null; - } - - if (aKeepInspector) { - this._currentInspector._freeze(); - } else { - this.store.deleteInspector(this.winID); - } - delete this._currentInspector; - - this.inspectorUICommand.setAttribute("checked", "false"); - - this.browser = this.win = null; // null out references to browser and window - this.winID = null; - this.selection = null; - this.closing = false; - this.isDirty = false; - - delete this.stylePanel; - delete this.inspectorUICommand; - delete this.inspectCommand; - delete this.toolbar; - - Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.CLOSED, null); - - if (!aKeepInspector) - Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.DESTROYED, winId); - }, - - /** - * Begin inspecting webpage, attach page event listeners, activate - * highlighter event listeners. - */ - startInspecting: function IUI_startInspecting() - { - this.inspectCommand.setAttribute("checked", "true"); - - this.inspecting = true; - this.highlighter.unlock(); - this._notifySelected(); - this._currentInspector.emit("unlocked"); - }, - - _notifySelected: function IUI__notifySelected(aFrom) - { - this._currentInspector._cancelLayoutChange(); - this._currentInspector.emit("select", aFrom); - }, - - /** - * Stop inspecting webpage, detach page listeners, disable highlighter - * event listeners. - * @param aPreventScroll - * Prevent scroll in the HTML tree? - */ - stopInspecting: function IUI_stopInspecting(aPreventScroll) - { - if (!this.inspecting) { - return; - } - - this.inspectCommand.setAttribute("checked", "false"); - - this.inspecting = false; - - if (this.closing) - return; - - if (this.highlighter.getNode()) { - this.select(this.highlighter.getNode(), true, !aPreventScroll); - } else { - this.select(null, true, true); - } - - this.highlighter.lock(); - this._notifySelected(); - this._currentInspector.emit("locked"); - }, - - /** - * Select an object in the inspector. - * @param aNode - * node to inspect - * @param forceUpdate - * force an update? - * @param aScroll boolean - * scroll the tree panel? - * @param aFrom [optional] string - * which part of the UI the selection occured from - */ - select: function IUI_select(aNode, forceUpdate, aScroll, aFrom) - { - if (!aNode) - aNode = this.defaultSelection; - - if (forceUpdate || aNode != this.selection) { - if (aFrom != "breadcrumbs") { - this.clearPseudoClassLocks(); - } - - this.selection = aNode; - if (!this.inspecting) { - this.highlighter.highlight(this.selection); - } - } - - this.breadcrumbs.update(); - this.chromeWin.Tilt.update(aNode); - - this._notifySelected(aFrom); - }, - - /** - * Toggle the pseudo-class lock on the currently inspected element. If the - * pseudo-class is :hover or :active, that pseudo-class will also be toggled - * on every ancestor of the element, mirroring real :hover and :active - * behavior. - * - * @param aPseudo the pseudo-class lock to toggle, e.g. ":hover" - */ - togglePseudoClassLock: function IUI_togglePseudoClassLock(aPseudo) - { - if (DOMUtils.hasPseudoClassLock(this.selection, aPseudo)) { - this.breadcrumbs.nodeHierarchy.forEach(function(crumb) { - DOMUtils.removePseudoClassLock(crumb.node, aPseudo); - }); - } else { - let hierarchical = aPseudo == ":hover" || aPseudo == ":active"; - let node = this.selection; - do { - DOMUtils.addPseudoClassLock(node, aPseudo); - node = node.parentNode; - } while (hierarchical && node.parentNode) - } - this.nodeChanged("pseudoclass"); - }, - - /** - * Clear all pseudo-class locks applied to elements in the node hierarchy - */ - clearPseudoClassLocks: function IUI_clearPseudoClassLocks() - { - this.breadcrumbs.nodeHierarchy.forEach(function(crumb) { - if (LayoutHelpers.isNodeConnected(crumb.node)) { - DOMUtils.clearPseudoClassLocks(crumb.node); - } - }); - }, - - /** - * Called when the highlighted node is changed by a tool. - * - * @param object aUpdater - * The tool that triggered the update (if any), that tool's - * onChanged will not be called. - */ - nodeChanged: function IUI_nodeChanged(aUpdater) - { - this.highlighter.updateInfobar(); - this.highlighter.invalidateSize(); - this.breadcrumbs.updateSelectors(); - this._currentInspector.emit("change", aUpdater); - }, - - ///////////////////////////////////////////////////////////////////////// - //// Event Handling - - highlighterReady: function IUI_highlighterReady() - { - let self = this; - - this.highlighter.addListener("locked", function() { - self.stopInspecting(); - }); - - this.highlighter.addListener("unlocked", function() { - self.startInspecting(); - }); - - this.highlighter.addListener("nodeselected", function() { - self.select(self.highlighter.getNode(), false, false); - }); - - this.highlighter.addListener("pseudoclasstoggled", function(aPseudo) { - self.togglePseudoClassLock(aPseudo); - }); - - if (this.currentInspector._inspecting) { - this.startInspecting(); - this.highlighter.unlock(); - } else { - this.highlighter.lock(); - } - - Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null); - - this.highlighter.highlight(); - - if (this.currentInspector._sidebarOpen) { - this._sidebar.show(); - } - - Services.obs.notifyObservers({wrappedJSObject: this}, - INSPECTOR_NOTIFICATIONS.OPENED, null); - }, - - /** - * Main callback handler for events. - * - * @param event - * The event to be handled. - */ - handleEvent: function IUI_handleEvent(event) - { - let winID = null; - let win = null; - let inspectorClosed = false; - - switch (event.type) { - case "TabSelect": - winID = this.getWindowID(this.tabbrowser.selectedBrowser.contentWindow); - if (this.isInspectorOpen && winID != this.winID) { - this.closeInspectorUI(true); - inspectorClosed = true; - } - - if (winID && this.store.hasID(winID)) { - if (inspectorClosed && this.closing) { - Services.obs.addObserver(function reopenInspectorForTab() { - Services.obs.removeObserver(reopenInspectorForTab, - INSPECTOR_NOTIFICATIONS.CLOSED, false); - - this.openInspectorUI(); - }.bind(this), INSPECTOR_NOTIFICATIONS.CLOSED, false); - } else { - this.openInspectorUI(); - } - } - - if (this.store.isEmpty()) { - this.tabbrowser.tabContainer.removeEventListener("TabSelect", this, - false); - } - break; - case "keypress": - switch (event.keyCode) { - case this.chromeWin.KeyEvent.DOM_VK_ESCAPE: - this.closeInspectorUI(false); - event.preventDefault(); - event.stopPropagation(); - break; - } - case "pagehide": - win = event.originalTarget.defaultView; - // Skip iframes/frames. - if (!win || win.frameElement || win.top != win) { - break; - } - - win.removeEventListener(event.type, this, true); - - winID = this.getWindowID(win); - if (winID && winID != this.winID) { - this.store.deleteInspector(winID); - } - - if (this.store.isEmpty()) { - this.tabbrowser.tabContainer.removeEventListener("TabSelect", this, - false); - } - break; - case "mouseleave": - this.highlighter.show(); - break; - case "mouseenter": - this.highlighter.hide(); - break; - } - }, - - /* - * handles "keypress" events. - */ - onKeypress: function IUI_onKeypress(event) - { - let node = null; - let bc = this.breadcrumbs; - switch (event.keyCode) { - case this.chromeWin.KeyEvent.DOM_VK_LEFT: - if (bc.currentIndex != 0) - node = bc.nodeHierarchy[bc.currentIndex - 1].node; - if (node && this.highlighter.isNodeHighlightable(node)) - this.highlighter.highlight(node); - event.preventDefault(); - event.stopPropagation(); - break; - case this.chromeWin.KeyEvent.DOM_VK_RIGHT: - if (bc.currentIndex < bc.nodeHierarchy.length - 1) - node = bc.nodeHierarchy[bc.currentIndex + 1].node; - if (node && this.highlighter.isNodeHighlightable(node)) { - this.highlighter.highlight(node); - } - event.preventDefault(); - event.stopPropagation(); - break; - case this.chromeWin.KeyEvent.DOM_VK_UP: - if (this.selection) { - // Find a previous sibling that is highlightable. - node = this.selection.previousSibling; - while (node && !this.highlighter.isNodeHighlightable(node)) { - node = node.previousSibling; - } - } - if (node && this.highlighter.isNodeHighlightable(node)) { - this.highlighter.highlight(node, true); - } - event.preventDefault(); - event.stopPropagation(); - break; - case this.chromeWin.KeyEvent.DOM_VK_DOWN: - if (this.selection) { - // Find a next sibling that is highlightable. - node = this.selection.nextSibling; - while (node && !this.highlighter.isNodeHighlightable(node)) { - node = node.nextSibling; - } - } - if (node && this.highlighter.isNodeHighlightable(node)) { - this.highlighter.highlight(node, true); - } - event.preventDefault(); - event.stopPropagation(); - break; - } - }, - - /** - * Return the currently-selected node for the purposes of the - * context menu. This is usually the highlighter selection, unless - * the markup panel has a selected node that can't be highlighted - * (such as a text node). This will be fixed once the highlighter/inspector - * is confortable with non-element nodes being the current selection. - * See bug 785180. - */ - _contextSelection: function IUI__contextSelection() - { - let inspector = this.currentInspector; - if (inspector.markup) { - return inspector.markup.selected; - } - return this.selection; - }, - - /** - * Copy the innerHTML of the selected Node to the clipboard. Called via the - * Inspector:CopyInner command. - */ - copyInnerHTML: function IUI_copyInnerHTML() - { - let selection = this._contextSelection(); - clipboardHelper.copyString(selection.innerHTML, selection.ownerDocument); - }, - - /** - * Copy the outerHTML of the selected Node to the clipboard. Called via the - * Inspector:CopyOuter command. - */ - copyOuterHTML: function IUI_copyOuterHTML() - { - let selection = this._contextSelection(); - clipboardHelper.copyString(selection.outerHTML, selection.ownerDocument); - }, - - /** - * Delete the selected node. Called via the Inspector:DeleteNode command. - */ - deleteNode: function IUI_deleteNode() - { - let selection = this._contextSelection(); - - let root = selection.ownerDocument.documentElement; - if (selection === root) { - // We can't delete the root element. - return; - } - - let parent = selection.parentNode; - - // If the markup panel is active, use the markup panel to delete - // the node, making this an undoable action. - let markup = this.currentInspector.markup; - if (markup) { - markup.deleteNode(selection); - } else { - // remove the node from content - parent.removeChild(selection); - } - - // Otherwise, just delete the node. - this.breadcrumbs.invalidateHierarchy(); - - // select the parent node in the highlighter and breadcrumbs - this.inspectNode(parent); - }, - - ///////////////////////////////////////////////////////////////////////// - //// Utility Methods - - /** - * inspect the given node, highlighting it on the page and selecting the - * correct row in the tree panel - * - * @param aNode - * the element in the document to inspect - * @param aScroll - * force scroll? - */ - inspectNode: function IUI_inspectNode(aNode, aScroll) - { - if (aNode.ownerDocument === this.chromeDoc) { - // This should never happen, but just in case, we don't let the inspector - // inspect browser nodes. - return; - } - this.select(aNode, true, true); - this.highlighter.highlight(aNode, aScroll); - }, - - /////////////////////////////////////////////////////////////////////////// - //// Utility functions - - /** - * Retrieve the unique ID of a window object. - * - * @param nsIDOMWindow aWindow - * @returns integer ID - */ - getWindowID: function IUI_getWindowID(aWindow) - { - if (!aWindow) { - return null; - } - - let util = {}; - - try { - util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindowUtils); - } catch (ex) { } - - return util.currentInnerWindowID; - }, - - /** - * @param msg - * text message to send to the log - */ - _log: function LOG(msg) - { - Services.console.logStringMessage(msg); - }, - - /** - * Debugging function. - * @param msg - * text to show with the stack trace. - */ - _trace: function TRACE(msg) - { - this._log("TRACE: " + msg); - let frame = Components.stack.caller; - while (frame = frame.caller) { - if (frame.language == Ci.nsIProgrammingLanguage.JAVASCRIPT || - frame.language == Ci.nsIProgrammingLanguage.JAVASCRIPT2) { - this._log("filename: " + frame.filename + " lineNumber: " + frame.lineNumber + - " functionName: " + frame.name); - } - } - this._log("END TRACE"); - }, - - /** - * Get the toolbar button name for a given id string. Used by the - * registerTools API to retrieve a consistent name for toolbar buttons - * based on the ID of the tool. - * @param anId String - * id of the tool to be buttonized - * @returns String - */ - getToolbarButtonId: function IUI_createButtonId(anId) - { - return "inspector-" + anId + "-toolbutton"; - }, - - /** - * Destroy the InspectorUI instance. This is called by the InspectorUI API - * "user", see gBrowserInit.onUnload() in browser.js. - */ - destroy: function IUI_destroy() - { - if (this.isInspectorOpen) { - this.closeInspectorUI(); - } - - delete this.store; - delete this.chromeDoc; - delete this.chromeWin; - delete this.tabbrowser; - }, -}; - -/** - * The Inspector store is used for storing data specific to each tab window. - * @constructor - */ -function InspectorStore() -{ - this.store = {}; -} -InspectorStore.prototype = { - length: 0, - - /** - * Check if there is any data recorded for any tab/window. - * - * @returns boolean True if there are no stores for any window/tab, or false - * otherwise. - */ - isEmpty: function IS_isEmpty() - { - return this.length == 0 ? true : false; - }, - - /** - * Add a new inspector. - * - * @param string aID The Store ID you want created. - * @param Inspector aInspector The inspector to add. - * @returns boolean True if the store was added successfully, or false - * otherwise. - */ - addInspector: function IS_addInspector(aID, aInspector) - { - let result = false; - - if (!(aID in this.store)) { - this.store[aID] = aInspector; - this.length++; - result = true; - } - - return result; - }, - - /** - * Get the inspector for a window, if any. - * - * @param string aID The Store ID you want created. - */ - getInspector: function IS_getInspector(aID) - { - return this.store[aID] || null; - }, - - /** - * Delete an inspector by ID. - * - * @param string aID The store ID you want deleted. - * @returns boolean True if the store was removed successfully, or false - * otherwise. - */ - deleteInspector: function IS_deleteInspector(aID) - { - let result = false; - - if (aID in this.store) { - this.store[aID]._destroy(); - delete this.store[aID]; - this.length--; - result = true; - } - - return result; - }, - - /** - * Check store existence. - * - * @param string aID The store ID you want to check. - * @returns boolean True if the store ID is registered, or false otherwise. - */ - hasID: function IS_hasID(aID) - { - return (aID in this.store); - }, -}; - -/** - * The InspectorProgressListener object is an nsIWebProgressListener which - * handles onStateChange events for the inspected browser. If the user makes - * changes to the web page and he tries to navigate away, he is prompted to - * confirm page navigation, such that he's given the chance to prevent the loss - * of edits. - * - * @constructor - * @param object aInspector - * InspectorUI instance object. - */ -function InspectorProgressListener(aInspector) -{ - this.IUI = aInspector; - this.IUI.tabbrowser.addProgressListener(this); -} - -InspectorProgressListener.prototype = { - onStateChange: - function IPL_onStateChange(aProgress, aRequest, aFlag, aStatus) - { - // Remove myself if the Inspector is no longer open. - if (!this.IUI.isInspectorOpen) { - this.destroy(); - return; - } - - let isStart = aFlag & Ci.nsIWebProgressListener.STATE_START; - let isDocument = aFlag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT; - let isNetwork = aFlag & Ci.nsIWebProgressListener.STATE_IS_NETWORK; - let isRequest = aFlag & Ci.nsIWebProgressListener.STATE_IS_REQUEST; - - // Skip non-interesting states. - if (!isStart || !isDocument || !isRequest || !isNetwork) { - return; - } - - // If the request is about to happen in a new window, we are not concerned - // about the request. - if (aProgress.DOMWindow != this.IUI.win) { - return; - } - - if (this.IUI.isDirty) { - this.showNotification(aRequest); - } else { - this.IUI.closeInspectorUI(); - } - }, - - /** - * Show an asynchronous notification which asks the user to confirm or cancel - * the page navigation request. - * - * @param nsIRequest aRequest - * The request initiated by the user or by the page itself. - * @returns void - */ - showNotification: function IPL_showNotification(aRequest) - { - aRequest.suspend(); - - let notificationBox = this.IUI.tabbrowser.getNotificationBox(this.IUI.browser); - let notification = notificationBox. - getNotificationWithValue("inspector-page-navigation"); - - if (notification) { - notificationBox.removeNotification(notification, true); - } - - let cancelRequest = function onCancelRequest() { - if (aRequest) { - aRequest.cancel(Cr.NS_BINDING_ABORTED); - aRequest.resume(); // needed to allow the connection to be cancelled. - aRequest = null; - } - }; - - let eventCallback = function onNotificationCallback(aEvent) { - if (aEvent == "removed") { - cancelRequest(); - } - }; - - let buttons = [ - { - id: "inspector.confirmNavigationAway.buttonLeave", - label: this.IUI.strings. - GetStringFromName("confirmNavigationAway.buttonLeave"), - accessKey: this.IUI.strings. - GetStringFromName("confirmNavigationAway.buttonLeaveAccesskey"), - callback: function onButtonLeave() { - if (aRequest) { - aRequest.resume(); - aRequest = null; - this.IUI.closeInspectorUI(); - return true; - } - return false; - }.bind(this), - }, - { - id: "inspector.confirmNavigationAway.buttonStay", - label: this.IUI.strings. - GetStringFromName("confirmNavigationAway.buttonStay"), - accessKey: this.IUI.strings. - GetStringFromName("confirmNavigationAway.buttonStayAccesskey"), - callback: cancelRequest - }, - ]; - - let message = this.IUI.strings. - GetStringFromName("confirmNavigationAway.message"); - - notification = notificationBox.appendNotification(message, - "inspector-page-navigation", "chrome://browser/skin/Info.png", - notificationBox.PRIORITY_WARNING_HIGH, buttons, eventCallback); - - // Make sure this not a transient notification, to avoid the automatic - // transient notification removal. - notification.persistence = -1; - }, - - /** - * Destroy the progress listener instance. - */ - destroy: function IPL_destroy() - { - this.IUI.tabbrowser.removeProgressListener(this); - - let notificationBox = this.IUI.tabbrowser.getNotificationBox(this.IUI.browser); - let notification = notificationBox. - getNotificationWithValue("inspector-page-navigation"); - - if (notification) { - notificationBox.removeNotification(notification, true); - } - - delete this.IUI; - }, -}; - -InspectorUI._registeredSidebars = []; - -/** - * Register an inspector sidebar template. - * Already running sidebars will not be affected, see bug 740665. - * - * @param aRegistration Object - * { - * id: "toolname", - * label: "Button or tab label", - * icon: "chrome://somepath.png", - * tooltiptext: "Button tooltip", - * accesskey: "S", - * contentURL: string URI, source of the tool's iframe content. - * load: Called when the sidebar has been created and the contentURL loaded. - * Passed an Inspector object and an iframe object. - * destroy: Called when the sidebar is destroyed by the inspector. - * Passed whatever was returned by the tool's create function. - * } - */ -InspectorUI.registerSidebar = function IUI_registerSidebar(aRegistration) -{ - // Only allow a given tool ID to be registered once. - if (InspectorUI._registeredSidebars.some(function(elt) elt.id == aRegistration.id)) - return false; - - InspectorUI._registeredSidebars.push(aRegistration); - - return true; -} - -/** - * Unregister a previously-registered inspector sidebar. - * Already running sidebars will not be affected, see bug 740665. - * - * @param aID string - */ -InspectorUI.unregisterSidebar = function IUI_unregisterSidebar(aID) -{ - InspectorUI._registeredSidebars = InspectorUI._registeredSidebars.filter(function(aReg) aReg.id != aID); -} - -/////////////////////////////////////////////////////////////////////////// -//// Style Sidebar - -/** - * Manages the UI and loading of registered sidebar tools. - * @param aOptions object - * Initialization information for the style sidebar, including: - * document: The chrome document in which the style sidebar - * should be created. - * inspector: The Inspector object tied to this sidebar. - */ -function InspectorStyleSidebar(aOptions) -{ - this._tools = {}; - this._chromeDoc = aOptions.document; - this._inspector = aOptions.inspector; -} - -InspectorStyleSidebar.prototype = { - - get visible() !this._box.hasAttribute("hidden"), - get activePanel() this._deck.selectedPanel._toolID, - - destroy: function ISS_destroy() - { - // close the Layout View - if (this._layoutview) { - this._layoutview.destroy(); - this._layoutview = null; - } - - for each (let toolID in Object.getOwnPropertyNames(this._tools)) { - this.removeTool(toolID); - } - delete this._tools; - this._teardown(); - }, - - /** - * Called by InspectorUI to create the UI for a registered sidebar tool. - * Will create a toolbar button and an iframe for the tool. - * @param aRegObj object - * See the documentation for InspectorUI.registerSidebar(). - */ - addTool: function ISS_addTool(aRegObj) - { - if (aRegObj.id in this._tools) { - return; - } - - let btn = this._chromeDoc.createElement("toolbarbutton"); - btn.setAttribute("label", aRegObj.label); - btn.setAttribute("class", "devtools-toolbarbutton"); - btn.setAttribute("tooltiptext", aRegObj.tooltiptext); - btn.setAttribute("accesskey", aRegObj.accesskey); - btn.setAttribute("image", aRegObj.icon || ""); - btn.setAttribute("type", "radio"); - btn.setAttribute("group", "sidebar-tools"); - - let spacer = this._toolbar.querySelector("spacer"); - this._toolbar.insertBefore(btn, spacer); - // create tool iframe - let frame = this._chromeDoc.createElement("iframe"); - frame.setAttribute("flex", "1"); - frame._toolID = aRegObj.id; - - // This is needed to enable tooltips inside the iframe document. - frame.setAttribute("tooltip", "aHTMLTooltip"); - - this._deck.appendChild(frame); - - // wire up button to show the iframe - let onClick = function() { - this.activatePanel(aRegObj.id); - }.bind(this); - btn.addEventListener("click", onClick, true); - - this._tools[aRegObj.id] = { - id: aRegObj.id, - registration: aRegObj, - button: btn, - frame: frame, - loaded: false, - context: null, - onClick: onClick - }; - }, - - /** - * Remove a tool from the sidebar. - * - * @param aID string - * The string ID of the tool to remove. - */ - removeTool: function ISS_removeTool(aID) - { - if (!aID in this._tools) { - return; - } - let tool = this._tools[aID]; - delete this._tools[aID]; - - if (tool.loaded && tool.registration.destroy) { - tool.registration.destroy(tool.context); - } - - if (tool.onLoad) { - tool.frame.removeEventListener("load", tool.onLoad, true); - delete tool.onLoad; - } - - if (tool.onClick) { - tool.button.removeEventListener("click", tool.onClick, true); - delete tool.onClick; - } - - tool.button.parentNode.removeChild(tool.button); - tool.frame.parentNode.removeChild(tool.frame); - }, - - /** - * Hide or show the sidebar. - */ - toggle: function ISS_toggle() - { - if (!this.visible) { - this.show(); - } else { - this.hide(); - } - }, - - /** - * Shows the sidebar, updating the stored visibility pref. - */ - show: function ISS_show() - { - this._box.removeAttribute("hidden"); - this._splitter.removeAttribute("hidden"); - this._toggleButton.checked = true; - - this._showDefault(); - - this._inspector._sidebarOpen = true; - Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", true); - - // Instantiate the Layout View if needed. - if (Services.prefs.getBoolPref("devtools.layoutview.enabled") - && !this._layoutview) { - this._layoutview = new LayoutView({ - document: this._chromeDoc, - inspector: this._inspector, - }); - } - }, - - /** - * Hides the sidebar, updating the stored visibility pref. - */ - hide: function ISS_hide() - { - this._teardown(); - this._inspector._sidebarOpen = false; - Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", false); - }, - - /** - * Hides the sidebar UI elements. - */ - _teardown: function ISS__teardown() - { - this._toggleButton.checked = false; - this._box.setAttribute("hidden", true); - this._splitter.setAttribute("hidden", true); - }, - - /** - * Sets the current sidebar panel. - * - * @param aID string - * The ID of the panel to make visible. - */ - activatePanel: function ISS_activatePanel(aID) { - let tool = this._tools[aID]; - Services.prefs.setCharPref("devtools.inspector.activeSidebar", aID); - this._inspector._activeSidebar = aID; - this._deck.selectedPanel = tool.frame; - this._showContent(tool); - tool.button.setAttribute("checked", "true"); - let hasSelected = Array.forEach(this._toolbar.children, function(btn) { - if (btn != tool.button) { - btn.removeAttribute("checked"); - } - }); - }, - - /** - * Make the iframe content of a given tool visible. If this is the first - * time the tool has been shown, load its iframe content and call the - * registration object's load method. - * - * @param aTool object - * The tool object we're loading. - */ - _showContent: function ISS__showContent(aTool) - { - // If the current tool is already loaded, notify that we're - // showing this sidebar. - if (aTool.loaded) { - this._inspector.emit("sidebaractivated", aTool.id); - this._inspector.emit("sidebaractivated-" + aTool.id); - return; - } - - // If we're already loading, we're done. - if (aTool.onLoad) { - return; - } - - // This will be canceled in removeTool if necessary. - aTool.onLoad = function(evt) { - if (evt.target.location != aTool.registration.contentURL) { - return; - } - aTool.frame.removeEventListener("load", aTool.onLoad, true); - delete aTool.onLoad; - aTool.loaded = true; - aTool.context = aTool.registration.load(this._inspector, aTool.frame); - - this._inspector.emit("sidebaractivated", aTool.id); - - // Send an event specific to the activation of this panel. For - // this initial event, include a "createpanel" argument - // to let panels watch sidebaractivated to refresh themselves - // but ignore the one immediately after their load. - // I don't really like this, we should find a better solution. - this._inspector.emit("sidebaractivated-" + aTool.id, "createpanel"); - }.bind(this); - aTool.frame.addEventListener("load", aTool.onLoad, true); - aTool.frame.setAttribute("src", aTool.registration.contentURL); - }, - - /** - * For testing purposes, mostly - return the tool-provided context - * for a given tool. Will only work after the tool has been loaded - * and instantiated. - */ - _toolContext: function ISS__toolContext(aID) { - return aID in this._tools ? this._tools[aID].context : null; - }, - - /** - * Also mostly for testing, return the list of tool objects stored in - * the sidebar. - */ - _toolObjects: function ISS__toolObjects() { - return [this._tools[i] for each (i in Object.getOwnPropertyNames(this._tools))]; - }, - - /** - * If no tool is already selected, show the last-used sidebar. If there - * was no last-used sidebar, just show the first one. - */ - _showDefault: function ISS__showDefault() - { - let hasSelected = Array.some(this._toolbar.children, - function(btn) btn.hasAttribute("checked")); - - // Make sure the selected panel is loaded... - this._showContent(this._tools[this.activePanel]); - - if (hasSelected) { - return; - } - - let activeID = this._inspector._activeSidebar; - if (!activeID || !(activeID in this._tools)) { - activeID = Object.getOwnPropertyNames(this._tools)[0]; - } - this.activatePanel(activeID); - }, - - // DOM elements - get _toggleButton() this._chromeDoc.getElementById("inspector-style-button"), - get _box() this._chromeDoc.getElementById("devtools-sidebar-box"), - get _splitter() this._chromeDoc.getElementById("devtools-side-splitter"), - get _toolbar() this._chromeDoc.getElementById("devtools-sidebar-toolbar"), - get _deck() this._chromeDoc.getElementById("devtools-sidebar-deck"), -}; - -/////////////////////////////////////////////////////////////////////////// -//// HTML Breadcrumbs - -/** - * Display the ancestors of the current node and its children. - * Only one "branch" of children are displayed (only one line). - * - * Mechanism: - * . If no nodes displayed yet: - * then display the ancestor of the selected node and the selected node; - * else select the node; - * . If the selected node is the last node displayed, append its first (if any). - * - * @param object aInspector - * The InspectorUI instance. - */ -function HTMLBreadcrumbs(aInspector) -{ - this.IUI = aInspector; - this.DOMHelpers = new DOMHelpers(this.IUI.win); - this._init(); -} - -HTMLBreadcrumbs.prototype = { - _init: function BC__init() - { - this.container = this.IUI.chromeDoc.getElementById("inspector-breadcrumbs"); - this.container.addEventListener("mousedown", this, true); - - // We will save a list of already displayed nodes in this array. - this.nodeHierarchy = []; - - // Last selected node in nodeHierarchy. - this.currentIndex = -1; - - // Siblings menu - this.menu = this.IUI.chromeDoc.createElement("menupopup"); - this.menu.id = "inspector-breadcrumbs-menu"; - - let popupSet = this.IUI.chromeDoc.getElementById("mainPopupSet"); - popupSet.appendChild(this.menu); - - // By default, hide the arrows. We let the show them - // in case of overflow. - this.container.removeAttribute("overflows"); - this.container._scrollButtonUp.collapsed = true; - this.container._scrollButtonDown.collapsed = true; - - this.onscrollboxreflow = function() { - if (this.container._scrollButtonDown.collapsed) - this.container.removeAttribute("overflows"); - else - this.container.setAttribute("overflows", true); - }.bind(this); - - this.container.addEventListener("underflow", this.onscrollboxreflow, false); - this.container.addEventListener("overflow", this.onscrollboxreflow, false); - - this.menu.addEventListener("popuphiding", (function() { - while (this.menu.hasChildNodes()) { - this.menu.removeChild(this.menu.firstChild); - } - let button = this.container.querySelector("button[siblings-menu-open]"); - button.removeAttribute("siblings-menu-open"); - }).bind(this), false); - }, - - /** - * Build a string that represents the node: tagName#id.class1.class2. - * - * @param aNode The node to pretty-print - * @returns a string - */ - prettyPrintNodeAsText: function BC_prettyPrintNodeText(aNode) - { - let text = aNode.tagName.toLowerCase(); - if (aNode.id) { - text += "#" + aNode.id; - } - for (let i = 0; i < aNode.classList.length; i++) { - text += "." + aNode.classList[i]; - } - for (let i = 0; i < PSEUDO_CLASSES.length; i++) { - let pseudo = PSEUDO_CLASSES[i]; - if (DOMUtils.hasPseudoClassLock(aNode, pseudo)) { - text += pseudo; - } - } - - return text; - }, - - - /** - * Build