From 31def0da05280ab834c3585c37ec1b5cde29c89c Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Thu, 22 Aug 2019 08:25:44 -0400 Subject: [MCP Applications] Add %WIDGET_TOOLKIT% to the AUS update url for Pale Moon and Basilisk Also removes the redundant branding version of app.update.url in Pale Moon that was missed when many prefs were merged back into application preferences --- application/basilisk/branding/official/pref/basilisk-branding.js | 2 +- application/palemoon/app/profile/palemoon.js | 2 +- application/palemoon/branding/shared/pref/preferences.inc | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/application/basilisk/branding/official/pref/basilisk-branding.js b/application/basilisk/branding/official/pref/basilisk-branding.js index 190b84e78..946902f88 100644 --- a/application/basilisk/branding/official/pref/basilisk-branding.js +++ b/application/basilisk/branding/official/pref/basilisk-branding.js @@ -11,7 +11,7 @@ #define BRANDING_RELNOTESPATH releasenotes.shtml #define BRANDING_FIRSTRUNPATH firstrun/ #define BRANDING_APPUPDATEURL aus.@BRANDING_BASEURL@ -#define BRANDING_APPUPDATEPATH ?application=%PRODUCT%&version=%VERSION%&arch=%BUILD_TARGET%&buildid=%BUILD_ID%&channel=%CHANNEL% +#define BRANDING_APPUPDATEPATH ?application=%PRODUCT%&version=%VERSION%&arch=%BUILD_TARGET%&toolkit=%WIDGET_TOOLKIT%&buildid=%BUILD_ID%&channel=%CHANNEL% // Shared Branding Preferences // XXX: These should REALLY go back to application preferences diff --git a/application/palemoon/app/profile/palemoon.js b/application/palemoon/app/profile/palemoon.js index d0e078eb7..ee71c4ea1 100644 --- a/application/palemoon/app/profile/palemoon.js +++ b/application/palemoon/app/profile/palemoon.js @@ -160,7 +160,7 @@ pref("app.update.silent", false); pref("app.update.staging.enabled", true); // Update service URL: -pref("app.update.url", "https://aus.palemoon.org/?application=%PRODUCT%&version=%VERSION%&arch=%BUILD_TARGET%&buildid=%BUILD_ID%&channel=%CHANNEL%"); +pref("app.update.url", "https://aus.palemoon.org/?application=%PRODUCT%&version=%VERSION%&arch=%BUILD_TARGET%&toolkit=%WIDGET_TOOLKIT%&buildid=%BUILD_ID%&channel=%CHANNEL%"); // app.update.url.manual is in branding section // app.update.url.details is in branding section diff --git a/application/palemoon/branding/shared/pref/preferences.inc b/application/palemoon/branding/shared/pref/preferences.inc index 12803f285..fe1a2f003 100644 --- a/application/palemoon/branding/shared/pref/preferences.inc +++ b/application/palemoon/branding/shared/pref/preferences.inc @@ -12,8 +12,6 @@ pref("browser.identity.ssl_domain_display", 1); //show domain verified SSL (blue // ===| Application Update Service |=========================================== -pref("app.update.url", "https://aus.palemoon.org/?application=%PRODUCT%&version=%VERSION%&arch=%BUILD_TARGET%&buildid=%BUILD_ID%&channel=%CHANNEL%"); - // The time interval between the downloading of mar file chunks in the // background (in seconds) pref("app.update.download.backgroundInterval", 600); -- cgit v1.2.3 From b58a8779c02e8d4abdaae49e1fcc11efeb407bee Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 28 Aug 2019 14:39:17 +0200 Subject: Issue #1221: Pass the original element into nsXMLContentSerializer::CheckElementEnd so that we can properly determine whether it has children. This resolves #1221 --- dom/base/nsDocumentEncoder.cpp | 39 ++++++++++++++++++++++++++++------- dom/base/nsHTMLContentSerializer.cpp | 1 + dom/base/nsHTMLContentSerializer.h | 1 + dom/base/nsIContentSerializer.h | 1 + dom/base/nsPlainTextSerializer.cpp | 1 + dom/base/nsPlainTextSerializer.h | 1 + dom/base/nsXHTMLContentSerializer.cpp | 3 ++- dom/base/nsXHTMLContentSerializer.h | 1 + dom/base/nsXMLContentSerializer.cpp | 9 ++++---- dom/base/nsXMLContentSerializer.h | 2 ++ 10 files changed, 46 insertions(+), 13 deletions(-) diff --git a/dom/base/nsDocumentEncoder.cpp b/dom/base/nsDocumentEncoder.cpp index 84b128b15..34eb6ed38 100644 --- a/dom/base/nsDocumentEncoder.cpp +++ b/dom/base/nsDocumentEncoder.cpp @@ -82,7 +82,9 @@ protected: nsAString& aStr, bool aDontSerializeRoot, uint32_t aMaxLength = 0); - nsresult SerializeNodeEnd(nsINode* aNode, nsAString& aStr); + nsresult SerializeNodeEnd(nsINode* aOriginalNode, + nsAString& aStr, + nsINode* aFixupNode = nullptr); // This serializes the content of aNode. nsresult SerializeToStringIterative(nsINode* aNode, nsAString& aStr); @@ -405,14 +407,37 @@ nsDocumentEncoder::SerializeNodeStart(nsINode* aNode, } nsresult -nsDocumentEncoder::SerializeNodeEnd(nsINode* aNode, - nsAString& aStr) +nsDocumentEncoder::SerializeNodeEnd(nsINode* aOriginalNode, + nsAString& aStr, + nsINode* aFixupNode) { - if (!IsVisibleNode(aNode)) + if (!IsVisibleNode(aOriginalNode)) return NS_OK; - if (aNode->IsElement()) { - mSerializer->AppendElementEnd(aNode->AsElement(), aStr); + nsINode* node = nullptr; + nsCOMPtr fixedNodeKungfuDeathGrip; + + // Caller didn't do fixup, so we'll do it ourselves + if (!aFixupNode) { + aFixupNode = aOriginalNode; + if (mNodeFixup) { + bool dummy; + nsCOMPtr domNodeIn = do_QueryInterface(aOriginalNode); + nsCOMPtr domNodeOut; + mNodeFixup->FixupNode(domNodeIn, &dummy, getter_AddRefs(domNodeOut)); + fixedNodeKungfuDeathGrip = do_QueryInterface(domNodeOut); + node = fixedNodeKungfuDeathGrip; + } + } + + // Fall back to original node if needed. + if (!node) + node = aOriginalNode; + + if (node->IsElement()) { + mSerializer->AppendElementEnd(node->AsElement(), + aOriginalNode->AsElement(), + aStr); } return NS_OK; } @@ -481,7 +506,7 @@ nsDocumentEncoder::SerializeToStringRecursive(nsINode* aNode, } if (!aDontSerializeRoot) { - rv = SerializeNodeEnd(maybeFixedNode, aStr); + rv = SerializeNodeEnd(aNode, aStr, maybeFixedNode); NS_ENSURE_SUCCESS(rv, rv); } diff --git a/dom/base/nsHTMLContentSerializer.cpp b/dom/base/nsHTMLContentSerializer.cpp index ab8b4f2b2..c135c4cf8 100644 --- a/dom/base/nsHTMLContentSerializer.cpp +++ b/dom/base/nsHTMLContentSerializer.cpp @@ -301,6 +301,7 @@ nsHTMLContentSerializer::AppendElementStart(Element* aElement, NS_IMETHODIMP nsHTMLContentSerializer::AppendElementEnd(Element* aElement, + Element* aOriginalElement /* unused */, nsAString& aStr) { NS_ENSURE_ARG(aElement); diff --git a/dom/base/nsHTMLContentSerializer.h b/dom/base/nsHTMLContentSerializer.h index 6f3500e01..8e23d54ca 100644 --- a/dom/base/nsHTMLContentSerializer.h +++ b/dom/base/nsHTMLContentSerializer.h @@ -31,6 +31,7 @@ class nsHTMLContentSerializer final : public nsXHTMLContentSerializer { nsAString& aStr) override; NS_IMETHOD AppendElementEnd(mozilla::dom::Element* aElement, + mozilla::dom::Element* aOriginalElement, nsAString& aStr) override; NS_IMETHOD AppendDocumentStart(nsIDocument *aDocument, diff --git a/dom/base/nsIContentSerializer.h b/dom/base/nsIContentSerializer.h index f023cbc90..35014bd2c 100644 --- a/dom/base/nsIContentSerializer.h +++ b/dom/base/nsIContentSerializer.h @@ -55,6 +55,7 @@ class nsIContentSerializer : public nsISupports { nsAString& aStr) = 0; NS_IMETHOD AppendElementEnd(mozilla::dom::Element* aElement, + mozilla::dom::Element* aOriginalElement, nsAString& aStr) = 0; NS_IMETHOD Flush(nsAString& aStr) = 0; diff --git a/dom/base/nsPlainTextSerializer.cpp b/dom/base/nsPlainTextSerializer.cpp index ef6bdcac7..8097c4ec8 100644 --- a/dom/base/nsPlainTextSerializer.cpp +++ b/dom/base/nsPlainTextSerializer.cpp @@ -390,6 +390,7 @@ nsPlainTextSerializer::AppendElementStart(Element* aElement, NS_IMETHODIMP nsPlainTextSerializer::AppendElementEnd(Element* aElement, + Element* aOriginalElement /* unused */, nsAString& aStr) { NS_ENSURE_ARG(aElement); diff --git a/dom/base/nsPlainTextSerializer.h b/dom/base/nsPlainTextSerializer.h index 95cf5590c..5055c75a0 100644 --- a/dom/base/nsPlainTextSerializer.h +++ b/dom/base/nsPlainTextSerializer.h @@ -61,6 +61,7 @@ public: mozilla::dom::Element* aOriginalElement, nsAString& aStr) override; NS_IMETHOD AppendElementEnd(mozilla::dom::Element* aElement, + mozilla::dom::Element* aOriginalElement, nsAString& aStr) override; NS_IMETHOD Flush(nsAString& aStr) override; diff --git a/dom/base/nsXHTMLContentSerializer.cpp b/dom/base/nsXHTMLContentSerializer.cpp index 111ed46c7..0a39ef663 100755 --- a/dom/base/nsXHTMLContentSerializer.cpp +++ b/dom/base/nsXHTMLContentSerializer.cpp @@ -514,6 +514,7 @@ nsXHTMLContentSerializer::CheckElementStart(nsIContent * aContent, bool nsXHTMLContentSerializer::CheckElementEnd(mozilla::dom::Element* aElement, + mozilla::dom::Element* aOriginalElement, bool& aForceFormat, nsAString& aStr) { @@ -532,7 +533,7 @@ nsXHTMLContentSerializer::CheckElementEnd(mozilla::dom::Element* aElement, } bool dummyFormat; - return nsXMLContentSerializer::CheckElementEnd(aElement, dummyFormat, aStr); + return nsXMLContentSerializer::CheckElementEnd(aElement, aOriginalElement, dummyFormat, aStr); } bool diff --git a/dom/base/nsXHTMLContentSerializer.h b/dom/base/nsXHTMLContentSerializer.h index 7473ba074..79ecf28f1 100644 --- a/dom/base/nsXHTMLContentSerializer.h +++ b/dom/base/nsXHTMLContentSerializer.h @@ -53,6 +53,7 @@ class nsXHTMLContentSerializer : public nsXMLContentSerializer { nsAString& aStr) override; virtual bool CheckElementEnd(mozilla::dom::Element* aContent, + mozilla::dom::Element* aOriginalElement, bool& aForceFormat, nsAString& aStr) override; diff --git a/dom/base/nsXMLContentSerializer.cpp b/dom/base/nsXMLContentSerializer.cpp index 54fadaa94..f12bb8fdc 100644 --- a/dom/base/nsXMLContentSerializer.cpp +++ b/dom/base/nsXMLContentSerializer.cpp @@ -1028,6 +1028,7 @@ nsXMLContentSerializer::AppendEndOfElementStart(Element* aElement, NS_IMETHODIMP nsXMLContentSerializer::AppendElementEnd(Element* aElement, + Element* aOriginalElement, nsAString& aStr) { NS_ENSURE_ARG(aElement); @@ -1035,7 +1036,7 @@ nsXMLContentSerializer::AppendElementEnd(Element* aElement, nsIContent* content = aElement; bool forceFormat = false, outputElementEnd; - outputElementEnd = CheckElementEnd(aElement, forceFormat, aStr); + outputElementEnd = CheckElementEnd(aElement, aOriginalElement, forceFormat, aStr); nsIAtom *name = content->NodeInfo()->NameAtom(); @@ -1161,16 +1162,14 @@ nsXMLContentSerializer::CheckElementStart(nsIContent * aContent, bool nsXMLContentSerializer::CheckElementEnd(Element* aElement, + Element* aOriginalElement, bool& aForceFormat, nsAString& aStr) { // We don't output a separate end tag for empty element aForceFormat = false; - // XXXbz this is a bit messed up, but by now we don't have our fixed-up - // version of aElement anymore. Let's hope fixup never changes the localName - // or namespace... - return ElementNeedsSeparateEndTag(aElement, aElement); + return ElementNeedsSeparateEndTag(aElement, aOriginalElement); } bool diff --git a/dom/base/nsXMLContentSerializer.h b/dom/base/nsXMLContentSerializer.h index 941acb179..2f76b0892 100644 --- a/dom/base/nsXMLContentSerializer.h +++ b/dom/base/nsXMLContentSerializer.h @@ -59,6 +59,7 @@ class nsXMLContentSerializer : public nsIContentSerializer { nsAString& aStr) override; NS_IMETHOD AppendElementEnd(mozilla::dom::Element* aElement, + mozilla::dom::Element* aOriginalElement, nsAString& aStr) override; NS_IMETHOD Flush(nsAString& aStr) override { return NS_OK; } @@ -263,6 +264,7 @@ class nsXMLContentSerializer : public nsIContentSerializer { * @return boolean true if the element can be output */ virtual bool CheckElementEnd(mozilla::dom::Element* aElement, + mozilla::dom::Element* aOriginalElement, bool& aForceFormat, nsAString& aStr); -- cgit v1.2.3 From 11965adc1debca20b7115802d4542aee747b3a1c Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 28 Aug 2019 14:49:13 +0200 Subject: New cycle version bump --- application/palemoon/config/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/palemoon/config/version.txt b/application/palemoon/config/version.txt index ff225a990..7ce3ee6a8 100644 --- a/application/palemoon/config/version.txt +++ b/application/palemoon/config/version.txt @@ -1 +1 @@ -28.7.0a1 \ No newline at end of file +28.8.0a1 \ No newline at end of file -- cgit v1.2.3 From f67e3f054fcdd04e78a7779ec7e7f44643ec47e5 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 1 Sep 2019 15:15:19 +0200 Subject: Issue #1179: fix indentation --- dom/base/nsObjectLoadingContent.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dom/base/nsObjectLoadingContent.cpp b/dom/base/nsObjectLoadingContent.cpp index 3c850c4cd..590f236c0 100644 --- a/dom/base/nsObjectLoadingContent.cpp +++ b/dom/base/nsObjectLoadingContent.cpp @@ -718,9 +718,9 @@ nsObjectLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent) if (mType == eType_Plugin) { nsIDocument* doc = thisContent->GetComposedDoc(); if (doc && doc->IsActive()) { - nsCOMPtr ev = new nsSimplePluginEvent(doc, - NS_LITERAL_STRING("PluginRemoved")); - NS_DispatchToCurrentThread(ev); + nsCOMPtr ev = new nsSimplePluginEvent(doc, + NS_LITERAL_STRING("PluginRemoved")); + NS_DispatchToCurrentThread(ev); } } } -- cgit v1.2.3 From 09a8b2f19689b679b1268a3004ec5e3f37b9732a Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 1 Sep 2019 16:39:40 +0200 Subject: Correctly return zero vertices if clipping plane 0 or 2 clip away the entire polygon. This fixes a regression caused by the fix for CVE-2016-5252 --- gfx/2d/Matrix.h | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/gfx/2d/Matrix.h b/gfx/2d/Matrix.h index 22a01ca10..84c9a5280 100644 --- a/gfx/2d/Matrix.h +++ b/gfx/2d/Matrix.h @@ -734,7 +734,8 @@ public: // Initialize a double-buffered array of points in homogenous space with // the input rectangle, aRect. Point4DTyped points[2][kTransformAndClipRectMaxVerts]; - Point4DTyped* dstPoint = points[0]; + Point4DTyped* dstPointStart = points[0]; + Point4DTyped* dstPoint = dstPointStart; *dstPoint++ = TransformPoint(Point4DTyped(aRect.x, aRect.y, 0, 1)); *dstPoint++ = TransformPoint(Point4DTyped(aRect.XMost(), aRect.y, 0, 1)); @@ -754,11 +755,11 @@ public: // points[1]. for (int plane=0; plane < 4; plane++) { planeNormals[plane].Normalize(); - Point4DTyped* srcPoint = points[plane & 1]; + Point4DTyped* srcPoint = dstPointStart; Point4DTyped* srcPointEnd = dstPoint; - dstPoint = points[~plane & 1]; - Point4DTyped* dstPointStart = dstPoint; + dstPointStart = points[~plane & 1]; + dstPoint = dstPointStart; Point4DTyped* prevPoint = srcPointEnd - 1; F prevDot = planeNormals[plane].DotProduct(*prevPoint); @@ -787,10 +788,10 @@ public: } } - size_t dstPointCount = 0; - size_t srcPointCount = dstPoint - points[0]; - for (Point4DTyped* srcPoint = points[0]; srcPoint < points[0] + srcPointCount; srcPoint++) { - + Point4DTyped* srcPoint = dstPointStart; + Point4DTyped* srcPointEnd = dstPoint; + size_t vertCount = 0; + while (srcPoint < srcPointEnd) { PointTyped p; if (srcPoint->w == 0.0) { // If a point lies on the intersection of the clipping planes at @@ -800,12 +801,13 @@ public: p = srcPoint->As2DPoint(); } // Emit only unique points - if (dstPointCount == 0 || p != aVerts[dstPointCount - 1]) { - aVerts[dstPointCount++] = p; + if (vertCount == 0 || p != aVerts[vertCount - 1]) { + aVerts[vertCount++] = p; } + srcPoint++; } - return dstPointCount; + return vertCount; } static const int kTransformAndClipRectMaxVerts = 32; -- cgit v1.2.3 From 8f7d40e854f2941c2ddd75fb6532407e07a72609 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 1 Sep 2019 16:44:47 +0200 Subject: Issue #1222: Don't load plugin instances when they have no `src` URI. Favor fallback content in that case (if present). Fallback is always considered "good" in this case so may end up doing nothing which is what we'd want for corner cases that hammer this routine with no content. --- application/palemoon/app/profile/palemoon.js | 9 +++++++++ dom/base/nsObjectLoadingContent.cpp | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/application/palemoon/app/profile/palemoon.js b/application/palemoon/app/profile/palemoon.js index ee71c4ea1..df46ea4b6 100644 --- a/application/palemoon/app/profile/palemoon.js +++ b/application/palemoon/app/profile/palemoon.js @@ -682,6 +682,15 @@ pref("plugins.update.notifyUser", false); //Enable tri-state option (Always/Never/Ask) pref("plugins.click_to_play", true); +// Platform pref is to enable all plugins by default. +// Uncomment this pref to default to click-to-play +// pref("plugin.default.state", 1); + +// Don't load plugin instances with no src declared. +// These prefs are documented in detail in all.js. +pref("plugins.favorfallback.mode", "follow-ctp"); +pref("plugins.favorfallback.rules", "nosrc"); + #ifdef XP_WIN pref("browser.preferences.instantApply", false); #else diff --git a/dom/base/nsObjectLoadingContent.cpp b/dom/base/nsObjectLoadingContent.cpp index 590f236c0..4978744e8 100644 --- a/dom/base/nsObjectLoadingContent.cpp +++ b/dom/base/nsObjectLoadingContent.cpp @@ -3628,6 +3628,14 @@ nsObjectLoadingContent::HasGoodFallback() { } } + // RULE "nosrc": + // Use fallback content if the object has not specified a src URI. + if (rulesList[i].EqualsLiteral("nosrc")) { + if (!mOriginalURI) { + return true; + } + } + // RULE "adobelink": // Don't use fallback content when it has a link to adobe's website. if (rulesList[i].EqualsLiteral("adobelink")) { -- cgit v1.2.3 From 44cfc6d210eec360b4eec333b14bef3fcbac2f60 Mon Sep 17 00:00:00 2001 From: Ascrod <32915892+Ascrod@users.noreply.github.com> Date: Sun, 1 Sep 2019 17:36:03 -0400 Subject: Issue #1217 - Add support for (later versions of) the Windows 10 SDK --- build/moz.configure/windows.configure | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/build/moz.configure/windows.configure b/build/moz.configure/windows.configure index b9a3898a1..631049566 100644 --- a/build/moz.configure/windows.configure +++ b/build/moz.configure/windows.configure @@ -361,10 +361,10 @@ set_config('LIB', lib_path) option(env='MT', nargs=1, help='Path to the Microsoft Manifest Tool') -@depends_win(valid_windows_sdk_dir) +@depends_win(valid_windows_sdk_dir, valid_ucrt_sdk_dir) @imports(_from='os', _import='environ') @imports('platform') -def sdk_bin_path(valid_windows_sdk_dir): +def sdk_bin_path(valid_windows_sdk_dir, valid_ucrt_sdk_dir): if not valid_windows_sdk_dir: return @@ -373,13 +373,17 @@ def sdk_bin_path(valid_windows_sdk_dir): 'AMD64': 'x64', }.get(platform.machine()) + # From version 10.0.15063.0 onwards the bin path contains the version number. + versioned_bin = ('bin' if valid_ucrt_sdk_dir.version < '10.0.15063.0' + else os.path.join('bin', str(valid_ucrt_sdk_dir.version))) + result = [ environ['PATH'], - os.path.join(valid_windows_sdk_dir.path, 'bin', vc_host) + os.path.join(valid_windows_sdk_dir.path, versioned_bin, vc_host) ] if vc_host == 'x64': result.append( - os.path.join(valid_windows_sdk_dir.path, 'bin', 'x86')) + os.path.join(valid_windows_sdk_dir.path, versioned_bin, 'x86')) return result -- cgit v1.2.3 From 69ac6335874875b3dd14a391e5ddcd73ad60f23d Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 4 Sep 2019 12:00:36 +0200 Subject: Revert "Correctly return zero vertices if clipping plane 0 or 2 clip away the" This reverts commit 09a8b2f19689b679b1268a3004ec5e3f37b9732a. --- gfx/2d/Matrix.h | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/gfx/2d/Matrix.h b/gfx/2d/Matrix.h index 84c9a5280..22a01ca10 100644 --- a/gfx/2d/Matrix.h +++ b/gfx/2d/Matrix.h @@ -734,8 +734,7 @@ public: // Initialize a double-buffered array of points in homogenous space with // the input rectangle, aRect. Point4DTyped points[2][kTransformAndClipRectMaxVerts]; - Point4DTyped* dstPointStart = points[0]; - Point4DTyped* dstPoint = dstPointStart; + Point4DTyped* dstPoint = points[0]; *dstPoint++ = TransformPoint(Point4DTyped(aRect.x, aRect.y, 0, 1)); *dstPoint++ = TransformPoint(Point4DTyped(aRect.XMost(), aRect.y, 0, 1)); @@ -755,11 +754,11 @@ public: // points[1]. for (int plane=0; plane < 4; plane++) { planeNormals[plane].Normalize(); - Point4DTyped* srcPoint = dstPointStart; + Point4DTyped* srcPoint = points[plane & 1]; Point4DTyped* srcPointEnd = dstPoint; - dstPointStart = points[~plane & 1]; - dstPoint = dstPointStart; + dstPoint = points[~plane & 1]; + Point4DTyped* dstPointStart = dstPoint; Point4DTyped* prevPoint = srcPointEnd - 1; F prevDot = planeNormals[plane].DotProduct(*prevPoint); @@ -788,10 +787,10 @@ public: } } - Point4DTyped* srcPoint = dstPointStart; - Point4DTyped* srcPointEnd = dstPoint; - size_t vertCount = 0; - while (srcPoint < srcPointEnd) { + size_t dstPointCount = 0; + size_t srcPointCount = dstPoint - points[0]; + for (Point4DTyped* srcPoint = points[0]; srcPoint < points[0] + srcPointCount; srcPoint++) { + PointTyped p; if (srcPoint->w == 0.0) { // If a point lies on the intersection of the clipping planes at @@ -801,13 +800,12 @@ public: p = srcPoint->As2DPoint(); } // Emit only unique points - if (vertCount == 0 || p != aVerts[vertCount - 1]) { - aVerts[vertCount++] = p; + if (dstPointCount == 0 || p != aVerts[dstPointCount - 1]) { + aVerts[dstPointCount++] = p; } - srcPoint++; } - return vertCount; + return dstPointCount; } static const int kTransformAndClipRectMaxVerts = 32; -- cgit v1.2.3 From 7c09f0ed7e3bbc2f4da4df7496478594b4f0fa0e Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Wed, 4 Sep 2019 12:00:52 +0200 Subject: Correctly return zero vertices if clipping plane 0 or 2 clip away the entire polygon. This fixes a bug that was introduced three years ago in BZ bug 1268854. What happened was that the final pass over the polygon assumed that the current polygon was living in plane[0]. But due to the double buffering, the "current" polygon alternates between plane[0] and plane[1]. The bug had also introduced an early exit so that we could hit the final pass at a time where the current, now empty, polygon was in plane[1]. So we would incorrectly treat all 32 points in plane[0] as part of the final polygon. This bug was responsible for intermittently unreasonable numbers in CompositorOGL's fill rate / overdraw overlay. This fixes a regression caused by the fix for CVE-2016-5252. --- gfx/2d/Matrix.h | 44 +++++++++++++++++++----------- gfx/tests/gtest/TestMatrix.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++ gfx/tests/gtest/moz.build | 1 + 3 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 gfx/tests/gtest/TestMatrix.cpp diff --git a/gfx/2d/Matrix.h b/gfx/2d/Matrix.h index 22a01ca10..d6835c8e6 100644 --- a/gfx/2d/Matrix.h +++ b/gfx/2d/Matrix.h @@ -723,7 +723,7 @@ public: * The resulting vertices are populated in aVerts. aVerts must be * pre-allocated to hold at least kTransformAndClipRectMaxVerts Points. * The vertex count is returned by TransformAndClipRect. It is possible to - * emit fewer that 3 vertices, indicating that aRect will not be visible + * emit fewer than 3 vertices, indicating that aRect will not be visible * within aClip. */ template @@ -734,7 +734,8 @@ public: // Initialize a double-buffered array of points in homogenous space with // the input rectangle, aRect. Point4DTyped points[2][kTransformAndClipRectMaxVerts]; - Point4DTyped* dstPoint = points[0]; + Point4DTyped* dstPointStart = points[0]; + Point4DTyped* dstPoint = dstPointStart; *dstPoint++ = TransformPoint(Point4DTyped(aRect.x, aRect.y, 0, 1)); *dstPoint++ = TransformPoint(Point4DTyped(aRect.XMost(), aRect.y, 0, 1)); @@ -750,16 +751,26 @@ public: planeNormals[3] = Point4DTyped(0.0, -1.0, 0.0, aClip.YMost()); // Iterate through each clipping plane and clip the polygon. - // In each pass, we double buffer, alternating between points[0] and - // points[1]. + // For each clipping plane, we intersect the plane with all polygon edges. + // Each pass can increase or decrease the number of points that make up the + // current clipped polygon. We double buffer that set of points, alternating + // between points[0] and points[1]. for (int plane=0; plane < 4; plane++) { planeNormals[plane].Normalize(); - Point4DTyped* srcPoint = points[plane & 1]; + Point4DTyped* srcPoint = dstPointStart; Point4DTyped* srcPointEnd = dstPoint; - dstPoint = points[~plane & 1]; - Point4DTyped* dstPointStart = dstPoint; - + dstPointStart = points[~plane & 1]; + dstPoint = dstPointStart; + + // Iterate over the polygon edges. In each iteration the current edge is + // the edge from prevPoint to srcPoint. If the two end points lie on + // different sides of the plane, we have an intersection. Otherwise, the + // edge is either completely "inside" the half-space created by the + // clipping plane, and we add srcPoint, or it is completely "outside", and + // we discard srcPoint. + // We may create duplicated points in the polygon. We keep those around + // until all clipping is done and then filter out duplicates at the end. Point4DTyped* prevPoint = srcPointEnd - 1; F prevDot = planeNormals[plane].DotProduct(*prevPoint); while (srcPoint < srcPointEnd && ((dstPoint - dstPointStart) < kTransformAndClipRectMaxVerts)) { @@ -783,14 +794,16 @@ public: } if (dstPoint == dstPointStart) { + // No polygon points were produced, so the polygon has been + // completely clipped away by the current clipping plane. Exit. break; } } - size_t dstPointCount = 0; - size_t srcPointCount = dstPoint - points[0]; - for (Point4DTyped* srcPoint = points[0]; srcPoint < points[0] + srcPointCount; srcPoint++) { - + Point4DTyped* srcPoint = dstPointStart; + Point4DTyped* srcPointEnd = dstPoint; + size_t vertCount = 0; + while (srcPoint < srcPointEnd) { PointTyped p; if (srcPoint->w == 0.0) { // If a point lies on the intersection of the clipping planes at @@ -800,12 +813,13 @@ public: p = srcPoint->As2DPoint(); } // Emit only unique points - if (dstPointCount == 0 || p != aVerts[dstPointCount - 1]) { - aVerts[dstPointCount++] = p; + if (vertCount == 0 || p != aVerts[vertCount - 1]) { + aVerts[vertCount++] = p; } + srcPoint++; } - return dstPointCount; + return vertCount; } static const int kTransformAndClipRectMaxVerts = 32; diff --git a/gfx/tests/gtest/TestMatrix.cpp b/gfx/tests/gtest/TestMatrix.cpp new file mode 100644 index 000000000..bc2f9e63c --- /dev/null +++ b/gfx/tests/gtest/TestMatrix.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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 "gtest/gtest.h" +#include "mozilla/gfx/Matrix.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +static Rect NudgedToInt(const Rect& aRect) { + Rect r(aRect); + r.NudgeToIntegers(); + return r; +} + +TEST(Matrix, TransformAndClipRect) +{ + Rect c(100, 100, 100, 100); + Matrix4x4 m; + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 50, 20, 20), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(250, 50, 20, 20), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(250, 250, 20, 20), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 250, 20, 20), c).IsEmpty()); + + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 50, 100, 20), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(150, 50, 100, 20), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 250, 100, 20), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(150, 250, 100, 20), c).IsEmpty()); + + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 50, 20, 100), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 150, 20, 100), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(250, 50, 20, 100), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(250, 150, 20, 100), c).IsEmpty()); + + EXPECT_TRUE(NudgedToInt(m.TransformAndClipBounds(Rect(50, 50, 100, 100), c)) + .IsEqualInterior(Rect(100, 100, 50, 50))); + EXPECT_TRUE(NudgedToInt(m.TransformAndClipBounds(Rect(150, 50, 100, 100), c)) + .IsEqualInterior(Rect(150, 100, 50, 50))); + EXPECT_TRUE(NudgedToInt(m.TransformAndClipBounds(Rect(150, 150, 100, 100), c)) + .IsEqualInterior(Rect(150, 150, 50, 50))); + EXPECT_TRUE(NudgedToInt(m.TransformAndClipBounds(Rect(50, 150, 100, 100), c)) + .IsEqualInterior(Rect(100, 150, 50, 50))); + + EXPECT_TRUE(NudgedToInt(m.TransformAndClipBounds(Rect(110, 110, 80, 80), c)) + .IsEqualInterior(Rect(110, 110, 80, 80))); + + EXPECT_TRUE(NudgedToInt(m.TransformAndClipBounds(Rect(50, 50, 200, 200), c)) + .IsEqualInterior(Rect(100, 100, 100, 100))); + + EXPECT_TRUE(NudgedToInt(m.TransformAndClipBounds(Rect(50, 50, 200, 100), c)) + .IsEqualInterior(Rect(100, 100, 100, 50))); + EXPECT_TRUE(NudgedToInt(m.TransformAndClipBounds(Rect(50, 150, 200, 100), c)) + .IsEqualInterior(Rect(100, 150, 100, 50))); + EXPECT_TRUE(NudgedToInt(m.TransformAndClipBounds(Rect(50, 50, 100, 200), c)) + .IsEqualInterior(Rect(100, 100, 50, 100))); + EXPECT_TRUE(NudgedToInt(m.TransformAndClipBounds(Rect(150, 50, 100, 200), c)) + .IsEqualInterior(Rect(150, 100, 50, 100))); +} diff --git a/gfx/tests/gtest/moz.build b/gfx/tests/gtest/moz.build index 23b019d1b..ea18c1e3b 100644 --- a/gfx/tests/gtest/moz.build +++ b/gfx/tests/gtest/moz.build @@ -17,6 +17,7 @@ UNIFIED_SOURCES += [ 'TestGfxWidgets.cpp', 'TestJobScheduler.cpp', 'TestLayers.cpp', + 'TestMatrix.cpp', 'TestMoz2D.cpp', 'TestPolygon.cpp', 'TestQcms.cpp', -- cgit v1.2.3 From 0ce08b418d84a57bae22a0aa395fecb7412ed931 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 4 Sep 2019 15:27:40 +0200 Subject: Fix an issue with the html5 tokenizer and tree builder. --- parser/html/jArray.h | 43 ++++++++++++++++++++++++++++++++---- parser/html/javasrc/Tokenizer.java | 33 +++++++++++++++++++++++---- parser/html/javasrc/TreeBuilder.java | 22 +++++++----------- parser/html/nsHtml5Tokenizer.cpp | 19 +++++++++------- parser/html/nsHtml5Tokenizer.h | 3 ++- parser/html/nsHtml5TreeBuilder.cpp | 22 +++++++++++------- 6 files changed, 103 insertions(+), 39 deletions(-) diff --git a/parser/html/jArray.h b/parser/html/jArray.h index 45548a077..98889059c 100644 --- a/parser/html/jArray.h +++ b/parser/html/jArray.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2008-2015 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -44,32 +45,66 @@ struct staticJArray { } }; -template -struct jArray { +template +class autoJArray; + +template +class jArray { + friend class autoJArray; + +private: T* arr; + +public: L length; + static jArray newJArray(L const len) { MOZ_ASSERT(len >= 0, "Negative length."); jArray newArray = { new T[size_t(len)], len }; return newArray; } + static jArray newFallibleJArray(L const len) { MOZ_ASSERT(len >= 0, "Negative length."); T* a = new (mozilla::fallible) T[size_t(len)]; jArray newArray = { a, a ? len : 0 }; return newArray; } - operator T*() { return arr; } + + operator T*() { + return arr; + } + T& operator[] (L const index) { MOZ_ASSERT(index >= 0, "Array access with negative index."); MOZ_ASSERT(index < length, "Array index out of bounds."); return arr[index]; } + void operator=(staticJArray& other) { arr = (T*)other.arr; length = other.length; } -}; + + MOZ_IMPLICIT jArray(decltype(nullptr)) + : arr(nullptr) + , length(0) + { + } + + jArray() + : arr(nullptr) + , length(0) + { + } + +private: + jArray(T* aArr, L aLength) + : arr(aArr) + , length(aLength) + { + } +}; // class jArray template class autoJArray { diff --git a/parser/html/javasrc/Tokenizer.java b/parser/html/javasrc/Tokenizer.java index 70e1df75c..f141d94d7 100644 --- a/parser/html/javasrc/Tokenizer.java +++ b/parser/html/javasrc/Tokenizer.java @@ -1,6 +1,7 @@ /* * Copyright (c) 2005-2007 Henri Sivonen * Copyright (c) 2007-2015 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * Portions of comments Copyright 2004-2010 Apple Computer, Inc., Mozilla * Foundation, and Opera Software ASA. * @@ -680,6 +681,22 @@ public class Tokenizer implements Locator { * * @param specialTokenizerState * the tokenizer state to set + */ + public void setState(int specialTokenizerState) { + this.stateSave = specialTokenizerState; + this.endTagExpectation = null; + this.endTagExpectationAsArray = null; + } + + // [NOCPP[ + + /** + * Sets the tokenizer state and the associated element name. This should + * only ever used to put the tokenizer into one of the states that have + * a special end tag expectation. For use from the tokenizer test harness. + * + * @param specialTokenizerState + * the tokenizer state to set * @param endTagExpectation * the expected end tag for transitioning back to normal */ @@ -695,6 +712,8 @@ public class Tokenizer implements Locator { endTagExpectationToArray(); } + // ]NOCPP] + /** * Sets the tokenizer state and the associated element name. This should * only ever used to put the tokenizer into one of the states that have @@ -3759,11 +3778,17 @@ public class Tokenizer implements Locator { c = checkChar(buf, pos); /* * ASSERT! when entering this state, set index to 0 and - * call clearStrBufBeforeUse() assert (contentModelElement != - * null); Let's implement the above without lookahead. - * strBuf is the 'temporary buffer'. + * call clearStrBufBeforeUse(); Let's implement the above + * without lookahead. strBuf is the 'temporary buffer'. */ - if (index < endTagExpectationAsArray.length) { + if (endTagExpectationAsArray == null) { + tokenHandler.characters(Tokenizer.LT_SOLIDUS, + 0, 2); + cstart = pos; + reconsume = true; + state = transition(state, returnState, reconsume, pos); + continue stateloop; + } else if (index < endTagExpectationAsArray.length) { char e = endTagExpectationAsArray[index]; char folded = c; if (c >= 'A' && c <= 'Z') { diff --git a/parser/html/javasrc/TreeBuilder.java b/parser/html/javasrc/TreeBuilder.java index 5e83d1847..58074086d 100644 --- a/parser/html/javasrc/TreeBuilder.java +++ b/parser/html/javasrc/TreeBuilder.java @@ -1,6 +1,7 @@ /* * Copyright (c) 2007 Henri Sivonen * Copyright (c) 2007-2015 Mozilla Foundation + * Copyright (c) 2018-2019 Moonchild Productions * Portions of comments Copyright 2004-2008 Apple Computer, Inc., Mozilla * Foundation, and Opera Software ASA. * @@ -640,8 +641,7 @@ public abstract class TreeBuilder implements TokenHandler, ); currentPtr++; stack[currentPtr] = node; - tokenizer.setStateAndEndTagExpectation(Tokenizer.DATA, - contextName); + tokenizer.setState(Tokenizer.DATA); // The frameset-ok flag is set even though never // ends up being allowed as HTML frameset in the fragment case. mode = FRAMESET_OK; @@ -671,8 +671,7 @@ public abstract class TreeBuilder implements TokenHandler, ); currentPtr++; stack[currentPtr] = node; - tokenizer.setStateAndEndTagExpectation(Tokenizer.DATA, - contextName); + tokenizer.setState(Tokenizer.DATA); // The frameset-ok flag is set even though never // ends up being allowed as HTML frameset in the fragment case. mode = FRAMESET_OK; @@ -691,23 +690,18 @@ public abstract class TreeBuilder implements TokenHandler, resetTheInsertionMode(); formPointer = getFormPointerForContext(contextNode); if ("title" == contextName || "textarea" == contextName) { - tokenizer.setStateAndEndTagExpectation(Tokenizer.RCDATA, - contextName); + tokenizer.setState(Tokenizer.RCDATA); } else if ("style" == contextName || "xmp" == contextName || "iframe" == contextName || "noembed" == contextName || "noframes" == contextName || (scriptingEnabled && "noscript" == contextName)) { - tokenizer.setStateAndEndTagExpectation(Tokenizer.RAWTEXT, - contextName); + tokenizer.setState(Tokenizer.RAWTEXT); } else if ("plaintext" == contextName) { - tokenizer.setStateAndEndTagExpectation(Tokenizer.PLAINTEXT, - contextName); + tokenizer.setState(Tokenizer.PLAINTEXT); } else if ("script" == contextName) { - tokenizer.setStateAndEndTagExpectation( - Tokenizer.SCRIPT_DATA, contextName); + tokenizer.setState(Tokenizer.SCRIPT_DATA); } else { - tokenizer.setStateAndEndTagExpectation(Tokenizer.DATA, - contextName); + tokenizer.setState(Tokenizer.DATA); } } contextName = null; diff --git a/parser/html/nsHtml5Tokenizer.cpp b/parser/html/nsHtml5Tokenizer.cpp index e70c081bf..8aae5613a 100644 --- a/parser/html/nsHtml5Tokenizer.cpp +++ b/parser/html/nsHtml5Tokenizer.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2005-2007 Henri Sivonen * Copyright (c) 2007-2015 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * Portions of comments Copyright 2004-2010 Apple Computer, Inc., Mozilla * Foundation, and Opera Software ASA. * @@ -127,15 +128,11 @@ nsHtml5Tokenizer::isViewingXmlSource() } void -nsHtml5Tokenizer::setStateAndEndTagExpectation(int32_t specialTokenizerState, nsIAtom* endTagExpectation) +nsHtml5Tokenizer::setState(int32_t specialTokenizerState) { this->stateSave = specialTokenizerState; - if (specialTokenizerState == NS_HTML5TOKENIZER_DATA) { - return; - } - autoJArray asArray = nsHtml5Portability::newCharArrayFromLocal(endTagExpectation); - this->endTagExpectation = nsHtml5ElementName::elementNameByBuffer(asArray, 0, asArray.length, interner); - endTagExpectationToArray(); + this->endTagExpectation = nullptr; + this->endTagExpectationAsArray = nullptr; } void @@ -2040,7 +2037,13 @@ nsHtml5Tokenizer::stateLoop(int32_t state, char16_t c, int32_t pos, char16_t* bu NS_HTML5_BREAK(stateloop); } c = checkChar(buf, pos); - if (index < endTagExpectationAsArray.length) { + if (!endTagExpectationAsArray) { + tokenHandler->characters(nsHtml5Tokenizer::LT_SOLIDUS, 0, 2); + cstart = pos; + reconsume = true; + state = P::transition(mViewSource, returnState, reconsume, pos); + NS_HTML5_CONTINUE(stateloop); + } else if (index < endTagExpectationAsArray.length) { char16_t e = endTagExpectationAsArray[index]; char16_t folded = c; if (c >= 'A' && c <= 'Z') { diff --git a/parser/html/nsHtml5Tokenizer.h b/parser/html/nsHtml5Tokenizer.h index 00cca9a9c..11a9bab1b 100644 --- a/parser/html/nsHtml5Tokenizer.h +++ b/parser/html/nsHtml5Tokenizer.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2005-2007 Henri Sivonen * Copyright (c) 2007-2015 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * Portions of comments Copyright 2004-2010 Apple Computer, Inc., Mozilla * Foundation, and Opera Software ASA. * @@ -143,7 +144,7 @@ class nsHtml5Tokenizer void setInterner(nsHtml5AtomTable* interner); void initLocation(nsHtml5String newPublicId, nsHtml5String newSystemId); bool isViewingXmlSource(); - void setStateAndEndTagExpectation(int32_t specialTokenizerState, nsIAtom* endTagExpectation); + void setState(int32_t specialTokenizerState); void setStateAndEndTagExpectation(int32_t specialTokenizerState, nsHtml5ElementName* endTagExpectation); private: void endTagExpectationToArray(); diff --git a/parser/html/nsHtml5TreeBuilder.cpp b/parser/html/nsHtml5TreeBuilder.cpp index 457c7deb1..e5040d050 100644 --- a/parser/html/nsHtml5TreeBuilder.cpp +++ b/parser/html/nsHtml5TreeBuilder.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2007 Henri Sivonen * Copyright (c) 2007-2015 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * Portions of comments Copyright 2004-2008 Apple Computer, Inc., Mozilla * Foundation, and Opera Software ASA. * @@ -105,7 +106,7 @@ nsHtml5TreeBuilder::startTokenization(nsHtml5Tokenizer* self) nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elementName->camelCaseName, elt); currentPtr++; stack[currentPtr] = node; - tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_DATA, contextName); + tokenizer->setState(NS_HTML5TOKENIZER_DATA); mode = NS_HTML5TREE_BUILDER_FRAMESET_OK; } else if (contextNamespace == kNameSpaceID_MathML) { nsHtml5ElementName* elementName = nsHtml5ElementName::ELT_MATH; @@ -117,7 +118,7 @@ nsHtml5TreeBuilder::startTokenization(nsHtml5Tokenizer* self) nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt, elementName->name, false); currentPtr++; stack[currentPtr] = node; - tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_DATA, contextName); + tokenizer->setState(NS_HTML5TOKENIZER_DATA); mode = NS_HTML5TREE_BUILDER_FRAMESET_OK; } else { nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_HTML, elt); @@ -129,15 +130,20 @@ nsHtml5TreeBuilder::startTokenization(nsHtml5Tokenizer* self) resetTheInsertionMode(); formPointer = getFormPointerForContext(contextNode); if (nsHtml5Atoms::title == contextName || nsHtml5Atoms::textarea == contextName) { - tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_RCDATA, contextName); - } else if (nsHtml5Atoms::style == contextName || nsHtml5Atoms::xmp == contextName || nsHtml5Atoms::iframe == contextName || nsHtml5Atoms::noembed == contextName || nsHtml5Atoms::noframes == contextName || (scriptingEnabled && nsHtml5Atoms::noscript == contextName)) { - tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_RAWTEXT, contextName); + tokenizer->setState(NS_HTML5TOKENIZER_RCDATA); + } else if (nsHtml5Atoms::style == contextName || + nsHtml5Atoms::xmp == contextName || + nsHtml5Atoms::iframe == contextName || + nsHtml5Atoms::noembed == contextName || + nsHtml5Atoms::noframes == contextName || + (scriptingEnabled && nsHtml5Atoms::noscript == contextName)) { + tokenizer->setState(NS_HTML5TOKENIZER_RAWTEXT); } else if (nsHtml5Atoms::plaintext == contextName) { - tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_PLAINTEXT, contextName); + tokenizer->setState(NS_HTML5TOKENIZER_PLAINTEXT); } else if (nsHtml5Atoms::script == contextName) { - tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_SCRIPT_DATA, contextName); + tokenizer->setState(NS_HTML5TOKENIZER_SCRIPT_DATA); } else { - tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_DATA, contextName); + tokenizer->setState(NS_HTML5TOKENIZER_DATA); } } contextName = nullptr; -- cgit v1.2.3 From abc6b2f3aa14adac670da9132dc115f3b79f085d Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 4 Sep 2019 15:28:21 +0200 Subject: Belatedly fix html5 parser attribution for files not covered by the MPL. --- parser/html/nsHtml5ArrayCopy.h | 1 + parser/html/nsHtml5AttributeName.cpp | 1 + parser/html/nsHtml5AttributeName.h | 1 + parser/html/nsHtml5ElementName.cpp | 1 + parser/html/nsHtml5ElementName.h | 1 + parser/html/nsHtml5HtmlAttributes.cpp | 1 + parser/html/nsHtml5HtmlAttributes.h | 1 + parser/html/nsHtml5MetaScanner.cpp | 1 + parser/html/nsHtml5MetaScanner.h | 1 + parser/html/nsHtml5Portability.h | 1 + parser/html/nsHtml5StackNode.cpp | 1 + parser/html/nsHtml5StackNode.h | 1 + parser/html/nsHtml5StateSnapshot.cpp | 1 + parser/html/nsHtml5StateSnapshot.h | 1 + parser/html/nsHtml5TreeBuilder.h | 1 + parser/html/nsHtml5UTF16Buffer.cpp | 1 + parser/html/nsHtml5UTF16Buffer.h | 1 + 17 files changed, 17 insertions(+) diff --git a/parser/html/nsHtml5ArrayCopy.h b/parser/html/nsHtml5ArrayCopy.h index 594a801ab..5e2b37858 100644 --- a/parser/html/nsHtml5ArrayCopy.h +++ b/parser/html/nsHtml5ArrayCopy.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2008 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5AttributeName.cpp b/parser/html/nsHtml5AttributeName.cpp index dc546c111..8c7db5689 100644 --- a/parser/html/nsHtml5AttributeName.cpp +++ b/parser/html/nsHtml5AttributeName.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2008-2011 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5AttributeName.h b/parser/html/nsHtml5AttributeName.h index d0b93341b..9d00e2da0 100644 --- a/parser/html/nsHtml5AttributeName.h +++ b/parser/html/nsHtml5AttributeName.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2008-2011 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5ElementName.cpp b/parser/html/nsHtml5ElementName.cpp index fb523e7ef..95adda073 100644 --- a/parser/html/nsHtml5ElementName.cpp +++ b/parser/html/nsHtml5ElementName.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2008-2014 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5ElementName.h b/parser/html/nsHtml5ElementName.h index b5f0e4b9b..4aba0d3e1 100644 --- a/parser/html/nsHtml5ElementName.h +++ b/parser/html/nsHtml5ElementName.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2008-2014 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5HtmlAttributes.cpp b/parser/html/nsHtml5HtmlAttributes.cpp index 62b9ae2b2..132d33da1 100644 --- a/parser/html/nsHtml5HtmlAttributes.cpp +++ b/parser/html/nsHtml5HtmlAttributes.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2007 Henri Sivonen * Copyright (c) 2008-2011 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5HtmlAttributes.h b/parser/html/nsHtml5HtmlAttributes.h index 12149a0b5..ba3992788 100644 --- a/parser/html/nsHtml5HtmlAttributes.h +++ b/parser/html/nsHtml5HtmlAttributes.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2007 Henri Sivonen * Copyright (c) 2008-2011 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5MetaScanner.cpp b/parser/html/nsHtml5MetaScanner.cpp index 24f17b02b..3449026f1 100644 --- a/parser/html/nsHtml5MetaScanner.cpp +++ b/parser/html/nsHtml5MetaScanner.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2007 Henri Sivonen * Copyright (c) 2008-2015 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5MetaScanner.h b/parser/html/nsHtml5MetaScanner.h index a4d308147..08a771860 100644 --- a/parser/html/nsHtml5MetaScanner.h +++ b/parser/html/nsHtml5MetaScanner.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2007 Henri Sivonen * Copyright (c) 2008-2015 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5Portability.h b/parser/html/nsHtml5Portability.h index a3214dd2f..17004fb5d 100644 --- a/parser/html/nsHtml5Portability.h +++ b/parser/html/nsHtml5Portability.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2008-2015 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5StackNode.cpp b/parser/html/nsHtml5StackNode.cpp index 41163ae40..f38510f4c 100644 --- a/parser/html/nsHtml5StackNode.cpp +++ b/parser/html/nsHtml5StackNode.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2007 Henri Sivonen * Copyright (c) 2007-2011 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5StackNode.h b/parser/html/nsHtml5StackNode.h index 1677ec571..1f39cd0b5 100644 --- a/parser/html/nsHtml5StackNode.h +++ b/parser/html/nsHtml5StackNode.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2007 Henri Sivonen * Copyright (c) 2007-2011 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5StateSnapshot.cpp b/parser/html/nsHtml5StateSnapshot.cpp index 90780738b..2c2b0ec41 100644 --- a/parser/html/nsHtml5StateSnapshot.cpp +++ b/parser/html/nsHtml5StateSnapshot.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2009-2010 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5StateSnapshot.h b/parser/html/nsHtml5StateSnapshot.h index 119570499..3594c8cd6 100644 --- a/parser/html/nsHtml5StateSnapshot.h +++ b/parser/html/nsHtml5StateSnapshot.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2009-2010 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5TreeBuilder.h b/parser/html/nsHtml5TreeBuilder.h index 67f5010c5..15c9d2628 100644 --- a/parser/html/nsHtml5TreeBuilder.h +++ b/parser/html/nsHtml5TreeBuilder.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2007 Henri Sivonen * Copyright (c) 2007-2015 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * Portions of comments Copyright 2004-2008 Apple Computer, Inc., Mozilla * Foundation, and Opera Software ASA. * diff --git a/parser/html/nsHtml5UTF16Buffer.cpp b/parser/html/nsHtml5UTF16Buffer.cpp index 0d6870bc4..e6db76374 100644 --- a/parser/html/nsHtml5UTF16Buffer.cpp +++ b/parser/html/nsHtml5UTF16Buffer.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2008-2010 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), diff --git a/parser/html/nsHtml5UTF16Buffer.h b/parser/html/nsHtml5UTF16Buffer.h index c94245f74..cc38f6daf 100644 --- a/parser/html/nsHtml5UTF16Buffer.h +++ b/parser/html/nsHtml5UTF16Buffer.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2008-2010 Mozilla Foundation + * Copyright (c) 2019 Moonchild Productions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), -- cgit v1.2.3 From c6446f1126232935c85397aac493113dd38496cd Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 4 Sep 2019 16:33:37 +0200 Subject: Remove HTML5 parser java docs and generation code. java->C++ automated translation is not something we want to deal with now or in the future. --- parser/html/java/Makefile | 59 - parser/html/java/README.txt | 46 - parser/html/java/manifest.txt | 2 - parser/html/java/named-character-references.html | 7 - parser/html/javasrc/AttributeName.java | 2473 -------- parser/html/javasrc/ElementName.java | 1609 ----- parser/html/javasrc/HtmlAttributes.java | 618 -- parser/html/javasrc/MetaScanner.java | 854 --- parser/html/javasrc/Portability.java | 150 - parser/html/javasrc/README.txt | 6 - parser/html/javasrc/StackNode.java | 295 - parser/html/javasrc/StateSnapshot.java | 204 - parser/html/javasrc/Tokenizer.java | 7089 ---------------------- parser/html/javasrc/TreeBuilder.java | 6552 -------------------- parser/html/javasrc/UTF16Buffer.java | 151 - parser/html/nsHtml5AttributeName.cpp | 5 - parser/html/nsHtml5AttributeName.h | 5 - parser/html/nsHtml5ElementName.cpp | 5 - parser/html/nsHtml5ElementName.h | 5 - parser/html/nsHtml5HtmlAttributes.cpp | 5 - parser/html/nsHtml5HtmlAttributes.h | 5 - parser/html/nsHtml5MetaScanner.cpp | 5 - parser/html/nsHtml5MetaScanner.h | 5 - parser/html/nsHtml5Portability.h | 5 - parser/html/nsHtml5StackNode.cpp | 5 - parser/html/nsHtml5StackNode.h | 5 - parser/html/nsHtml5StateSnapshot.cpp | 5 - parser/html/nsHtml5StateSnapshot.h | 5 - parser/html/nsHtml5Tokenizer.cpp | 5 - parser/html/nsHtml5Tokenizer.h | 5 - parser/html/nsHtml5TreeBuilder.cpp | 5 - parser/html/nsHtml5TreeBuilder.h | 5 - parser/html/nsHtml5UTF16Buffer.cpp | 5 - parser/html/nsHtml5UTF16Buffer.h | 5 - 34 files changed, 20210 deletions(-) delete mode 100644 parser/html/java/Makefile delete mode 100644 parser/html/java/README.txt delete mode 100644 parser/html/java/manifest.txt delete mode 100644 parser/html/java/named-character-references.html delete mode 100644 parser/html/javasrc/AttributeName.java delete mode 100644 parser/html/javasrc/ElementName.java delete mode 100644 parser/html/javasrc/HtmlAttributes.java delete mode 100644 parser/html/javasrc/MetaScanner.java delete mode 100644 parser/html/javasrc/Portability.java delete mode 100644 parser/html/javasrc/README.txt delete mode 100644 parser/html/javasrc/StackNode.java delete mode 100644 parser/html/javasrc/StateSnapshot.java delete mode 100644 parser/html/javasrc/Tokenizer.java delete mode 100644 parser/html/javasrc/TreeBuilder.java delete mode 100644 parser/html/javasrc/UTF16Buffer.java diff --git a/parser/html/java/Makefile b/parser/html/java/Makefile deleted file mode 100644 index b43523e03..000000000 --- a/parser/html/java/Makefile +++ /dev/null @@ -1,59 +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/. - -libs:: translator - -translator:: javaparser \ -; mkdir -p htmlparser/bin && \ - find htmlparser/translator-src/nu/validator/htmlparser -name "*.java" | \ - xargs javac -cp javaparser.jar -g -d htmlparser/bin && \ - jar cfm translator.jar manifest.txt -C htmlparser/bin . - -javaparser:: \ -; mkdir -p javaparser/bin && \ - find javaparser/src -name "*.java" | \ - xargs javac -encoding ISO-8859-1 -g -d javaparser/bin && \ - jar cf javaparser.jar -C javaparser/bin . - -sync_javaparser:: \ -; if [ ! -d javaparser/.git ] ; \ - then rm -rf javaparser ; \ - git clone https://github.com/javaparser/javaparser.git ; \ - fi ; \ - cd javaparser ; git checkout javaparser-1.0.6 ; cd .. - -sync_htmlparser:: \ -; if [ -d htmlparser/.hg ] ; \ - then cd htmlparser ; hg pull --rebase ; cd .. ; \ - else \ - rm -rf htmlparser ; \ - hg clone https://hg.mozilla.org/projects/htmlparser ; \ - fi - -sync:: sync_javaparser sync_htmlparser - -translate:: translator \ -; mkdir -p ../javasrc ; \ - java -jar translator.jar \ - htmlparser/src/nu/validator/htmlparser/impl \ - .. ../nsHtml5AtomList.h - -translate_from_snapshot:: translator \ -; mkdir -p ../javasrc ; \ - java -jar translator.jar \ - ../javasrc \ - .. ../nsHtml5AtomList.h - -named_characters:: translator \ -; java -cp translator.jar \ - nu.validator.htmlparser.generator.GenerateNamedCharactersCpp \ - named-character-references.html ../ - -clean_javaparser:: \ -; rm -rf javaparser/bin javaparser.jar - -clean_htmlparser:: \ -; rm -rf htmlparser/bin translator.jar - -clean:: clean_javaparser clean_htmlparser diff --git a/parser/html/java/README.txt b/parser/html/java/README.txt deleted file mode 100644 index df1bfcd4c..000000000 --- a/parser/html/java/README.txt +++ /dev/null @@ -1,46 +0,0 @@ -If this is your first time building the HTML5 parser, you need to execute the -following commands (from this directory) to bootstrap the translation: - - make sync # fetch remote source files and licenses - make translate # perform the Java-to-C++ translation from the remote - # sources - make named_characters # Generate tables for named character tokenization - -If you make changes to the translator or the javaparser, you can rebuild by -retyping 'make' in this directory. If you make changes to the HTML5 Java -implementation, you can retranslate the Java sources from the htmlparser -repository by retyping 'make translate' in this directory. - -The makefile supports the following targets: - -sync_htmlparser: - Retrieves the HTML parser and Java to C++ translator sources from Mozilla's - htmlparser repository. -sync_javaparser: - Retrieves the javaparser sources from GitHub. -sync: - Runs both sync_javaparser and sync_htmlparser. -javaparser: - Builds the javaparser library retrieved earlier by sync_javaparser. -translator: - Runs the javaparser target and then builds the Java to C++ translator from - sources retrieved earlier by sync_htmlparser. -libs: - The default target. Alias for translator -translate: - Runs the translator target and then translates the HTML parser sources - retrieved by sync_htmlparser copying the Java sources to ../javasrc. -translate_from_snapshot: - Runs the translator target and then translates the HTML parser sources - stored in ../javasrc. -named_characters: - Generates data tables for named character tokenization. -clean_javaparser: - Removes the build products of the javaparser target. -clean_htmlparser: - Removes the build products of the translator target. -clean: - Runs both clean_javaparser and clean_htmlparser. - -Ben Newman (23 September 2009) -Henri Sivonen (11 August 2016) diff --git a/parser/html/java/manifest.txt b/parser/html/java/manifest.txt deleted file mode 100644 index 14cd9d081..000000000 --- a/parser/html/java/manifest.txt +++ /dev/null @@ -1,2 +0,0 @@ -Main-Class: nu.validator.htmlparser.cpptranslate.Main -Class-Path: javaparser.jar diff --git a/parser/html/java/named-character-references.html b/parser/html/java/named-character-references.html deleted file mode 100644 index c8d1e08da..000000000 --- a/parser/html/java/named-character-references.html +++ /dev/null @@ -1,7 +0,0 @@ - - -
Name Character
AElig; U+000C6
AElig U+000C6
AMP; U+00026
AMP U+00026
Aacute; U+000C1
Aacute U+000C1
Abreve; U+00102
Acirc; U+000C2
Acirc U+000C2
Acy; U+00410
Afr; U+1D504
Agrave; U+000C0
Agrave U+000C0
Alpha; U+00391
Amacr; U+00100
And; U+02A53
Aogon; U+00104
Aopf; U+1D538
ApplyFunction; U+02061
Aring; U+000C5
Aring U+000C5
Ascr; U+1D49C
Assign; U+02254
Atilde; U+000C3
Atilde U+000C3
Auml; U+000C4
Auml U+000C4
Backslash; U+02216
Barv; U+02AE7
Barwed; U+02306
Bcy; U+00411
Because; U+02235
Bernoullis; U+0212C
Beta; U+00392
Bfr; U+1D505
Bopf; U+1D539
Breve; U+002D8
Bscr; U+0212C
Bumpeq; U+0224E
CHcy; U+00427
COPY; U+000A9
COPY U+000A9
Cacute; U+00106
Cap; U+022D2
CapitalDifferentialD; U+02145
Cayleys; U+0212D
Ccaron; U+0010C
Ccedil; U+000C7
Ccedil U+000C7
Ccirc; U+00108
Cconint; U+02230
Cdot; U+0010A
Cedilla; U+000B8
CenterDot; U+000B7
Cfr; U+0212D
Chi; U+003A7
CircleDot; U+02299
CircleMinus; U+02296
CirclePlus; U+02295
CircleTimes; U+02297
ClockwiseContourIntegral; U+02232
CloseCurlyDoubleQuote; U+0201D
CloseCurlyQuote; U+02019
Colon; U+02237
Colone; U+02A74
Congruent; U+02261
Conint; U+0222F
ContourIntegral; U+0222E
Copf; U+02102
Coproduct; U+02210
CounterClockwiseContourIntegral; U+02233
Cross; U+02A2F
Cscr; U+1D49E
Cup; U+022D3
CupCap; U+0224D
DD; U+02145
DDotrahd; U+02911
DJcy; U+00402
DScy; U+00405
DZcy; U+0040F
Dagger; U+02021
Darr; U+021A1
Dashv; U+02AE4
Dcaron; U+0010E
Dcy; U+00414
Del; U+02207
Delta; U+00394
Dfr; U+1D507
DiacriticalAcute; U+000B4
DiacriticalDot; U+002D9
DiacriticalDoubleAcute; U+002DD
DiacriticalGrave; U+00060
DiacriticalTilde; U+002DC
Diamond; U+022C4
DifferentialD; U+02146
Dopf; U+1D53B
Dot; U+000A8
DotDot; U+020DC
DotEqual; U+02250
DoubleContourIntegral; U+0222F
DoubleDot; U+000A8
DoubleDownArrow; U+021D3
DoubleLeftArrow; U+021D0
DoubleLeftRightArrow; U+021D4
DoubleLeftTee; U+02AE4
DoubleLongLeftArrow; U+027F8
DoubleLongLeftRightArrow; U+027FA
DoubleLongRightArrow; U+027F9
DoubleRightArrow; U+021D2
DoubleRightTee; U+022A8
DoubleUpArrow; U+021D1
DoubleUpDownArrow; U+021D5
DoubleVerticalBar; U+02225
DownArrow; U+02193
DownArrowBar; U+02913
DownArrowUpArrow; U+021F5
DownBreve; U+00311
DownLeftRightVector; U+02950
DownLeftTeeVector; U+0295E
DownLeftVector; U+021BD
DownLeftVectorBar; U+02956
DownRightTeeVector; U+0295F
DownRightVector; U+021C1
DownRightVectorBar; U+02957
DownTee; U+022A4
DownTeeArrow; U+021A7
Downarrow; U+021D3
Dscr; U+1D49F
Dstrok; U+00110
ENG; U+0014A
ETH; U+000D0
ETH U+000D0
Eacute; U+000C9
Eacute U+000C9
Ecaron; U+0011A
Ecirc; U+000CA
Ecirc U+000CA
Ecy; U+0042D
Edot; U+00116
Efr; U+1D508
Egrave; U+000C8
Egrave U+000C8
Element; U+02208
Emacr; U+00112
EmptySmallSquare; U+025FB
EmptyVerySmallSquare; U+025AB
Eogon; U+00118
Eopf; U+1D53C
Epsilon; U+00395
Equal; U+02A75
EqualTilde; U+02242
Equilibrium; U+021CC
Escr; U+02130
Esim; U+02A73
Eta; U+00397
Euml; U+000CB
Euml U+000CB
Exists; U+02203
ExponentialE; U+02147
Fcy; U+00424
Ffr; U+1D509
FilledSmallSquare; U+025FC
FilledVerySmallSquare; U+025AA
Fopf; U+1D53D
ForAll; U+02200
Fouriertrf; U+02131
Fscr; U+02131
GJcy; U+00403
GT; U+0003E
GT U+0003E
Gamma; U+00393
Gammad; U+003DC
Gbreve; U+0011E
Gcedil; U+00122
Gcirc; U+0011C
Gcy; U+00413
Gdot; U+00120
Gfr; U+1D50A
Gg; U+022D9
Gopf; U+1D53E
GreaterEqual; U+02265
GreaterEqualLess; U+022DB
GreaterFullEqual; U+02267
GreaterGreater; U+02AA2
GreaterLess; U+02277
GreaterSlantEqual; U+02A7E
GreaterTilde; U+02273
Gscr; U+1D4A2
Gt; U+0226B
HARDcy; U+0042A
Hacek; U+002C7
Hat; U+0005E
Hcirc; U+00124
Hfr; U+0210C
HilbertSpace; U+0210B
Hopf; U+0210D
HorizontalLine; U+02500
Hscr; U+0210B
Hstrok; U+00126
HumpDownHump; U+0224E
HumpEqual; U+0224F
IEcy; U+00415
IJlig; U+00132
IOcy; U+00401
Iacute; U+000CD
Iacute U+000CD
Icirc; U+000CE
Icirc U+000CE
Icy; U+00418
Idot; U+00130
Ifr; U+02111
Igrave; U+000CC
Igrave U+000CC
Im; U+02111
Imacr; U+0012A
ImaginaryI; U+02148
Implies; U+021D2
Int; U+0222C
Integral; U+0222B
Intersection; U+022C2
InvisibleComma; U+02063
InvisibleTimes; U+02062
Iogon; U+0012E
Iopf; U+1D540
Iota; U+00399
Iscr; U+02110
Itilde; U+00128
Iukcy; U+00406
Iuml; U+000CF
Iuml U+000CF
Jcirc; U+00134
Jcy; U+00419
Jfr; U+1D50D
Jopf; U+1D541
Jscr; U+1D4A5
Jsercy; U+00408
Jukcy; U+00404
KHcy; U+00425
KJcy; U+0040C
Kappa; U+0039A
Kcedil; U+00136
Kcy; U+0041A
Kfr; U+1D50E
Kopf; U+1D542
Kscr; U+1D4A6
LJcy; U+00409
LT; U+0003C
LT U+0003C
Lacute; U+00139
Lambda; U+0039B
Lang; U+027EA
Laplacetrf; U+02112
Larr; U+0219E
Lcaron; U+0013D
Lcedil; U+0013B
Lcy; U+0041B
LeftAngleBracket; U+027E8
LeftArrow; U+02190
LeftArrowBar; U+021E4
LeftArrowRightArrow; U+021C6
LeftCeiling; U+02308
LeftDoubleBracket; U+027E6
LeftDownTeeVector; U+02961
LeftDownVector; U+021C3
LeftDownVectorBar; U+02959
LeftFloor; U+0230A
LeftRightArrow; U+02194
LeftRightVector; U+0294E
LeftTee; U+022A3
LeftTeeArrow; U+021A4
LeftTeeVector; U+0295A
LeftTriangle; U+022B2
LeftTriangleBar; U+029CF
LeftTriangleEqual; U+022B4
LeftUpDownVector; U+02951
LeftUpTeeVector; U+02960
LeftUpVector; U+021BF
LeftUpVectorBar; U+02958
LeftVector; U+021BC
LeftVectorBar; U+02952
Leftarrow; U+021D0
Leftrightarrow; U+021D4
LessEqualGreater; U+022DA
LessFullEqual; U+02266
LessGreater; U+02276
LessLess; U+02AA1
LessSlantEqual; U+02A7D
LessTilde; U+02272
Lfr; U+1D50F
Ll; U+022D8
Lleftarrow; U+021DA
Lmidot; U+0013F
LongLeftArrow; U+027F5
LongLeftRightArrow; U+027F7
LongRightArrow; U+027F6
Longleftarrow; U+027F8
Longleftrightarrow; U+027FA
Longrightarrow; U+027F9
Lopf; U+1D543
LowerLeftArrow; U+02199
LowerRightArrow; U+02198
Lscr; U+02112
Lsh; U+021B0
Lstrok; U+00141
Lt; U+0226A
Map; U+02905
Mcy; U+0041C
MediumSpace; U+0205F
Mellintrf; U+02133
Mfr; U+1D510
MinusPlus; U+02213
Mopf; U+1D544
Mscr; U+02133
Mu; U+0039C
NJcy; U+0040A
Nacute; U+00143
Ncaron; U+00147
Ncedil; U+00145
Ncy; U+0041D
NegativeMediumSpace; U+0200B
NegativeThickSpace; U+0200B
NegativeThinSpace; U+0200B
NegativeVeryThinSpace; U+0200B
NestedGreaterGreater; U+0226B
NestedLessLess; U+0226A
NewLine; U+0000A
Nfr; U+1D511
NoBreak; U+02060
NonBreakingSpace; U+000A0
Nopf; U+02115
Not; U+02AEC
NotCongruent; U+02262
NotCupCap; U+0226D
NotDoubleVerticalBar; U+02226
NotElement; U+02209
NotEqual; U+02260
NotExists; U+02204
NotGreater; U+0226F
NotGreaterEqual; U+02271
NotGreaterLess; U+02279
NotGreaterTilde; U+02275
NotLeftTriangle; U+022EA
NotLeftTriangleEqual; U+022EC
NotLess; U+0226E
NotLessEqual; U+02270
NotLessGreater; U+02278
NotLessTilde; U+02274
NotPrecedes; U+02280
NotPrecedesSlantEqual; U+022E0
NotReverseElement; U+0220C
NotRightTriangle; U+022EB
NotRightTriangleEqual; U+022ED
NotSquareSubsetEqual; U+022E2
NotSquareSupersetEqual; U+022E3
NotSubsetEqual; U+02288
NotSucceeds; U+02281
NotSucceedsSlantEqual; U+022E1
NotSupersetEqual; U+02289
NotTilde; U+02241
NotTildeEqual; U+02244
NotTildeFullEqual; U+02247
NotTildeTilde; U+02249
NotVerticalBar; U+02224
Nscr; U+1D4A9
Ntilde; U+000D1
Ntilde U+000D1
Nu; U+0039D
OElig; U+00152
Oacute; U+000D3
Oacute U+000D3
Ocirc; U+000D4
Ocirc U+000D4
Ocy; U+0041E
Odblac; U+00150
Ofr; U+1D512
Ograve; U+000D2
Ograve U+000D2
Omacr; U+0014C
Omega; U+003A9
Omicron; U+0039F
Oopf; U+1D546
OpenCurlyDoubleQuote; U+0201C
OpenCurlyQuote; U+02018
Or; U+02A54
Oscr; U+1D4AA
Oslash; U+000D8
Oslash U+000D8
Otilde; U+000D5
Otilde U+000D5
Otimes; U+02A37
Ouml; U+000D6
Ouml U+000D6
OverBar; U+0203E
OverBrace; U+023DE
OverBracket; U+023B4
OverParenthesis; U+023DC
PartialD; U+02202
Pcy; U+0041F
Pfr; U+1D513
Phi; U+003A6
Pi; U+003A0
PlusMinus; U+000B1
Poincareplane; U+0210C
Popf; U+02119
Pr; U+02ABB
Precedes; U+0227A
PrecedesEqual; U+02AAF
PrecedesSlantEqual; U+0227C
PrecedesTilde; U+0227E
Prime; U+02033
Product; U+0220F
Proportion; U+02237
Proportional; U+0221D
Pscr; U+1D4AB
Psi; U+003A8
QUOT; U+00022
QUOT U+00022
Qfr; U+1D514
Qopf; U+0211A
Qscr; U+1D4AC
RBarr; U+02910
REG; U+000AE
REG U+000AE
Racute; U+00154
Rang; U+027EB
Rarr; U+021A0
Rarrtl; U+02916
Rcaron; U+00158
Rcedil; U+00156
Rcy; U+00420
Re; U+0211C
ReverseElement; U+0220B
ReverseEquilibrium; U+021CB
ReverseUpEquilibrium; U+0296F
Rfr; U+0211C
Rho; U+003A1
RightAngleBracket; U+027E9
RightArrow; U+02192
RightArrowBar; U+021E5
RightArrowLeftArrow; U+021C4
RightCeiling; U+02309
RightDoubleBracket; U+027E7
RightDownTeeVector; U+0295D
RightDownVector; U+021C2
RightDownVectorBar; U+02955
RightFloor; U+0230B
RightTee; U+022A2
RightTeeArrow; U+021A6
RightTeeVector; U+0295B
RightTriangle; U+022B3
RightTriangleBar; U+029D0
RightTriangleEqual; U+022B5
RightUpDownVector; U+0294F
RightUpTeeVector; U+0295C
RightUpVector; U+021BE
RightUpVectorBar; U+02954
RightVector; U+021C0
RightVectorBar; U+02953
Rightarrow; U+021D2
Ropf; U+0211D
RoundImplies; U+02970
Rrightarrow; U+021DB
Rscr; U+0211B
Rsh; U+021B1
RuleDelayed; U+029F4
SHCHcy; U+00429
SHcy; U+00428
SOFTcy; U+0042C
Sacute; U+0015A
Sc; U+02ABC
Scaron; U+00160
Scedil; U+0015E
Scirc; U+0015C
Scy; U+00421
Sfr; U+1D516
ShortDownArrow; U+02193
ShortLeftArrow; U+02190
ShortRightArrow; U+02192
ShortUpArrow; U+02191
Sigma; U+003A3
SmallCircle; U+02218
Sopf; U+1D54A
Sqrt; U+0221A
Square; U+025A1
SquareIntersection; U+02293
SquareSubset; U+0228F
SquareSubsetEqual; U+02291
SquareSuperset; U+02290
SquareSupersetEqual; U+02292
SquareUnion; U+02294
Sscr; U+1D4AE
Star; U+022C6
Sub; U+022D0
Subset; U+022D0
SubsetEqual; U+02286
Succeeds; U+0227B
SucceedsEqual; U+02AB0
SucceedsSlantEqual; U+0227D
SucceedsTilde; U+0227F
SuchThat; U+0220B
Sum; U+02211
Sup; U+022D1
Superset; U+02283
SupersetEqual; U+02287
Supset; U+022D1
THORN; U+000DE
THORN U+000DE
TRADE; U+02122
TSHcy; U+0040B
TScy; U+00426
Tab; U+00009
Tau; U+003A4
Tcaron; U+00164
Tcedil; U+00162
Tcy; U+00422
Tfr; U+1D517
Therefore; U+02234
Theta; U+00398
ThinSpace; U+02009
Tilde; U+0223C
TildeEqual; U+02243
TildeFullEqual; U+02245
TildeTilde; U+02248
Topf; U+1D54B
TripleDot; U+020DB
Tscr; U+1D4AF
Tstrok; U+00166
Uacute; U+000DA
Uacute U+000DA
Uarr; U+0219F
Uarrocir; U+02949
Ubrcy; U+0040E
Ubreve; U+0016C
Ucirc; U+000DB
Ucirc U+000DB
Ucy; U+00423
Udblac; U+00170
Ufr; U+1D518
Ugrave; U+000D9
Ugrave U+000D9
Umacr; U+0016A
UnderBar; U+0005F
UnderBrace; U+023DF
UnderBracket; U+023B5
UnderParenthesis; U+023DD
Union; U+022C3
UnionPlus; U+0228E
Uogon; U+00172
Uopf; U+1D54C
UpArrow; U+02191
UpArrowBar; U+02912
UpArrowDownArrow; U+021C5
UpDownArrow; U+02195
UpEquilibrium; U+0296E
UpTee; U+022A5
UpTeeArrow; U+021A5
Uparrow; U+021D1
Updownarrow; U+021D5
UpperLeftArrow; U+02196
UpperRightArrow; U+02197
Upsi; U+003D2
Upsilon; U+003A5
Uring; U+0016E
Uscr; U+1D4B0
Utilde; U+00168
Uuml; U+000DC
Uuml U+000DC
VDash; U+022AB
Vbar; U+02AEB
Vcy; U+00412
Vdash; U+022A9
Vdashl; U+02AE6
Vee; U+022C1
Verbar; U+02016
Vert; U+02016
VerticalBar; U+02223
VerticalLine; U+0007C
VerticalSeparator; U+02758
VerticalTilde; U+02240
VeryThinSpace; U+0200A
Vfr; U+1D519
Vopf; U+1D54D
Vscr; U+1D4B1
Vvdash; U+022AA
Wcirc; U+00174
Wedge; U+022C0
Wfr; U+1D51A
Wopf; U+1D54E
Wscr; U+1D4B2
Xfr; U+1D51B
Xi; U+0039E
Xopf; U+1D54F
Xscr; U+1D4B3
YAcy; U+0042F
YIcy; U+00407
YUcy; U+0042E
Yacute; U+000DD
Yacute U+000DD
Ycirc; U+00176
Ycy; U+0042B
Yfr; U+1D51C
Yopf; U+1D550
Yscr; U+1D4B4
Yuml; U+00178
ZHcy; U+00416
Zacute; U+00179
Zcaron; U+0017D
Zcy; U+00417
Zdot; U+0017B
ZeroWidthSpace; U+0200B
Zeta; U+00396
Zfr; U+02128
Zopf; U+02124
Zscr; U+1D4B5
aacute; U+000E1
aacute U+000E1
abreve; U+00103
ac; U+0223E
acd; U+0223F
acirc; U+000E2
acirc U+000E2
acute; U+000B4
acute U+000B4
acy; U+00430
aelig; U+000E6
aelig U+000E6
af; U+02061
afr; U+1D51E
agrave; U+000E0
agrave U+000E0
alefsym; U+02135
aleph; U+02135
alpha; U+003B1
amacr; U+00101
amalg; U+02A3F
amp; U+00026
amp U+00026
and; U+02227
andand; U+02A55
andd; U+02A5C
andslope; U+02A58
andv; U+02A5A
ang; U+02220
ange; U+029A4
angle; U+02220
angmsd; U+02221
angmsdaa; U+029A8
angmsdab; U+029A9
angmsdac; U+029AA
angmsdad; U+029AB
angmsdae; U+029AC
angmsdaf; U+029AD
angmsdag; U+029AE
angmsdah; U+029AF
angrt; U+0221F
angrtvb; U+022BE
angrtvbd; U+0299D
angsph; U+02222
angst; U+000C5
angzarr; U+0237C
aogon; U+00105
aopf; U+1D552
ap; U+02248
apE; U+02A70
apacir; U+02A6F
ape; U+0224A
apid; U+0224B
apos; U+00027
approx; U+02248
approxeq; U+0224A
aring; U+000E5
aring U+000E5
ascr; U+1D4B6
ast; U+0002A
asymp; U+02248
asympeq; U+0224D
atilde; U+000E3
atilde U+000E3
auml; U+000E4
auml U+000E4
awconint; U+02233
awint; U+02A11
bNot; U+02AED
backcong; U+0224C
backepsilon; U+003F6
backprime; U+02035
backsim; U+0223D
backsimeq; U+022CD
barvee; U+022BD
barwed; U+02305
barwedge; U+02305
bbrk; U+023B5
bbrktbrk; U+023B6
bcong; U+0224C
bcy; U+00431
bdquo; U+0201E
becaus; U+02235
because; U+02235
bemptyv; U+029B0
bepsi; U+003F6
bernou; U+0212C
beta; U+003B2
beth; U+02136
between; U+0226C
bfr; U+1D51F
bigcap; U+022C2
bigcirc; U+025EF
bigcup; U+022C3
bigodot; U+02A00
bigoplus; U+02A01
bigotimes; U+02A02
bigsqcup; U+02A06
bigstar; U+02605
bigtriangledown; U+025BD
bigtriangleup; U+025B3
biguplus; U+02A04
bigvee; U+022C1
bigwedge; U+022C0
bkarow; U+0290D
blacklozenge; U+029EB
blacksquare; U+025AA
blacktriangle; U+025B4
blacktriangledown; U+025BE
blacktriangleleft; U+025C2
blacktriangleright; U+025B8
blank; U+02423
blk12; U+02592
blk14; U+02591
blk34; U+02593
block; U+02588
bnot; U+02310
bopf; U+1D553
bot; U+022A5
bottom; U+022A5
bowtie; U+022C8
boxDL; U+02557
boxDR; U+02554
boxDl; U+02556
boxDr; U+02553
boxH; U+02550
boxHD; U+02566
boxHU; U+02569
boxHd; U+02564
boxHu; U+02567
boxUL; U+0255D
boxUR; U+0255A
boxUl; U+0255C
boxUr; U+02559
boxV; U+02551
boxVH; U+0256C
boxVL; U+02563
boxVR; U+02560
boxVh; U+0256B
boxVl; U+02562
boxVr; U+0255F
boxbox; U+029C9
boxdL; U+02555
boxdR; U+02552
boxdl; U+02510
boxdr; U+0250C
boxh; U+02500
boxhD; U+02565
boxhU; U+02568
boxhd; U+0252C
boxhu; U+02534
boxminus; U+0229F
boxplus; U+0229E
boxtimes; U+022A0
boxuL; U+0255B
boxuR; U+02558
boxul; U+02518
boxur; U+02514
boxv; U+02502
boxvH; U+0256A
boxvL; U+02561
boxvR; U+0255E
boxvh; U+0253C
boxvl; U+02524
boxvr; U+0251C
bprime; U+02035
breve; U+002D8
brvbar; U+000A6
brvbar U+000A6
bscr; U+1D4B7
bsemi; U+0204F
bsim; U+0223D
bsime; U+022CD
bsol; U+0005C
bsolb; U+029C5
bsolhsub; U+027C8
bull; U+02022
bullet; U+02022
bump; U+0224E
bumpE; U+02AAE
bumpe; U+0224F
bumpeq; U+0224F
cacute; U+00107
cap; U+02229
capand; U+02A44
capbrcup; U+02A49
capcap; U+02A4B
capcup; U+02A47
capdot; U+02A40
caret; U+02041
caron; U+002C7
ccaps; U+02A4D
ccaron; U+0010D
ccedil; U+000E7
ccedil U+000E7
ccirc; U+00109
ccups; U+02A4C
ccupssm; U+02A50
cdot; U+0010B
cedil; U+000B8
cedil U+000B8
cemptyv; U+029B2
cent; U+000A2
cent U+000A2
centerdot; U+000B7
cfr; U+1D520
chcy; U+00447
check; U+02713
checkmark; U+02713
chi; U+003C7
cir; U+025CB
cirE; U+029C3
circ; U+002C6
circeq; U+02257
circlearrowleft; U+021BA
circlearrowright; U+021BB
circledR; U+000AE
circledS; U+024C8
circledast; U+0229B
circledcirc; U+0229A
circleddash; U+0229D
cire; U+02257
cirfnint; U+02A10
cirmid; U+02AEF
cirscir; U+029C2
clubs; U+02663
clubsuit; U+02663
colon; U+0003A
colone; U+02254
coloneq; U+02254
comma; U+0002C
commat; U+00040
comp; U+02201
compfn; U+02218
complement; U+02201
complexes; U+02102
cong; U+02245
congdot; U+02A6D
conint; U+0222E
copf; U+1D554
coprod; U+02210
copy; U+000A9
copy U+000A9
copysr; U+02117
crarr; U+021B5
cross; U+02717
cscr; U+1D4B8
csub; U+02ACF
csube; U+02AD1
csup; U+02AD0
csupe; U+02AD2
ctdot; U+022EF
cudarrl; U+02938
cudarrr; U+02935
cuepr; U+022DE
cuesc; U+022DF
cularr; U+021B6
cularrp; U+0293D
cup; U+0222A
cupbrcap; U+02A48
cupcap; U+02A46
cupcup; U+02A4A
cupdot; U+0228D
cupor; U+02A45
curarr; U+021B7
curarrm; U+0293C
curlyeqprec; U+022DE
curlyeqsucc; U+022DF
curlyvee; U+022CE
curlywedge; U+022CF
curren; U+000A4
curren U+000A4
curvearrowleft; U+021B6
curvearrowright; U+021B7
cuvee; U+022CE
cuwed; U+022CF
cwconint; U+02232
cwint; U+02231
cylcty; U+0232D
dArr; U+021D3
dHar; U+02965
dagger; U+02020
daleth; U+02138
darr; U+02193
dash; U+02010
dashv; U+022A3
dbkarow; U+0290F
dblac; U+002DD
dcaron; U+0010F
dcy; U+00434
dd; U+02146
ddagger; U+02021
ddarr; U+021CA
ddotseq; U+02A77
deg; U+000B0
deg U+000B0
delta; U+003B4
demptyv; U+029B1
dfisht; U+0297F
dfr; U+1D521
dharl; U+021C3
dharr; U+021C2
diam; U+022C4
diamond; U+022C4
diamondsuit; U+02666
diams; U+02666
die; U+000A8
digamma; U+003DD
disin; U+022F2
div; U+000F7
divide; U+000F7
divide U+000F7
divideontimes; U+022C7
divonx; U+022C7
djcy; U+00452
dlcorn; U+0231E
dlcrop; U+0230D
dollar; U+00024
dopf; U+1D555
dot; U+002D9
doteq; U+02250
doteqdot; U+02251
dotminus; U+02238
dotplus; U+02214
dotsquare; U+022A1
doublebarwedge; U+02306
downarrow; U+02193
downdownarrows; U+021CA
downharpoonleft; U+021C3
downharpoonright; U+021C2
drbkarow; U+02910
drcorn; U+0231F
drcrop; U+0230C
dscr; U+1D4B9
dscy; U+00455
dsol; U+029F6
dstrok; U+00111
dtdot; U+022F1
dtri; U+025BF
dtrif; U+025BE
duarr; U+021F5
duhar; U+0296F
dwangle; U+029A6
dzcy; U+0045F
dzigrarr; U+027FF
eDDot; U+02A77
eDot; U+02251
eacute; U+000E9
eacute U+000E9
easter; U+02A6E
ecaron; U+0011B
ecir; U+02256
ecirc; U+000EA
ecirc U+000EA
ecolon; U+02255
ecy; U+0044D
edot; U+00117
ee; U+02147
efDot; U+02252
efr; U+1D522
eg; U+02A9A
egrave; U+000E8
egrave U+000E8
egs; U+02A96
egsdot; U+02A98
el; U+02A99
elinters; U+023E7
ell; U+02113
els; U+02A95
elsdot; U+02A97
emacr; U+00113
empty; U+02205
emptyset; U+02205
emptyv; U+02205
emsp13; U+02004
emsp14; U+02005
emsp; U+02003
eng; U+0014B
ensp; U+02002
eogon; U+00119
eopf; U+1D556
epar; U+022D5
eparsl; U+029E3
eplus; U+02A71
epsi; U+003B5
epsilon; U+003B5
epsiv; U+003F5
eqcirc; U+02256
eqcolon; U+02255
eqsim; U+02242
eqslantgtr; U+02A96
eqslantless; U+02A95
equals; U+0003D
equest; U+0225F
equiv; U+02261
equivDD; U+02A78
eqvparsl; U+029E5
erDot; U+02253
erarr; U+02971
escr; U+0212F
esdot; U+02250
esim; U+02242
eta; U+003B7
eth; U+000F0
eth U+000F0
euml; U+000EB
euml U+000EB
euro; U+020AC
excl; U+00021
exist; U+02203
expectation; U+02130
exponentiale; U+02147
fallingdotseq; U+02252
fcy; U+00444
female; U+02640
ffilig; U+0FB03
fflig; U+0FB00
ffllig; U+0FB04
ffr; U+1D523
filig; U+0FB01
flat; U+0266D
fllig; U+0FB02
fltns; U+025B1
fnof; U+00192
fopf; U+1D557
forall; U+02200
fork; U+022D4
forkv; U+02AD9
fpartint; U+02A0D
frac12; U+000BD
frac12 U+000BD
frac13; U+02153
frac14; U+000BC
frac14 U+000BC
frac15; U+02155
frac16; U+02159
frac18; U+0215B
frac23; U+02154
frac25; U+02156
frac34; U+000BE
frac34 U+000BE
frac35; U+02157
frac38; U+0215C
frac45; U+02158
frac56; U+0215A
frac58; U+0215D
frac78; U+0215E
frasl; U+02044
frown; U+02322
fscr; U+1D4BB
gE; U+02267
gEl; U+02A8C
gacute; U+001F5
gamma; U+003B3
gammad; U+003DD
gap; U+02A86
gbreve; U+0011F
gcirc; U+0011D
gcy; U+00433
gdot; U+00121
ge; U+02265
gel; U+022DB
geq; U+02265
geqq; U+02267
geqslant; U+02A7E
ges; U+02A7E
gescc; U+02AA9
gesdot; U+02A80
gesdoto; U+02A82
gesdotol; U+02A84
gesles; U+02A94
gfr; U+1D524
gg; U+0226B
ggg; U+022D9
gimel; U+02137
gjcy; U+00453
gl; U+02277
glE; U+02A92
gla; U+02AA5
glj; U+02AA4
gnE; U+02269
gnap; U+02A8A
gnapprox; U+02A8A
gne; U+02A88
gneq; U+02A88
gneqq; U+02269
gnsim; U+022E7
gopf; U+1D558
grave; U+00060
gscr; U+0210A
gsim; U+02273
gsime; U+02A8E
gsiml; U+02A90
gt; U+0003E
gt U+0003E
gtcc; U+02AA7
gtcir; U+02A7A
gtdot; U+022D7
gtlPar; U+02995
gtquest; U+02A7C
gtrapprox; U+02A86
gtrarr; U+02978
gtrdot; U+022D7
gtreqless; U+022DB
gtreqqless; U+02A8C
gtrless; U+02277
gtrsim; U+02273
hArr; U+021D4
hairsp; U+0200A
half; U+000BD
hamilt; U+0210B
hardcy; U+0044A
harr; U+02194
harrcir; U+02948
harrw; U+021AD
hbar; U+0210F
hcirc; U+00125
hearts; U+02665
heartsuit; U+02665
hellip; U+02026
hercon; U+022B9
hfr; U+1D525
hksearow; U+02925
hkswarow; U+02926
hoarr; U+021FF
homtht; U+0223B
hookleftarrow; U+021A9
hookrightarrow; U+021AA
hopf; U+1D559
horbar; U+02015
hscr; U+1D4BD
hslash; U+0210F
hstrok; U+00127
hybull; U+02043
hyphen; U+02010
iacute; U+000ED
iacute U+000ED
ic; U+02063
icirc; U+000EE
icirc U+000EE
icy; U+00438
iecy; U+00435
iexcl; U+000A1
iexcl U+000A1
iff; U+021D4
ifr; U+1D526
igrave; U+000EC
igrave U+000EC
ii; U+02148
iiiint; U+02A0C
iiint; U+0222D
iinfin; U+029DC
iiota; U+02129
ijlig; U+00133
imacr; U+0012B
image; U+02111
imagline; U+02110
imagpart; U+02111
imath; U+00131
imof; U+022B7
imped; U+001B5
in; U+02208
incare; U+02105
infin; U+0221E
infintie; U+029DD
inodot; U+00131
int; U+0222B
intcal; U+022BA
integers; U+02124
intercal; U+022BA
intlarhk; U+02A17
intprod; U+02A3C
iocy; U+00451
iogon; U+0012F
iopf; U+1D55A
iota; U+003B9
iprod; U+02A3C
iquest; U+000BF
iquest U+000BF
iscr; U+1D4BE
isin; U+02208
isinE; U+022F9
isindot; U+022F5
isins; U+022F4
isinsv; U+022F3
isinv; U+02208
it; U+02062
itilde; U+00129
iukcy; U+00456
iuml; U+000EF
iuml U+000EF
jcirc; U+00135
jcy; U+00439
jfr; U+1D527
jmath; U+00237
jopf; U+1D55B
jscr; U+1D4BF
jsercy; U+00458
jukcy; U+00454
kappa; U+003BA
kappav; U+003F0
kcedil; U+00137
kcy; U+0043A
kfr; U+1D528
kgreen; U+00138
khcy; U+00445
kjcy; U+0045C
kopf; U+1D55C
kscr; U+1D4C0
lAarr; U+021DA
lArr; U+021D0
lAtail; U+0291B
lBarr; U+0290E
lE; U+02266
lEg; U+02A8B
lHar; U+02962
lacute; U+0013A
laemptyv; U+029B4
lagran; U+02112
lambda; U+003BB
lang; U+027E8
langd; U+02991
langle; U+027E8
lap; U+02A85
laquo; U+000AB
laquo U+000AB
larr; U+02190
larrb; U+021E4
larrbfs; U+0291F
larrfs; U+0291D
larrhk; U+021A9
larrlp; U+021AB
larrpl; U+02939
larrsim; U+02973
larrtl; U+021A2
lat; U+02AAB
latail; U+02919
late; U+02AAD
lbarr; U+0290C
lbbrk; U+02772
lbrace; U+0007B
lbrack; U+0005B
lbrke; U+0298B
lbrksld; U+0298F
lbrkslu; U+0298D
lcaron; U+0013E
lcedil; U+0013C
lceil; U+02308
lcub; U+0007B
lcy; U+0043B
ldca; U+02936
ldquo; U+0201C
ldquor; U+0201E
ldrdhar; U+02967
ldrushar; U+0294B
ldsh; U+021B2
le; U+02264
leftarrow; U+02190
leftarrowtail; U+021A2
leftharpoondown; U+021BD
leftharpoonup; U+021BC
leftleftarrows; U+021C7
leftrightarrow; U+02194
leftrightarrows; U+021C6
leftrightharpoons; U+021CB
leftrightsquigarrow; U+021AD
leftthreetimes; U+022CB
leg; U+022DA
leq; U+02264
leqq; U+02266
leqslant; U+02A7D
les; U+02A7D
lescc; U+02AA8
lesdot; U+02A7F
lesdoto; U+02A81
lesdotor; U+02A83
lesges; U+02A93
lessapprox; U+02A85
lessdot; U+022D6
lesseqgtr; U+022DA
lesseqqgtr; U+02A8B
lessgtr; U+02276
lesssim; U+02272
lfisht; U+0297C
lfloor; U+0230A
lfr; U+1D529
lg; U+02276
lgE; U+02A91
lhard; U+021BD
lharu; U+021BC
lharul; U+0296A
lhblk; U+02584
ljcy; U+00459
ll; U+0226A
llarr; U+021C7
llcorner; U+0231E
llhard; U+0296B
lltri; U+025FA
lmidot; U+00140
lmoust; U+023B0
lmoustache; U+023B0
lnE; U+02268
lnap; U+02A89
lnapprox; U+02A89
lne; U+02A87
lneq; U+02A87
lneqq; U+02268
lnsim; U+022E6
loang; U+027EC
loarr; U+021FD
lobrk; U+027E6
longleftarrow; U+027F5
longleftrightarrow; U+027F7
longmapsto; U+027FC
longrightarrow; U+027F6
looparrowleft; U+021AB
looparrowright; U+021AC
lopar; U+02985
lopf; U+1D55D
loplus; U+02A2D
lotimes; U+02A34
lowast; U+02217
lowbar; U+0005F
loz; U+025CA
lozenge; U+025CA
lozf; U+029EB
lpar; U+00028
lparlt; U+02993
lrarr; U+021C6
lrcorner; U+0231F
lrhar; U+021CB
lrhard; U+0296D
lrm; U+0200E
lrtri; U+022BF
lsaquo; U+02039
lscr; U+1D4C1
lsh; U+021B0
lsim; U+02272
lsime; U+02A8D
lsimg; U+02A8F
lsqb; U+0005B
lsquo; U+02018
lsquor; U+0201A
lstrok; U+00142
lt; U+0003C
lt U+0003C
ltcc; U+02AA6
ltcir; U+02A79
ltdot; U+022D6
lthree; U+022CB
ltimes; U+022C9
ltlarr; U+02976
ltquest; U+02A7B
ltrPar; U+02996
ltri; U+025C3
ltrie; U+022B4
ltrif; U+025C2
lurdshar; U+0294A
luruhar; U+02966
mDDot; U+0223A
macr; U+000AF
macr U+000AF
male; U+02642
malt; U+02720
maltese; U+02720
map; U+021A6
mapsto; U+021A6
mapstodown; U+021A7
mapstoleft; U+021A4
mapstoup; U+021A5
marker; U+025AE
mcomma; U+02A29
mcy; U+0043C
mdash; U+02014
measuredangle; U+02221
mfr; U+1D52A
mho; U+02127
micro; U+000B5
micro U+000B5
mid; U+02223
midast; U+0002A
midcir; U+02AF0
middot; U+000B7
middot U+000B7
minus; U+02212
minusb; U+0229F
minusd; U+02238
minusdu; U+02A2A
mlcp; U+02ADB
mldr; U+02026
mnplus; U+02213
models; U+022A7
mopf; U+1D55E
mp; U+02213
mscr; U+1D4C2
mstpos; U+0223E
mu; U+003BC
multimap; U+022B8
mumap; U+022B8
nLeftarrow; U+021CD
nLeftrightarrow; U+021CE
nRightarrow; U+021CF
nVDash; U+022AF
nVdash; U+022AE
nabla; U+02207
nacute; U+00144
nap; U+02249
napos; U+00149
napprox; U+02249
natur; U+0266E
natural; U+0266E
naturals; U+02115
nbsp; U+000A0
nbsp U+000A0
ncap; U+02A43
ncaron; U+00148
ncedil; U+00146
ncong; U+02247
ncup; U+02A42
ncy; U+0043D
ndash; U+02013
ne; U+02260
neArr; U+021D7
nearhk; U+02924
nearr; U+02197
nearrow; U+02197
nequiv; U+02262
nesear; U+02928
nexist; U+02204
nexists; U+02204
nfr; U+1D52B
nge; U+02271
ngeq; U+02271
ngsim; U+02275
ngt; U+0226F
ngtr; U+0226F
nhArr; U+021CE
nharr; U+021AE
nhpar; U+02AF2
ni; U+0220B
nis; U+022FC
nisd; U+022FA
niv; U+0220B
njcy; U+0045A
nlArr; U+021CD
nlarr; U+0219A
nldr; U+02025
nle; U+02270
nleftarrow; U+0219A
nleftrightarrow; U+021AE
nleq; U+02270
nless; U+0226E
nlsim; U+02274
nlt; U+0226E
nltri; U+022EA
nltrie; U+022EC
nmid; U+02224
nopf; U+1D55F
not; U+000AC
not U+000AC
notin; U+02209
notinva; U+02209
notinvb; U+022F7
notinvc; U+022F6
notni; U+0220C
notniva; U+0220C
notnivb; U+022FE
notnivc; U+022FD
npar; U+02226
nparallel; U+02226
npolint; U+02A14
npr; U+02280
nprcue; U+022E0
nprec; U+02280
nrArr; U+021CF
nrarr; U+0219B
nrightarrow; U+0219B
nrtri; U+022EB
nrtrie; U+022ED
nsc; U+02281
nsccue; U+022E1
nscr; U+1D4C3
nshortmid; U+02224
nshortparallel; U+02226
nsim; U+02241
nsime; U+02244
nsimeq; U+02244
nsmid; U+02224
nspar; U+02226
nsqsube; U+022E2
nsqsupe; U+022E3
nsub; U+02284
nsube; U+02288
nsubseteq; U+02288
nsucc; U+02281
nsup; U+02285
nsupe; U+02289
nsupseteq; U+02289
ntgl; U+02279
ntilde; U+000F1
ntilde U+000F1
ntlg; U+02278
ntriangleleft; U+022EA
ntrianglelefteq; U+022EC
ntriangleright; U+022EB
ntrianglerighteq; U+022ED
nu; U+003BD
num; U+00023
numero; U+02116
numsp; U+02007
nvDash; U+022AD
nvHarr; U+02904
nvdash; U+022AC
nvinfin; U+029DE
nvlArr; U+02902
nvrArr; U+02903
nwArr; U+021D6
nwarhk; U+02923
nwarr; U+02196
nwarrow; U+02196
nwnear; U+02927
oS; U+024C8
oacute; U+000F3
oacute U+000F3
oast; U+0229B
ocir; U+0229A
ocirc; U+000F4
ocirc U+000F4
ocy; U+0043E
odash; U+0229D
odblac; U+00151
odiv; U+02A38
odot; U+02299
odsold; U+029BC
oelig; U+00153
ofcir; U+029BF
ofr; U+1D52C
ogon; U+002DB
ograve; U+000F2
ograve U+000F2
ogt; U+029C1
ohbar; U+029B5
ohm; U+003A9
oint; U+0222E
olarr; U+021BA
olcir; U+029BE
olcross; U+029BB
oline; U+0203E
olt; U+029C0
omacr; U+0014D
omega; U+003C9
omicron; U+003BF
omid; U+029B6
ominus; U+02296
oopf; U+1D560
opar; U+029B7
operp; U+029B9
oplus; U+02295
or; U+02228
orarr; U+021BB
ord; U+02A5D
order; U+02134
orderof; U+02134
ordf; U+000AA
ordf U+000AA
ordm; U+000BA
ordm U+000BA
origof; U+022B6
oror; U+02A56
orslope; U+02A57
orv; U+02A5B
oscr; U+02134
oslash; U+000F8
oslash U+000F8
osol; U+02298
otilde; U+000F5
otilde U+000F5
otimes; U+02297
otimesas; U+02A36
ouml; U+000F6
ouml U+000F6
ovbar; U+0233D
par; U+02225
para; U+000B6
para U+000B6
parallel; U+02225
parsim; U+02AF3
parsl; U+02AFD
part; U+02202
pcy; U+0043F
percnt; U+00025
period; U+0002E
permil; U+02030
perp; U+022A5
pertenk; U+02031
pfr; U+1D52D
phi; U+003C6
phiv; U+003D5
phmmat; U+02133
phone; U+0260E
pi; U+003C0
pitchfork; U+022D4
piv; U+003D6
planck; U+0210F
planckh; U+0210E
plankv; U+0210F
plus; U+0002B
plusacir; U+02A23
plusb; U+0229E
pluscir; U+02A22
plusdo; U+02214
plusdu; U+02A25
pluse; U+02A72
plusmn; U+000B1
plusmn U+000B1
plussim; U+02A26
plustwo; U+02A27
pm; U+000B1
pointint; U+02A15
popf; U+1D561
pound; U+000A3
pound U+000A3
pr; U+0227A
prE; U+02AB3
prap; U+02AB7
prcue; U+0227C
pre; U+02AAF
prec; U+0227A
precapprox; U+02AB7
preccurlyeq; U+0227C
preceq; U+02AAF
precnapprox; U+02AB9
precneqq; U+02AB5
precnsim; U+022E8
precsim; U+0227E
prime; U+02032
primes; U+02119
prnE; U+02AB5
prnap; U+02AB9
prnsim; U+022E8
prod; U+0220F
profalar; U+0232E
profline; U+02312
profsurf; U+02313
prop; U+0221D
propto; U+0221D
prsim; U+0227E
prurel; U+022B0
pscr; U+1D4C5
psi; U+003C8
puncsp; U+02008
qfr; U+1D52E
qint; U+02A0C
qopf; U+1D562
qprime; U+02057
qscr; U+1D4C6
quaternions; U+0210D
quatint; U+02A16
quest; U+0003F
questeq; U+0225F
quot; U+00022
quot U+00022
rAarr; U+021DB
rArr; U+021D2
rAtail; U+0291C
rBarr; U+0290F
rHar; U+02964
racute; U+00155
radic; U+0221A
raemptyv; U+029B3
rang; U+027E9
rangd; U+02992
range; U+029A5
rangle; U+027E9
raquo; U+000BB
raquo U+000BB
rarr; U+02192
rarrap; U+02975
rarrb; U+021E5
rarrbfs; U+02920
rarrc; U+02933
rarrfs; U+0291E
rarrhk; U+021AA
rarrlp; U+021AC
rarrpl; U+02945
rarrsim; U+02974
rarrtl; U+021A3
rarrw; U+0219D
ratail; U+0291A
ratio; U+02236
rationals; U+0211A
rbarr; U+0290D
rbbrk; U+02773
rbrace; U+0007D
rbrack; U+0005D
rbrke; U+0298C
rbrksld; U+0298E
rbrkslu; U+02990
rcaron; U+00159
rcedil; U+00157
rceil; U+02309
rcub; U+0007D
rcy; U+00440
rdca; U+02937
rdldhar; U+02969
rdquo; U+0201D
rdquor; U+0201D
rdsh; U+021B3
real; U+0211C
realine; U+0211B
realpart; U+0211C
reals; U+0211D
rect; U+025AD
reg; U+000AE
reg U+000AE
rfisht; U+0297D
rfloor; U+0230B
rfr; U+1D52F
rhard; U+021C1
rharu; U+021C0
rharul; U+0296C
rho; U+003C1
rhov; U+003F1
rightarrow; U+02192
rightarrowtail; U+021A3
rightharpoondown; U+021C1
rightharpoonup; U+021C0
rightleftarrows; U+021C4
rightleftharpoons; U+021CC
rightrightarrows; U+021C9
rightsquigarrow; U+0219D
rightthreetimes; U+022CC
ring; U+002DA
risingdotseq; U+02253
rlarr; U+021C4
rlhar; U+021CC
rlm; U+0200F
rmoust; U+023B1
rmoustache; U+023B1
rnmid; U+02AEE
roang; U+027ED
roarr; U+021FE
robrk; U+027E7
ropar; U+02986
ropf; U+1D563
roplus; U+02A2E
rotimes; U+02A35
rpar; U+00029
rpargt; U+02994
rppolint; U+02A12
rrarr; U+021C9
rsaquo; U+0203A
rscr; U+1D4C7
rsh; U+021B1
rsqb; U+0005D
rsquo; U+02019
rsquor; U+02019
rthree; U+022CC
rtimes; U+022CA
rtri; U+025B9
rtrie; U+022B5
rtrif; U+025B8
rtriltri; U+029CE
ruluhar; U+02968
rx; U+0211E
sacute; U+0015B
sbquo; U+0201A
sc; U+0227B
scE; U+02AB4
scap; U+02AB8
scaron; U+00161
sccue; U+0227D
sce; U+02AB0
scedil; U+0015F
scirc; U+0015D
scnE; U+02AB6
scnap; U+02ABA
scnsim; U+022E9
scpolint; U+02A13
scsim; U+0227F
scy; U+00441
sdot; U+022C5
sdotb; U+022A1
sdote; U+02A66
seArr; U+021D8
searhk; U+02925
searr; U+02198
searrow; U+02198
sect; U+000A7
sect U+000A7
semi; U+0003B
seswar; U+02929
setminus; U+02216
setmn; U+02216
sext; U+02736
sfr; U+1D530
sfrown; U+02322
sharp; U+0266F
shchcy; U+00449
shcy; U+00448
shortmid; U+02223
shortparallel; U+02225
shy; U+000AD
shy U+000AD
sigma; U+003C3
sigmaf; U+003C2
sigmav; U+003C2
sim; U+0223C
simdot; U+02A6A
sime; U+02243
simeq; U+02243
simg; U+02A9E
simgE; U+02AA0
siml; U+02A9D
simlE; U+02A9F
simne; U+02246
simplus; U+02A24
simrarr; U+02972
slarr; U+02190
smallsetminus; U+02216
smashp; U+02A33
smeparsl; U+029E4
smid; U+02223
smile; U+02323
smt; U+02AAA
smte; U+02AAC
softcy; U+0044C
sol; U+0002F
solb; U+029C4
solbar; U+0233F
sopf; U+1D564
spades; U+02660
spadesuit; U+02660
spar; U+02225
sqcap; U+02293
sqcup; U+02294
sqsub; U+0228F
sqsube; U+02291
sqsubset; U+0228F
sqsubseteq; U+02291
sqsup; U+02290
sqsupe; U+02292
sqsupset; U+02290
sqsupseteq; U+02292
squ; U+025A1
square; U+025A1
squarf; U+025AA
squf; U+025AA
srarr; U+02192
sscr; U+1D4C8
ssetmn; U+02216
ssmile; U+02323
sstarf; U+022C6
star; U+02606
starf; U+02605
straightepsilon; U+003F5
straightphi; U+003D5
strns; U+000AF
sub; U+02282
subE; U+02AC5
subdot; U+02ABD
sube; U+02286
subedot; U+02AC3
submult; U+02AC1
subnE; U+02ACB
subne; U+0228A
subplus; U+02ABF
subrarr; U+02979
subset; U+02282
subseteq; U+02286
subseteqq; U+02AC5
subsetneq; U+0228A
subsetneqq; U+02ACB
subsim; U+02AC7
subsub; U+02AD5
subsup; U+02AD3
succ; U+0227B
succapprox; U+02AB8
succcurlyeq; U+0227D
succeq; U+02AB0
succnapprox; U+02ABA
succneqq; U+02AB6
succnsim; U+022E9
succsim; U+0227F
sum; U+02211
sung; U+0266A
sup1; U+000B9
sup1 U+000B9
sup2; U+000B2
sup2 U+000B2
sup3; U+000B3
sup3 U+000B3
sup; U+02283
supE; U+02AC6
supdot; U+02ABE
supdsub; U+02AD8
supe; U+02287
supedot; U+02AC4
suphsol; U+027C9
suphsub; U+02AD7
suplarr; U+0297B
supmult; U+02AC2
supnE; U+02ACC
supne; U+0228B
supplus; U+02AC0
supset; U+02283
supseteq; U+02287
supseteqq; U+02AC6
supsetneq; U+0228B
supsetneqq; U+02ACC
supsim; U+02AC8
supsub; U+02AD4
supsup; U+02AD6
swArr; U+021D9
swarhk; U+02926
swarr; U+02199
swarrow; U+02199
swnwar; U+0292A
szlig; U+000DF
szlig U+000DF
target; U+02316
tau; U+003C4
tbrk; U+023B4
tcaron; U+00165
tcedil; U+00163
tcy; U+00442
tdot; U+020DB
telrec; U+02315
tfr; U+1D531
there4; U+02234
therefore; U+02234
theta; U+003B8
thetasym; U+003D1
thetav; U+003D1
thickapprox; U+02248
thicksim; U+0223C
thinsp; U+02009
thkap; U+02248
thksim; U+0223C
thorn; U+000FE
thorn U+000FE
tilde; U+002DC
times; U+000D7
times U+000D7
timesb; U+022A0
timesbar; U+02A31
timesd; U+02A30
tint; U+0222D
toea; U+02928
top; U+022A4
topbot; U+02336
topcir; U+02AF1
topf; U+1D565
topfork; U+02ADA
tosa; U+02929
tprime; U+02034
trade; U+02122
triangle; U+025B5
triangledown; U+025BF
triangleleft; U+025C3
trianglelefteq; U+022B4
triangleq; U+0225C
triangleright; U+025B9
trianglerighteq; U+022B5
tridot; U+025EC
trie; U+0225C
triminus; U+02A3A
triplus; U+02A39
trisb; U+029CD
tritime; U+02A3B
trpezium; U+023E2
tscr; U+1D4C9
tscy; U+00446
tshcy; U+0045B
tstrok; U+00167
twixt; U+0226C
twoheadleftarrow; U+0219E
twoheadrightarrow; U+021A0
uArr; U+021D1
uHar; U+02963
uacute; U+000FA
uacute U+000FA
uarr; U+02191
ubrcy; U+0045E
ubreve; U+0016D
ucirc; U+000FB
ucirc U+000FB
ucy; U+00443
udarr; U+021C5
udblac; U+00171
udhar; U+0296E
ufisht; U+0297E
ufr; U+1D532
ugrave; U+000F9
ugrave U+000F9
uharl; U+021BF
uharr; U+021BE
uhblk; U+02580
ulcorn; U+0231C
ulcorner; U+0231C
ulcrop; U+0230F
ultri; U+025F8
umacr; U+0016B
uml; U+000A8
uml U+000A8
uogon; U+00173
uopf; U+1D566
uparrow; U+02191
updownarrow; U+02195
upharpoonleft; U+021BF
upharpoonright; U+021BE
uplus; U+0228E
upsi; U+003C5
upsih; U+003D2
upsilon; U+003C5
upuparrows; U+021C8
urcorn; U+0231D
urcorner; U+0231D
urcrop; U+0230E
uring; U+0016F
urtri; U+025F9
uscr; U+1D4CA
utdot; U+022F0
utilde; U+00169
utri; U+025B5
utrif; U+025B4
uuarr; U+021C8
uuml; U+000FC
uuml U+000FC
uwangle; U+029A7
vArr; U+021D5
vBar; U+02AE8
vBarv; U+02AE9
vDash; U+022A8
vangrt; U+0299C
varepsilon; U+003F5
varkappa; U+003F0
varnothing; U+02205
varphi; U+003D5
varpi; U+003D6
varpropto; U+0221D
varr; U+02195
varrho; U+003F1
varsigma; U+003C2
vartheta; U+003D1
vartriangleleft; U+022B2
vartriangleright; U+022B3
vcy; U+00432
vdash; U+022A2
vee; U+02228
veebar; U+022BB
veeeq; U+0225A
vellip; U+022EE
verbar; U+0007C
vert; U+0007C
vfr; U+1D533
vltri; U+022B2
vopf; U+1D567
vprop; U+0221D
vrtri; U+022B3
vscr; U+1D4CB
vzigzag; U+0299A
wcirc; U+00175
wedbar; U+02A5F
wedge; U+02227
wedgeq; U+02259
weierp; U+02118
wfr; U+1D534
wopf; U+1D568
wp; U+02118
wr; U+02240
wreath; U+02240
wscr; U+1D4CC
xcap; U+022C2
xcirc; U+025EF
xcup; U+022C3
xdtri; U+025BD
xfr; U+1D535
xhArr; U+027FA
xharr; U+027F7
xi; U+003BE
xlArr; U+027F8
xlarr; U+027F5
xmap; U+027FC
xnis; U+022FB
xodot; U+02A00
xopf; U+1D569
xoplus; U+02A01
xotime; U+02A02
xrArr; U+027F9
xrarr; U+027F6
xscr; U+1D4CD
xsqcup; U+02A06
xuplus; U+02A04
xutri; U+025B3
xvee; U+022C1
xwedge; U+022C0
yacute; U+000FD
yacute U+000FD
yacy; U+0044F
ycirc; U+00177
ycy; U+0044B
yen; U+000A5
yen U+000A5
yfr; U+1D536
yicy; U+00457
yopf; U+1D56A
yscr; U+1D4CE
yucy; U+0044E
yuml; U+000FF
yuml U+000FF
zacute; U+0017A
zcaron; U+0017E
zcy; U+00437
zdot; U+0017C
zeetrf; U+02128
zeta; U+003B6
zfr; U+1D537
zhcy; U+00436
zigrarr; U+021DD
zopf; U+1D56B
zscr; U+1D4CF
zwj; U+0200D
zwnj; U+0200C
- - \ No newline at end of file diff --git a/parser/html/javasrc/AttributeName.java b/parser/html/javasrc/AttributeName.java deleted file mode 100644 index 7b889e71e..000000000 --- a/parser/html/javasrc/AttributeName.java +++ /dev/null @@ -1,2473 +0,0 @@ -/* - * Copyright (c) 2008-2011 Mozilla Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -package nu.validator.htmlparser.impl; - -import java.util.Arrays; - -import nu.validator.htmlparser.annotation.Local; -import nu.validator.htmlparser.annotation.NoLength; -import nu.validator.htmlparser.annotation.NsUri; -import nu.validator.htmlparser.annotation.Prefix; -import nu.validator.htmlparser.annotation.QName; -import nu.validator.htmlparser.annotation.Virtual; -import nu.validator.htmlparser.common.Interner; - -public final class AttributeName -// Uncomment to regenerate -// implements Comparable -{ - // [NOCPP[ - - public static final int NCNAME_HTML = 1; - - public static final int NCNAME_FOREIGN = (1 << 1) | (1 << 2); - - public static final int NCNAME_LANG = (1 << 3); - - public static final int IS_XMLNS = (1 << 4); - - public static final int CASE_FOLDED = (1 << 5); - - public static final int BOOLEAN = (1 << 6); - - // ]NOCPP] - - /** - * An array representing no namespace regardless of namespace mode (HTML, - * SVG, MathML, lang-mapping HTML) used. - */ - static final @NoLength @NsUri String[] ALL_NO_NS = { "", "", "", - // [NOCPP[ - "" - // ]NOCPP] - }; - - /** - * An array that has no namespace for the HTML mode but the XMLNS namespace - * for the SVG and MathML modes. - */ - private static final @NoLength @NsUri String[] XMLNS_NS = { "", - "http://www.w3.org/2000/xmlns/", "http://www.w3.org/2000/xmlns/", - // [NOCPP[ - "" - // ]NOCPP] - }; - - /** - * An array that has no namespace for the HTML mode but the XML namespace - * for the SVG and MathML modes. - */ - private static final @NoLength @NsUri String[] XML_NS = { "", - "http://www.w3.org/XML/1998/namespace", - "http://www.w3.org/XML/1998/namespace", - // [NOCPP[ - "" - // ]NOCPP] - }; - - /** - * An array that has no namespace for the HTML mode but the XLink namespace - * for the SVG and MathML modes. - */ - private static final @NoLength @NsUri String[] XLINK_NS = { "", - "http://www.w3.org/1999/xlink", "http://www.w3.org/1999/xlink", - // [NOCPP[ - "" - // ]NOCPP] - }; - - // [NOCPP[ - /** - * An array that has no namespace for the HTML, SVG and MathML modes but has - * the XML namespace for the lang-mapping HTML mode. - */ - private static final @NoLength @NsUri String[] LANG_NS = { "", "", "", - "http://www.w3.org/XML/1998/namespace" }; - - // ]NOCPP] - - /** - * An array for no prefixes in any mode. - */ - static final @NoLength @Prefix String[] ALL_NO_PREFIX = { null, null, null, - // [NOCPP[ - null - // ]NOCPP] - }; - - /** - * An array for no prefixe in the HTML mode and the xmlns - * prefix in the SVG and MathML modes. - */ - private static final @NoLength @Prefix String[] XMLNS_PREFIX = { null, - "xmlns", "xmlns", - // [NOCPP[ - null - // ]NOCPP] - }; - - /** - * An array for no prefixe in the HTML mode and the xlink - * prefix in the SVG and MathML modes. - */ - private static final @NoLength @Prefix String[] XLINK_PREFIX = { null, - "xlink", "xlink", - // [NOCPP[ - null - // ]NOCPP] - }; - - /** - * An array for no prefixe in the HTML mode and the xml prefix - * in the SVG and MathML modes. - */ - private static final @NoLength @Prefix String[] XML_PREFIX = { null, "xml", - "xml", - // [NOCPP[ - null - // ]NOCPP] - }; - - // [NOCPP[ - - private static final @NoLength @Prefix String[] LANG_PREFIX = { null, null, - null, "xml" }; - - private static @QName String[] COMPUTE_QNAME(String[] local, String[] prefix) { - @QName String[] arr = new String[4]; - for (int i = 0; i < arr.length; i++) { - if (prefix[i] == null) { - arr[i] = local[i]; - } else { - arr[i] = (prefix[i] + ':' + local[i]).intern(); - } - } - return arr; - } - - // ]NOCPP] - - /** - * An initialization helper for having a one name in the SVG mode and - * another name in the other modes. - * - * @param name - * the name for the non-SVG modes - * @param camel - * the name for the SVG mode - * @return the initialized name array - */ - private static @NoLength @Local String[] SVG_DIFFERENT(@Local String name, - @Local String camel) { - @NoLength @Local String[] arr = new String[4]; - arr[0] = name; - arr[1] = name; - arr[2] = camel; - // [NOCPP[ - arr[3] = name; - // ]NOCPP] - return arr; - } - - /** - * An initialization helper for having a one name in the MathML mode and - * another name in the other modes. - * - * @param name - * the name for the non-MathML modes - * @param camel - * the name for the MathML mode - * @return the initialized name array - */ - private static @NoLength @Local String[] MATH_DIFFERENT(@Local String name, - @Local String camel) { - @NoLength @Local String[] arr = new String[4]; - arr[0] = name; - arr[1] = camel; - arr[2] = name; - // [NOCPP[ - arr[3] = name; - // ]NOCPP] - return arr; - } - - /** - * An initialization helper for having a different local name in the HTML - * mode and the SVG and MathML modes. - * - * @param name - * the name for the HTML mode - * @param suffix - * the name for the SVG and MathML modes - * @return the initialized name array - */ - private static @NoLength @Local String[] COLONIFIED_LOCAL( - @Local String name, @Local String suffix) { - @NoLength @Local String[] arr = new String[4]; - arr[0] = name; - arr[1] = suffix; - arr[2] = suffix; - // [NOCPP[ - arr[3] = name; - // ]NOCPP] - return arr; - } - - /** - * An initialization helper for having the same local name in all modes. - * - * @param name - * the name - * @return the initialized name array - */ - static @NoLength @Local String[] SAME_LOCAL(@Local String name) { - @NoLength @Local String[] arr = new String[4]; - arr[0] = name; - arr[1] = name; - arr[2] = name; - // [NOCPP[ - arr[3] = name; - // ]NOCPP] - return arr; - } - - /** - * Returns an attribute name by buffer. - * - *

- * C++ ownership: The return value is either released by the caller if the - * attribute is a duplicate or the ownership is transferred to - * HtmlAttributes and released upon clearing or destroying that object. - * - * @param buf - * the buffer - * @param offset - * ignored - * @param length - * length of data - * @param checkNcName - * whether to check ncnameness - * @return an AttributeName corresponding to the argument data - */ - static AttributeName nameByBuffer(@NoLength char[] buf, int offset, - int length - // [NOCPP[ - , boolean checkNcName - // ]NOCPP] - , Interner interner) { - // XXX deal with offset - int hash = AttributeName.bufToHash(buf, length); - int index = Arrays.binarySearch(AttributeName.ATTRIBUTE_HASHES, hash); - if (index < 0) { - return AttributeName.createAttributeName( - Portability.newLocalNameFromBuffer(buf, offset, length, - interner) - // [NOCPP[ - , checkNcName - // ]NOCPP] - ); - } else { - AttributeName attributeName = AttributeName.ATTRIBUTE_NAMES[index]; - @Local String name = attributeName.getLocal(AttributeName.HTML); - if (!Portability.localEqualsBuffer(name, buf, offset, length)) { - return AttributeName.createAttributeName( - Portability.newLocalNameFromBuffer(buf, offset, length, - interner) - // [NOCPP[ - , checkNcName - // ]NOCPP] - ); - } - return attributeName; - } - } - - /** - * This method has to return a unique integer for each well-known - * lower-cased attribute name. - * - * @param buf - * @param len - * @return - */ - private static int bufToHash(@NoLength char[] buf, int len) { - int hash2 = 0; - int hash = len; - hash <<= 5; - hash += buf[0] - 0x60; - int j = len; - for (int i = 0; i < 4 && j > 0; i++) { - j--; - hash <<= 5; - hash += buf[j] - 0x60; - hash2 <<= 6; - hash2 += buf[i] - 0x5F; - } - return hash ^ hash2; - } - - /** - * The mode value for HTML. - */ - public static final int HTML = 0; - - /** - * The mode value for MathML. - */ - public static final int MATHML = 1; - - /** - * The mode value for SVG. - */ - public static final int SVG = 2; - - // [NOCPP[ - - /** - * The mode value for lang-mapping HTML. - */ - public static final int HTML_LANG = 3; - - // ]NOCPP] - - /** - * The namespaces indexable by mode. - */ - private final @NsUri @NoLength String[] uri; - - /** - * The local names indexable by mode. - */ - private final @Local @NoLength String[] local; - - /** - * The prefixes indexably by mode. - */ - private final @Prefix @NoLength String[] prefix; - - // [NOCPP[ - - private final int flags; - - /** - * The qnames indexable by mode. - */ - private final @QName @NoLength String[] qName; - - // ]NOCPP] - - /** - * The startup-time constructor. - * - * @param uri - * the namespace - * @param local - * the local name - * @param prefix - * the prefix - * @param ncname - * the ncnameness - * @param xmlns - * whether this is an xmlns attribute - */ - protected AttributeName(@NsUri @NoLength String[] uri, - @Local @NoLength String[] local, @Prefix @NoLength String[] prefix - // [NOCPP[ - , int flags - // ]NOCPP] - ) { - this.uri = uri; - this.local = local; - this.prefix = prefix; - // [NOCPP[ - this.qName = COMPUTE_QNAME(local, prefix); - this.flags = flags; - // ]NOCPP] - } - - /** - * Creates an AttributeName for a local name. - * - * @param name - * the name - * @param checkNcName - * whether to check ncnameness - * @return an AttributeName - */ - private static AttributeName createAttributeName(@Local String name - // [NOCPP[ - , boolean checkNcName - // ]NOCPP] - ) { - // [NOCPP[ - int flags = NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG; - if (name.startsWith("xmlns:")) { - flags = IS_XMLNS; - } else if (checkNcName && !NCName.isNCName(name)) { - flags = 0; - } - // ]NOCPP] - return new AttributeName(AttributeName.ALL_NO_NS, - AttributeName.SAME_LOCAL(name), ALL_NO_PREFIX, flags); - } - - /** - * Deletes runtime-allocated instances in C++. - */ - @Virtual void release() { - // No-op in Java. - // Implement as |delete this;| in subclass. - } - - /** - * The C++ destructor. - */ - @SuppressWarnings("unused") @Virtual private void destructor() { - Portability.deleteArray(local); - } - - /** - * Clones the attribute using an interner. Returns this in Java - * and for non-dynamic instances in C++. - * - * @param interner - * an interner - * @return a clone - */ - @Virtual public AttributeName cloneAttributeName(Interner interner) { - return this; - } - - // [NOCPP[ - /** - * Creator for use when the XML violation policy requires an attribute name - * to be changed. - * - * @param name - * the name of the attribute to create - */ - static AttributeName create(@Local String name) { - return new AttributeName(AttributeName.ALL_NO_NS, - AttributeName.SAME_LOCAL(name), ALL_NO_PREFIX, - NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - } - - /** - * Queries whether this name is an XML 1.0 4th ed. NCName. - * - * @param mode - * the SVG/MathML/HTML mode - * @return true if this is an NCName in the given mode - */ - public boolean isNcName(int mode) { - return (flags & (1 << mode)) != 0; - } - - /** - * Queries whether this is an xmlns attribute. - * - * @return true if this is an xmlns attribute - */ - public boolean isXmlns() { - return (flags & IS_XMLNS) != 0; - } - - /** - * Queries whether this attribute has a case-folded value in the HTML4 mode - * of the parser. - * - * @return true if the value is case-folded - */ - boolean isCaseFolded() { - return (flags & CASE_FOLDED) != 0; - } - - boolean isBoolean() { - return (flags & BOOLEAN) != 0; - } - - public @QName String getQName(int mode) { - return qName[mode]; - } - - // ]NOCPP] - - public @NsUri String getUri(int mode) { - return uri[mode]; - } - - public @Local String getLocal(int mode) { - return local[mode]; - } - - public @Prefix String getPrefix(int mode) { - return prefix[mode]; - } - - boolean equalsAnother(AttributeName another) { - return this.getLocal(AttributeName.HTML) == another.getLocal(AttributeName.HTML); - } - - // START CODE ONLY USED FOR GENERATING CODE uncomment to regenerate - -// /** -// * @see java.lang.Object#toString() -// */ -// @Override public String toString() { -// return "(" + formatNs() + ", " + formatLocal() + ", " + formatPrefix() -// + ", " + formatFlags() + ")"; -// } -// -// private String formatFlags() { -// StringBuilder builder = new StringBuilder(); -// if ((flags & NCNAME_HTML) != 0) { -// if (builder.length() != 0) { -// builder.append(" | "); -// } -// builder.append("NCNAME_HTML"); -// } -// if ((flags & NCNAME_FOREIGN) != 0) { -// if (builder.length() != 0) { -// builder.append(" | "); -// } -// builder.append("NCNAME_FOREIGN"); -// } -// if ((flags & NCNAME_LANG) != 0) { -// if (builder.length() != 0) { -// builder.append(" | "); -// } -// builder.append("NCNAME_LANG"); -// } -// if (isXmlns()) { -// if (builder.length() != 0) { -// builder.append(" | "); -// } -// builder.append("IS_XMLNS"); -// } -// if (isCaseFolded()) { -// if (builder.length() != 0) { -// builder.append(" | "); -// } -// builder.append("CASE_FOLDED"); -// } -// if (isBoolean()) { -// if (builder.length() != 0) { -// builder.append(" | "); -// } -// builder.append("BOOLEAN"); -// } -// if (builder.length() == 0) { -// return "0"; -// } -// return builder.toString(); -// } -// -// public int compareTo(AttributeName other) { -// int thisHash = this.hash(); -// int otherHash = other.hash(); -// if (thisHash < otherHash) { -// return -1; -// } else if (thisHash == otherHash) { -// return 0; -// } else { -// return 1; -// } -// } -// -// private String formatPrefix() { -// if (prefix[0] == null && prefix[1] == null && prefix[2] == null -// && prefix[3] == null) { -// return "ALL_NO_PREFIX"; -// } else if (prefix[0] == null && prefix[1] == prefix[2] -// && prefix[3] == null) { -// if ("xmlns".equals(prefix[1])) { -// return "XMLNS_PREFIX"; -// } else if ("xml".equals(prefix[1])) { -// return "XML_PREFIX"; -// } else if ("xlink".equals(prefix[1])) { -// return "XLINK_PREFIX"; -// } else { -// throw new IllegalStateException(); -// } -// } else if (prefix[0] == null && prefix[1] == null && prefix[2] == null -// && prefix[3] == "xml") { -// return "LANG_PREFIX"; -// } else { -// throw new IllegalStateException(); -// } -// } -// -// private String formatLocal() { -// if (local[0] == local[1] && local[0] == local[3] -// && local[0] != local[2]) { -// return "SVG_DIFFERENT(\"" + local[0] + "\", \"" + local[2] + "\")"; -// } -// if (local[0] == local[2] && local[0] == local[3] -// && local[0] != local[1]) { -// return "MATH_DIFFERENT(\"" + local[0] + "\", \"" + local[1] + "\")"; -// } -// if (local[0] == local[3] && local[1] == local[2] -// && local[0] != local[1]) { -// return "COLONIFIED_LOCAL(\"" + local[0] + "\", \"" + local[1] -// + "\")"; -// } -// for (int i = 1; i < local.length; i++) { -// if (local[0] != local[i]) { -// throw new IllegalStateException(); -// } -// } -// return "SAME_LOCAL(\"" + local[0] + "\")"; -// } -// -// private String formatNs() { -// if (uri[0] == "" && uri[1] == "" && uri[2] == "" && uri[3] == "") { -// return "ALL_NO_NS"; -// } else if (uri[0] == "" && uri[1] == uri[2] && uri[3] == "") { -// if ("http://www.w3.org/2000/xmlns/".equals(uri[1])) { -// return "XMLNS_NS"; -// } else if ("http://www.w3.org/XML/1998/namespace".equals(uri[1])) { -// return "XML_NS"; -// } else if ("http://www.w3.org/1999/xlink".equals(uri[1])) { -// return "XLINK_NS"; -// } else { -// throw new IllegalStateException(); -// } -// } else if (uri[0] == "" && uri[1] == "" && uri[2] == "" -// && uri[3] == "http://www.w3.org/XML/1998/namespace") { -// return "LANG_NS"; -// } else { -// throw new IllegalStateException(); -// } -// } -// -// private String constName() { -// String name = getLocal(HTML); -// char[] buf = new char[name.length()]; -// for (int i = 0; i < name.length(); i++) { -// char c = name.charAt(i); -// if (c == '-' || c == ':') { -// buf[i] = '_'; -// } else if (c >= 'a' && c <= 'z') { -// buf[i] = (char) (c - 0x20); -// } else { -// buf[i] = c; -// } -// } -// return new String(buf); -// } -// -// private int hash() { -// String name = getLocal(HTML); -// return bufToHash(name.toCharArray(), name.length()); -// } -// -// /** -// * Regenerate self -// * -// * @param args -// */ -// public static void main(String[] args) { -// Arrays.sort(ATTRIBUTE_NAMES); -// for (int i = 1; i < ATTRIBUTE_NAMES.length; i++) { -// if (ATTRIBUTE_NAMES[i].hash() == ATTRIBUTE_NAMES[i - 1].hash()) { -// System.err.println("Hash collision: " -// + ATTRIBUTE_NAMES[i].getLocal(HTML) + ", " -// + ATTRIBUTE_NAMES[i - 1].getLocal(HTML)); -// return; -// } -// } -// for (int i = 0; i < ATTRIBUTE_NAMES.length; i++) { -// AttributeName att = ATTRIBUTE_NAMES[i]; -// System.out.println("public static final AttributeName " -// + att.constName() + " = new AttributeName" + att.toString() -// + ";"); -// } -// System.out.println("private final static @NoLength AttributeName[] ATTRIBUTE_NAMES = {"); -// for (int i = 0; i < ATTRIBUTE_NAMES.length; i++) { -// AttributeName att = ATTRIBUTE_NAMES[i]; -// System.out.println(att.constName() + ","); -// } -// System.out.println("};"); -// System.out.println("private final static int[] ATTRIBUTE_HASHES = {"); -// for (int i = 0; i < ATTRIBUTE_NAMES.length; i++) { -// AttributeName att = ATTRIBUTE_NAMES[i]; -// System.out.println(Integer.toString(att.hash()) + ","); -// } -// System.out.println("};"); -// } - - // START GENERATED CODE - public static final AttributeName D = new AttributeName(ALL_NO_NS, SAME_LOCAL("d"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName K = new AttributeName(ALL_NO_NS, SAME_LOCAL("k"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName R = new AttributeName(ALL_NO_NS, SAME_LOCAL("r"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName X = new AttributeName(ALL_NO_NS, SAME_LOCAL("x"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName Y = new AttributeName(ALL_NO_NS, SAME_LOCAL("y"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName Z = new AttributeName(ALL_NO_NS, SAME_LOCAL("z"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName BY = new AttributeName(ALL_NO_NS, SAME_LOCAL("by"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CX = new AttributeName(ALL_NO_NS, SAME_LOCAL("cx"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CY = new AttributeName(ALL_NO_NS, SAME_LOCAL("cy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DX = new AttributeName(ALL_NO_NS, SAME_LOCAL("dx"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DY = new AttributeName(ALL_NO_NS, SAME_LOCAL("dy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName G2 = new AttributeName(ALL_NO_NS, SAME_LOCAL("g2"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName G1 = new AttributeName(ALL_NO_NS, SAME_LOCAL("g1"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FX = new AttributeName(ALL_NO_NS, SAME_LOCAL("fx"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FY = new AttributeName(ALL_NO_NS, SAME_LOCAL("fy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName K4 = new AttributeName(ALL_NO_NS, SAME_LOCAL("k4"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName K2 = new AttributeName(ALL_NO_NS, SAME_LOCAL("k2"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName K3 = new AttributeName(ALL_NO_NS, SAME_LOCAL("k3"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName K1 = new AttributeName(ALL_NO_NS, SAME_LOCAL("k1"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ID = new AttributeName(ALL_NO_NS, SAME_LOCAL("id"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName IN = new AttributeName(ALL_NO_NS, SAME_LOCAL("in"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName U2 = new AttributeName(ALL_NO_NS, SAME_LOCAL("u2"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName U1 = new AttributeName(ALL_NO_NS, SAME_LOCAL("u1"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName RT = new AttributeName(ALL_NO_NS, SAME_LOCAL("rt"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName RX = new AttributeName(ALL_NO_NS, SAME_LOCAL("rx"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName RY = new AttributeName(ALL_NO_NS, SAME_LOCAL("ry"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TO = new AttributeName(ALL_NO_NS, SAME_LOCAL("to"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName Y2 = new AttributeName(ALL_NO_NS, SAME_LOCAL("y2"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName Y1 = new AttributeName(ALL_NO_NS, SAME_LOCAL("y1"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName X1 = new AttributeName(ALL_NO_NS, SAME_LOCAL("x1"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName X2 = new AttributeName(ALL_NO_NS, SAME_LOCAL("x2"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ALT = new AttributeName(ALL_NO_NS, SAME_LOCAL("alt"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DIR = new AttributeName(ALL_NO_NS, SAME_LOCAL("dir"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName DUR = new AttributeName(ALL_NO_NS, SAME_LOCAL("dur"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName END = new AttributeName(ALL_NO_NS, SAME_LOCAL("end"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("for"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName IN2 = new AttributeName(ALL_NO_NS, SAME_LOCAL("in2"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MAX = new AttributeName(ALL_NO_NS, SAME_LOCAL("max"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("min"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LOW = new AttributeName(ALL_NO_NS, SAME_LOCAL("low"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REL = new AttributeName(ALL_NO_NS, SAME_LOCAL("rel"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REV = new AttributeName(ALL_NO_NS, SAME_LOCAL("rev"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SRC = new AttributeName(ALL_NO_NS, SAME_LOCAL("src"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName AXIS = new AttributeName(ALL_NO_NS, SAME_LOCAL("axis"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ABBR = new AttributeName(ALL_NO_NS, SAME_LOCAL("abbr"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName BBOX = new AttributeName(ALL_NO_NS, SAME_LOCAL("bbox"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CITE = new AttributeName(ALL_NO_NS, SAME_LOCAL("cite"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CODE = new AttributeName(ALL_NO_NS, SAME_LOCAL("code"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName BIAS = new AttributeName(ALL_NO_NS, SAME_LOCAL("bias"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COLS = new AttributeName(ALL_NO_NS, SAME_LOCAL("cols"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CLIP = new AttributeName(ALL_NO_NS, SAME_LOCAL("clip"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CHAR = new AttributeName(ALL_NO_NS, SAME_LOCAL("char"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName BASE = new AttributeName(ALL_NO_NS, SAME_LOCAL("base"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName EDGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("edge"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DATA = new AttributeName(ALL_NO_NS, SAME_LOCAL("data"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FILL = new AttributeName(ALL_NO_NS, SAME_LOCAL("fill"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FROM = new AttributeName(ALL_NO_NS, SAME_LOCAL("from"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FORM = new AttributeName(ALL_NO_NS, SAME_LOCAL("form"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("face"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HIGH = new AttributeName(ALL_NO_NS, SAME_LOCAL("high"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HREF = new AttributeName(ALL_NO_NS, SAME_LOCAL("href"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName OPEN = new AttributeName(ALL_NO_NS, SAME_LOCAL("open"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ICON = new AttributeName(ALL_NO_NS, SAME_LOCAL("icon"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName NAME = new AttributeName(ALL_NO_NS, SAME_LOCAL("name"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MODE = new AttributeName(ALL_NO_NS, SAME_LOCAL("mode"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MASK = new AttributeName(ALL_NO_NS, SAME_LOCAL("mask"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LINK = new AttributeName(ALL_NO_NS, SAME_LOCAL("link"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LANG = new AttributeName(LANG_NS, SAME_LOCAL("lang"), LANG_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LOOP = new AttributeName(ALL_NO_NS, SAME_LOCAL("loop"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LIST = new AttributeName(ALL_NO_NS, SAME_LOCAL("list"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TYPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("type"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName WHEN = new AttributeName(ALL_NO_NS, SAME_LOCAL("when"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName WRAP = new AttributeName(ALL_NO_NS, SAME_LOCAL("wrap"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TEXT = new AttributeName(ALL_NO_NS, SAME_LOCAL("text"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PATH = new AttributeName(ALL_NO_NS, SAME_LOCAL("path"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PING = new AttributeName(ALL_NO_NS, SAME_LOCAL("ping"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REFX = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("refx", "refX"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REFY = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("refy", "refY"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("size"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SEED = new AttributeName(ALL_NO_NS, SAME_LOCAL("seed"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ROWS = new AttributeName(ALL_NO_NS, SAME_LOCAL("rows"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SPAN = new AttributeName(ALL_NO_NS, SAME_LOCAL("span"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STEP = new AttributeName(ALL_NO_NS, SAME_LOCAL("step"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName ROLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("role"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName XREF = new AttributeName(ALL_NO_NS, SAME_LOCAL("xref"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ASYNC = new AttributeName(ALL_NO_NS, SAME_LOCAL("async"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName ALINK = new AttributeName(ALL_NO_NS, SAME_LOCAL("alink"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ALIGN = new AttributeName(ALL_NO_NS, SAME_LOCAL("align"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName CLOSE = new AttributeName(ALL_NO_NS, SAME_LOCAL("close"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COLOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("color"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CLASS = new AttributeName(ALL_NO_NS, SAME_LOCAL("class"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CLEAR = new AttributeName(ALL_NO_NS, SAME_LOCAL("clear"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName BEGIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("begin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DEPTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("depth"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DEFER = new AttributeName(ALL_NO_NS, SAME_LOCAL("defer"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName FENCE = new AttributeName(ALL_NO_NS, SAME_LOCAL("fence"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FRAME = new AttributeName(ALL_NO_NS, SAME_LOCAL("frame"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName ISMAP = new AttributeName(ALL_NO_NS, SAME_LOCAL("ismap"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName ONEND = new AttributeName(ALL_NO_NS, SAME_LOCAL("onend"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName INDEX = new AttributeName(ALL_NO_NS, SAME_LOCAL("index"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ORDER = new AttributeName(ALL_NO_NS, SAME_LOCAL("order"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName OTHER = new AttributeName(ALL_NO_NS, SAME_LOCAL("other"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONCUT = new AttributeName(ALL_NO_NS, SAME_LOCAL("oncut"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName NARGS = new AttributeName(ALL_NO_NS, SAME_LOCAL("nargs"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MEDIA = new AttributeName(ALL_NO_NS, SAME_LOCAL("media"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LABEL = new AttributeName(ALL_NO_NS, SAME_LOCAL("label"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LOCAL = new AttributeName(ALL_NO_NS, SAME_LOCAL("local"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName WIDTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("width"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TITLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("title"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VLINK = new AttributeName(ALL_NO_NS, SAME_LOCAL("vlink"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VALUE = new AttributeName(ALL_NO_NS, SAME_LOCAL("value"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SLOPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("slope"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SHAPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("shape"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName SCOPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("scope"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName SCALE = new AttributeName(ALL_NO_NS, SAME_LOCAL("scale"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SPEED = new AttributeName(ALL_NO_NS, SAME_LOCAL("speed"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STYLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("style"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName RULES = new AttributeName(ALL_NO_NS, SAME_LOCAL("rules"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName STEMH = new AttributeName(ALL_NO_NS, SAME_LOCAL("stemh"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SIZES = new AttributeName(ALL_NO_NS, SAME_LOCAL("sizes"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STEMV = new AttributeName(ALL_NO_NS, SAME_LOCAL("stemv"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName START = new AttributeName(ALL_NO_NS, SAME_LOCAL("start"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName XMLNS = new AttributeName(XMLNS_NS, SAME_LOCAL("xmlns"), ALL_NO_PREFIX, IS_XMLNS); - public static final AttributeName ACCEPT = new AttributeName(ALL_NO_NS, SAME_LOCAL("accept"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ACCENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("accent"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ASCENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("ascent"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ACTIVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("active"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName ALTIMG = new AttributeName(ALL_NO_NS, SAME_LOCAL("altimg"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ACTION = new AttributeName(ALL_NO_NS, SAME_LOCAL("action"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName BORDER = new AttributeName(ALL_NO_NS, SAME_LOCAL("border"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CURSOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("cursor"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COORDS = new AttributeName(ALL_NO_NS, SAME_LOCAL("coords"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FILTER = new AttributeName(ALL_NO_NS, SAME_LOCAL("filter"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FORMAT = new AttributeName(ALL_NO_NS, SAME_LOCAL("format"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HIDDEN = new AttributeName(ALL_NO_NS, SAME_LOCAL("hidden"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("hspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("height"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONMOVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmove"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONLOAD = new AttributeName(ALL_NO_NS, SAME_LOCAL("onload"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDRAG = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondrag"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ORIGIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("origin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONZOOM = new AttributeName(ALL_NO_NS, SAME_LOCAL("onzoom"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONHELP = new AttributeName(ALL_NO_NS, SAME_LOCAL("onhelp"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONSTOP = new AttributeName(ALL_NO_NS, SAME_LOCAL("onstop"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDROP = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondrop"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONBLUR = new AttributeName(ALL_NO_NS, SAME_LOCAL("onblur"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName OBJECT = new AttributeName(ALL_NO_NS, SAME_LOCAL("object"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName OFFSET = new AttributeName(ALL_NO_NS, SAME_LOCAL("offset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ORIENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("orient"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONCOPY = new AttributeName(ALL_NO_NS, SAME_LOCAL("oncopy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName NOWRAP = new AttributeName(ALL_NO_NS, SAME_LOCAL("nowrap"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName NOHREF = new AttributeName(ALL_NO_NS, SAME_LOCAL("nohref"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName MACROS = new AttributeName(ALL_NO_NS, SAME_LOCAL("macros"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName METHOD = new AttributeName(ALL_NO_NS, SAME_LOCAL("method"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName LOWSRC = new AttributeName(ALL_NO_NS, SAME_LOCAL("lowsrc"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("lspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LQUOTE = new AttributeName(ALL_NO_NS, SAME_LOCAL("lquote"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName USEMAP = new AttributeName(ALL_NO_NS, SAME_LOCAL("usemap"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName WIDTHS = new AttributeName(ALL_NO_NS, SAME_LOCAL("widths"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TARGET = new AttributeName(ALL_NO_NS, SAME_LOCAL("target"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VALUES = new AttributeName(ALL_NO_NS, SAME_LOCAL("values"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VALIGN = new AttributeName(ALL_NO_NS, SAME_LOCAL("valign"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName VSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("vspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName POSTER = new AttributeName(ALL_NO_NS, SAME_LOCAL("poster"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName POINTS = new AttributeName(ALL_NO_NS, SAME_LOCAL("points"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PROMPT = new AttributeName(ALL_NO_NS, SAME_LOCAL("prompt"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SRCDOC = new AttributeName(ALL_NO_NS, SAME_LOCAL("srcdoc"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SCOPED = new AttributeName(ALL_NO_NS, SAME_LOCAL("scoped"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STRING = new AttributeName(ALL_NO_NS, SAME_LOCAL("string"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SCHEME = new AttributeName(ALL_NO_NS, SAME_LOCAL("scheme"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STROKE = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName RADIUS = new AttributeName(ALL_NO_NS, SAME_LOCAL("radius"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName RESULT = new AttributeName(ALL_NO_NS, SAME_LOCAL("result"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REPEAT = new AttributeName(ALL_NO_NS, SAME_LOCAL("repeat"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SRCSET = new AttributeName(ALL_NO_NS, SAME_LOCAL("srcset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName RSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("rspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ROTATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("rotate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName RQUOTE = new AttributeName(ALL_NO_NS, SAME_LOCAL("rquote"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ALTTEXT = new AttributeName(ALL_NO_NS, SAME_LOCAL("alttext"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARCHIVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("archive"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName AZIMUTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("azimuth"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CLOSURE = new AttributeName(ALL_NO_NS, SAME_LOCAL("closure"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CHECKED = new AttributeName(ALL_NO_NS, SAME_LOCAL("checked"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName CLASSID = new AttributeName(ALL_NO_NS, SAME_LOCAL("classid"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CHAROFF = new AttributeName(ALL_NO_NS, SAME_LOCAL("charoff"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName BGCOLOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("bgcolor"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COLSPAN = new AttributeName(ALL_NO_NS, SAME_LOCAL("colspan"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CHARSET = new AttributeName(ALL_NO_NS, SAME_LOCAL("charset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COMPACT = new AttributeName(ALL_NO_NS, SAME_LOCAL("compact"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName CONTENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("content"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ENCTYPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("enctype"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName DATASRC = new AttributeName(ALL_NO_NS, SAME_LOCAL("datasrc"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DATAFLD = new AttributeName(ALL_NO_NS, SAME_LOCAL("datafld"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DECLARE = new AttributeName(ALL_NO_NS, SAME_LOCAL("declare"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName DISPLAY = new AttributeName(ALL_NO_NS, SAME_LOCAL("display"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DIVISOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("divisor"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DEFAULT = new AttributeName(ALL_NO_NS, SAME_LOCAL("default"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName DESCENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("descent"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName KERNING = new AttributeName(ALL_NO_NS, SAME_LOCAL("kerning"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HANGING = new AttributeName(ALL_NO_NS, SAME_LOCAL("hanging"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HEADERS = new AttributeName(ALL_NO_NS, SAME_LOCAL("headers"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONPASTE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onpaste"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONCLICK = new AttributeName(ALL_NO_NS, SAME_LOCAL("onclick"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName OPTIMUM = new AttributeName(ALL_NO_NS, SAME_LOCAL("optimum"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONBEGIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbegin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONKEYUP = new AttributeName(ALL_NO_NS, SAME_LOCAL("onkeyup"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONFOCUS = new AttributeName(ALL_NO_NS, SAME_LOCAL("onfocus"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONERROR = new AttributeName(ALL_NO_NS, SAME_LOCAL("onerror"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONINPUT = new AttributeName(ALL_NO_NS, SAME_LOCAL("oninput"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONABORT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onabort"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONSTART = new AttributeName(ALL_NO_NS, SAME_LOCAL("onstart"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONRESET = new AttributeName(ALL_NO_NS, SAME_LOCAL("onreset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName OPACITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("opacity"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName NOSHADE = new AttributeName(ALL_NO_NS, SAME_LOCAL("noshade"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName MINSIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("minsize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MAXSIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("maxsize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LARGEOP = new AttributeName(ALL_NO_NS, SAME_LOCAL("largeop"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName UNICODE = new AttributeName(ALL_NO_NS, SAME_LOCAL("unicode"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TARGETX = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("targetx", "targetX"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TARGETY = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("targety", "targetY"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VIEWBOX = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("viewbox", "viewBox"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VERSION = new AttributeName(ALL_NO_NS, SAME_LOCAL("version"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PATTERN = new AttributeName(ALL_NO_NS, SAME_LOCAL("pattern"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PROFILE = new AttributeName(ALL_NO_NS, SAME_LOCAL("profile"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("spacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName RESTART = new AttributeName(ALL_NO_NS, SAME_LOCAL("restart"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ROWSPAN = new AttributeName(ALL_NO_NS, SAME_LOCAL("rowspan"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SANDBOX = new AttributeName(ALL_NO_NS, SAME_LOCAL("sandbox"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SUMMARY = new AttributeName(ALL_NO_NS, SAME_LOCAL("summary"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STANDBY = new AttributeName(ALL_NO_NS, SAME_LOCAL("standby"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REPLACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("replace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName AUTOPLAY = new AttributeName(ALL_NO_NS, SAME_LOCAL("autoplay"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ADDITIVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("additive"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CALCMODE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("calcmode", "calcMode"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CODETYPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("codetype"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CODEBASE = new AttributeName(ALL_NO_NS, SAME_LOCAL("codebase"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CONTROLS = new AttributeName(ALL_NO_NS, SAME_LOCAL("controls"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName BEVELLED = new AttributeName(ALL_NO_NS, SAME_LOCAL("bevelled"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName BASELINE = new AttributeName(ALL_NO_NS, SAME_LOCAL("baseline"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName EXPONENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("exponent"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName EDGEMODE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("edgemode", "edgeMode"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ENCODING = new AttributeName(ALL_NO_NS, SAME_LOCAL("encoding"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName GLYPHREF = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("glyphref", "glyphRef"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DATETIME = new AttributeName(ALL_NO_NS, SAME_LOCAL("datetime"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DISABLED = new AttributeName(ALL_NO_NS, SAME_LOCAL("disabled"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName FONTSIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("fontsize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName KEYTIMES = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("keytimes", "keyTimes"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PANOSE_1 = new AttributeName(ALL_NO_NS, SAME_LOCAL("panose-1"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HREFLANG = new AttributeName(ALL_NO_NS, SAME_LOCAL("hreflang"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONRESIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onresize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONCHANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onchange"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONBOUNCE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbounce"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONUNLOAD = new AttributeName(ALL_NO_NS, SAME_LOCAL("onunload"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONFINISH = new AttributeName(ALL_NO_NS, SAME_LOCAL("onfinish"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONSCROLL = new AttributeName(ALL_NO_NS, SAME_LOCAL("onscroll"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName OPERATOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("operator"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName OVERFLOW = new AttributeName(ALL_NO_NS, SAME_LOCAL("overflow"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONSUBMIT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onsubmit"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONREPEAT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onrepeat"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONSELECT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onselect"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName NOTATION = new AttributeName(ALL_NO_NS, SAME_LOCAL("notation"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName NORESIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("noresize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName MANIFEST = new AttributeName(ALL_NO_NS, SAME_LOCAL("manifest"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MATHSIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("mathsize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MULTIPLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("multiple"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName LONGDESC = new AttributeName(ALL_NO_NS, SAME_LOCAL("longdesc"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LANGUAGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("language"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TEMPLATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("template"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TABINDEX = new AttributeName(ALL_NO_NS, SAME_LOCAL("tabindex"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PROPERTY = new AttributeName(ALL_NO_NS, SAME_LOCAL("property"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName READONLY = new AttributeName(ALL_NO_NS, SAME_LOCAL("readonly"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName SELECTED = new AttributeName(ALL_NO_NS, SAME_LOCAL("selected"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName ROWLINES = new AttributeName(ALL_NO_NS, SAME_LOCAL("rowlines"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SEAMLESS = new AttributeName(ALL_NO_NS, SAME_LOCAL("seamless"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ROWALIGN = new AttributeName(ALL_NO_NS, SAME_LOCAL("rowalign"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STRETCHY = new AttributeName(ALL_NO_NS, SAME_LOCAL("stretchy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REQUIRED = new AttributeName(ALL_NO_NS, SAME_LOCAL("required"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName XML_BASE = new AttributeName(XML_NS, COLONIFIED_LOCAL("xml:base", "base"), XML_PREFIX, NCNAME_FOREIGN); - public static final AttributeName XML_LANG = new AttributeName(XML_NS, COLONIFIED_LOCAL("xml:lang", "lang"), XML_PREFIX, NCNAME_FOREIGN); - public static final AttributeName X_HEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("x-height"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_OWNS = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-owns"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName AUTOFOCUS = new AttributeName(ALL_NO_NS, SAME_LOCAL("autofocus"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName ARIA_SORT = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-sort"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ACCESSKEY = new AttributeName(ALL_NO_NS, SAME_LOCAL("accesskey"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_BUSY = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-busy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_GRAB = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-grab"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName AMPLITUDE = new AttributeName(ALL_NO_NS, SAME_LOCAL("amplitude"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_LIVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-live"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CLIP_RULE = new AttributeName(ALL_NO_NS, SAME_LOCAL("clip-rule"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CLIP_PATH = new AttributeName(ALL_NO_NS, SAME_LOCAL("clip-path"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName EQUALROWS = new AttributeName(ALL_NO_NS, SAME_LOCAL("equalrows"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ELEVATION = new AttributeName(ALL_NO_NS, SAME_LOCAL("elevation"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DIRECTION = new AttributeName(ALL_NO_NS, SAME_LOCAL("direction"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DRAGGABLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("draggable"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FILL_RULE = new AttributeName(ALL_NO_NS, SAME_LOCAL("fill-rule"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FONTSTYLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("fontstyle"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FONT_SIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-size"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName KEYSYSTEM = new AttributeName(ALL_NO_NS, SAME_LOCAL("keysystem"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName KEYPOINTS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("keypoints", "keyPoints"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HIDEFOCUS = new AttributeName(ALL_NO_NS, SAME_LOCAL("hidefocus"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONMESSAGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmessage"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName INTERCEPT = new AttributeName(ALL_NO_NS, SAME_LOCAL("intercept"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDRAGEND = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondragend"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONMOVEEND = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmoveend"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONINVALID = new AttributeName(ALL_NO_NS, SAME_LOCAL("oninvalid"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName INTEGRITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("integrity"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONKEYDOWN = new AttributeName(ALL_NO_NS, SAME_LOCAL("onkeydown"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONFOCUSIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("onfocusin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONMOUSEUP = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmouseup"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName INPUTMODE = new AttributeName(ALL_NO_NS, SAME_LOCAL("inputmode"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONROWEXIT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onrowexit"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MATHCOLOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("mathcolor"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MASKUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("maskunits", "maskUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MAXLENGTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("maxlength"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LINEBREAK = new AttributeName(ALL_NO_NS, SAME_LOCAL("linebreak"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TRANSFORM = new AttributeName(ALL_NO_NS, SAME_LOCAL("transform"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName V_HANGING = new AttributeName(ALL_NO_NS, SAME_LOCAL("v-hanging"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VALUETYPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("valuetype"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName POINTSATZ = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("pointsatz", "pointsAtZ"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName POINTSATX = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("pointsatx", "pointsAtX"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName POINTSATY = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("pointsaty", "pointsAtY"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SYMMETRIC = new AttributeName(ALL_NO_NS, SAME_LOCAL("symmetric"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SCROLLING = new AttributeName(ALL_NO_NS, SAME_LOCAL("scrolling"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName REPEATDUR = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("repeatdur", "repeatDur"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SELECTION = new AttributeName(ALL_NO_NS, SAME_LOCAL("selection"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SEPARATOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("separator"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName XML_SPACE = new AttributeName(XML_NS, COLONIFIED_LOCAL("xml:space", "space"), XML_PREFIX, NCNAME_FOREIGN); - public static final AttributeName AUTOSUBMIT = new AttributeName(ALL_NO_NS, SAME_LOCAL("autosubmit"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN); - public static final AttributeName ALPHABETIC = new AttributeName(ALL_NO_NS, SAME_LOCAL("alphabetic"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ACTIONTYPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("actiontype"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ACCUMULATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("accumulate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_LEVEL = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-level"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COLUMNSPAN = new AttributeName(ALL_NO_NS, SAME_LOCAL("columnspan"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CAP_HEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("cap-height"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName BACKGROUND = new AttributeName(ALL_NO_NS, SAME_LOCAL("background"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName GLYPH_NAME = new AttributeName(ALL_NO_NS, SAME_LOCAL("glyph-name"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName GROUPALIGN = new AttributeName(ALL_NO_NS, SAME_LOCAL("groupalign"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FONTFAMILY = new AttributeName(ALL_NO_NS, SAME_LOCAL("fontfamily"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FONTWEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("fontweight"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FONT_STYLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-style"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName KEYSPLINES = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("keysplines", "keySplines"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HTTP_EQUIV = new AttributeName(ALL_NO_NS, SAME_LOCAL("http-equiv"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONACTIVATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onactivate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName OCCURRENCE = new AttributeName(ALL_NO_NS, SAME_LOCAL("occurrence"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName IRRELEVANT = new AttributeName(ALL_NO_NS, SAME_LOCAL("irrelevant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDBLCLICK = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondblclick"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDRAGDROP = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondragdrop"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONKEYPRESS = new AttributeName(ALL_NO_NS, SAME_LOCAL("onkeypress"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONROWENTER = new AttributeName(ALL_NO_NS, SAME_LOCAL("onrowenter"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDRAGOVER = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondragover"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONFOCUSOUT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onfocusout"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONMOUSEOUT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmouseout"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName NUMOCTAVES = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("numoctaves", "numOctaves"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MARKER_MID = new AttributeName(ALL_NO_NS, SAME_LOCAL("marker-mid"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MARKER_END = new AttributeName(ALL_NO_NS, SAME_LOCAL("marker-end"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TEXTLENGTH = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("textlength", "textLength"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VISIBILITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("visibility"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VIEWTARGET = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("viewtarget", "viewTarget"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VERT_ADV_Y = new AttributeName(ALL_NO_NS, SAME_LOCAL("vert-adv-y"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PATHLENGTH = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("pathlength", "pathLength"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REPEAT_MAX = new AttributeName(ALL_NO_NS, SAME_LOCAL("repeat-max"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName RADIOGROUP = new AttributeName(ALL_NO_NS, SAME_LOCAL("radiogroup"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STOP_COLOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("stop-color"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SEPARATORS = new AttributeName(ALL_NO_NS, SAME_LOCAL("separators"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REPEAT_MIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("repeat-min"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ROWSPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("rowspacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ZOOMANDPAN = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("zoomandpan", "zoomAndPan"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName XLINK_TYPE = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:type", "type"), XLINK_PREFIX, NCNAME_FOREIGN); - public static final AttributeName XLINK_ROLE = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:role", "role"), XLINK_PREFIX, NCNAME_FOREIGN); - public static final AttributeName XLINK_HREF = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:href", "href"), XLINK_PREFIX, NCNAME_FOREIGN); - public static final AttributeName XLINK_SHOW = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:show", "show"), XLINK_PREFIX, NCNAME_FOREIGN); - public static final AttributeName ACCENTUNDER = new AttributeName(ALL_NO_NS, SAME_LOCAL("accentunder"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_SECRET = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-secret"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_ATOMIC = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-atomic"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_HIDDEN = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-hidden"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_FLOWTO = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-flowto"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARABIC_FORM = new AttributeName(ALL_NO_NS, SAME_LOCAL("arabic-form"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CELLPADDING = new AttributeName(ALL_NO_NS, SAME_LOCAL("cellpadding"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CELLSPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("cellspacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COLUMNWIDTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("columnwidth"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CROSSORIGIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("crossorigin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COLUMNALIGN = new AttributeName(ALL_NO_NS, SAME_LOCAL("columnalign"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COLUMNLINES = new AttributeName(ALL_NO_NS, SAME_LOCAL("columnlines"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CONTEXTMENU = new AttributeName(ALL_NO_NS, SAME_LOCAL("contextmenu"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName BASEPROFILE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("baseprofile", "baseProfile"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FONT_FAMILY = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-family"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FRAMEBORDER = new AttributeName(ALL_NO_NS, SAME_LOCAL("frameborder"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FILTERUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("filterunits", "filterUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FLOOD_COLOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("flood-color"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FONT_WEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-weight"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HORIZ_ADV_X = new AttributeName(ALL_NO_NS, SAME_LOCAL("horiz-adv-x"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDRAGLEAVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondragleave"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONMOUSEMOVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmousemove"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ORIENTATION = new AttributeName(ALL_NO_NS, SAME_LOCAL("orientation"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONMOUSEDOWN = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmousedown"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONMOUSEOVER = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmouseover"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDRAGENTER = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondragenter"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName IDEOGRAPHIC = new AttributeName(ALL_NO_NS, SAME_LOCAL("ideographic"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONBEFORECUT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforecut"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONFORMINPUT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onforminput"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDRAGSTART = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondragstart"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONMOVESTART = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmovestart"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MARKERUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("markerunits", "markerUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MATHVARIANT = new AttributeName(ALL_NO_NS, SAME_LOCAL("mathvariant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MARGINWIDTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("marginwidth"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MARKERWIDTH = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("markerwidth", "markerWidth"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TEXT_ANCHOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("text-anchor"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TABLEVALUES = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("tablevalues", "tableValues"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SCRIPTLEVEL = new AttributeName(ALL_NO_NS, SAME_LOCAL("scriptlevel"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REPEATCOUNT = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("repeatcount", "repeatCount"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STITCHTILES = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("stitchtiles", "stitchTiles"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STARTOFFSET = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("startoffset", "startOffset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SCROLLDELAY = new AttributeName(ALL_NO_NS, SAME_LOCAL("scrolldelay"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName XMLNS_XLINK = new AttributeName(XMLNS_NS, COLONIFIED_LOCAL("xmlns:xlink", "xlink"), XMLNS_PREFIX, IS_XMLNS); - public static final AttributeName XLINK_TITLE = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:title", "title"), XLINK_PREFIX, NCNAME_FOREIGN); - public static final AttributeName ARIA_INVALID = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-invalid"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_PRESSED = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-pressed"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_CHECKED = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-checked"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName AUTOCOMPLETE = new AttributeName(ALL_NO_NS, SAME_LOCAL("autocomplete"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName ARIA_SETSIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-setsize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_CHANNEL = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-channel"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName EQUALCOLUMNS = new AttributeName(ALL_NO_NS, SAME_LOCAL("equalcolumns"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DISPLAYSTYLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("displaystyle"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DATAFORMATAS = new AttributeName(ALL_NO_NS, SAME_LOCAL("dataformatas"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED); - public static final AttributeName FILL_OPACITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("fill-opacity"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FONT_VARIANT = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-variant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FONT_STRETCH = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-stretch"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FRAMESPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("framespacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName KERNELMATRIX = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("kernelmatrix", "kernelMatrix"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDEACTIVATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondeactivate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONROWSDELETE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onrowsdelete"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONMOUSELEAVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmouseleave"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONFORMCHANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onformchange"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONCELLCHANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("oncellchange"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONMOUSEWHEEL = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmousewheel"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONMOUSEENTER = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmouseenter"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONAFTERPRINT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onafterprint"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONBEFORECOPY = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforecopy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MARGINHEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("marginheight"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MARKERHEIGHT = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("markerheight", "markerHeight"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MARKER_START = new AttributeName(ALL_NO_NS, SAME_LOCAL("marker-start"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MATHEMATICAL = new AttributeName(ALL_NO_NS, SAME_LOCAL("mathematical"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LENGTHADJUST = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("lengthadjust", "lengthAdjust"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName UNSELECTABLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("unselectable"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName UNICODE_BIDI = new AttributeName(ALL_NO_NS, SAME_LOCAL("unicode-bidi"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName UNITS_PER_EM = new AttributeName(ALL_NO_NS, SAME_LOCAL("units-per-em"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName WORD_SPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("word-spacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName WRITING_MODE = new AttributeName(ALL_NO_NS, SAME_LOCAL("writing-mode"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName V_ALPHABETIC = new AttributeName(ALL_NO_NS, SAME_LOCAL("v-alphabetic"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PATTERNUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("patternunits", "patternUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SPREADMETHOD = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("spreadmethod", "spreadMethod"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SURFACESCALE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("surfacescale", "surfaceScale"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STROKE_WIDTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-width"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REPEAT_START = new AttributeName(ALL_NO_NS, SAME_LOCAL("repeat-start"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STDDEVIATION = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("stddeviation", "stdDeviation"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STOP_OPACITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("stop-opacity"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_CONTROLS = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-controls"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_HASPOPUP = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-haspopup"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ACCENT_HEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("accent-height"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_VALUENOW = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-valuenow"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_RELEVANT = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-relevant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_POSINSET = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-posinset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_VALUEMAX = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-valuemax"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_READONLY = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-readonly"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_SELECTED = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-selected"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_REQUIRED = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-required"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_EXPANDED = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-expanded"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_DISABLED = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-disabled"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ATTRIBUTETYPE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("attributetype", "attributeType"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ATTRIBUTENAME = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("attributename", "attributeName"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_DATATYPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-datatype"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_VALUEMIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-valuemin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName BASEFREQUENCY = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("basefrequency", "baseFrequency"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COLUMNSPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("columnspacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COLOR_PROFILE = new AttributeName(ALL_NO_NS, SAME_LOCAL("color-profile"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CLIPPATHUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("clippathunits", "clipPathUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DEFINITIONURL = new AttributeName(ALL_NO_NS, MATH_DIFFERENT("definitionurl", "definitionURL"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName GRADIENTUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("gradientunits", "gradientUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FLOOD_OPACITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("flood-opacity"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONAFTERUPDATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onafterupdate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONERRORUPDATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onerrorupdate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONBEFOREPASTE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforepaste"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONLOSECAPTURE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onlosecapture"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONCONTEXTMENU = new AttributeName(ALL_NO_NS, SAME_LOCAL("oncontextmenu"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONSELECTSTART = new AttributeName(ALL_NO_NS, SAME_LOCAL("onselectstart"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONBEFOREPRINT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforeprint"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MOVABLELIMITS = new AttributeName(ALL_NO_NS, SAME_LOCAL("movablelimits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LINETHICKNESS = new AttributeName(ALL_NO_NS, SAME_LOCAL("linethickness"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName UNICODE_RANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("unicode-range"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName THINMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("thinmathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VERT_ORIGIN_X = new AttributeName(ALL_NO_NS, SAME_LOCAL("vert-origin-x"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VERT_ORIGIN_Y = new AttributeName(ALL_NO_NS, SAME_LOCAL("vert-origin-y"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName V_IDEOGRAPHIC = new AttributeName(ALL_NO_NS, SAME_LOCAL("v-ideographic"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PRESERVEALPHA = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("preservealpha", "preserveAlpha"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SCRIPTMINSIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("scriptminsize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SPECIFICATION = new AttributeName(ALL_NO_NS, SAME_LOCAL("specification"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName XLINK_ACTUATE = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:actuate", "actuate"), XLINK_PREFIX, NCNAME_FOREIGN); - public static final AttributeName XLINK_ARCROLE = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:arcrole", "arcrole"), XLINK_PREFIX, NCNAME_FOREIGN); - public static final AttributeName ACCEPT_CHARSET = new AttributeName(ALL_NO_NS, SAME_LOCAL("accept-charset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ALIGNMENTSCOPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("alignmentscope"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_MULTILINE = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-multiline"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName BASELINE_SHIFT = new AttributeName(ALL_NO_NS, SAME_LOCAL("baseline-shift"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HORIZ_ORIGIN_X = new AttributeName(ALL_NO_NS, SAME_LOCAL("horiz-origin-x"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName HORIZ_ORIGIN_Y = new AttributeName(ALL_NO_NS, SAME_LOCAL("horiz-origin-y"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONBEFOREUPDATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforeupdate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONFILTERCHANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onfilterchange"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONROWSINSERTED = new AttributeName(ALL_NO_NS, SAME_LOCAL("onrowsinserted"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONBEFOREUNLOAD = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforeunload"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MATHBACKGROUND = new AttributeName(ALL_NO_NS, SAME_LOCAL("mathbackground"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LETTER_SPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("letter-spacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LIGHTING_COLOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("lighting-color"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName THICKMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("thickmathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TEXT_RENDERING = new AttributeName(ALL_NO_NS, SAME_LOCAL("text-rendering"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName V_MATHEMATICAL = new AttributeName(ALL_NO_NS, SAME_LOCAL("v-mathematical"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName POINTER_EVENTS = new AttributeName(ALL_NO_NS, SAME_LOCAL("pointer-events"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PRIMITIVEUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("primitiveunits", "primitiveUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REFERRERPOLICY = new AttributeName(ALL_NO_NS, SAME_LOCAL("referrerpolicy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SYSTEMLANGUAGE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("systemlanguage", "systemLanguage"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STROKE_LINECAP = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-linecap"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SUBSCRIPTSHIFT = new AttributeName(ALL_NO_NS, SAME_LOCAL("subscriptshift"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STROKE_OPACITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-opacity"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_DROPEFFECT = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-dropeffect"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_LABELLEDBY = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-labelledby"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_TEMPLATEID = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-templateid"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COLOR_RENDERING = new AttributeName(ALL_NO_NS, SAME_LOCAL("color-rendering"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName CONTENTEDITABLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("contenteditable"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DIFFUSECONSTANT = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("diffuseconstant", "diffuseConstant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDATAAVAILABLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondataavailable"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONCONTROLSELECT = new AttributeName(ALL_NO_NS, SAME_LOCAL("oncontrolselect"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName IMAGE_RENDERING = new AttributeName(ALL_NO_NS, SAME_LOCAL("image-rendering"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MEDIUMMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("mediummathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName TEXT_DECORATION = new AttributeName(ALL_NO_NS, SAME_LOCAL("text-decoration"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SHAPE_RENDERING = new AttributeName(ALL_NO_NS, SAME_LOCAL("shape-rendering"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STROKE_LINEJOIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-linejoin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REPEAT_TEMPLATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("repeat-template"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_DESCRIBEDBY = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-describedby"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName FONT_SIZE_ADJUST = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-size-adjust"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName KERNELUNITLENGTH = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("kernelunitlength", "kernelUnitLength"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONBEFOREACTIVATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforeactivate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONPROPERTYCHANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onpropertychange"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDATASETCHANGED = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondatasetchanged"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName MASKCONTENTUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("maskcontentunits", "maskContentUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PATTERNTRANSFORM = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("patterntransform", "patternTransform"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REQUIREDFEATURES = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("requiredfeatures", "requiredFeatures"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName RENDERING_INTENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("rendering-intent"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SPECULAREXPONENT = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("specularexponent", "specularExponent"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SPECULARCONSTANT = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("specularconstant", "specularConstant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SUPERSCRIPTSHIFT = new AttributeName(ALL_NO_NS, SAME_LOCAL("superscriptshift"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STROKE_DASHARRAY = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-dasharray"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName XCHANNELSELECTOR = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("xchannelselector", "xChannelSelector"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName YCHANNELSELECTOR = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("ychannelselector", "yChannelSelector"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_AUTOCOMPLETE = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-autocomplete"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ENABLE_BACKGROUND = new AttributeName(ALL_NO_NS, SAME_LOCAL("enable-background"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName DOMINANT_BASELINE = new AttributeName(ALL_NO_NS, SAME_LOCAL("dominant-baseline"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName GRADIENTTRANSFORM = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("gradienttransform", "gradientTransform"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONBEFORDEACTIVATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbefordeactivate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONDATASETCOMPLETE = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondatasetcomplete"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName OVERLINE_POSITION = new AttributeName(ALL_NO_NS, SAME_LOCAL("overline-position"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONBEFOREEDITFOCUS = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforeeditfocus"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName LIMITINGCONEANGLE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("limitingconeangle", "limitingConeAngle"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VERYTHINMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("verythinmathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STROKE_DASHOFFSET = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-dashoffset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STROKE_MITERLIMIT = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-miterlimit"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ALIGNMENT_BASELINE = new AttributeName(ALL_NO_NS, SAME_LOCAL("alignment-baseline"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ONREADYSTATECHANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onreadystatechange"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName OVERLINE_THICKNESS = new AttributeName(ALL_NO_NS, SAME_LOCAL("overline-thickness"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName UNDERLINE_POSITION = new AttributeName(ALL_NO_NS, SAME_LOCAL("underline-position"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VERYTHICKMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("verythickmathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName REQUIREDEXTENSIONS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("requiredextensions", "requiredExtensions"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COLOR_INTERPOLATION = new AttributeName(ALL_NO_NS, SAME_LOCAL("color-interpolation"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName UNDERLINE_THICKNESS = new AttributeName(ALL_NO_NS, SAME_LOCAL("underline-thickness"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PRESERVEASPECTRATIO = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("preserveaspectratio", "preserveAspectRatio"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName PATTERNCONTENTUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("patterncontentunits", "patternContentUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_MULTISELECTABLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-multiselectable"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName SCRIPTSIZEMULTIPLIER = new AttributeName(ALL_NO_NS, SAME_LOCAL("scriptsizemultiplier"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName ARIA_ACTIVEDESCENDANT = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-activedescendant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VERYVERYTHINMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("veryverythinmathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName VERYVERYTHICKMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("veryverythickmathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STRIKETHROUGH_POSITION = new AttributeName(ALL_NO_NS, SAME_LOCAL("strikethrough-position"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName STRIKETHROUGH_THICKNESS = new AttributeName(ALL_NO_NS, SAME_LOCAL("strikethrough-thickness"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName GLYPH_ORIENTATION_VERTICAL = new AttributeName(ALL_NO_NS, SAME_LOCAL("glyph-orientation-vertical"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName COLOR_INTERPOLATION_FILTERS = new AttributeName(ALL_NO_NS, SAME_LOCAL("color-interpolation-filters"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - public static final AttributeName GLYPH_ORIENTATION_HORIZONTAL = new AttributeName(ALL_NO_NS, SAME_LOCAL("glyph-orientation-horizontal"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG); - private final static @NoLength AttributeName[] ATTRIBUTE_NAMES = { - D, - K, - R, - X, - Y, - Z, - BY, - CX, - CY, - DX, - DY, - G2, - G1, - FX, - FY, - K4, - K2, - K3, - K1, - ID, - IN, - U2, - U1, - RT, - RX, - RY, - TO, - Y2, - Y1, - X1, - X2, - ALT, - DIR, - DUR, - END, - FOR, - IN2, - MAX, - MIN, - LOW, - REL, - REV, - SRC, - AXIS, - ABBR, - BBOX, - CITE, - CODE, - BIAS, - COLS, - CLIP, - CHAR, - BASE, - EDGE, - DATA, - FILL, - FROM, - FORM, - FACE, - HIGH, - HREF, - OPEN, - ICON, - NAME, - MODE, - MASK, - LINK, - LANG, - LOOP, - LIST, - TYPE, - WHEN, - WRAP, - TEXT, - PATH, - PING, - REFX, - REFY, - SIZE, - SEED, - ROWS, - SPAN, - STEP, - ROLE, - XREF, - ASYNC, - ALINK, - ALIGN, - CLOSE, - COLOR, - CLASS, - CLEAR, - BEGIN, - DEPTH, - DEFER, - FENCE, - FRAME, - ISMAP, - ONEND, - INDEX, - ORDER, - OTHER, - ONCUT, - NARGS, - MEDIA, - LABEL, - LOCAL, - WIDTH, - TITLE, - VLINK, - VALUE, - SLOPE, - SHAPE, - SCOPE, - SCALE, - SPEED, - STYLE, - RULES, - STEMH, - SIZES, - STEMV, - START, - XMLNS, - ACCEPT, - ACCENT, - ASCENT, - ACTIVE, - ALTIMG, - ACTION, - BORDER, - CURSOR, - COORDS, - FILTER, - FORMAT, - HIDDEN, - HSPACE, - HEIGHT, - ONMOVE, - ONLOAD, - ONDRAG, - ORIGIN, - ONZOOM, - ONHELP, - ONSTOP, - ONDROP, - ONBLUR, - OBJECT, - OFFSET, - ORIENT, - ONCOPY, - NOWRAP, - NOHREF, - MACROS, - METHOD, - LOWSRC, - LSPACE, - LQUOTE, - USEMAP, - WIDTHS, - TARGET, - VALUES, - VALIGN, - VSPACE, - POSTER, - POINTS, - PROMPT, - SRCDOC, - SCOPED, - STRING, - SCHEME, - STROKE, - RADIUS, - RESULT, - REPEAT, - SRCSET, - RSPACE, - ROTATE, - RQUOTE, - ALTTEXT, - ARCHIVE, - AZIMUTH, - CLOSURE, - CHECKED, - CLASSID, - CHAROFF, - BGCOLOR, - COLSPAN, - CHARSET, - COMPACT, - CONTENT, - ENCTYPE, - DATASRC, - DATAFLD, - DECLARE, - DISPLAY, - DIVISOR, - DEFAULT, - DESCENT, - KERNING, - HANGING, - HEADERS, - ONPASTE, - ONCLICK, - OPTIMUM, - ONBEGIN, - ONKEYUP, - ONFOCUS, - ONERROR, - ONINPUT, - ONABORT, - ONSTART, - ONRESET, - OPACITY, - NOSHADE, - MINSIZE, - MAXSIZE, - LARGEOP, - UNICODE, - TARGETX, - TARGETY, - VIEWBOX, - VERSION, - PATTERN, - PROFILE, - SPACING, - RESTART, - ROWSPAN, - SANDBOX, - SUMMARY, - STANDBY, - REPLACE, - AUTOPLAY, - ADDITIVE, - CALCMODE, - CODETYPE, - CODEBASE, - CONTROLS, - BEVELLED, - BASELINE, - EXPONENT, - EDGEMODE, - ENCODING, - GLYPHREF, - DATETIME, - DISABLED, - FONTSIZE, - KEYTIMES, - PANOSE_1, - HREFLANG, - ONRESIZE, - ONCHANGE, - ONBOUNCE, - ONUNLOAD, - ONFINISH, - ONSCROLL, - OPERATOR, - OVERFLOW, - ONSUBMIT, - ONREPEAT, - ONSELECT, - NOTATION, - NORESIZE, - MANIFEST, - MATHSIZE, - MULTIPLE, - LONGDESC, - LANGUAGE, - TEMPLATE, - TABINDEX, - PROPERTY, - READONLY, - SELECTED, - ROWLINES, - SEAMLESS, - ROWALIGN, - STRETCHY, - REQUIRED, - XML_BASE, - XML_LANG, - X_HEIGHT, - ARIA_OWNS, - AUTOFOCUS, - ARIA_SORT, - ACCESSKEY, - ARIA_BUSY, - ARIA_GRAB, - AMPLITUDE, - ARIA_LIVE, - CLIP_RULE, - CLIP_PATH, - EQUALROWS, - ELEVATION, - DIRECTION, - DRAGGABLE, - FILL_RULE, - FONTSTYLE, - FONT_SIZE, - KEYSYSTEM, - KEYPOINTS, - HIDEFOCUS, - ONMESSAGE, - INTERCEPT, - ONDRAGEND, - ONMOVEEND, - ONINVALID, - INTEGRITY, - ONKEYDOWN, - ONFOCUSIN, - ONMOUSEUP, - INPUTMODE, - ONROWEXIT, - MATHCOLOR, - MASKUNITS, - MAXLENGTH, - LINEBREAK, - TRANSFORM, - V_HANGING, - VALUETYPE, - POINTSATZ, - POINTSATX, - POINTSATY, - SYMMETRIC, - SCROLLING, - REPEATDUR, - SELECTION, - SEPARATOR, - XML_SPACE, - AUTOSUBMIT, - ALPHABETIC, - ACTIONTYPE, - ACCUMULATE, - ARIA_LEVEL, - COLUMNSPAN, - CAP_HEIGHT, - BACKGROUND, - GLYPH_NAME, - GROUPALIGN, - FONTFAMILY, - FONTWEIGHT, - FONT_STYLE, - KEYSPLINES, - HTTP_EQUIV, - ONACTIVATE, - OCCURRENCE, - IRRELEVANT, - ONDBLCLICK, - ONDRAGDROP, - ONKEYPRESS, - ONROWENTER, - ONDRAGOVER, - ONFOCUSOUT, - ONMOUSEOUT, - NUMOCTAVES, - MARKER_MID, - MARKER_END, - TEXTLENGTH, - VISIBILITY, - VIEWTARGET, - VERT_ADV_Y, - PATHLENGTH, - REPEAT_MAX, - RADIOGROUP, - STOP_COLOR, - SEPARATORS, - REPEAT_MIN, - ROWSPACING, - ZOOMANDPAN, - XLINK_TYPE, - XLINK_ROLE, - XLINK_HREF, - XLINK_SHOW, - ACCENTUNDER, - ARIA_SECRET, - ARIA_ATOMIC, - ARIA_HIDDEN, - ARIA_FLOWTO, - ARABIC_FORM, - CELLPADDING, - CELLSPACING, - COLUMNWIDTH, - CROSSORIGIN, - COLUMNALIGN, - COLUMNLINES, - CONTEXTMENU, - BASEPROFILE, - FONT_FAMILY, - FRAMEBORDER, - FILTERUNITS, - FLOOD_COLOR, - FONT_WEIGHT, - HORIZ_ADV_X, - ONDRAGLEAVE, - ONMOUSEMOVE, - ORIENTATION, - ONMOUSEDOWN, - ONMOUSEOVER, - ONDRAGENTER, - IDEOGRAPHIC, - ONBEFORECUT, - ONFORMINPUT, - ONDRAGSTART, - ONMOVESTART, - MARKERUNITS, - MATHVARIANT, - MARGINWIDTH, - MARKERWIDTH, - TEXT_ANCHOR, - TABLEVALUES, - SCRIPTLEVEL, - REPEATCOUNT, - STITCHTILES, - STARTOFFSET, - SCROLLDELAY, - XMLNS_XLINK, - XLINK_TITLE, - ARIA_INVALID, - ARIA_PRESSED, - ARIA_CHECKED, - AUTOCOMPLETE, - ARIA_SETSIZE, - ARIA_CHANNEL, - EQUALCOLUMNS, - DISPLAYSTYLE, - DATAFORMATAS, - FILL_OPACITY, - FONT_VARIANT, - FONT_STRETCH, - FRAMESPACING, - KERNELMATRIX, - ONDEACTIVATE, - ONROWSDELETE, - ONMOUSELEAVE, - ONFORMCHANGE, - ONCELLCHANGE, - ONMOUSEWHEEL, - ONMOUSEENTER, - ONAFTERPRINT, - ONBEFORECOPY, - MARGINHEIGHT, - MARKERHEIGHT, - MARKER_START, - MATHEMATICAL, - LENGTHADJUST, - UNSELECTABLE, - UNICODE_BIDI, - UNITS_PER_EM, - WORD_SPACING, - WRITING_MODE, - V_ALPHABETIC, - PATTERNUNITS, - SPREADMETHOD, - SURFACESCALE, - STROKE_WIDTH, - REPEAT_START, - STDDEVIATION, - STOP_OPACITY, - ARIA_CONTROLS, - ARIA_HASPOPUP, - ACCENT_HEIGHT, - ARIA_VALUENOW, - ARIA_RELEVANT, - ARIA_POSINSET, - ARIA_VALUEMAX, - ARIA_READONLY, - ARIA_SELECTED, - ARIA_REQUIRED, - ARIA_EXPANDED, - ARIA_DISABLED, - ATTRIBUTETYPE, - ATTRIBUTENAME, - ARIA_DATATYPE, - ARIA_VALUEMIN, - BASEFREQUENCY, - COLUMNSPACING, - COLOR_PROFILE, - CLIPPATHUNITS, - DEFINITIONURL, - GRADIENTUNITS, - FLOOD_OPACITY, - ONAFTERUPDATE, - ONERRORUPDATE, - ONBEFOREPASTE, - ONLOSECAPTURE, - ONCONTEXTMENU, - ONSELECTSTART, - ONBEFOREPRINT, - MOVABLELIMITS, - LINETHICKNESS, - UNICODE_RANGE, - THINMATHSPACE, - VERT_ORIGIN_X, - VERT_ORIGIN_Y, - V_IDEOGRAPHIC, - PRESERVEALPHA, - SCRIPTMINSIZE, - SPECIFICATION, - XLINK_ACTUATE, - XLINK_ARCROLE, - ACCEPT_CHARSET, - ALIGNMENTSCOPE, - ARIA_MULTILINE, - BASELINE_SHIFT, - HORIZ_ORIGIN_X, - HORIZ_ORIGIN_Y, - ONBEFOREUPDATE, - ONFILTERCHANGE, - ONROWSINSERTED, - ONBEFOREUNLOAD, - MATHBACKGROUND, - LETTER_SPACING, - LIGHTING_COLOR, - THICKMATHSPACE, - TEXT_RENDERING, - V_MATHEMATICAL, - POINTER_EVENTS, - PRIMITIVEUNITS, - REFERRERPOLICY, - SYSTEMLANGUAGE, - STROKE_LINECAP, - SUBSCRIPTSHIFT, - STROKE_OPACITY, - ARIA_DROPEFFECT, - ARIA_LABELLEDBY, - ARIA_TEMPLATEID, - COLOR_RENDERING, - CONTENTEDITABLE, - DIFFUSECONSTANT, - ONDATAAVAILABLE, - ONCONTROLSELECT, - IMAGE_RENDERING, - MEDIUMMATHSPACE, - TEXT_DECORATION, - SHAPE_RENDERING, - STROKE_LINEJOIN, - REPEAT_TEMPLATE, - ARIA_DESCRIBEDBY, - FONT_SIZE_ADJUST, - KERNELUNITLENGTH, - ONBEFOREACTIVATE, - ONPROPERTYCHANGE, - ONDATASETCHANGED, - MASKCONTENTUNITS, - PATTERNTRANSFORM, - REQUIREDFEATURES, - RENDERING_INTENT, - SPECULAREXPONENT, - SPECULARCONSTANT, - SUPERSCRIPTSHIFT, - STROKE_DASHARRAY, - XCHANNELSELECTOR, - YCHANNELSELECTOR, - ARIA_AUTOCOMPLETE, - ENABLE_BACKGROUND, - DOMINANT_BASELINE, - GRADIENTTRANSFORM, - ONBEFORDEACTIVATE, - ONDATASETCOMPLETE, - OVERLINE_POSITION, - ONBEFOREEDITFOCUS, - LIMITINGCONEANGLE, - VERYTHINMATHSPACE, - STROKE_DASHOFFSET, - STROKE_MITERLIMIT, - ALIGNMENT_BASELINE, - ONREADYSTATECHANGE, - OVERLINE_THICKNESS, - UNDERLINE_POSITION, - VERYTHICKMATHSPACE, - REQUIREDEXTENSIONS, - COLOR_INTERPOLATION, - UNDERLINE_THICKNESS, - PRESERVEASPECTRATIO, - PATTERNCONTENTUNITS, - ARIA_MULTISELECTABLE, - SCRIPTSIZEMULTIPLIER, - ARIA_ACTIVEDESCENDANT, - VERYVERYTHINMATHSPACE, - VERYVERYTHICKMATHSPACE, - STRIKETHROUGH_POSITION, - STRIKETHROUGH_THICKNESS, - GLYPH_ORIENTATION_VERTICAL, - COLOR_INTERPOLATION_FILTERS, - GLYPH_ORIENTATION_HORIZONTAL, - }; - private final static int[] ATTRIBUTE_HASHES = { - 1153, - 1383, - 1601, - 1793, - 1827, - 1857, - 68600, - 69146, - 69177, - 70237, - 70270, - 71572, - 71669, - 72415, - 72444, - 74846, - 74904, - 74943, - 75001, - 75276, - 75590, - 84742, - 84839, - 85575, - 85963, - 85992, - 87204, - 88074, - 88171, - 89130, - 89163, - 3207892, - 3283895, - 3284791, - 3338752, - 3358197, - 3369562, - 3539124, - 3562402, - 3574260, - 3670335, - 3696933, - 3721879, - 135280021, - 135346322, - 136317019, - 136475749, - 136548517, - 136652214, - 136884919, - 136902418, - 136942992, - 137292068, - 139120259, - 139785574, - 142250603, - 142314056, - 142331176, - 142519584, - 144752417, - 145106895, - 146147200, - 146765926, - 148805544, - 149655723, - 149809441, - 150018784, - 150445028, - 150813181, - 150923321, - 152528754, - 152536216, - 152647366, - 152962785, - 155219321, - 155654904, - 157317483, - 157350248, - 157437941, - 157447478, - 157604838, - 157685404, - 157894402, - 158315188, - 166078431, - 169409980, - 169700259, - 169856932, - 170007032, - 170409695, - 170466488, - 170513710, - 170608367, - 173028944, - 173896963, - 176090625, - 176129212, - 179390001, - 179489057, - 179627464, - 179840468, - 179849042, - 180004216, - 181779081, - 183027151, - 183645319, - 183698797, - 185922012, - 185997252, - 188312483, - 188675799, - 190977533, - 190992569, - 191006194, - 191033518, - 191038774, - 191096249, - 191166163, - 191194426, - 191443343, - 191522106, - 191568039, - 200104642, - 202506661, - 202537381, - 202602917, - 203070590, - 203120766, - 203389054, - 203690071, - 203971238, - 203986524, - 209040857, - 209125756, - 212055489, - 212322418, - 212746849, - 213002877, - 213055164, - 213088023, - 213259873, - 213273386, - 213435118, - 213437318, - 213438231, - 213493071, - 213532268, - 213542834, - 213584431, - 213659891, - 215285828, - 215880731, - 216112976, - 216684637, - 217369699, - 217565298, - 217576549, - 218186795, - 219743185, - 220082234, - 221623802, - 221986406, - 222283890, - 223089542, - 223138630, - 223311265, - 224431494, - 224547358, - 224587256, - 224589550, - 224655650, - 224785518, - 224810917, - 224813302, - 225126263, - 225429618, - 225432950, - 225440869, - 236107233, - 236709921, - 236838947, - 237117095, - 237143271, - 237172455, - 237209953, - 237354143, - 237372743, - 237668065, - 237703073, - 237714273, - 239743521, - 240512803, - 240522627, - 240560417, - 240656513, - 241015715, - 241062755, - 241065383, - 243523041, - 245865199, - 246261793, - 246556195, - 246774817, - 246923491, - 246928419, - 246981667, - 247014847, - 247058369, - 247112833, - 247118177, - 247119137, - 247128739, - 247316903, - 249533729, - 250235623, - 250269543, - 251402351, - 252339047, - 253260911, - 253293679, - 254844367, - 255547879, - 256077281, - 256345377, - 258124199, - 258354465, - 258605063, - 258744193, - 258845603, - 258856961, - 258926689, - 269869248, - 270174334, - 270709417, - 270778994, - 270781796, - 271102503, - 271478858, - 271490090, - 272870654, - 273335275, - 273369140, - 273924313, - 274108530, - 274116736, - 276818662, - 277476156, - 279156579, - 279349675, - 280108533, - 280128712, - 280132869, - 280162403, - 280280292, - 280413430, - 280506130, - 280677397, - 280678580, - 280686710, - 280689066, - 282736758, - 283110901, - 283275116, - 283823226, - 283890012, - 284479340, - 284606461, - 286700477, - 286798916, - 290055764, - 291557706, - 291665349, - 291804100, - 292138018, - 292166446, - 292418738, - 292451039, - 300298041, - 300374839, - 300597935, - 303073389, - 303083839, - 303266673, - 303354997, - 303430688, - 303576261, - 303724281, - 303819694, - 304242723, - 304382625, - 306247792, - 307227811, - 307468786, - 307724489, - 310252031, - 310358241, - 310373094, - 310833159, - 311015256, - 313357609, - 313683893, - 313701861, - 313706996, - 313707317, - 313710350, - 313795700, - 314027746, - 314038181, - 314091299, - 314205627, - 314233813, - 316741830, - 316797986, - 317486755, - 317794164, - 320076137, - 322657125, - 322887778, - 323506876, - 323572412, - 323605180, - 325060058, - 325320188, - 325398738, - 325541490, - 325671619, - 333868843, - 336806130, - 337212108, - 337282686, - 337285434, - 337585223, - 338036037, - 338298087, - 338566051, - 340943551, - 341190970, - 342995704, - 343352124, - 343912673, - 344585053, - 346977248, - 347218098, - 347262163, - 347278576, - 347438191, - 347655959, - 347684788, - 347726430, - 347727772, - 347776035, - 347776629, - 349500753, - 350880161, - 350887073, - 353384123, - 355496998, - 355906922, - 355979793, - 356545959, - 358637867, - 358905016, - 359164318, - 359247286, - 359350571, - 359579447, - 365560330, - 367399355, - 367420285, - 367510727, - 368013212, - 370234760, - 370353345, - 370710317, - 371074566, - 371122285, - 371194213, - 371448425, - 371448430, - 371545055, - 371593469, - 371596922, - 371758751, - 371964792, - 372151328, - 376550136, - 376710172, - 376795771, - 376826271, - 376906556, - 380514830, - 380774774, - 380775037, - 381030322, - 381136500, - 381281631, - 381282269, - 381285504, - 381330595, - 381331422, - 381335911, - 381336484, - 383907298, - 383917408, - 384595009, - 384595013, - 387799894, - 387823201, - 392581647, - 392584937, - 392742684, - 392906485, - 393003349, - 400644707, - 400973830, - 404428547, - 404432113, - 404432865, - 404469244, - 404478897, - 404694860, - 406887479, - 408294949, - 408789955, - 410022510, - 410467324, - 410586448, - 410945965, - 411845275, - 414327152, - 414327932, - 414329781, - 414346257, - 414346439, - 414639928, - 414835998, - 414894517, - 414986533, - 417465377, - 417465381, - 417492216, - 418259232, - 419310946, - 420103495, - 420242342, - 420380455, - 420658662, - 420717432, - 423183880, - 424539259, - 425929170, - 425972964, - 426050649, - 426126450, - 426142833, - 426607922, - 437289840, - 437347469, - 437412335, - 437423943, - 437455540, - 437462252, - 437597991, - 437617485, - 437986305, - 437986507, - 437986828, - 437987072, - 438015591, - 438034813, - 438038966, - 438179623, - 438347971, - 438483573, - 438547062, - 438895551, - 441592676, - 442032555, - 443548979, - 447881379, - 447881655, - 447881895, - 447887844, - 448416189, - 448445746, - 448449012, - 450942191, - 452816744, - 453668677, - 454434495, - 456610076, - 456642844, - 456738709, - 457544600, - 459451897, - 459680944, - 468058810, - 468083581, - 470964084, - 471470955, - 471567278, - 472267822, - 481177859, - 481210627, - 481435874, - 481455115, - 481485378, - 481490218, - 485105638, - 486005878, - 486383494, - 487988916, - 488103783, - 490661867, - 491574090, - 491578272, - 492891370, - 493041952, - 493441205, - 493582844, - 493716979, - 504577572, - 504740359, - 505091638, - 505592418, - 505656212, - 509516275, - 514998531, - 515571132, - 515594682, - 518712698, - 521362273, - 526592419, - 526807354, - 527348842, - 538294791, - 544689535, - 545535009, - 548544752, - 548563346, - 548595116, - 551679010, - 558034099, - 560329411, - 560356209, - 560671018, - 560671152, - 560692590, - 560845442, - 569212097, - 569474241, - 572252718, - 575326764, - 576174758, - 576190819, - 582099184, - 582099438, - 582372519, - 582558889, - 586552164, - 591325418, - 594231990, - 594243961, - 605711268, - 615672071, - 616086845, - 621792370, - 624879850, - 627432831, - 640040548, - 654392808, - 658675477, - 659420283, - 672891587, - 694768102, - 705890982, - 725543146, - 759097578, - 761686526, - 795383908, - 878105336, - 908643300, - 945213471, - }; -} diff --git a/parser/html/javasrc/ElementName.java b/parser/html/javasrc/ElementName.java deleted file mode 100644 index ee551a737..000000000 --- a/parser/html/javasrc/ElementName.java +++ /dev/null @@ -1,1609 +0,0 @@ -/* - * Copyright (c) 2008-2014 Mozilla Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -package nu.validator.htmlparser.impl; - -import java.util.Arrays; - -import nu.validator.htmlparser.annotation.Inline; -import nu.validator.htmlparser.annotation.Local; -import nu.validator.htmlparser.annotation.NoLength; -import nu.validator.htmlparser.annotation.Virtual; -import nu.validator.htmlparser.common.Interner; - -public final class ElementName -// uncomment when regenerating self -// implements Comparable -{ - - /** - * The mask for extracting the dispatch group. - */ - public static final int GROUP_MASK = 127; - - /** - * Indicates that the element is not a pre-interned element. Forbidden - * on preinterned elements. - */ - public static final int CUSTOM = (1 << 30); - - /** - * Indicates that the element is in the "special" category. This bit - * should not be pre-set on MathML or SVG specials--only on HTML specials. - */ - public static final int SPECIAL = (1 << 29); - - /** - * The element is foster-parenting. This bit should be pre-set on elements - * that are foster-parenting as HTML. - */ - public static final int FOSTER_PARENTING = (1 << 28); - - /** - * The element is scoping. This bit should be pre-set on elements - * that are scoping as HTML. - */ - public static final int SCOPING = (1 << 27); - - /** - * The element is scoping as SVG. - */ - public static final int SCOPING_AS_SVG = (1 << 26); - - /** - * The element is scoping as MathML. - */ - public static final int SCOPING_AS_MATHML = (1 << 25); - - /** - * The element is an HTML integration point. - */ - public static final int HTML_INTEGRATION_POINT = (1 << 24); - - /** - * The element has an optional end tag. - */ - public static final int OPTIONAL_END_TAG = (1 << 23); - - public static final ElementName NULL_ELEMENT_NAME = new ElementName(null); - - public final @Local String name; - - public final @Local String camelCaseName; - - /** - * The lowest 7 bits are the dispatch group. The high bits are flags. - */ - public final int flags; - - @Inline public int getFlags() { - return flags; - } - - public int getGroup() { - return flags & GROUP_MASK; - } - - public boolean isCustom() { - return (flags & CUSTOM) != 0; - } - - static ElementName elementNameByBuffer(@NoLength char[] buf, int offset, int length, Interner interner) { - int hash = ElementName.bufToHash(buf, length); - int index = Arrays.binarySearch(ElementName.ELEMENT_HASHES, hash); - if (index < 0) { - return new ElementName(Portability.newLocalNameFromBuffer(buf, offset, length, interner)); - } else { - ElementName elementName = ElementName.ELEMENT_NAMES[index]; - @Local String name = elementName.name; - if (!Portability.localEqualsBuffer(name, buf, offset, length)) { - return new ElementName(Portability.newLocalNameFromBuffer(buf, - offset, length, interner)); - } - return elementName; - } - } - - /** - * This method has to return a unique integer for each well-known - * lower-cased element name. - * - * @param buf - * @param len - * @return - */ - private static int bufToHash(@NoLength char[] buf, int len) { - int hash = len; - hash <<= 5; - hash += buf[0] - 0x60; - int j = len; - for (int i = 0; i < 4 && j > 0; i++) { - j--; - hash <<= 5; - hash += buf[j] - 0x60; - } - return hash; - } - - private ElementName(@Local String name, @Local String camelCaseName, - int flags) { - this.name = name; - this.camelCaseName = camelCaseName; - this.flags = flags; - } - - protected ElementName(@Local String name) { - this.name = name; - this.camelCaseName = name; - this.flags = TreeBuilder.OTHER | CUSTOM; - } - - @Virtual void release() { - // No-op in Java. - // Implement as delete this in subclass. - // Be sure to release the local name - } - - @SuppressWarnings("unused") @Virtual private void destructor() { - } - - @Virtual public ElementName cloneElementName(Interner interner) { - return this; - } - - // START CODE ONLY USED FOR GENERATING CODE uncomment and run to regenerate - -// /** -// * @see java.lang.Object#toString() -// */ -// @Override public String toString() { -// return "(\"" + name + "\", \"" + camelCaseName + "\", " + decomposedFlags() + ")"; -// } -// -// private String decomposedFlags() { -// StringBuilder buf = new StringBuilder("TreeBuilder."); -// buf.append(treeBuilderGroupToName()); -// if ((flags & SPECIAL) != 0) { -// buf.append(" | SPECIAL"); -// } -// if ((flags & FOSTER_PARENTING) != 0) { -// buf.append(" | FOSTER_PARENTING"); -// } -// if ((flags & SCOPING) != 0) { -// buf.append(" | SCOPING"); -// } -// if ((flags & SCOPING_AS_MATHML) != 0) { -// buf.append(" | SCOPING_AS_MATHML"); -// } -// if ((flags & SCOPING_AS_SVG) != 0) { -// buf.append(" | SCOPING_AS_SVG"); -// } -// if ((flags & OPTIONAL_END_TAG) != 0) { -// buf.append(" | OPTIONAL_END_TAG"); -// } -// return buf.toString(); -// } -// -// private String constName() { -// char[] buf = new char[name.length()]; -// for (int i = 0; i < name.length(); i++) { -// char c = name.charAt(i); -// if (c == '-') { -// buf[i] = '_'; -// } else if (c >= '0' && c <= '9') { -// buf[i] = c; -// } else { -// buf[i] = (char) (c - 0x20); -// } -// } -// return new String(buf); -// } -// -// private int hash() { -// return bufToHash(name.toCharArray(), name.length()); -// } -// -// public int compareTo(ElementName other) { -// int thisHash = this.hash(); -// int otherHash = other.hash(); -// if (thisHash < otherHash) { -// return -1; -// } else if (thisHash == otherHash) { -// return 0; -// } else { -// return 1; -// } -// } -// -// private String treeBuilderGroupToName() { -// switch (getGroup()) { -// case TreeBuilder.OTHER: -// return "OTHER"; -// case TreeBuilder.A: -// return "A"; -// case TreeBuilder.BASE: -// return "BASE"; -// case TreeBuilder.BODY: -// return "BODY"; -// case TreeBuilder.BR: -// return "BR"; -// case TreeBuilder.BUTTON: -// return "BUTTON"; -// case TreeBuilder.CAPTION: -// return "CAPTION"; -// case TreeBuilder.COL: -// return "COL"; -// case TreeBuilder.COLGROUP: -// return "COLGROUP"; -// case TreeBuilder.FONT: -// return "FONT"; -// case TreeBuilder.FORM: -// return "FORM"; -// case TreeBuilder.FRAME: -// return "FRAME"; -// case TreeBuilder.FRAMESET: -// return "FRAMESET"; -// case TreeBuilder.IMAGE: -// return "IMAGE"; -// case TreeBuilder.INPUT: -// return "INPUT"; -// case TreeBuilder.ISINDEX: -// return "ISINDEX"; -// case TreeBuilder.LI: -// return "LI"; -// case TreeBuilder.LINK_OR_BASEFONT_OR_BGSOUND: -// return "LINK_OR_BASEFONT_OR_BGSOUND"; -// case TreeBuilder.MATH: -// return "MATH"; -// case TreeBuilder.META: -// return "META"; -// case TreeBuilder.SVG: -// return "SVG"; -// case TreeBuilder.HEAD: -// return "HEAD"; -// case TreeBuilder.HR: -// return "HR"; -// case TreeBuilder.HTML: -// return "HTML"; -// case TreeBuilder.KEYGEN: -// return "KEYGEN"; -// case TreeBuilder.NOBR: -// return "NOBR"; -// case TreeBuilder.NOFRAMES: -// return "NOFRAMES"; -// case TreeBuilder.NOSCRIPT: -// return "NOSCRIPT"; -// case TreeBuilder.OPTGROUP: -// return "OPTGROUP"; -// case TreeBuilder.OPTION: -// return "OPTION"; -// case TreeBuilder.P: -// return "P"; -// case TreeBuilder.PLAINTEXT: -// return "PLAINTEXT"; -// case TreeBuilder.SCRIPT: -// return "SCRIPT"; -// case TreeBuilder.SELECT: -// return "SELECT"; -// case TreeBuilder.STYLE: -// return "STYLE"; -// case TreeBuilder.TABLE: -// return "TABLE"; -// case TreeBuilder.TEXTAREA: -// return "TEXTAREA"; -// case TreeBuilder.TITLE: -// return "TITLE"; -// case TreeBuilder.TEMPLATE: -// return "TEMPLATE"; -// case TreeBuilder.TR: -// return "TR"; -// case TreeBuilder.XMP: -// return "XMP"; -// case TreeBuilder.TBODY_OR_THEAD_OR_TFOOT: -// return "TBODY_OR_THEAD_OR_TFOOT"; -// case TreeBuilder.TD_OR_TH: -// return "TD_OR_TH"; -// case TreeBuilder.DD_OR_DT: -// return "DD_OR_DT"; -// case TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6: -// return "H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6"; -// case TreeBuilder.OBJECT: -// return "OBJECT"; -// case TreeBuilder.OUTPUT: -// return "OUTPUT"; -// case TreeBuilder.MARQUEE_OR_APPLET: -// return "MARQUEE_OR_APPLET"; -// case TreeBuilder.PRE_OR_LISTING: -// return "PRE_OR_LISTING"; -// case TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U: -// return "B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U"; -// case TreeBuilder.UL_OR_OL_OR_DL: -// return "UL_OR_OL_OR_DL"; -// case TreeBuilder.IFRAME: -// return "IFRAME"; -// case TreeBuilder.NOEMBED: -// return "NOEMBED"; -// case TreeBuilder.EMBED: -// return "EMBED"; -// case TreeBuilder.IMG: -// return "IMG"; -// case TreeBuilder.AREA_OR_WBR: -// return "AREA_OR_WBR"; -// case TreeBuilder.DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU: -// return "DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU"; -// case TreeBuilder.FIELDSET: -// return "FIELDSET"; -// case TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY: -// return "ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY"; -// case TreeBuilder.RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR: -// return "RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR"; -// case TreeBuilder.RB_OR_RTC: -// return "RB_OR_RTC"; -// case TreeBuilder.RT_OR_RP: -// return "RT_OR_RP"; -// case TreeBuilder.PARAM_OR_SOURCE_OR_TRACK: -// return "PARAM_OR_SOURCE_OR_TRACK"; -// case TreeBuilder.MGLYPH_OR_MALIGNMARK: -// return "MGLYPH_OR_MALIGNMARK"; -// case TreeBuilder.MI_MO_MN_MS_MTEXT: -// return "MI_MO_MN_MS_MTEXT"; -// case TreeBuilder.ANNOTATION_XML: -// return "ANNOTATION_XML"; -// case TreeBuilder.FOREIGNOBJECT_OR_DESC: -// return "FOREIGNOBJECT_OR_DESC"; -// case TreeBuilder.MENUITEM: -// return "MENUITEM"; -// } -// return null; -// } -// -// /** -// * Regenerate self -// * -// * @param args -// */ -// public static void main(String[] args) { -// Arrays.sort(ELEMENT_NAMES); -// for (int i = 1; i < ELEMENT_NAMES.length; i++) { -// if (ELEMENT_NAMES[i].hash() == ELEMENT_NAMES[i - 1].hash()) { -// System.err.println("Hash collision: " + ELEMENT_NAMES[i].name -// + ", " + ELEMENT_NAMES[i - 1].name); -// return; -// } -// } -// for (int i = 0; i < ELEMENT_NAMES.length; i++) { -// ElementName el = ELEMENT_NAMES[i]; -// System.out.println("public static final ElementName " -// + el.constName() + " = new ElementName" + el.toString() -// + ";"); -// } -// System.out.println("private final static @NoLength ElementName[] ELEMENT_NAMES = {"); -// for (int i = 0; i < ELEMENT_NAMES.length; i++) { -// ElementName el = ELEMENT_NAMES[i]; -// System.out.println(el.constName() + ","); -// } -// System.out.println("};"); -// System.out.println("private final static int[] ELEMENT_HASHES = {"); -// for (int i = 0; i < ELEMENT_NAMES.length; i++) { -// ElementName el = ELEMENT_NAMES[i]; -// System.out.println(Integer.toString(el.hash()) + ","); -// } -// System.out.println("};"); -// } - - // START GENERATED CODE - public static final ElementName A = new ElementName("a", "a", TreeBuilder.A); - public static final ElementName B = new ElementName("b", "b", TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U); - public static final ElementName G = new ElementName("g", "g", TreeBuilder.OTHER); - public static final ElementName I = new ElementName("i", "i", TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U); - public static final ElementName P = new ElementName("p", "p", TreeBuilder.P | SPECIAL | OPTIONAL_END_TAG); - public static final ElementName Q = new ElementName("q", "q", TreeBuilder.OTHER); - public static final ElementName S = new ElementName("s", "s", TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U); - public static final ElementName U = new ElementName("u", "u", TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U); - public static final ElementName BR = new ElementName("br", "br", TreeBuilder.BR | SPECIAL); - public static final ElementName CI = new ElementName("ci", "ci", TreeBuilder.OTHER); - public static final ElementName CN = new ElementName("cn", "cn", TreeBuilder.OTHER); - public static final ElementName DD = new ElementName("dd", "dd", TreeBuilder.DD_OR_DT | SPECIAL | OPTIONAL_END_TAG); - public static final ElementName DL = new ElementName("dl", "dl", TreeBuilder.UL_OR_OL_OR_DL | SPECIAL); - public static final ElementName DT = new ElementName("dt", "dt", TreeBuilder.DD_OR_DT | SPECIAL | OPTIONAL_END_TAG); - public static final ElementName EM = new ElementName("em", "em", TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U); - public static final ElementName EQ = new ElementName("eq", "eq", TreeBuilder.OTHER); - public static final ElementName FN = new ElementName("fn", "fn", TreeBuilder.OTHER); - public static final ElementName H1 = new ElementName("h1", "h1", TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL); - public static final ElementName H2 = new ElementName("h2", "h2", TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL); - public static final ElementName H3 = new ElementName("h3", "h3", TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL); - public static final ElementName H4 = new ElementName("h4", "h4", TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL); - public static final ElementName H5 = new ElementName("h5", "h5", TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL); - public static final ElementName H6 = new ElementName("h6", "h6", TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL); - public static final ElementName GT = new ElementName("gt", "gt", TreeBuilder.OTHER); - public static final ElementName HR = new ElementName("hr", "hr", TreeBuilder.HR | SPECIAL); - public static final ElementName IN = new ElementName("in", "in", TreeBuilder.OTHER); - public static final ElementName LI = new ElementName("li", "li", TreeBuilder.LI | SPECIAL | OPTIONAL_END_TAG); - public static final ElementName LN = new ElementName("ln", "ln", TreeBuilder.OTHER); - public static final ElementName LT = new ElementName("lt", "lt", TreeBuilder.OTHER); - public static final ElementName MI = new ElementName("mi", "mi", TreeBuilder.MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML); - public static final ElementName MN = new ElementName("mn", "mn", TreeBuilder.MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML); - public static final ElementName MO = new ElementName("mo", "mo", TreeBuilder.MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML); - public static final ElementName MS = new ElementName("ms", "ms", TreeBuilder.MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML); - public static final ElementName OL = new ElementName("ol", "ol", TreeBuilder.UL_OR_OL_OR_DL | SPECIAL); - public static final ElementName OR = new ElementName("or", "or", TreeBuilder.OTHER); - public static final ElementName PI = new ElementName("pi", "pi", TreeBuilder.OTHER); - public static final ElementName RB = new ElementName("rb", "rb", TreeBuilder.RB_OR_RTC | OPTIONAL_END_TAG); - public static final ElementName RP = new ElementName("rp", "rp", TreeBuilder.RT_OR_RP | OPTIONAL_END_TAG); - public static final ElementName RT = new ElementName("rt", "rt", TreeBuilder.RT_OR_RP | OPTIONAL_END_TAG); - public static final ElementName TD = new ElementName("td", "td", TreeBuilder.TD_OR_TH | SPECIAL | SCOPING | OPTIONAL_END_TAG); - public static final ElementName TH = new ElementName("th", "th", TreeBuilder.TD_OR_TH | SPECIAL | SCOPING | OPTIONAL_END_TAG); - public static final ElementName TR = new ElementName("tr", "tr", TreeBuilder.TR | SPECIAL | FOSTER_PARENTING | OPTIONAL_END_TAG); - public static final ElementName TT = new ElementName("tt", "tt", TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U); - public static final ElementName UL = new ElementName("ul", "ul", TreeBuilder.UL_OR_OL_OR_DL | SPECIAL); - public static final ElementName AND = new ElementName("and", "and", TreeBuilder.OTHER); - public static final ElementName ARG = new ElementName("arg", "arg", TreeBuilder.OTHER); - public static final ElementName ABS = new ElementName("abs", "abs", TreeBuilder.OTHER); - public static final ElementName BIG = new ElementName("big", "big", TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U); - public static final ElementName BDO = new ElementName("bdo", "bdo", TreeBuilder.OTHER); - public static final ElementName CSC = new ElementName("csc", "csc", TreeBuilder.OTHER); - public static final ElementName COL = new ElementName("col", "col", TreeBuilder.COL | SPECIAL); - public static final ElementName COS = new ElementName("cos", "cos", TreeBuilder.OTHER); - public static final ElementName COT = new ElementName("cot", "cot", TreeBuilder.OTHER); - public static final ElementName DEL = new ElementName("del", "del", TreeBuilder.OTHER); - public static final ElementName DFN = new ElementName("dfn", "dfn", TreeBuilder.OTHER); - public static final ElementName DIR = new ElementName("dir", "dir", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName DIV = new ElementName("div", "div", TreeBuilder.DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU | SPECIAL); - public static final ElementName EXP = new ElementName("exp", "exp", TreeBuilder.OTHER); - public static final ElementName GCD = new ElementName("gcd", "gcd", TreeBuilder.OTHER); - public static final ElementName GEQ = new ElementName("geq", "geq", TreeBuilder.OTHER); - public static final ElementName IMG = new ElementName("img", "img", TreeBuilder.IMG | SPECIAL); - public static final ElementName INS = new ElementName("ins", "ins", TreeBuilder.OTHER); - public static final ElementName INT = new ElementName("int", "int", TreeBuilder.OTHER); - public static final ElementName KBD = new ElementName("kbd", "kbd", TreeBuilder.OTHER); - public static final ElementName LOG = new ElementName("log", "log", TreeBuilder.OTHER); - public static final ElementName LCM = new ElementName("lcm", "lcm", TreeBuilder.OTHER); - public static final ElementName LEQ = new ElementName("leq", "leq", TreeBuilder.OTHER); - public static final ElementName MTD = new ElementName("mtd", "mtd", TreeBuilder.OTHER); - public static final ElementName MIN = new ElementName("min", "min", TreeBuilder.OTHER); - public static final ElementName MAP = new ElementName("map", "map", TreeBuilder.OTHER); - public static final ElementName MTR = new ElementName("mtr", "mtr", TreeBuilder.OTHER); - public static final ElementName MAX = new ElementName("max", "max", TreeBuilder.OTHER); - public static final ElementName NEQ = new ElementName("neq", "neq", TreeBuilder.OTHER); - public static final ElementName NOT = new ElementName("not", "not", TreeBuilder.OTHER); - public static final ElementName NAV = new ElementName("nav", "nav", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName PRE = new ElementName("pre", "pre", TreeBuilder.PRE_OR_LISTING | SPECIAL); - public static final ElementName RTC = new ElementName("rtc", "rtc", TreeBuilder.RB_OR_RTC | OPTIONAL_END_TAG); - public static final ElementName REM = new ElementName("rem", "rem", TreeBuilder.OTHER); - public static final ElementName SUB = new ElementName("sub", "sub", TreeBuilder.RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR); - public static final ElementName SEC = new ElementName("sec", "sec", TreeBuilder.OTHER); - public static final ElementName SVG = new ElementName("svg", "svg", TreeBuilder.SVG); - public static final ElementName SUM = new ElementName("sum", "sum", TreeBuilder.OTHER); - public static final ElementName SIN = new ElementName("sin", "sin", TreeBuilder.OTHER); - public static final ElementName SEP = new ElementName("sep", "sep", TreeBuilder.OTHER); - public static final ElementName SUP = new ElementName("sup", "sup", TreeBuilder.RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR); - public static final ElementName SET = new ElementName("set", "set", TreeBuilder.OTHER); - public static final ElementName TAN = new ElementName("tan", "tan", TreeBuilder.OTHER); - public static final ElementName USE = new ElementName("use", "use", TreeBuilder.OTHER); - public static final ElementName VAR = new ElementName("var", "var", TreeBuilder.RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR); - public static final ElementName WBR = new ElementName("wbr", "wbr", TreeBuilder.AREA_OR_WBR | SPECIAL); - public static final ElementName XMP = new ElementName("xmp", "xmp", TreeBuilder.XMP | SPECIAL); - public static final ElementName XOR = new ElementName("xor", "xor", TreeBuilder.OTHER); - public static final ElementName AREA = new ElementName("area", "area", TreeBuilder.AREA_OR_WBR | SPECIAL); - public static final ElementName ABBR = new ElementName("abbr", "abbr", TreeBuilder.OTHER); - public static final ElementName BASE = new ElementName("base", "base", TreeBuilder.BASE | SPECIAL); - public static final ElementName BVAR = new ElementName("bvar", "bvar", TreeBuilder.OTHER); - public static final ElementName BODY = new ElementName("body", "body", TreeBuilder.BODY | SPECIAL | OPTIONAL_END_TAG); - public static final ElementName CARD = new ElementName("card", "card", TreeBuilder.OTHER); - public static final ElementName CODE = new ElementName("code", "code", TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U); - public static final ElementName CITE = new ElementName("cite", "cite", TreeBuilder.OTHER); - public static final ElementName CSCH = new ElementName("csch", "csch", TreeBuilder.OTHER); - public static final ElementName COSH = new ElementName("cosh", "cosh", TreeBuilder.OTHER); - public static final ElementName COTH = new ElementName("coth", "coth", TreeBuilder.OTHER); - public static final ElementName CURL = new ElementName("curl", "curl", TreeBuilder.OTHER); - public static final ElementName DESC = new ElementName("desc", "desc", TreeBuilder.FOREIGNOBJECT_OR_DESC | SCOPING_AS_SVG); - public static final ElementName DIFF = new ElementName("diff", "diff", TreeBuilder.OTHER); - public static final ElementName DEFS = new ElementName("defs", "defs", TreeBuilder.OTHER); - public static final ElementName FORM = new ElementName("form", "form", TreeBuilder.FORM | SPECIAL); - public static final ElementName FONT = new ElementName("font", "font", TreeBuilder.FONT); - public static final ElementName GRAD = new ElementName("grad", "grad", TreeBuilder.OTHER); - public static final ElementName HEAD = new ElementName("head", "head", TreeBuilder.HEAD | SPECIAL | OPTIONAL_END_TAG); - public static final ElementName HTML = new ElementName("html", "html", TreeBuilder.HTML | SPECIAL | SCOPING | OPTIONAL_END_TAG); - public static final ElementName LINE = new ElementName("line", "line", TreeBuilder.OTHER); - public static final ElementName LINK = new ElementName("link", "link", TreeBuilder.LINK_OR_BASEFONT_OR_BGSOUND | SPECIAL); - public static final ElementName LIST = new ElementName("list", "list", TreeBuilder.OTHER); - public static final ElementName META = new ElementName("meta", "meta", TreeBuilder.META | SPECIAL); - public static final ElementName MSUB = new ElementName("msub", "msub", TreeBuilder.OTHER); - public static final ElementName MODE = new ElementName("mode", "mode", TreeBuilder.OTHER); - public static final ElementName MATH = new ElementName("math", "math", TreeBuilder.MATH); - public static final ElementName MARK = new ElementName("mark", "mark", TreeBuilder.OTHER); - public static final ElementName MASK = new ElementName("mask", "mask", TreeBuilder.OTHER); - public static final ElementName MEAN = new ElementName("mean", "mean", TreeBuilder.OTHER); - public static final ElementName MAIN = new ElementName("main", "main", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName MSUP = new ElementName("msup", "msup", TreeBuilder.OTHER); - public static final ElementName MENU = new ElementName("menu", "menu", TreeBuilder.DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU | SPECIAL); - public static final ElementName MROW = new ElementName("mrow", "mrow", TreeBuilder.OTHER); - public static final ElementName NONE = new ElementName("none", "none", TreeBuilder.OTHER); - public static final ElementName NOBR = new ElementName("nobr", "nobr", TreeBuilder.NOBR); - public static final ElementName NEST = new ElementName("nest", "nest", TreeBuilder.OTHER); - public static final ElementName PATH = new ElementName("path", "path", TreeBuilder.OTHER); - public static final ElementName PLUS = new ElementName("plus", "plus", TreeBuilder.OTHER); - public static final ElementName RULE = new ElementName("rule", "rule", TreeBuilder.OTHER); - public static final ElementName REAL = new ElementName("real", "real", TreeBuilder.OTHER); - public static final ElementName RELN = new ElementName("reln", "reln", TreeBuilder.OTHER); - public static final ElementName RECT = new ElementName("rect", "rect", TreeBuilder.OTHER); - public static final ElementName ROOT = new ElementName("root", "root", TreeBuilder.OTHER); - public static final ElementName RUBY = new ElementName("ruby", "ruby", TreeBuilder.RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR); - public static final ElementName SECH = new ElementName("sech", "sech", TreeBuilder.OTHER); - public static final ElementName SINH = new ElementName("sinh", "sinh", TreeBuilder.OTHER); - public static final ElementName SPAN = new ElementName("span", "span", TreeBuilder.RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR); - public static final ElementName SAMP = new ElementName("samp", "samp", TreeBuilder.OTHER); - public static final ElementName STOP = new ElementName("stop", "stop", TreeBuilder.OTHER); - public static final ElementName SDEV = new ElementName("sdev", "sdev", TreeBuilder.OTHER); - public static final ElementName TIME = new ElementName("time", "time", TreeBuilder.OTHER); - public static final ElementName TRUE = new ElementName("true", "true", TreeBuilder.OTHER); - public static final ElementName TREF = new ElementName("tref", "tref", TreeBuilder.OTHER); - public static final ElementName TANH = new ElementName("tanh", "tanh", TreeBuilder.OTHER); - public static final ElementName TEXT = new ElementName("text", "text", TreeBuilder.OTHER); - public static final ElementName VIEW = new ElementName("view", "view", TreeBuilder.OTHER); - public static final ElementName ASIDE = new ElementName("aside", "aside", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName AUDIO = new ElementName("audio", "audio", TreeBuilder.OTHER); - public static final ElementName APPLY = new ElementName("apply", "apply", TreeBuilder.OTHER); - public static final ElementName EMBED = new ElementName("embed", "embed", TreeBuilder.EMBED | SPECIAL); - public static final ElementName FRAME = new ElementName("frame", "frame", TreeBuilder.FRAME | SPECIAL); - public static final ElementName FALSE = new ElementName("false", "false", TreeBuilder.OTHER); - public static final ElementName FLOOR = new ElementName("floor", "floor", TreeBuilder.OTHER); - public static final ElementName GLYPH = new ElementName("glyph", "glyph", TreeBuilder.OTHER); - public static final ElementName HKERN = new ElementName("hkern", "hkern", TreeBuilder.OTHER); - public static final ElementName IMAGE = new ElementName("image", "image", TreeBuilder.IMAGE); - public static final ElementName IDENT = new ElementName("ident", "ident", TreeBuilder.OTHER); - public static final ElementName INPUT = new ElementName("input", "input", TreeBuilder.INPUT | SPECIAL); - public static final ElementName LABEL = new ElementName("label", "label", TreeBuilder.OTHER); - public static final ElementName LIMIT = new ElementName("limit", "limit", TreeBuilder.OTHER); - public static final ElementName MFRAC = new ElementName("mfrac", "mfrac", TreeBuilder.OTHER); - public static final ElementName MPATH = new ElementName("mpath", "mpath", TreeBuilder.OTHER); - public static final ElementName METER = new ElementName("meter", "meter", TreeBuilder.OTHER); - public static final ElementName MOVER = new ElementName("mover", "mover", TreeBuilder.OTHER); - public static final ElementName MINUS = new ElementName("minus", "minus", TreeBuilder.OTHER); - public static final ElementName MROOT = new ElementName("mroot", "mroot", TreeBuilder.OTHER); - public static final ElementName MSQRT = new ElementName("msqrt", "msqrt", TreeBuilder.OTHER); - public static final ElementName MTEXT = new ElementName("mtext", "mtext", TreeBuilder.MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML); - public static final ElementName NOTIN = new ElementName("notin", "notin", TreeBuilder.OTHER); - public static final ElementName PIECE = new ElementName("piece", "piece", TreeBuilder.OTHER); - public static final ElementName PARAM = new ElementName("param", "param", TreeBuilder.PARAM_OR_SOURCE_OR_TRACK | SPECIAL); - public static final ElementName POWER = new ElementName("power", "power", TreeBuilder.OTHER); - public static final ElementName REALS = new ElementName("reals", "reals", TreeBuilder.OTHER); - public static final ElementName STYLE = new ElementName("style", "style", TreeBuilder.STYLE | SPECIAL); - public static final ElementName SMALL = new ElementName("small", "small", TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U); - public static final ElementName THEAD = new ElementName("thead", "thead", TreeBuilder.TBODY_OR_THEAD_OR_TFOOT | SPECIAL | FOSTER_PARENTING | OPTIONAL_END_TAG); - public static final ElementName TABLE = new ElementName("table", "table", TreeBuilder.TABLE | SPECIAL | FOSTER_PARENTING | SCOPING); - public static final ElementName TITLE = new ElementName("title", "title", TreeBuilder.TITLE | SPECIAL | SCOPING_AS_SVG); - public static final ElementName TRACK = new ElementName("track", "track", TreeBuilder.PARAM_OR_SOURCE_OR_TRACK | SPECIAL); - public static final ElementName TSPAN = new ElementName("tspan", "tspan", TreeBuilder.OTHER); - public static final ElementName TIMES = new ElementName("times", "times", TreeBuilder.OTHER); - public static final ElementName TFOOT = new ElementName("tfoot", "tfoot", TreeBuilder.TBODY_OR_THEAD_OR_TFOOT | SPECIAL | FOSTER_PARENTING | OPTIONAL_END_TAG); - public static final ElementName TBODY = new ElementName("tbody", "tbody", TreeBuilder.TBODY_OR_THEAD_OR_TFOOT | SPECIAL | FOSTER_PARENTING | OPTIONAL_END_TAG); - public static final ElementName UNION = new ElementName("union", "union", TreeBuilder.OTHER); - public static final ElementName VKERN = new ElementName("vkern", "vkern", TreeBuilder.OTHER); - public static final ElementName VIDEO = new ElementName("video", "video", TreeBuilder.OTHER); - public static final ElementName ARCSEC = new ElementName("arcsec", "arcsec", TreeBuilder.OTHER); - public static final ElementName ARCCSC = new ElementName("arccsc", "arccsc", TreeBuilder.OTHER); - public static final ElementName ARCTAN = new ElementName("arctan", "arctan", TreeBuilder.OTHER); - public static final ElementName ARCSIN = new ElementName("arcsin", "arcsin", TreeBuilder.OTHER); - public static final ElementName ARCCOS = new ElementName("arccos", "arccos", TreeBuilder.OTHER); - public static final ElementName APPLET = new ElementName("applet", "applet", TreeBuilder.MARQUEE_OR_APPLET | SPECIAL | SCOPING); - public static final ElementName ARCCOT = new ElementName("arccot", "arccot", TreeBuilder.OTHER); - public static final ElementName APPROX = new ElementName("approx", "approx", TreeBuilder.OTHER); - public static final ElementName BUTTON = new ElementName("button", "button", TreeBuilder.BUTTON | SPECIAL); - public static final ElementName CIRCLE = new ElementName("circle", "circle", TreeBuilder.OTHER); - public static final ElementName CENTER = new ElementName("center", "center", TreeBuilder.DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU | SPECIAL); - public static final ElementName CURSOR = new ElementName("cursor", "cursor", TreeBuilder.OTHER); - public static final ElementName CANVAS = new ElementName("canvas", "canvas", TreeBuilder.OTHER); - public static final ElementName DIVIDE = new ElementName("divide", "divide", TreeBuilder.OTHER); - public static final ElementName DEGREE = new ElementName("degree", "degree", TreeBuilder.OTHER); - public static final ElementName DOMAIN = new ElementName("domain", "domain", TreeBuilder.OTHER); - public static final ElementName EXISTS = new ElementName("exists", "exists", TreeBuilder.OTHER); - public static final ElementName FETILE = new ElementName("fetile", "feTile", TreeBuilder.OTHER); - public static final ElementName FIGURE = new ElementName("figure", "figure", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName FORALL = new ElementName("forall", "forall", TreeBuilder.OTHER); - public static final ElementName FILTER = new ElementName("filter", "filter", TreeBuilder.OTHER); - public static final ElementName FOOTER = new ElementName("footer", "footer", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName HGROUP = new ElementName("hgroup", "hgroup", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName HEADER = new ElementName("header", "header", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName IFRAME = new ElementName("iframe", "iframe", TreeBuilder.IFRAME | SPECIAL); - public static final ElementName KEYGEN = new ElementName("keygen", "keygen", TreeBuilder.KEYGEN); - public static final ElementName LAMBDA = new ElementName("lambda", "lambda", TreeBuilder.OTHER); - public static final ElementName LEGEND = new ElementName("legend", "legend", TreeBuilder.OTHER); - public static final ElementName MSPACE = new ElementName("mspace", "mspace", TreeBuilder.OTHER); - public static final ElementName MTABLE = new ElementName("mtable", "mtable", TreeBuilder.OTHER); - public static final ElementName MSTYLE = new ElementName("mstyle", "mstyle", TreeBuilder.OTHER); - public static final ElementName MGLYPH = new ElementName("mglyph", "mglyph", TreeBuilder.MGLYPH_OR_MALIGNMARK); - public static final ElementName MEDIAN = new ElementName("median", "median", TreeBuilder.OTHER); - public static final ElementName MUNDER = new ElementName("munder", "munder", TreeBuilder.OTHER); - public static final ElementName MARKER = new ElementName("marker", "marker", TreeBuilder.OTHER); - public static final ElementName MERROR = new ElementName("merror", "merror", TreeBuilder.OTHER); - public static final ElementName MOMENT = new ElementName("moment", "moment", TreeBuilder.OTHER); - public static final ElementName MATRIX = new ElementName("matrix", "matrix", TreeBuilder.OTHER); - public static final ElementName OPTION = new ElementName("option", "option", TreeBuilder.OPTION | OPTIONAL_END_TAG); - public static final ElementName OBJECT = new ElementName("object", "object", TreeBuilder.OBJECT | SPECIAL | SCOPING); - public static final ElementName OUTPUT = new ElementName("output", "output", TreeBuilder.OUTPUT); - public static final ElementName PRIMES = new ElementName("primes", "primes", TreeBuilder.OTHER); - public static final ElementName SOURCE = new ElementName("source", "source", TreeBuilder.PARAM_OR_SOURCE_OR_TRACK); - public static final ElementName STRIKE = new ElementName("strike", "strike", TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U); - public static final ElementName STRONG = new ElementName("strong", "strong", TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U); - public static final ElementName SWITCH = new ElementName("switch", "switch", TreeBuilder.OTHER); - public static final ElementName SYMBOL = new ElementName("symbol", "symbol", TreeBuilder.OTHER); - public static final ElementName SELECT = new ElementName("select", "select", TreeBuilder.SELECT | SPECIAL); - public static final ElementName SUBSET = new ElementName("subset", "subset", TreeBuilder.OTHER); - public static final ElementName SCRIPT = new ElementName("script", "script", TreeBuilder.SCRIPT | SPECIAL); - public static final ElementName TBREAK = new ElementName("tbreak", "tbreak", TreeBuilder.OTHER); - public static final ElementName VECTOR = new ElementName("vector", "vector", TreeBuilder.OTHER); - public static final ElementName ARTICLE = new ElementName("article", "article", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName ANIMATE = new ElementName("animate", "animate", TreeBuilder.OTHER); - public static final ElementName ARCSECH = new ElementName("arcsech", "arcsech", TreeBuilder.OTHER); - public static final ElementName ARCCSCH = new ElementName("arccsch", "arccsch", TreeBuilder.OTHER); - public static final ElementName ARCTANH = new ElementName("arctanh", "arctanh", TreeBuilder.OTHER); - public static final ElementName ARCSINH = new ElementName("arcsinh", "arcsinh", TreeBuilder.OTHER); - public static final ElementName ARCCOSH = new ElementName("arccosh", "arccosh", TreeBuilder.OTHER); - public static final ElementName ARCCOTH = new ElementName("arccoth", "arccoth", TreeBuilder.OTHER); - public static final ElementName ACRONYM = new ElementName("acronym", "acronym", TreeBuilder.OTHER); - public static final ElementName ADDRESS = new ElementName("address", "address", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName BGSOUND = new ElementName("bgsound", "bgsound", TreeBuilder.LINK_OR_BASEFONT_OR_BGSOUND | SPECIAL); - public static final ElementName COMPOSE = new ElementName("compose", "compose", TreeBuilder.OTHER); - public static final ElementName CEILING = new ElementName("ceiling", "ceiling", TreeBuilder.OTHER); - public static final ElementName CSYMBOL = new ElementName("csymbol", "csymbol", TreeBuilder.OTHER); - public static final ElementName CAPTION = new ElementName("caption", "caption", TreeBuilder.CAPTION | SPECIAL | SCOPING); - public static final ElementName DISCARD = new ElementName("discard", "discard", TreeBuilder.OTHER); - public static final ElementName DECLARE = new ElementName("declare", "declare", TreeBuilder.OTHER); - public static final ElementName DETAILS = new ElementName("details", "details", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName ELLIPSE = new ElementName("ellipse", "ellipse", TreeBuilder.OTHER); - public static final ElementName FEFUNCA = new ElementName("fefunca", "feFuncA", TreeBuilder.OTHER); - public static final ElementName FEFUNCB = new ElementName("fefuncb", "feFuncB", TreeBuilder.OTHER); - public static final ElementName FEBLEND = new ElementName("feblend", "feBlend", TreeBuilder.OTHER); - public static final ElementName FEFLOOD = new ElementName("feflood", "feFlood", TreeBuilder.OTHER); - public static final ElementName FEIMAGE = new ElementName("feimage", "feImage", TreeBuilder.OTHER); - public static final ElementName FEMERGE = new ElementName("femerge", "feMerge", TreeBuilder.OTHER); - public static final ElementName FEFUNCG = new ElementName("fefuncg", "feFuncG", TreeBuilder.OTHER); - public static final ElementName FEFUNCR = new ElementName("fefuncr", "feFuncR", TreeBuilder.OTHER); - public static final ElementName HANDLER = new ElementName("handler", "handler", TreeBuilder.OTHER); - public static final ElementName INVERSE = new ElementName("inverse", "inverse", TreeBuilder.OTHER); - public static final ElementName IMPLIES = new ElementName("implies", "implies", TreeBuilder.OTHER); - public static final ElementName ISINDEX = new ElementName("isindex", "isindex", TreeBuilder.ISINDEX | SPECIAL); - public static final ElementName LOGBASE = new ElementName("logbase", "logbase", TreeBuilder.OTHER); - public static final ElementName LISTING = new ElementName("listing", "listing", TreeBuilder.PRE_OR_LISTING | SPECIAL); - public static final ElementName MFENCED = new ElementName("mfenced", "mfenced", TreeBuilder.OTHER); - public static final ElementName MPADDED = new ElementName("mpadded", "mpadded", TreeBuilder.OTHER); - public static final ElementName MARQUEE = new ElementName("marquee", "marquee", TreeBuilder.MARQUEE_OR_APPLET | SPECIAL | SCOPING); - public static final ElementName MACTION = new ElementName("maction", "maction", TreeBuilder.OTHER); - public static final ElementName MSUBSUP = new ElementName("msubsup", "msubsup", TreeBuilder.OTHER); - public static final ElementName NOEMBED = new ElementName("noembed", "noembed", TreeBuilder.NOEMBED | SPECIAL); - public static final ElementName POLYGON = new ElementName("polygon", "polygon", TreeBuilder.OTHER); - public static final ElementName PATTERN = new ElementName("pattern", "pattern", TreeBuilder.OTHER); - public static final ElementName PICTURE = new ElementName("picture", "picture", TreeBuilder.OTHER); - public static final ElementName PRODUCT = new ElementName("product", "product", TreeBuilder.OTHER); - public static final ElementName SETDIFF = new ElementName("setdiff", "setdiff", TreeBuilder.OTHER); - public static final ElementName SECTION = new ElementName("section", "section", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName SUMMARY = new ElementName("summary", "summary", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName TENDSTO = new ElementName("tendsto", "tendsto", TreeBuilder.OTHER); - public static final ElementName UPLIMIT = new ElementName("uplimit", "uplimit", TreeBuilder.OTHER); - public static final ElementName ALTGLYPH = new ElementName("altglyph", "altGlyph", TreeBuilder.OTHER); - public static final ElementName BASEFONT = new ElementName("basefont", "basefont", TreeBuilder.LINK_OR_BASEFONT_OR_BGSOUND | SPECIAL); - public static final ElementName CLIPPATH = new ElementName("clippath", "clipPath", TreeBuilder.OTHER); - public static final ElementName CODOMAIN = new ElementName("codomain", "codomain", TreeBuilder.OTHER); - public static final ElementName COLGROUP = new ElementName("colgroup", "colgroup", TreeBuilder.COLGROUP | SPECIAL | OPTIONAL_END_TAG); - public static final ElementName EMPTYSET = new ElementName("emptyset", "emptyset", TreeBuilder.OTHER); - public static final ElementName FACTOROF = new ElementName("factorof", "factorof", TreeBuilder.OTHER); - public static final ElementName FIELDSET = new ElementName("fieldset", "fieldset", TreeBuilder.FIELDSET | SPECIAL); - public static final ElementName FRAMESET = new ElementName("frameset", "frameset", TreeBuilder.FRAMESET | SPECIAL); - public static final ElementName FEOFFSET = new ElementName("feoffset", "feOffset", TreeBuilder.OTHER); - public static final ElementName GLYPHREF = new ElementName("glyphref", "glyphRef", TreeBuilder.OTHER); - public static final ElementName INTERVAL = new ElementName("interval", "interval", TreeBuilder.OTHER); - public static final ElementName INTEGERS = new ElementName("integers", "integers", TreeBuilder.OTHER); - public static final ElementName INFINITY = new ElementName("infinity", "infinity", TreeBuilder.OTHER); - public static final ElementName LISTENER = new ElementName("listener", "listener", TreeBuilder.OTHER); - public static final ElementName LOWLIMIT = new ElementName("lowlimit", "lowlimit", TreeBuilder.OTHER); - public static final ElementName METADATA = new ElementName("metadata", "metadata", TreeBuilder.OTHER); - public static final ElementName MENCLOSE = new ElementName("menclose", "menclose", TreeBuilder.OTHER); - public static final ElementName MENUITEM = new ElementName("menuitem", "menuitem", TreeBuilder.MENUITEM); - public static final ElementName MPHANTOM = new ElementName("mphantom", "mphantom", TreeBuilder.OTHER); - public static final ElementName NOFRAMES = new ElementName("noframes", "noframes", TreeBuilder.NOFRAMES | SPECIAL); - public static final ElementName NOSCRIPT = new ElementName("noscript", "noscript", TreeBuilder.NOSCRIPT | SPECIAL); - public static final ElementName OPTGROUP = new ElementName("optgroup", "optgroup", TreeBuilder.OPTGROUP | OPTIONAL_END_TAG); - public static final ElementName POLYLINE = new ElementName("polyline", "polyline", TreeBuilder.OTHER); - public static final ElementName PREFETCH = new ElementName("prefetch", "prefetch", TreeBuilder.OTHER); - public static final ElementName PROGRESS = new ElementName("progress", "progress", TreeBuilder.OTHER); - public static final ElementName PRSUBSET = new ElementName("prsubset", "prsubset", TreeBuilder.OTHER); - public static final ElementName QUOTIENT = new ElementName("quotient", "quotient", TreeBuilder.OTHER); - public static final ElementName SELECTOR = new ElementName("selector", "selector", TreeBuilder.OTHER); - public static final ElementName TEXTAREA = new ElementName("textarea", "textarea", TreeBuilder.TEXTAREA | SPECIAL); - public static final ElementName TEMPLATE = new ElementName("template", "template", TreeBuilder.TEMPLATE | SPECIAL | SCOPING); - public static final ElementName TEXTPATH = new ElementName("textpath", "textPath", TreeBuilder.OTHER); - public static final ElementName VARIANCE = new ElementName("variance", "variance", TreeBuilder.OTHER); - public static final ElementName ANIMATION = new ElementName("animation", "animation", TreeBuilder.OTHER); - public static final ElementName CONJUGATE = new ElementName("conjugate", "conjugate", TreeBuilder.OTHER); - public static final ElementName CONDITION = new ElementName("condition", "condition", TreeBuilder.OTHER); - public static final ElementName COMPLEXES = new ElementName("complexes", "complexes", TreeBuilder.OTHER); - public static final ElementName FONT_FACE = new ElementName("font-face", "font-face", TreeBuilder.OTHER); - public static final ElementName FACTORIAL = new ElementName("factorial", "factorial", TreeBuilder.OTHER); - public static final ElementName INTERSECT = new ElementName("intersect", "intersect", TreeBuilder.OTHER); - public static final ElementName IMAGINARY = new ElementName("imaginary", "imaginary", TreeBuilder.OTHER); - public static final ElementName LAPLACIAN = new ElementName("laplacian", "laplacian", TreeBuilder.OTHER); - public static final ElementName MATRIXROW = new ElementName("matrixrow", "matrixrow", TreeBuilder.OTHER); - public static final ElementName NOTSUBSET = new ElementName("notsubset", "notsubset", TreeBuilder.OTHER); - public static final ElementName OTHERWISE = new ElementName("otherwise", "otherwise", TreeBuilder.OTHER); - public static final ElementName PIECEWISE = new ElementName("piecewise", "piecewise", TreeBuilder.OTHER); - public static final ElementName PLAINTEXT = new ElementName("plaintext", "plaintext", TreeBuilder.PLAINTEXT | SPECIAL); - public static final ElementName RATIONALS = new ElementName("rationals", "rationals", TreeBuilder.OTHER); - public static final ElementName SEMANTICS = new ElementName("semantics", "semantics", TreeBuilder.OTHER); - public static final ElementName TRANSPOSE = new ElementName("transpose", "transpose", TreeBuilder.OTHER); - public static final ElementName ANNOTATION = new ElementName("annotation", "annotation", TreeBuilder.OTHER); - public static final ElementName BLOCKQUOTE = new ElementName("blockquote", "blockquote", TreeBuilder.DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU | SPECIAL); - public static final ElementName DIVERGENCE = new ElementName("divergence", "divergence", TreeBuilder.OTHER); - public static final ElementName EULERGAMMA = new ElementName("eulergamma", "eulergamma", TreeBuilder.OTHER); - public static final ElementName EQUIVALENT = new ElementName("equivalent", "equivalent", TreeBuilder.OTHER); - public static final ElementName FIGCAPTION = new ElementName("figcaption", "figcaption", TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL); - public static final ElementName IMAGINARYI = new ElementName("imaginaryi", "imaginaryi", TreeBuilder.OTHER); - public static final ElementName MALIGNMARK = new ElementName("malignmark", "malignmark", TreeBuilder.MGLYPH_OR_MALIGNMARK); - public static final ElementName MUNDEROVER = new ElementName("munderover", "munderover", TreeBuilder.OTHER); - public static final ElementName MLABELEDTR = new ElementName("mlabeledtr", "mlabeledtr", TreeBuilder.OTHER); - public static final ElementName NOTANUMBER = new ElementName("notanumber", "notanumber", TreeBuilder.OTHER); - public static final ElementName SOLIDCOLOR = new ElementName("solidcolor", "solidcolor", TreeBuilder.OTHER); - public static final ElementName ALTGLYPHDEF = new ElementName("altglyphdef", "altGlyphDef", TreeBuilder.OTHER); - public static final ElementName DETERMINANT = new ElementName("determinant", "determinant", TreeBuilder.OTHER); - public static final ElementName FEMERGENODE = new ElementName("femergenode", "feMergeNode", TreeBuilder.OTHER); - public static final ElementName FECOMPOSITE = new ElementName("fecomposite", "feComposite", TreeBuilder.OTHER); - public static final ElementName FESPOTLIGHT = new ElementName("fespotlight", "feSpotLight", TreeBuilder.OTHER); - public static final ElementName MALIGNGROUP = new ElementName("maligngroup", "maligngroup", TreeBuilder.OTHER); - public static final ElementName MPRESCRIPTS = new ElementName("mprescripts", "mprescripts", TreeBuilder.OTHER); - public static final ElementName MOMENTABOUT = new ElementName("momentabout", "momentabout", TreeBuilder.OTHER); - public static final ElementName NOTPRSUBSET = new ElementName("notprsubset", "notprsubset", TreeBuilder.OTHER); - public static final ElementName PARTIALDIFF = new ElementName("partialdiff", "partialdiff", TreeBuilder.OTHER); - public static final ElementName ALTGLYPHITEM = new ElementName("altglyphitem", "altGlyphItem", TreeBuilder.OTHER); - public static final ElementName ANIMATECOLOR = new ElementName("animatecolor", "animateColor", TreeBuilder.OTHER); - public static final ElementName DATATEMPLATE = new ElementName("datatemplate", "datatemplate", TreeBuilder.OTHER); - public static final ElementName EXPONENTIALE = new ElementName("exponentiale", "exponentiale", TreeBuilder.OTHER); - public static final ElementName FETURBULENCE = new ElementName("feturbulence", "feTurbulence", TreeBuilder.OTHER); - public static final ElementName FEPOINTLIGHT = new ElementName("fepointlight", "fePointLight", TreeBuilder.OTHER); - public static final ElementName FEDROPSHADOW = new ElementName("fedropshadow", "feDropShadow", TreeBuilder.OTHER); - public static final ElementName FEMORPHOLOGY = new ElementName("femorphology", "feMorphology", TreeBuilder.OTHER); - public static final ElementName OUTERPRODUCT = new ElementName("outerproduct", "outerproduct", TreeBuilder.OTHER); - public static final ElementName ANIMATEMOTION = new ElementName("animatemotion", "animateMotion", TreeBuilder.OTHER); - public static final ElementName COLOR_PROFILE = new ElementName("color-profile", "color-profile", TreeBuilder.OTHER); - public static final ElementName FONT_FACE_SRC = new ElementName("font-face-src", "font-face-src", TreeBuilder.OTHER); - public static final ElementName FONT_FACE_URI = new ElementName("font-face-uri", "font-face-uri", TreeBuilder.OTHER); - public static final ElementName FOREIGNOBJECT = new ElementName("foreignobject", "foreignObject", TreeBuilder.FOREIGNOBJECT_OR_DESC | SCOPING_AS_SVG); - public static final ElementName FECOLORMATRIX = new ElementName("fecolormatrix", "feColorMatrix", TreeBuilder.OTHER); - public static final ElementName MISSING_GLYPH = new ElementName("missing-glyph", "missing-glyph", TreeBuilder.OTHER); - public static final ElementName MMULTISCRIPTS = new ElementName("mmultiscripts", "mmultiscripts", TreeBuilder.OTHER); - public static final ElementName SCALARPRODUCT = new ElementName("scalarproduct", "scalarproduct", TreeBuilder.OTHER); - public static final ElementName VECTORPRODUCT = new ElementName("vectorproduct", "vectorproduct", TreeBuilder.OTHER); - public static final ElementName ANNOTATION_XML = new ElementName("annotation-xml", "annotation-xml", TreeBuilder.ANNOTATION_XML | SCOPING_AS_MATHML); - public static final ElementName DEFINITION_SRC = new ElementName("definition-src", "definition-src", TreeBuilder.OTHER); - public static final ElementName FONT_FACE_NAME = new ElementName("font-face-name", "font-face-name", TreeBuilder.OTHER); - public static final ElementName FEGAUSSIANBLUR = new ElementName("fegaussianblur", "feGaussianBlur", TreeBuilder.OTHER); - public static final ElementName FEDISTANTLIGHT = new ElementName("fedistantlight", "feDistantLight", TreeBuilder.OTHER); - public static final ElementName LINEARGRADIENT = new ElementName("lineargradient", "linearGradient", TreeBuilder.OTHER); - public static final ElementName NATURALNUMBERS = new ElementName("naturalnumbers", "naturalnumbers", TreeBuilder.OTHER); - public static final ElementName RADIALGRADIENT = new ElementName("radialgradient", "radialGradient", TreeBuilder.OTHER); - public static final ElementName ANIMATETRANSFORM = new ElementName("animatetransform", "animateTransform", TreeBuilder.OTHER); - public static final ElementName CARTESIANPRODUCT = new ElementName("cartesianproduct", "cartesianproduct", TreeBuilder.OTHER); - public static final ElementName FONT_FACE_FORMAT = new ElementName("font-face-format", "font-face-format", TreeBuilder.OTHER); - public static final ElementName FECONVOLVEMATRIX = new ElementName("feconvolvematrix", "feConvolveMatrix", TreeBuilder.OTHER); - public static final ElementName FEDIFFUSELIGHTING = new ElementName("fediffuselighting", "feDiffuseLighting", TreeBuilder.OTHER); - public static final ElementName FEDISPLACEMENTMAP = new ElementName("fedisplacementmap", "feDisplacementMap", TreeBuilder.OTHER); - public static final ElementName FESPECULARLIGHTING = new ElementName("fespecularlighting", "feSpecularLighting", TreeBuilder.OTHER); - public static final ElementName DOMAINOFAPPLICATION = new ElementName("domainofapplication", "domainofapplication", TreeBuilder.OTHER); - public static final ElementName FECOMPONENTTRANSFER = new ElementName("fecomponenttransfer", "feComponentTransfer", TreeBuilder.OTHER); - private final static @NoLength ElementName[] ELEMENT_NAMES = { - A, - B, - G, - I, - P, - Q, - S, - U, - BR, - CI, - CN, - DD, - DL, - DT, - EM, - EQ, - FN, - H1, - H2, - H3, - H4, - H5, - H6, - GT, - HR, - IN, - LI, - LN, - LT, - MI, - MN, - MO, - MS, - OL, - OR, - PI, - RB, - RP, - RT, - TD, - TH, - TR, - TT, - UL, - AND, - ARG, - ABS, - BIG, - BDO, - CSC, - COL, - COS, - COT, - DEL, - DFN, - DIR, - DIV, - EXP, - GCD, - GEQ, - IMG, - INS, - INT, - KBD, - LOG, - LCM, - LEQ, - MTD, - MIN, - MAP, - MTR, - MAX, - NEQ, - NOT, - NAV, - PRE, - RTC, - REM, - SUB, - SEC, - SVG, - SUM, - SIN, - SEP, - SUP, - SET, - TAN, - USE, - VAR, - WBR, - XMP, - XOR, - AREA, - ABBR, - BASE, - BVAR, - BODY, - CARD, - CODE, - CITE, - CSCH, - COSH, - COTH, - CURL, - DESC, - DIFF, - DEFS, - FORM, - FONT, - GRAD, - HEAD, - HTML, - LINE, - LINK, - LIST, - META, - MSUB, - MODE, - MATH, - MARK, - MASK, - MEAN, - MAIN, - MSUP, - MENU, - MROW, - NONE, - NOBR, - NEST, - PATH, - PLUS, - RULE, - REAL, - RELN, - RECT, - ROOT, - RUBY, - SECH, - SINH, - SPAN, - SAMP, - STOP, - SDEV, - TIME, - TRUE, - TREF, - TANH, - TEXT, - VIEW, - ASIDE, - AUDIO, - APPLY, - EMBED, - FRAME, - FALSE, - FLOOR, - GLYPH, - HKERN, - IMAGE, - IDENT, - INPUT, - LABEL, - LIMIT, - MFRAC, - MPATH, - METER, - MOVER, - MINUS, - MROOT, - MSQRT, - MTEXT, - NOTIN, - PIECE, - PARAM, - POWER, - REALS, - STYLE, - SMALL, - THEAD, - TABLE, - TITLE, - TRACK, - TSPAN, - TIMES, - TFOOT, - TBODY, - UNION, - VKERN, - VIDEO, - ARCSEC, - ARCCSC, - ARCTAN, - ARCSIN, - ARCCOS, - APPLET, - ARCCOT, - APPROX, - BUTTON, - CIRCLE, - CENTER, - CURSOR, - CANVAS, - DIVIDE, - DEGREE, - DOMAIN, - EXISTS, - FETILE, - FIGURE, - FORALL, - FILTER, - FOOTER, - HGROUP, - HEADER, - IFRAME, - KEYGEN, - LAMBDA, - LEGEND, - MSPACE, - MTABLE, - MSTYLE, - MGLYPH, - MEDIAN, - MUNDER, - MARKER, - MERROR, - MOMENT, - MATRIX, - OPTION, - OBJECT, - OUTPUT, - PRIMES, - SOURCE, - STRIKE, - STRONG, - SWITCH, - SYMBOL, - SELECT, - SUBSET, - SCRIPT, - TBREAK, - VECTOR, - ARTICLE, - ANIMATE, - ARCSECH, - ARCCSCH, - ARCTANH, - ARCSINH, - ARCCOSH, - ARCCOTH, - ACRONYM, - ADDRESS, - BGSOUND, - COMPOSE, - CEILING, - CSYMBOL, - CAPTION, - DISCARD, - DECLARE, - DETAILS, - ELLIPSE, - FEFUNCA, - FEFUNCB, - FEBLEND, - FEFLOOD, - FEIMAGE, - FEMERGE, - FEFUNCG, - FEFUNCR, - HANDLER, - INVERSE, - IMPLIES, - ISINDEX, - LOGBASE, - LISTING, - MFENCED, - MPADDED, - MARQUEE, - MACTION, - MSUBSUP, - NOEMBED, - POLYGON, - PATTERN, - PICTURE, - PRODUCT, - SETDIFF, - SECTION, - SUMMARY, - TENDSTO, - UPLIMIT, - ALTGLYPH, - BASEFONT, - CLIPPATH, - CODOMAIN, - COLGROUP, - EMPTYSET, - FACTOROF, - FIELDSET, - FRAMESET, - FEOFFSET, - GLYPHREF, - INTERVAL, - INTEGERS, - INFINITY, - LISTENER, - LOWLIMIT, - METADATA, - MENCLOSE, - MENUITEM, - MPHANTOM, - NOFRAMES, - NOSCRIPT, - OPTGROUP, - POLYLINE, - PREFETCH, - PROGRESS, - PRSUBSET, - QUOTIENT, - SELECTOR, - TEXTAREA, - TEMPLATE, - TEXTPATH, - VARIANCE, - ANIMATION, - CONJUGATE, - CONDITION, - COMPLEXES, - FONT_FACE, - FACTORIAL, - INTERSECT, - IMAGINARY, - LAPLACIAN, - MATRIXROW, - NOTSUBSET, - OTHERWISE, - PIECEWISE, - PLAINTEXT, - RATIONALS, - SEMANTICS, - TRANSPOSE, - ANNOTATION, - BLOCKQUOTE, - DIVERGENCE, - EULERGAMMA, - EQUIVALENT, - FIGCAPTION, - IMAGINARYI, - MALIGNMARK, - MUNDEROVER, - MLABELEDTR, - NOTANUMBER, - SOLIDCOLOR, - ALTGLYPHDEF, - DETERMINANT, - FEMERGENODE, - FECOMPOSITE, - FESPOTLIGHT, - MALIGNGROUP, - MPRESCRIPTS, - MOMENTABOUT, - NOTPRSUBSET, - PARTIALDIFF, - ALTGLYPHITEM, - ANIMATECOLOR, - DATATEMPLATE, - EXPONENTIALE, - FETURBULENCE, - FEPOINTLIGHT, - FEDROPSHADOW, - FEMORPHOLOGY, - OUTERPRODUCT, - ANIMATEMOTION, - COLOR_PROFILE, - FONT_FACE_SRC, - FONT_FACE_URI, - FOREIGNOBJECT, - FECOLORMATRIX, - MISSING_GLYPH, - MMULTISCRIPTS, - SCALARPRODUCT, - VECTORPRODUCT, - ANNOTATION_XML, - DEFINITION_SRC, - FONT_FACE_NAME, - FEGAUSSIANBLUR, - FEDISTANTLIGHT, - LINEARGRADIENT, - NATURALNUMBERS, - RADIALGRADIENT, - ANIMATETRANSFORM, - CARTESIANPRODUCT, - FONT_FACE_FORMAT, - FECONVOLVEMATRIX, - FEDIFFUSELIGHTING, - FEDISPLACEMENTMAP, - FESPECULARLIGHTING, - DOMAINOFAPPLICATION, - FECOMPONENTTRANSFER, - }; - private final static int[] ELEMENT_HASHES = { - 1057, - 1090, - 1255, - 1321, - 1552, - 1585, - 1651, - 1717, - 68162, - 68899, - 69059, - 69764, - 70020, - 70276, - 71077, - 71205, - 72134, - 72232, - 72264, - 72296, - 72328, - 72360, - 72392, - 73351, - 74312, - 75209, - 78124, - 78284, - 78476, - 79149, - 79309, - 79341, - 79469, - 81295, - 81487, - 82224, - 84050, - 84498, - 84626, - 86164, - 86292, - 86612, - 86676, - 87445, - 3183041, - 3186241, - 3198017, - 3218722, - 3226754, - 3247715, - 3256803, - 3263971, - 3264995, - 3289252, - 3291332, - 3295524, - 3299620, - 3326725, - 3379303, - 3392679, - 3448233, - 3460553, - 3461577, - 3510347, - 3546604, - 3552364, - 3556524, - 3576461, - 3586349, - 3588141, - 3590797, - 3596333, - 3622062, - 3625454, - 3627054, - 3675728, - 3739282, - 3749042, - 3771059, - 3771571, - 3776211, - 3782323, - 3782963, - 3784883, - 3785395, - 3788979, - 3815476, - 3839605, - 3885110, - 3917911, - 3948984, - 3951096, - 135304769, - 135858241, - 136498210, - 136906434, - 137138658, - 137512995, - 137531875, - 137548067, - 137629283, - 137645539, - 137646563, - 137775779, - 138529956, - 138615076, - 139040932, - 140954086, - 141179366, - 141690439, - 142738600, - 143013512, - 146979116, - 147175724, - 147475756, - 147902637, - 147936877, - 148017645, - 148131885, - 148228141, - 148229165, - 148309165, - 148317229, - 148395629, - 148551853, - 148618829, - 149076462, - 149490158, - 149572782, - 151277616, - 151639440, - 153268914, - 153486514, - 153563314, - 153750706, - 153763314, - 153914034, - 154406067, - 154417459, - 154600979, - 154678323, - 154680979, - 154866835, - 155366708, - 155375188, - 155391572, - 155465780, - 155869364, - 158045494, - 168988979, - 169321621, - 169652752, - 173151309, - 174240818, - 174247297, - 174669292, - 175391532, - 176638123, - 177380397, - 177879204, - 177886734, - 180753473, - 181020073, - 181503558, - 181686320, - 181999237, - 181999311, - 182048201, - 182074866, - 182078003, - 182083764, - 182920847, - 184716457, - 184976961, - 185145071, - 187281445, - 187872052, - 188100653, - 188875944, - 188919873, - 188920457, - 189107250, - 189203987, - 189371817, - 189414886, - 189567458, - 190266670, - 191318187, - 191337609, - 202479203, - 202493027, - 202835587, - 202843747, - 203013219, - 203036048, - 203045987, - 203177552, - 203898516, - 204648562, - 205067918, - 205078130, - 205096654, - 205689142, - 205690439, - 205988909, - 207213161, - 207794484, - 207800999, - 208023602, - 208213644, - 208213647, - 210261490, - 210310273, - 210940978, - 213325049, - 213946445, - 214055079, - 215125040, - 215134273, - 215135028, - 215237420, - 215418148, - 215553166, - 215553394, - 215563858, - 215627949, - 215754324, - 217529652, - 217713834, - 217732628, - 218731945, - 221417045, - 221424946, - 221493746, - 221515401, - 221658189, - 221908140, - 221910626, - 221921586, - 222659762, - 225001091, - 236105833, - 236113965, - 236194995, - 236195427, - 236206132, - 236206387, - 236211683, - 236212707, - 236381647, - 236571826, - 237124271, - 238210544, - 238270764, - 238435405, - 238501172, - 239224867, - 239257644, - 239710497, - 240307721, - 241208789, - 241241557, - 241318060, - 241319404, - 241343533, - 241344069, - 241405397, - 241765845, - 243864964, - 244502085, - 244946220, - 245109902, - 247647266, - 247707956, - 248648814, - 248648836, - 248682161, - 248986932, - 249058914, - 249697357, - 252132601, - 252135604, - 251841204, - 252317348, - 255007012, - 255278388, - 255641645, - 256365156, - 257566121, - 269763372, - 271202790, - 271863856, - 272049197, - 272127474, - 274339449, - 274939471, - 275388004, - 275388005, - 275388006, - 275977800, - 278267602, - 278513831, - 278712622, - 281613765, - 281683369, - 282120228, - 282250732, - 282498697, - 282508942, - 283743649, - 283787570, - 284710386, - 285391148, - 285478533, - 285854898, - 285873762, - 286931113, - 288964227, - 289445441, - 289591340, - 289689648, - 291671489, - 303512884, - 305319975, - 305610036, - 305764101, - 308448294, - 308675890, - 312085683, - 312264750, - 315032867, - 316391000, - 317331042, - 317902135, - 318950711, - 319447220, - 321499182, - 322538804, - 323145200, - 337067316, - 337826293, - 339905989, - 340833697, - 341457068, - 342310196, - 345302593, - 349554733, - 349771471, - 349786245, - 350819405, - 356072847, - 370349192, - 373962798, - 375558638, - 375574835, - 376053993, - 383276530, - 383373833, - 383407586, - 384439906, - 386079012, - 404133513, - 404307343, - 407031852, - 408072233, - 409112005, - 409608425, - 409713793, - 409771500, - 419040932, - 437730612, - 439529766, - 442616365, - 442813037, - 443157674, - 443295316, - 450118444, - 450482697, - 456789668, - 459935396, - 471217869, - 474073645, - 476230702, - 476665218, - 476717289, - 483014825, - 485083298, - 489306281, - 538364390, - 540675748, - 543819186, - 543958612, - 576960820, - 577242548, - 610515252, - 642202932, - 644420819, - }; -} diff --git a/parser/html/javasrc/HtmlAttributes.java b/parser/html/javasrc/HtmlAttributes.java deleted file mode 100644 index 0ec25f96f..000000000 --- a/parser/html/javasrc/HtmlAttributes.java +++ /dev/null @@ -1,618 +0,0 @@ -/* - * Copyright (c) 2007 Henri Sivonen - * Copyright (c) 2008-2011 Mozilla Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -package nu.validator.htmlparser.impl; - -import nu.validator.htmlparser.annotation.Auto; -import nu.validator.htmlparser.annotation.IdType; -import nu.validator.htmlparser.annotation.Local; -import nu.validator.htmlparser.annotation.NsUri; -import nu.validator.htmlparser.annotation.Prefix; -import nu.validator.htmlparser.annotation.QName; -import nu.validator.htmlparser.common.Interner; -import nu.validator.htmlparser.common.XmlViolationPolicy; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -/** - * Be careful with this class. QName is the name in from HTML tokenization. - * Otherwise, please refer to the interface doc. - * - * @version $Id: AttributesImpl.java 206 2008-03-20 14:09:29Z hsivonen $ - * @author hsivonen - */ -public final class HtmlAttributes implements Attributes { - - // [NOCPP[ - - private static final AttributeName[] EMPTY_ATTRIBUTENAMES = new AttributeName[0]; - - private static final String[] EMPTY_STRINGS = new String[0]; - - // ]NOCPP] - - public static final HtmlAttributes EMPTY_ATTRIBUTES = new HtmlAttributes( - AttributeName.HTML); - - private int mode; - - private int length; - - private @Auto AttributeName[] names; - - private @Auto String[] values; // XXX perhaps make this @NoLength? - - // CPPONLY: private @Auto int[] lines; // XXX perhaps make this @NoLength? - - // [NOCPP[ - - private String idValue; - - private int xmlnsLength; - - private AttributeName[] xmlnsNames; - - private String[] xmlnsValues; - - // ]NOCPP] - - public HtmlAttributes(int mode) { - this.mode = mode; - this.length = 0; - /* - * The length of 5 covers covers 98.3% of elements - * according to Hixie, but lets round to the next power of two for - * jemalloc. - */ - this.names = new AttributeName[8]; - this.values = new String[8]; - // CPPONLY: this.lines = new int[8]; - - // [NOCPP[ - - this.idValue = null; - - this.xmlnsLength = 0; - - this.xmlnsNames = HtmlAttributes.EMPTY_ATTRIBUTENAMES; - - this.xmlnsValues = HtmlAttributes.EMPTY_STRINGS; - - // ]NOCPP] - } - /* - public HtmlAttributes(HtmlAttributes other) { - this.mode = other.mode; - this.length = other.length; - this.names = new AttributeName[other.length]; - this.values = new String[other.length]; - // [NOCPP[ - this.idValue = other.idValue; - this.xmlnsLength = other.xmlnsLength; - this.xmlnsNames = new AttributeName[other.xmlnsLength]; - this.xmlnsValues = new String[other.xmlnsLength]; - // ]NOCPP] - } - */ - - void destructor() { - clear(0); - } - - /** - * Only use with a static argument - * - * @param name - * @return - */ - public int getIndex(AttributeName name) { - for (int i = 0; i < length; i++) { - if (names[i] == name) { - return i; - } - } - return -1; - } - - /** - * Only use with static argument. - * - * @see org.xml.sax.Attributes#getValue(java.lang.String) - */ - public String getValue(AttributeName name) { - int index = getIndex(name); - if (index == -1) { - return null; - } else { - return getValueNoBoundsCheck(index); - } - } - - public int getLength() { - return length; - } - - /** - * Variant of getLocalName(int index) without bounds check. - * @param index a valid attribute index - * @return the local name at index - */ - public @Local String getLocalNameNoBoundsCheck(int index) { - // CPPONLY: assert index < length && index >= 0: "Index out of bounds"; - return names[index].getLocal(mode); - } - - /** - * Variant of getURI(int index) without bounds check. - * @param index a valid attribute index - * @return the namespace URI at index - */ - public @NsUri String getURINoBoundsCheck(int index) { - // CPPONLY: assert index < length && index >= 0: "Index out of bounds"; - return names[index].getUri(mode); - } - - /** - * Variant of getPrefix(int index) without bounds check. - * @param index a valid attribute index - * @return the namespace prefix at index - */ - public @Prefix String getPrefixNoBoundsCheck(int index) { - // CPPONLY: assert index < length && index >= 0: "Index out of bounds"; - return names[index].getPrefix(mode); - } - - /** - * Variant of getValue(int index) without bounds check. - * @param index a valid attribute index - * @return the attribute value at index - */ - public String getValueNoBoundsCheck(int index) { - // CPPONLY: assert index < length && index >= 0: "Index out of bounds"; - return values[index]; - } - - /** - * Variant of getAttributeName(int index) without bounds check. - * @param index a valid attribute index - * @return the attribute name at index - */ - public AttributeName getAttributeNameNoBoundsCheck(int index) { - // CPPONLY: assert index < length && index >= 0: "Index out of bounds"; - return names[index]; - } - - // CPPONLY: /** - // CPPONLY: * Obtains a line number without bounds check. - // CPPONLY: * @param index a valid attribute index - // CPPONLY: * @return the line number at index or -1 if unknown - // CPPONLY: */ - // CPPONLY: public int getLineNoBoundsCheck(int index) { - // CPPONLY: assert index < length && index >= 0: "Index out of bounds"; - // CPPONLY: return lines[index]; - // CPPONLY: } - - // [NOCPP[ - - /** - * Variant of getQName(int index) without bounds check. - * @param index a valid attribute index - * @return the QName at index - */ - public @QName String getQNameNoBoundsCheck(int index) { - return names[index].getQName(mode); - } - - /** - * Variant of getType(int index) without bounds check. - * @param index a valid attribute index - * @return the attribute type at index - */ - public @IdType String getTypeNoBoundsCheck(int index) { - return (names[index] == AttributeName.ID) ? "ID" : "CDATA"; - } - - public int getIndex(String qName) { - for (int i = 0; i < length; i++) { - if (names[i].getQName(mode).equals(qName)) { - return i; - } - } - return -1; - } - - public int getIndex(String uri, String localName) { - for (int i = 0; i < length; i++) { - if (names[i].getLocal(mode).equals(localName) - && names[i].getUri(mode).equals(uri)) { - return i; - } - } - return -1; - } - - public @IdType String getType(String qName) { - int index = getIndex(qName); - if (index == -1) { - return null; - } else { - return getType(index); - } - } - - public @IdType String getType(String uri, String localName) { - int index = getIndex(uri, localName); - if (index == -1) { - return null; - } else { - return getType(index); - } - } - - public String getValue(String qName) { - int index = getIndex(qName); - if (index == -1) { - return null; - } else { - return getValue(index); - } - } - - public String getValue(String uri, String localName) { - int index = getIndex(uri, localName); - if (index == -1) { - return null; - } else { - return getValue(index); - } - } - - public @Local String getLocalName(int index) { - if (index < length && index >= 0) { - return names[index].getLocal(mode); - } else { - return null; - } - } - - public @QName String getQName(int index) { - if (index < length && index >= 0) { - return names[index].getQName(mode); - } else { - return null; - } - } - - public @IdType String getType(int index) { - if (index < length && index >= 0) { - return (names[index] == AttributeName.ID) ? "ID" : "CDATA"; - } else { - return null; - } - } - - public AttributeName getAttributeName(int index) { - if (index < length && index >= 0) { - return names[index]; - } else { - return null; - } - } - - public @NsUri String getURI(int index) { - if (index < length && index >= 0) { - return names[index].getUri(mode); - } else { - return null; - } - } - - public @Prefix String getPrefix(int index) { - if (index < length && index >= 0) { - return names[index].getPrefix(mode); - } else { - return null; - } - } - - public String getValue(int index) { - if (index < length && index >= 0) { - return values[index]; - } else { - return null; - } - } - - public String getId() { - return idValue; - } - - public int getXmlnsLength() { - return xmlnsLength; - } - - public @Local String getXmlnsLocalName(int index) { - if (index < xmlnsLength && index >= 0) { - return xmlnsNames[index].getLocal(mode); - } else { - return null; - } - } - - public @NsUri String getXmlnsURI(int index) { - if (index < xmlnsLength && index >= 0) { - return xmlnsNames[index].getUri(mode); - } else { - return null; - } - } - - public String getXmlnsValue(int index) { - if (index < xmlnsLength && index >= 0) { - return xmlnsValues[index]; - } else { - return null; - } - } - - public int getXmlnsIndex(AttributeName name) { - for (int i = 0; i < xmlnsLength; i++) { - if (xmlnsNames[i] == name) { - return i; - } - } - return -1; - } - - public String getXmlnsValue(AttributeName name) { - int index = getXmlnsIndex(name); - if (index == -1) { - return null; - } else { - return getXmlnsValue(index); - } - } - - public AttributeName getXmlnsAttributeName(int index) { - if (index < xmlnsLength && index >= 0) { - return xmlnsNames[index]; - } else { - return null; - } - } - - // ]NOCPP] - - void addAttribute(AttributeName name, String value - // [NOCPP[ - , XmlViolationPolicy xmlnsPolicy - // ]NOCPP] - // CPPONLY: , int line - ) throws SAXException { - // [NOCPP[ - if (name == AttributeName.ID) { - idValue = value; - } - - if (name.isXmlns()) { - if (xmlnsNames.length == xmlnsLength) { - int newLen = xmlnsLength == 0 ? 2 : xmlnsLength << 1; - AttributeName[] newNames = new AttributeName[newLen]; - System.arraycopy(xmlnsNames, 0, newNames, 0, xmlnsNames.length); - xmlnsNames = newNames; - String[] newValues = new String[newLen]; - System.arraycopy(xmlnsValues, 0, newValues, 0, xmlnsValues.length); - xmlnsValues = newValues; - } - xmlnsNames[xmlnsLength] = name; - xmlnsValues[xmlnsLength] = value; - xmlnsLength++; - switch (xmlnsPolicy) { - case FATAL: - // this is ugly - throw new SAXException("Saw an xmlns attribute."); - case ALTER_INFOSET: - return; - case ALLOW: - // fall through - } - } - - // ]NOCPP] - - if (names.length == length) { - int newLen = length << 1; // The first growth covers virtually - // 100% of elements according to - // Hixie - AttributeName[] newNames = new AttributeName[newLen]; - System.arraycopy(names, 0, newNames, 0, names.length); - names = newNames; - String[] newValues = new String[newLen]; - System.arraycopy(values, 0, newValues, 0, values.length); - values = newValues; - // CPPONLY: int[] newLines = new int[newLen]; - // CPPONLY: System.arraycopy(lines, 0, newLines, 0, lines.length); - // CPPONLY: lines = newLines; - } - names[length] = name; - values[length] = value; - // CPPONLY: lines[length] = line; - length++; - } - - void clear(int m) { - for (int i = 0; i < length; i++) { - names[i].release(); - names[i] = null; - Portability.releaseString(values[i]); - values[i] = null; - } - length = 0; - mode = m; - // [NOCPP[ - idValue = null; - for (int i = 0; i < xmlnsLength; i++) { - xmlnsNames[i] = null; - xmlnsValues[i] = null; - } - xmlnsLength = 0; - // ]NOCPP] - } - - /** - * This is used in C++ to release special isindex - * attribute values whose ownership is not transferred. - */ - void releaseValue(int i) { - Portability.releaseString(values[i]); - } - - /** - * This is only used for AttributeName ownership transfer - * in the isindex case to avoid freeing custom names twice in C++. - */ - void clearWithoutReleasingContents() { - for (int i = 0; i < length; i++) { - names[i] = null; - values[i] = null; - } - length = 0; - } - - boolean contains(AttributeName name) { - for (int i = 0; i < length; i++) { - if (name.equalsAnother(names[i])) { - return true; - } - } - // [NOCPP[ - for (int i = 0; i < xmlnsLength; i++) { - if (name.equalsAnother(xmlnsNames[i])) { - return true; - } - } - // ]NOCPP] - return false; - } - - public void adjustForMath() { - mode = AttributeName.MATHML; - } - - public void adjustForSvg() { - mode = AttributeName.SVG; - } - - public HtmlAttributes cloneAttributes(Interner interner) - throws SAXException { - assert (length == 0 - // [NOCPP[ - && xmlnsLength == 0 - // ]NOCPP] - ) - || mode == 0 || mode == 3; - HtmlAttributes clone = new HtmlAttributes(0); - for (int i = 0; i < length; i++) { - clone.addAttribute(names[i].cloneAttributeName(interner), - Portability.newStringFromString(values[i]) - // [NOCPP[ - , XmlViolationPolicy.ALLOW - // ]NOCPP] - // CPPONLY: , lines[i] - ); - } - // [NOCPP[ - for (int i = 0; i < xmlnsLength; i++) { - clone.addAttribute(xmlnsNames[i], xmlnsValues[i], - XmlViolationPolicy.ALLOW); - } - // ]NOCPP] - return clone; // XXX!!! - } - - public boolean equalsAnother(HtmlAttributes other) { - assert mode == 0 || mode == 3 : "Trying to compare attributes in foreign content."; - int otherLength = other.getLength(); - if (length != otherLength) { - return false; - } - for (int i = 0; i < length; i++) { - // Work around the limitations of C++ - boolean found = false; - // The comparing just the local names is OK, since these attribute - // holders are both supposed to belong to HTML formatting elements - @Local String ownLocal = names[i].getLocal(AttributeName.HTML); - for (int j = 0; j < otherLength; j++) { - if (ownLocal == other.names[j].getLocal(AttributeName.HTML)) { - found = true; - if (!Portability.stringEqualsString(values[i], other.values[j])) { - return false; - } - } - } - if (!found) { - return false; - } - } - return true; - } - - // [NOCPP[ - - void processNonNcNames(TreeBuilder treeBuilder, XmlViolationPolicy namePolicy) throws SAXException { - for (int i = 0; i < length; i++) { - AttributeName attName = names[i]; - if (!attName.isNcName(mode)) { - String name = attName.getLocal(mode); - switch (namePolicy) { - case ALTER_INFOSET: - names[i] = AttributeName.create(NCName.escapeName(name)); - // fall through - case ALLOW: - if (attName != AttributeName.XML_LANG) { - treeBuilder.warn("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0."); - } - break; - case FATAL: - treeBuilder.fatal("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0."); - break; - } - } - } - } - - public void merge(HtmlAttributes attributes) throws SAXException { - int len = attributes.getLength(); - for (int i = 0; i < len; i++) { - AttributeName name = attributes.getAttributeNameNoBoundsCheck(i); - if (!contains(name)) { - addAttribute(name, attributes.getValueNoBoundsCheck(i), XmlViolationPolicy.ALLOW); - } - } - } - - - // ]NOCPP] - -} diff --git a/parser/html/javasrc/MetaScanner.java b/parser/html/javasrc/MetaScanner.java deleted file mode 100644 index be9aabfe3..000000000 --- a/parser/html/javasrc/MetaScanner.java +++ /dev/null @@ -1,854 +0,0 @@ -/* - * Copyright (c) 2007 Henri Sivonen - * Copyright (c) 2008-2015 Mozilla Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -package nu.validator.htmlparser.impl; - -import java.io.IOException; - -import nu.validator.htmlparser.annotation.Auto; -import nu.validator.htmlparser.annotation.Inline; -import nu.validator.htmlparser.common.ByteReadable; - -import org.xml.sax.SAXException; - -public abstract class MetaScanner { - - /** - * Constant for "charset". - */ - private static final char[] CHARSET = { 'h', 'a', 'r', 's', 'e', 't' }; - - /** - * Constant for "content". - */ - private static final char[] CONTENT = { 'o', 'n', 't', 'e', 'n', 't' }; - - /** - * Constant for "http-equiv". - */ - private static final char[] HTTP_EQUIV = { 't', 't', 'p', '-', 'e', 'q', - 'u', 'i', 'v' }; - - /** - * Constant for "content-type". - */ - private static final char[] CONTENT_TYPE = { 'c', 'o', 'n', 't', 'e', 'n', - 't', '-', 't', 'y', 'p', 'e' }; - - private static final int NO = 0; - - private static final int M = 1; - - private static final int E = 2; - - private static final int T = 3; - - private static final int A = 4; - - private static final int DATA = 0; - - private static final int TAG_OPEN = 1; - - private static final int SCAN_UNTIL_GT = 2; - - private static final int TAG_NAME = 3; - - private static final int BEFORE_ATTRIBUTE_NAME = 4; - - private static final int ATTRIBUTE_NAME = 5; - - private static final int AFTER_ATTRIBUTE_NAME = 6; - - private static final int BEFORE_ATTRIBUTE_VALUE = 7; - - private static final int ATTRIBUTE_VALUE_DOUBLE_QUOTED = 8; - - private static final int ATTRIBUTE_VALUE_SINGLE_QUOTED = 9; - - private static final int ATTRIBUTE_VALUE_UNQUOTED = 10; - - private static final int AFTER_ATTRIBUTE_VALUE_QUOTED = 11; - - private static final int MARKUP_DECLARATION_OPEN = 13; - - private static final int MARKUP_DECLARATION_HYPHEN = 14; - - private static final int COMMENT_START = 15; - - private static final int COMMENT_START_DASH = 16; - - private static final int COMMENT = 17; - - private static final int COMMENT_END_DASH = 18; - - private static final int COMMENT_END = 19; - - private static final int SELF_CLOSING_START_TAG = 20; - - private static final int HTTP_EQUIV_NOT_SEEN = 0; - - private static final int HTTP_EQUIV_CONTENT_TYPE = 1; - - private static final int HTTP_EQUIV_OTHER = 2; - - /** - * The data source. - */ - protected ByteReadable readable; - - /** - * The state of the state machine that recognizes the tag name "meta". - */ - private int metaState = NO; - - /** - * The current position in recognizing the attribute name "content". - */ - private int contentIndex = Integer.MAX_VALUE; - - /** - * The current position in recognizing the attribute name "charset". - */ - private int charsetIndex = Integer.MAX_VALUE; - - /** - * The current position in recognizing the attribute name "http-equive". - */ - private int httpEquivIndex = Integer.MAX_VALUE; - - /** - * The current position in recognizing the attribute value "content-type". - */ - private int contentTypeIndex = Integer.MAX_VALUE; - - /** - * The tokenizer state. - */ - protected int stateSave = DATA; - - /** - * The currently filled length of strBuf. - */ - private int strBufLen; - - /** - * Accumulation buffer for attribute values. - */ - private @Auto char[] strBuf; - - private String content; - - private String charset; - - private int httpEquivState; - - // CPPONLY: private TreeBuilder treeBuilder; - - public MetaScanner( - // CPPONLY: TreeBuilder tb - ) { - this.readable = null; - this.metaState = NO; - this.contentIndex = Integer.MAX_VALUE; - this.charsetIndex = Integer.MAX_VALUE; - this.httpEquivIndex = Integer.MAX_VALUE; - this.contentTypeIndex = Integer.MAX_VALUE; - this.stateSave = DATA; - this.strBufLen = 0; - this.strBuf = new char[36]; - this.content = null; - this.charset = null; - this.httpEquivState = HTTP_EQUIV_NOT_SEEN; - // CPPONLY: this.treeBuilder = tb; - } - - @SuppressWarnings("unused") private void destructor() { - Portability.releaseString(content); - Portability.releaseString(charset); - } - - // [NOCPP[ - - /** - * Reads a byte from the data source. - * - * -1 means end. - * @return - * @throws IOException - */ - protected int read() throws IOException { - return readable.readByte(); - } - - // ]NOCPP] - - // WARNING When editing this, makes sure the bytecode length shown by javap - // stays under 8000 bytes! - /** - * The runs the meta scanning algorithm. - */ - protected final void stateLoop(int state) - throws SAXException, IOException { - int c = -1; - boolean reconsume = false; - stateloop: for (;;) { - switch (state) { - case DATA: - dataloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - c = read(); - } - switch (c) { - case -1: - break stateloop; - case '<': - state = MetaScanner.TAG_OPEN; - break dataloop; // FALL THROUGH continue - // stateloop; - default: - continue; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case TAG_OPEN: - tagopenloop: for (;;) { - c = read(); - switch (c) { - case -1: - break stateloop; - case 'm': - case 'M': - metaState = M; - state = MetaScanner.TAG_NAME; - break tagopenloop; - // continue stateloop; - case '!': - state = MetaScanner.MARKUP_DECLARATION_OPEN; - continue stateloop; - case '?': - case '/': - state = MetaScanner.SCAN_UNTIL_GT; - continue stateloop; - case '>': - state = MetaScanner.DATA; - continue stateloop; - default: - if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) { - metaState = NO; - state = MetaScanner.TAG_NAME; - break tagopenloop; - // continue stateloop; - } - state = MetaScanner.DATA; - reconsume = true; - continue stateloop; - } - } - // FALL THROUGH DON'T REORDER - case TAG_NAME: - tagnameloop: for (;;) { - c = read(); - switch (c) { - case -1: - break stateloop; - case ' ': - case '\t': - case '\n': - case '\u000C': - state = MetaScanner.BEFORE_ATTRIBUTE_NAME; - break tagnameloop; - // continue stateloop; - case '/': - state = MetaScanner.SELF_CLOSING_START_TAG; - continue stateloop; - case '>': - state = MetaScanner.DATA; - continue stateloop; - case 'e': - case 'E': - if (metaState == M) { - metaState = E; - } else { - metaState = NO; - } - continue; - case 't': - case 'T': - if (metaState == E) { - metaState = T; - } else { - metaState = NO; - } - continue; - case 'a': - case 'A': - if (metaState == T) { - metaState = A; - } else { - metaState = NO; - } - continue; - default: - metaState = NO; - continue; - } - } - // FALLTHRU DON'T REORDER - case BEFORE_ATTRIBUTE_NAME: - beforeattributenameloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - c = read(); - } - /* - * Consume the next input character: - */ - switch (c) { - case -1: - break stateloop; - case ' ': - case '\t': - case '\n': - case '\u000C': - continue; - case '/': - state = MetaScanner.SELF_CLOSING_START_TAG; - continue stateloop; - case '>': - if (handleTag()) { - break stateloop; - } - state = DATA; - continue stateloop; - case 'c': - case 'C': - contentIndex = 0; - charsetIndex = 0; - httpEquivIndex = Integer.MAX_VALUE; - contentTypeIndex = Integer.MAX_VALUE; - state = MetaScanner.ATTRIBUTE_NAME; - break beforeattributenameloop; - case 'h': - case 'H': - contentIndex = Integer.MAX_VALUE; - charsetIndex = Integer.MAX_VALUE; - httpEquivIndex = 0; - contentTypeIndex = Integer.MAX_VALUE; - state = MetaScanner.ATTRIBUTE_NAME; - break beforeattributenameloop; - default: - contentIndex = Integer.MAX_VALUE; - charsetIndex = Integer.MAX_VALUE; - httpEquivIndex = Integer.MAX_VALUE; - contentTypeIndex = Integer.MAX_VALUE; - state = MetaScanner.ATTRIBUTE_NAME; - break beforeattributenameloop; - // continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case ATTRIBUTE_NAME: - attributenameloop: for (;;) { - c = read(); - switch (c) { - case -1: - break stateloop; - case ' ': - case '\t': - case '\n': - case '\u000C': - state = MetaScanner.AFTER_ATTRIBUTE_NAME; - continue stateloop; - case '/': - state = MetaScanner.SELF_CLOSING_START_TAG; - continue stateloop; - case '=': - strBufLen = 0; - contentTypeIndex = 0; - state = MetaScanner.BEFORE_ATTRIBUTE_VALUE; - break attributenameloop; - // continue stateloop; - case '>': - if (handleTag()) { - break stateloop; - } - state = MetaScanner.DATA; - continue stateloop; - default: - if (metaState == A) { - if (c >= 'A' && c <= 'Z') { - c += 0x20; - } - if (contentIndex < CONTENT.length && c == CONTENT[contentIndex]) { - ++contentIndex; - } else { - contentIndex = Integer.MAX_VALUE; - } - if (charsetIndex < CHARSET.length && c == CHARSET[charsetIndex]) { - ++charsetIndex; - } else { - charsetIndex = Integer.MAX_VALUE; - } - if (httpEquivIndex < HTTP_EQUIV.length && c == HTTP_EQUIV[httpEquivIndex]) { - ++httpEquivIndex; - } else { - httpEquivIndex = Integer.MAX_VALUE; - } - } - continue; - } - } - // FALLTHRU DON'T REORDER - case BEFORE_ATTRIBUTE_VALUE: - beforeattributevalueloop: for (;;) { - c = read(); - switch (c) { - case -1: - break stateloop; - case ' ': - case '\t': - case '\n': - case '\u000C': - continue; - case '"': - state = MetaScanner.ATTRIBUTE_VALUE_DOUBLE_QUOTED; - break beforeattributevalueloop; - // continue stateloop; - case '\'': - state = MetaScanner.ATTRIBUTE_VALUE_SINGLE_QUOTED; - continue stateloop; - case '>': - if (handleTag()) { - break stateloop; - } - state = MetaScanner.DATA; - continue stateloop; - default: - handleCharInAttributeValue(c); - state = MetaScanner.ATTRIBUTE_VALUE_UNQUOTED; - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case ATTRIBUTE_VALUE_DOUBLE_QUOTED: - attributevaluedoublequotedloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - c = read(); - } - switch (c) { - case -1: - break stateloop; - case '"': - handleAttributeValue(); - state = MetaScanner.AFTER_ATTRIBUTE_VALUE_QUOTED; - break attributevaluedoublequotedloop; - // continue stateloop; - default: - handleCharInAttributeValue(c); - continue; - } - } - // FALLTHRU DON'T REORDER - case AFTER_ATTRIBUTE_VALUE_QUOTED: - afterattributevaluequotedloop: for (;;) { - c = read(); - switch (c) { - case -1: - break stateloop; - case ' ': - case '\t': - case '\n': - case '\u000C': - state = MetaScanner.BEFORE_ATTRIBUTE_NAME; - continue stateloop; - case '/': - state = MetaScanner.SELF_CLOSING_START_TAG; - break afterattributevaluequotedloop; - // continue stateloop; - case '>': - if (handleTag()) { - break stateloop; - } - state = MetaScanner.DATA; - continue stateloop; - default: - state = MetaScanner.BEFORE_ATTRIBUTE_NAME; - reconsume = true; - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case SELF_CLOSING_START_TAG: - c = read(); - switch (c) { - case -1: - break stateloop; - case '>': - if (handleTag()) { - break stateloop; - } - state = MetaScanner.DATA; - continue stateloop; - default: - state = MetaScanner.BEFORE_ATTRIBUTE_NAME; - reconsume = true; - continue stateloop; - } - // XXX reorder point - case ATTRIBUTE_VALUE_UNQUOTED: - for (;;) { - if (reconsume) { - reconsume = false; - } else { - c = read(); - } - switch (c) { - case -1: - break stateloop; - case ' ': - case '\t': - case '\n': - - case '\u000C': - handleAttributeValue(); - state = MetaScanner.BEFORE_ATTRIBUTE_NAME; - continue stateloop; - case '>': - handleAttributeValue(); - if (handleTag()) { - break stateloop; - } - state = MetaScanner.DATA; - continue stateloop; - default: - handleCharInAttributeValue(c); - continue; - } - } - // XXX reorder point - case AFTER_ATTRIBUTE_NAME: - for (;;) { - c = read(); - switch (c) { - case -1: - break stateloop; - case ' ': - case '\t': - case '\n': - case '\u000C': - continue; - case '/': - handleAttributeValue(); - state = MetaScanner.SELF_CLOSING_START_TAG; - continue stateloop; - case '=': - strBufLen = 0; - contentTypeIndex = 0; - state = MetaScanner.BEFORE_ATTRIBUTE_VALUE; - continue stateloop; - case '>': - handleAttributeValue(); - if (handleTag()) { - break stateloop; - } - state = MetaScanner.DATA; - continue stateloop; - case 'c': - case 'C': - contentIndex = 0; - charsetIndex = 0; - state = MetaScanner.ATTRIBUTE_NAME; - continue stateloop; - default: - contentIndex = Integer.MAX_VALUE; - charsetIndex = Integer.MAX_VALUE; - state = MetaScanner.ATTRIBUTE_NAME; - continue stateloop; - } - } - // XXX reorder point - case MARKUP_DECLARATION_OPEN: - markupdeclarationopenloop: for (;;) { - c = read(); - switch (c) { - case -1: - break stateloop; - case '-': - state = MetaScanner.MARKUP_DECLARATION_HYPHEN; - break markupdeclarationopenloop; - // continue stateloop; - default: - state = MetaScanner.SCAN_UNTIL_GT; - reconsume = true; - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case MARKUP_DECLARATION_HYPHEN: - markupdeclarationhyphenloop: for (;;) { - c = read(); - switch (c) { - case -1: - break stateloop; - case '-': - state = MetaScanner.COMMENT_START; - break markupdeclarationhyphenloop; - // continue stateloop; - default: - state = MetaScanner.SCAN_UNTIL_GT; - reconsume = true; - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case COMMENT_START: - commentstartloop: for (;;) { - c = read(); - switch (c) { - case -1: - break stateloop; - case '-': - state = MetaScanner.COMMENT_START_DASH; - continue stateloop; - case '>': - state = MetaScanner.DATA; - continue stateloop; - default: - state = MetaScanner.COMMENT; - break commentstartloop; - // continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case COMMENT: - commentloop: for (;;) { - c = read(); - switch (c) { - case -1: - break stateloop; - case '-': - state = MetaScanner.COMMENT_END_DASH; - break commentloop; - // continue stateloop; - default: - continue; - } - } - // FALLTHRU DON'T REORDER - case COMMENT_END_DASH: - commentenddashloop: for (;;) { - c = read(); - switch (c) { - case -1: - break stateloop; - case '-': - state = MetaScanner.COMMENT_END; - break commentenddashloop; - // continue stateloop; - default: - state = MetaScanner.COMMENT; - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case COMMENT_END: - for (;;) { - c = read(); - switch (c) { - case -1: - break stateloop; - case '>': - state = MetaScanner.DATA; - continue stateloop; - case '-': - continue; - default: - state = MetaScanner.COMMENT; - continue stateloop; - } - } - // XXX reorder point - case COMMENT_START_DASH: - c = read(); - switch (c) { - case -1: - break stateloop; - case '-': - state = MetaScanner.COMMENT_END; - continue stateloop; - case '>': - state = MetaScanner.DATA; - continue stateloop; - default: - state = MetaScanner.COMMENT; - continue stateloop; - } - // XXX reorder point - case ATTRIBUTE_VALUE_SINGLE_QUOTED: - for (;;) { - if (reconsume) { - reconsume = false; - } else { - c = read(); - } - switch (c) { - case -1: - break stateloop; - case '\'': - handleAttributeValue(); - state = MetaScanner.AFTER_ATTRIBUTE_VALUE_QUOTED; - continue stateloop; - default: - handleCharInAttributeValue(c); - continue; - } - } - // XXX reorder point - case SCAN_UNTIL_GT: - for (;;) { - if (reconsume) { - reconsume = false; - } else { - c = read(); - } - switch (c) { - case -1: - break stateloop; - case '>': - state = MetaScanner.DATA; - continue stateloop; - default: - continue; - } - } - } - } - stateSave = state; - } - - private void handleCharInAttributeValue(int c) { - if (metaState == A) { - if (contentIndex == CONTENT.length || charsetIndex == CHARSET.length) { - addToBuffer(c); - } else if (httpEquivIndex == HTTP_EQUIV.length) { - if (contentTypeIndex < CONTENT_TYPE.length && toAsciiLowerCase(c) == CONTENT_TYPE[contentTypeIndex]) { - ++contentTypeIndex; - } else { - contentTypeIndex = Integer.MAX_VALUE; - } - } - } - } - - @Inline private int toAsciiLowerCase(int c) { - if (c >= 'A' && c <= 'Z') { - return c + 0x20; - } - return c; - } - - /** - * Adds a character to the accumulation buffer. - * @param c the character to add - */ - private void addToBuffer(int c) { - if (strBufLen == strBuf.length) { - char[] newBuf = new char[strBuf.length + (strBuf.length << 1)]; - System.arraycopy(strBuf, 0, newBuf, 0, strBuf.length); - strBuf = newBuf; - } - strBuf[strBufLen++] = (char)c; - } - - /** - * Attempts to extract a charset name from the accumulation buffer. - * @return true if successful - * @throws SAXException - */ - private void handleAttributeValue() throws SAXException { - if (metaState != A) { - return; - } - if (contentIndex == CONTENT.length && content == null) { - content = Portability.newStringFromBuffer(strBuf, 0, strBufLen - // CPPONLY: , treeBuilder - ); - return; - } - if (charsetIndex == CHARSET.length && charset == null) { - charset = Portability.newStringFromBuffer(strBuf, 0, strBufLen - // CPPONLY: , treeBuilder - ); - return; - } - if (httpEquivIndex == HTTP_EQUIV.length - && httpEquivState == HTTP_EQUIV_NOT_SEEN) { - httpEquivState = (contentTypeIndex == CONTENT_TYPE.length) ? HTTP_EQUIV_CONTENT_TYPE - : HTTP_EQUIV_OTHER; - return; - } - } - - private boolean handleTag() throws SAXException { - boolean stop = handleTagInner(); - Portability.releaseString(content); - content = null; - Portability.releaseString(charset); - charset = null; - httpEquivState = HTTP_EQUIV_NOT_SEEN; - return stop; - } - - private boolean handleTagInner() throws SAXException { - if (charset != null && tryCharset(charset)) { - return true; - } - if (content != null && httpEquivState == HTTP_EQUIV_CONTENT_TYPE) { - String extract = TreeBuilder.extractCharsetFromContent(content - // CPPONLY: , treeBuilder - ); - if (extract == null) { - return false; - } - boolean success = tryCharset(extract); - Portability.releaseString(extract); - return success; - } - return false; - } - - /** - * Tries to switch to an encoding. - * - * @param encoding - * @return true if successful - * @throws SAXException - */ - protected abstract boolean tryCharset(String encoding) throws SAXException; - -} diff --git a/parser/html/javasrc/Portability.java b/parser/html/javasrc/Portability.java deleted file mode 100644 index 485684ea1..000000000 --- a/parser/html/javasrc/Portability.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2008-2015 Mozilla Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -package nu.validator.htmlparser.impl; - -import nu.validator.htmlparser.annotation.Literal; -import nu.validator.htmlparser.annotation.Local; -import nu.validator.htmlparser.annotation.NoLength; -import nu.validator.htmlparser.common.Interner; - -public final class Portability { - - // Allocating methods - - /** - * Allocates a new local name object. In C++, the refcount must be set up in such a way that - * calling releaseLocal on the return value balances the refcount set by this method. - */ - public static @Local String newLocalNameFromBuffer(@NoLength char[] buf, int offset, int length, Interner interner) { - return new String(buf, offset, length).intern(); - } - - public static String newStringFromBuffer(@NoLength char[] buf, int offset, int length - // CPPONLY: , TreeBuilder treeBuilder - ) { - return new String(buf, offset, length); - } - - public static String newEmptyString() { - return ""; - } - - public static String newStringFromLiteral(@Literal String literal) { - return literal; - } - - public static String newStringFromString(String string) { - return string; - } - - // XXX get rid of this - public static char[] newCharArrayFromLocal(@Local String local) { - return local.toCharArray(); - } - - public static char[] newCharArrayFromString(String string) { - return string.toCharArray(); - } - - public static @Local String newLocalFromLocal(@Local String local, Interner interner) { - return local; - } - - // Deallocation methods - - public static void releaseString(String str) { - // No-op in Java - } - - // Comparison methods - - public static boolean localEqualsBuffer(@Local String local, @NoLength char[] buf, int offset, int length) { - if (local.length() != length) { - return false; - } - for (int i = 0; i < length; i++) { - if (local.charAt(i) != buf[offset + i]) { - return false; - } - } - return true; - } - - public static boolean lowerCaseLiteralIsPrefixOfIgnoreAsciiCaseString(@Literal String lowerCaseLiteral, - String string) { - if (string == null) { - return false; - } - if (lowerCaseLiteral.length() > string.length()) { - return false; - } - for (int i = 0; i < lowerCaseLiteral.length(); i++) { - char c0 = lowerCaseLiteral.charAt(i); - char c1 = string.charAt(i); - if (c1 >= 'A' && c1 <= 'Z') { - c1 += 0x20; - } - if (c0 != c1) { - return false; - } - } - return true; - } - - public static boolean lowerCaseLiteralEqualsIgnoreAsciiCaseString(@Literal String lowerCaseLiteral, - String string) { - if (string == null) { - return false; - } - if (lowerCaseLiteral.length() != string.length()) { - return false; - } - for (int i = 0; i < lowerCaseLiteral.length(); i++) { - char c0 = lowerCaseLiteral.charAt(i); - char c1 = string.charAt(i); - if (c1 >= 'A' && c1 <= 'Z') { - c1 += 0x20; - } - if (c0 != c1) { - return false; - } - } - return true; - } - - public static boolean literalEqualsString(@Literal String literal, String string) { - return literal.equals(string); - } - - public static boolean stringEqualsString(String one, String other) { - return one.equals(other); - } - - public static void delete(Object o) { - - } - - public static void deleteArray(Object o) { - - } -} diff --git a/parser/html/javasrc/README.txt b/parser/html/javasrc/README.txt deleted file mode 100644 index 4555969ca..000000000 --- a/parser/html/javasrc/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -The .java files in this directory were placed here by the Java-to-C++ -translator that lives in parser/html/java/translator. Together they represent -a snapshot of the Java code that was translated to produce the corresponding -.h and .cpp files in the parent directory. Changing these .java files is not -worthwhile, as they will just be overwritten by the next translation. See -parser/html/java/README.txt for information about performing the translation. diff --git a/parser/html/javasrc/StackNode.java b/parser/html/javasrc/StackNode.java deleted file mode 100644 index 9aeaba0be..000000000 --- a/parser/html/javasrc/StackNode.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright (c) 2007 Henri Sivonen - * Copyright (c) 2007-2011 Mozilla Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -package nu.validator.htmlparser.impl; - -import nu.validator.htmlparser.annotation.Inline; -import nu.validator.htmlparser.annotation.Local; -import nu.validator.htmlparser.annotation.NsUri; - -final class StackNode { - final int flags; - - final @Local String name; - - final @Local String popName; - - final @NsUri String ns; - - final T node; - - // Only used on the list of formatting elements - HtmlAttributes attributes; - - private int refcount = 1; - - // [NOCPP[ - - private final TaintableLocatorImpl locator; - - public TaintableLocatorImpl getLocator() { - return locator; - } - - // ]NOCPP] - - @Inline public int getFlags() { - return flags; - } - - public int getGroup() { - return flags & ElementName.GROUP_MASK; - } - - public boolean isScoping() { - return (flags & ElementName.SCOPING) != 0; - } - - public boolean isSpecial() { - return (flags & ElementName.SPECIAL) != 0; - } - - public boolean isFosterParenting() { - return (flags & ElementName.FOSTER_PARENTING) != 0; - } - - public boolean isHtmlIntegrationPoint() { - return (flags & ElementName.HTML_INTEGRATION_POINT) != 0; - } - - // [NOCPP[ - - public boolean isOptionalEndTag() { - return (flags & ElementName.OPTIONAL_END_TAG) != 0; - } - - // ]NOCPP] - - /** - * Constructor for copying. This doesn't take another StackNode - * because in C++ the caller is reponsible for reobtaining the local names - * from another interner. - * - * @param flags - * @param ns - * @param name - * @param node - * @param popName - * @param attributes - */ - StackNode(int flags, @NsUri String ns, @Local String name, T node, - @Local String popName, HtmlAttributes attributes - // [NOCPP[ - , TaintableLocatorImpl locator - // ]NOCPP] - ) { - this.flags = flags; - this.name = name; - this.popName = popName; - this.ns = ns; - this.node = node; - this.attributes = attributes; - this.refcount = 1; - // [NOCPP[ - this.locator = locator; - // ]NOCPP] - } - - /** - * Short hand for well-known HTML elements. - * - * @param elementName - * @param node - */ - StackNode(ElementName elementName, T node - // [NOCPP[ - , TaintableLocatorImpl locator - // ]NOCPP] - ) { - this.flags = elementName.getFlags(); - this.name = elementName.name; - this.popName = elementName.name; - this.ns = "http://www.w3.org/1999/xhtml"; - this.node = node; - this.attributes = null; - this.refcount = 1; - assert !elementName.isCustom() : "Don't use this constructor for custom elements."; - // [NOCPP[ - this.locator = locator; - // ]NOCPP] - } - - /** - * Constructor for HTML formatting elements. - * - * @param elementName - * @param node - * @param attributes - */ - StackNode(ElementName elementName, T node, HtmlAttributes attributes - // [NOCPP[ - , TaintableLocatorImpl locator - // ]NOCPP] - ) { - this.flags = elementName.getFlags(); - this.name = elementName.name; - this.popName = elementName.name; - this.ns = "http://www.w3.org/1999/xhtml"; - this.node = node; - this.attributes = attributes; - this.refcount = 1; - assert !elementName.isCustom() : "Don't use this constructor for custom elements."; - // [NOCPP[ - this.locator = locator; - // ]NOCPP] - } - - /** - * The common-case HTML constructor. - * - * @param elementName - * @param node - * @param popName - */ - StackNode(ElementName elementName, T node, @Local String popName - // [NOCPP[ - , TaintableLocatorImpl locator - // ]NOCPP] - ) { - this.flags = elementName.getFlags(); - this.name = elementName.name; - this.popName = popName; - this.ns = "http://www.w3.org/1999/xhtml"; - this.node = node; - this.attributes = null; - this.refcount = 1; - // [NOCPP[ - this.locator = locator; - // ]NOCPP] - } - - /** - * Constructor for SVG elements. Note that the order of the arguments is - * what distinguishes this from the HTML constructor. This is ugly, but - * AFAICT the least disruptive way to make this work with Java's generics - * and without unnecessary branches. :-( - * - * @param elementName - * @param popName - * @param node - */ - StackNode(ElementName elementName, @Local String popName, T node - // [NOCPP[ - , TaintableLocatorImpl locator - // ]NOCPP] - ) { - this.flags = prepareSvgFlags(elementName.getFlags()); - this.name = elementName.name; - this.popName = popName; - this.ns = "http://www.w3.org/2000/svg"; - this.node = node; - this.attributes = null; - this.refcount = 1; - // [NOCPP[ - this.locator = locator; - // ]NOCPP] - } - - /** - * Constructor for MathML. - * - * @param elementName - * @param node - * @param popName - * @param markAsIntegrationPoint - */ - StackNode(ElementName elementName, T node, @Local String popName, - boolean markAsIntegrationPoint - // [NOCPP[ - , TaintableLocatorImpl locator - // ]NOCPP] - ) { - this.flags = prepareMathFlags(elementName.getFlags(), - markAsIntegrationPoint); - this.name = elementName.name; - this.popName = popName; - this.ns = "http://www.w3.org/1998/Math/MathML"; - this.node = node; - this.attributes = null; - this.refcount = 1; - // [NOCPP[ - this.locator = locator; - // ]NOCPP] - } - - private static int prepareSvgFlags(int flags) { - flags &= ~(ElementName.FOSTER_PARENTING | ElementName.SCOPING - | ElementName.SPECIAL | ElementName.OPTIONAL_END_TAG); - if ((flags & ElementName.SCOPING_AS_SVG) != 0) { - flags |= (ElementName.SCOPING | ElementName.SPECIAL | ElementName.HTML_INTEGRATION_POINT); - } - return flags; - } - - private static int prepareMathFlags(int flags, - boolean markAsIntegrationPoint) { - flags &= ~(ElementName.FOSTER_PARENTING | ElementName.SCOPING - | ElementName.SPECIAL | ElementName.OPTIONAL_END_TAG); - if ((flags & ElementName.SCOPING_AS_MATHML) != 0) { - flags |= (ElementName.SCOPING | ElementName.SPECIAL); - } - if (markAsIntegrationPoint) { - flags |= ElementName.HTML_INTEGRATION_POINT; - } - return flags; - } - - @SuppressWarnings("unused") private void destructor() { - Portability.delete(attributes); - } - - public void dropAttributes() { - attributes = null; - } - - // [NOCPP[ - /** - * @see java.lang.Object#toString() - */ - @Override public @Local String toString() { - return name; - } - - // ]NOCPP] - - public void retain() { - refcount++; - } - - public void release() { - refcount--; - if (refcount == 0) { - Portability.delete(this); - } - } -} diff --git a/parser/html/javasrc/StateSnapshot.java b/parser/html/javasrc/StateSnapshot.java deleted file mode 100644 index ff89e0443..000000000 --- a/parser/html/javasrc/StateSnapshot.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2009-2010 Mozilla Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -package nu.validator.htmlparser.impl; - -import nu.validator.htmlparser.annotation.Auto; - - -public class StateSnapshot implements TreeBuilderState { - - private final @Auto StackNode[] stack; - - private final @Auto StackNode[] listOfActiveFormattingElements; - - private final @Auto int[] templateModeStack; - - private final T formPointer; - - private final T headPointer; - - private final T deepTreeSurrogateParent; - - private final int mode; - - private final int originalMode; - - private final boolean framesetOk; - - private final boolean needToDropLF; - - private final boolean quirks; - - /** - * @param stack - * @param listOfActiveFormattingElements - * @param templateModeStack - * @param formPointer - * @param headPointer - * @param deepTreeSurrogateParent - * @param mode - * @param originalMode - * @param framesetOk - * @param needToDropLF - * @param quirks - */ - StateSnapshot(StackNode[] stack, - StackNode[] listOfActiveFormattingElements, int[] templateModeStack, T formPointer, - T headPointer, T deepTreeSurrogateParent, int mode, int originalMode, - boolean framesetOk, boolean needToDropLF, boolean quirks) { - this.stack = stack; - this.listOfActiveFormattingElements = listOfActiveFormattingElements; - this.templateModeStack = templateModeStack; - this.formPointer = formPointer; - this.headPointer = headPointer; - this.deepTreeSurrogateParent = deepTreeSurrogateParent; - this.mode = mode; - this.originalMode = originalMode; - this.framesetOk = framesetOk; - this.needToDropLF = needToDropLF; - this.quirks = quirks; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getStack() - */ - public StackNode[] getStack() { - return stack; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getTemplateModeStack() - */ - public int[] getTemplateModeStack() { - return templateModeStack; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getListOfActiveFormattingElements() - */ - public StackNode[] getListOfActiveFormattingElements() { - return listOfActiveFormattingElements; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getFormPointer() - */ - public T getFormPointer() { - return formPointer; - } - - /** - * Returns the headPointer. - * - * @return the headPointer - */ - public T getHeadPointer() { - return headPointer; - } - - /** - * Returns the deepTreeSurrogateParent. - * - * @return the deepTreeSurrogateParent - */ - public T getDeepTreeSurrogateParent() { - return deepTreeSurrogateParent; - } - - /** - * Returns the mode. - * - * @return the mode - */ - public int getMode() { - return mode; - } - - /** - * Returns the originalMode. - * - * @return the originalMode - */ - public int getOriginalMode() { - return originalMode; - } - - /** - * Returns the framesetOk. - * - * @return the framesetOk - */ - public boolean isFramesetOk() { - return framesetOk; - } - - /** - * Returns the needToDropLF. - * - * @return the needToDropLF - */ - public boolean isNeedToDropLF() { - return needToDropLF; - } - - /** - * Returns the quirks. - * - * @return the quirks - */ - public boolean isQuirks() { - return quirks; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getListOfActiveFormattingElementsLength() - */ - public int getListOfActiveFormattingElementsLength() { - return listOfActiveFormattingElements.length; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getStackLength() - */ - public int getStackLength() { - return stack.length; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getTemplateModeStackLength() - */ - public int getTemplateModeStackLength() { - return templateModeStack.length; - } - - @SuppressWarnings("unused") private void destructor() { - for (int i = 0; i < stack.length; i++) { - stack[i].release(); - } - for (int i = 0; i < listOfActiveFormattingElements.length; i++) { - if (listOfActiveFormattingElements[i] != null) { - listOfActiveFormattingElements[i].release(); - } - } - } -} diff --git a/parser/html/javasrc/Tokenizer.java b/parser/html/javasrc/Tokenizer.java deleted file mode 100644 index f141d94d7..000000000 --- a/parser/html/javasrc/Tokenizer.java +++ /dev/null @@ -1,7089 +0,0 @@ -/* - * Copyright (c) 2005-2007 Henri Sivonen - * Copyright (c) 2007-2015 Mozilla Foundation - * Copyright (c) 2019 Moonchild Productions - * Portions of comments Copyright 2004-2010 Apple Computer, Inc., Mozilla - * Foundation, and Opera Software ASA. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -/* - * The comments following this one that use the same comment syntax as this - * comment are quotes from the WHATWG HTML 5 spec as of 2 June 2007 - * amended as of June 18 2008 and May 31 2010. - * That document came with this statement: - * "© Copyright 2004-2010 Apple Computer, Inc., Mozilla Foundation, and - * Opera Software ASA. You are granted a license to use, reproduce and - * create derivative works of this document." - */ - -package nu.validator.htmlparser.impl; - -import org.xml.sax.ErrorHandler; -import org.xml.sax.Locator; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; - -import nu.validator.htmlparser.annotation.Auto; -import nu.validator.htmlparser.annotation.CharacterName; -import nu.validator.htmlparser.annotation.Const; -import nu.validator.htmlparser.annotation.Inline; -import nu.validator.htmlparser.annotation.Local; -import nu.validator.htmlparser.annotation.NoLength; -import nu.validator.htmlparser.common.EncodingDeclarationHandler; -import nu.validator.htmlparser.common.Interner; -import nu.validator.htmlparser.common.TokenHandler; -import nu.validator.htmlparser.common.XmlViolationPolicy; - -/** - * An implementation of - * https://html.spec.whatwg.org/multipage/syntax.html#tokenization - * - * This class implements the Locator interface. This is not an - * incidental implementation detail: Users of this class are encouraged to make - * use of the Locator nature. - * - * By default, the tokenizer may report data that XML 1.0 bans. The tokenizer - * can be configured to treat these conditions as fatal or to coerce the infoset - * to something that XML 1.0 allows. - * - * @version $Id$ - * @author hsivonen - */ -public class Tokenizer implements Locator { - - private static final int DATA_AND_RCDATA_MASK = ~1; - - public static final int DATA = 0; - - public static final int RCDATA = 1; - - public static final int SCRIPT_DATA = 2; - - public static final int RAWTEXT = 3; - - public static final int SCRIPT_DATA_ESCAPED = 4; - - public static final int ATTRIBUTE_VALUE_DOUBLE_QUOTED = 5; - - public static final int ATTRIBUTE_VALUE_SINGLE_QUOTED = 6; - - public static final int ATTRIBUTE_VALUE_UNQUOTED = 7; - - public static final int PLAINTEXT = 8; - - public static final int TAG_OPEN = 9; - - public static final int CLOSE_TAG_OPEN = 10; - - public static final int TAG_NAME = 11; - - public static final int BEFORE_ATTRIBUTE_NAME = 12; - - public static final int ATTRIBUTE_NAME = 13; - - public static final int AFTER_ATTRIBUTE_NAME = 14; - - public static final int BEFORE_ATTRIBUTE_VALUE = 15; - - public static final int AFTER_ATTRIBUTE_VALUE_QUOTED = 16; - - public static final int BOGUS_COMMENT = 17; - - public static final int MARKUP_DECLARATION_OPEN = 18; - - public static final int DOCTYPE = 19; - - public static final int BEFORE_DOCTYPE_NAME = 20; - - public static final int DOCTYPE_NAME = 21; - - public static final int AFTER_DOCTYPE_NAME = 22; - - public static final int BEFORE_DOCTYPE_PUBLIC_IDENTIFIER = 23; - - public static final int DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED = 24; - - public static final int DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED = 25; - - public static final int AFTER_DOCTYPE_PUBLIC_IDENTIFIER = 26; - - public static final int BEFORE_DOCTYPE_SYSTEM_IDENTIFIER = 27; - - public static final int DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED = 28; - - public static final int DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED = 29; - - public static final int AFTER_DOCTYPE_SYSTEM_IDENTIFIER = 30; - - public static final int BOGUS_DOCTYPE = 31; - - public static final int COMMENT_START = 32; - - public static final int COMMENT_START_DASH = 33; - - public static final int COMMENT = 34; - - public static final int COMMENT_END_DASH = 35; - - public static final int COMMENT_END = 36; - - public static final int COMMENT_END_BANG = 37; - - public static final int NON_DATA_END_TAG_NAME = 38; - - public static final int MARKUP_DECLARATION_HYPHEN = 39; - - public static final int MARKUP_DECLARATION_OCTYPE = 40; - - public static final int DOCTYPE_UBLIC = 41; - - public static final int DOCTYPE_YSTEM = 42; - - public static final int AFTER_DOCTYPE_PUBLIC_KEYWORD = 43; - - public static final int BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS = 44; - - public static final int AFTER_DOCTYPE_SYSTEM_KEYWORD = 45; - - public static final int CONSUME_CHARACTER_REFERENCE = 46; - - public static final int CONSUME_NCR = 47; - - public static final int CHARACTER_REFERENCE_TAIL = 48; - - public static final int HEX_NCR_LOOP = 49; - - public static final int DECIMAL_NRC_LOOP = 50; - - public static final int HANDLE_NCR_VALUE = 51; - - public static final int HANDLE_NCR_VALUE_RECONSUME = 52; - - public static final int CHARACTER_REFERENCE_HILO_LOOKUP = 53; - - public static final int SELF_CLOSING_START_TAG = 54; - - public static final int CDATA_START = 55; - - public static final int CDATA_SECTION = 56; - - public static final int CDATA_RSQB = 57; - - public static final int CDATA_RSQB_RSQB = 58; - - public static final int SCRIPT_DATA_LESS_THAN_SIGN = 59; - - public static final int SCRIPT_DATA_ESCAPE_START = 60; - - public static final int SCRIPT_DATA_ESCAPE_START_DASH = 61; - - public static final int SCRIPT_DATA_ESCAPED_DASH = 62; - - public static final int SCRIPT_DATA_ESCAPED_DASH_DASH = 63; - - public static final int BOGUS_COMMENT_HYPHEN = 64; - - public static final int RAWTEXT_RCDATA_LESS_THAN_SIGN = 65; - - public static final int SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN = 66; - - public static final int SCRIPT_DATA_DOUBLE_ESCAPE_START = 67; - - public static final int SCRIPT_DATA_DOUBLE_ESCAPED = 68; - - public static final int SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN = 69; - - public static final int SCRIPT_DATA_DOUBLE_ESCAPED_DASH = 70; - - public static final int SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH = 71; - - public static final int SCRIPT_DATA_DOUBLE_ESCAPE_END = 72; - - public static final int PROCESSING_INSTRUCTION = 73; - - public static final int PROCESSING_INSTRUCTION_QUESTION_MARK = 74; - - /** - * Magic value for UTF-16 operations. - */ - private static final int LEAD_OFFSET = (0xD800 - (0x10000 >> 10)); - - /** - * UTF-16 code unit array containing less than and greater than for emitting - * those characters on certain parse errors. - */ - private static final @NoLength char[] LT_GT = { '<', '>' }; - - /** - * UTF-16 code unit array containing less than and solidus for emitting - * those characters on certain parse errors. - */ - private static final @NoLength char[] LT_SOLIDUS = { '<', '/' }; - - /** - * UTF-16 code unit array containing ]] for emitting those characters on - * state transitions. - */ - private static final @NoLength char[] RSQB_RSQB = { ']', ']' }; - - /** - * Array version of U+FFFD. - */ - private static final @NoLength char[] REPLACEMENT_CHARACTER = { '\uFFFD' }; - - // [NOCPP[ - - /** - * Array version of space. - */ - private static final @NoLength char[] SPACE = { ' ' }; - - // ]NOCPP] - - /** - * Array version of line feed. - */ - private static final @NoLength char[] LF = { '\n' }; - - /** - * "CDATA[" as char[] - */ - private static final @NoLength char[] CDATA_LSQB = { 'C', 'D', 'A', 'T', - 'A', '[' }; - - /** - * "octype" as char[] - */ - private static final @NoLength char[] OCTYPE = { 'o', 'c', 't', 'y', 'p', - 'e' }; - - /** - * "ublic" as char[] - */ - private static final @NoLength char[] UBLIC = { 'u', 'b', 'l', 'i', 'c' }; - - /** - * "ystem" as char[] - */ - private static final @NoLength char[] YSTEM = { 'y', 's', 't', 'e', 'm' }; - - private static final char[] TITLE_ARR = { 't', 'i', 't', 'l', 'e' }; - - private static final char[] SCRIPT_ARR = { 's', 'c', 'r', 'i', 'p', 't' }; - - private static final char[] STYLE_ARR = { 's', 't', 'y', 'l', 'e' }; - - private static final char[] PLAINTEXT_ARR = { 'p', 'l', 'a', 'i', 'n', 't', - 'e', 'x', 't' }; - - private static final char[] XMP_ARR = { 'x', 'm', 'p' }; - - private static final char[] TEXTAREA_ARR = { 't', 'e', 'x', 't', 'a', 'r', - 'e', 'a' }; - - private static final char[] IFRAME_ARR = { 'i', 'f', 'r', 'a', 'm', 'e' }; - - private static final char[] NOEMBED_ARR = { 'n', 'o', 'e', 'm', 'b', 'e', - 'd' }; - - private static final char[] NOSCRIPT_ARR = { 'n', 'o', 's', 'c', 'r', 'i', - 'p', 't' }; - - private static final char[] NOFRAMES_ARR = { 'n', 'o', 'f', 'r', 'a', 'm', - 'e', 's' }; - - /** - * The token handler. - */ - protected final TokenHandler tokenHandler; - - protected EncodingDeclarationHandler encodingDeclarationHandler; - - // [NOCPP[ - - /** - * The error handler. - */ - protected ErrorHandler errorHandler; - - // ]NOCPP] - - /** - * Whether the previous char read was CR. - */ - protected boolean lastCR; - - protected int stateSave; - - private int returnStateSave; - - protected int index; - - private boolean forceQuirks; - - private char additional; - - private int entCol; - - private int firstCharKey; - - private int lo; - - private int hi; - - private int candidate; - - private int charRefBufMark; - - protected int value; - - private boolean seenDigits; - - protected int cstart; - - /** - * The SAX public id for the resource being tokenized. (Only passed to back - * as part of locator data.) - */ - private String publicId; - - /** - * The SAX system id for the resource being tokenized. (Only passed to back - * as part of locator data.) - */ - private String systemId; - - /** - * Buffer for bufferable things other than those that fit the description - * of charRefBuf. - */ - private @Auto char[] strBuf; - - /** - * Number of significant chars in strBuf. - */ - private int strBufLen; - - /** - * Buffer for characters that might form a character reference but may - * end up not forming one. - */ - private final @Auto char[] charRefBuf; - - /** - * Number of significant chars in charRefBuf. - */ - private int charRefBufLen; - - /** - * Buffer for expanding NCRs falling into the Basic Multilingual Plane. - */ - private final @Auto char[] bmpChar; - - /** - * Buffer for expanding astral NCRs. - */ - private final @Auto char[] astralChar; - - /** - * The element whose end tag closes the current CDATA or RCDATA element. - */ - protected ElementName endTagExpectation = null; - - private char[] endTagExpectationAsArray; // not @Auto! - - /** - * true if tokenizing an end tag - */ - protected boolean endTag; - - /** - * The current tag token name. - */ - private ElementName tagName = null; - - /** - * The current attribute name. - */ - protected AttributeName attributeName = null; - - // [NOCPP[ - - /** - * Whether comment tokens are emitted. - */ - private boolean wantsComments = false; - - /** - * true when HTML4-specific additional errors are requested. - */ - protected boolean html4; - - /** - * Whether the stream is past the first 1024 bytes. - */ - private boolean metaBoundaryPassed; - - // ]NOCPP] - - /** - * The name of the current doctype token. - */ - private @Local String doctypeName; - - /** - * The public id of the current doctype token. - */ - private String publicIdentifier; - - /** - * The system id of the current doctype token. - */ - private String systemIdentifier; - - /** - * The attribute holder. - */ - private HtmlAttributes attributes; - - // [NOCPP[ - - /** - * The policy for vertical tab and form feed. - */ - private XmlViolationPolicy contentSpacePolicy = XmlViolationPolicy.ALTER_INFOSET; - - /** - * The policy for comments. - */ - private XmlViolationPolicy commentPolicy = XmlViolationPolicy.ALTER_INFOSET; - - private XmlViolationPolicy xmlnsPolicy = XmlViolationPolicy.ALTER_INFOSET; - - private XmlViolationPolicy namePolicy = XmlViolationPolicy.ALTER_INFOSET; - - private boolean html4ModeCompatibleWithXhtml1Schemata; - - private int mappingLangToXmlLang; - - // ]NOCPP] - - private final boolean newAttributesEachTime; - - private boolean shouldSuspend; - - protected boolean confident; - - private int line; - - /* - * The line number of the current attribute. First set to the line of the - * attribute name and if there is a value, set to the line the value - * started on. - */ - // CPPONLY: private int attributeLine; - - private Interner interner; - - // CPPONLY: private boolean viewingXmlSource; - - // [NOCPP[ - - protected LocatorImpl ampersandLocation; - - public Tokenizer(TokenHandler tokenHandler, boolean newAttributesEachTime) { - this.tokenHandler = tokenHandler; - this.encodingDeclarationHandler = null; - this.newAttributesEachTime = newAttributesEachTime; - // ∳ is the longest valid char ref and - // the semicolon never gets appended to the buffer. - this.charRefBuf = new char[32]; - this.bmpChar = new char[1]; - this.astralChar = new char[2]; - this.tagName = null; - this.attributeName = null; - this.doctypeName = null; - this.publicIdentifier = null; - this.systemIdentifier = null; - this.attributes = null; - } - - // ]NOCPP] - - /** - * The constructor. - * - * @param tokenHandler - * the handler for receiving tokens - */ - public Tokenizer(TokenHandler tokenHandler - // CPPONLY: , boolean viewingXmlSource - ) { - this.tokenHandler = tokenHandler; - this.encodingDeclarationHandler = null; - // [NOCPP[ - this.newAttributesEachTime = false; - // ]NOCPP] - // ∳ is the longest valid char ref and - // the semicolon never gets appended to the buffer. - this.charRefBuf = new char[32]; - this.bmpChar = new char[1]; - this.astralChar = new char[2]; - this.tagName = null; - this.attributeName = null; - this.doctypeName = null; - this.publicIdentifier = null; - this.systemIdentifier = null; - // [NOCPP[ - this.attributes = null; - // ]NOCPP] - // CPPONLY: this.attributes = tokenHandler.HasBuilder() ? new HtmlAttributes(mappingLangToXmlLang) : null; - // CPPONLY: this.newAttributesEachTime = !tokenHandler.HasBuilder(); - // CPPONLY: this.viewingXmlSource = viewingXmlSource; - } - - public void setInterner(Interner interner) { - this.interner = interner; - } - - public void initLocation(String newPublicId, String newSystemId) { - this.systemId = newSystemId; - this.publicId = newPublicId; - - } - - // CPPONLY: boolean isViewingXmlSource() { - // CPPONLY: return viewingXmlSource; - // CPPONLY: } - - // [NOCPP[ - - /** - * Returns the mappingLangToXmlLang. - * - * @return the mappingLangToXmlLang - */ - public boolean isMappingLangToXmlLang() { - return mappingLangToXmlLang == AttributeName.HTML_LANG; - } - - /** - * Sets the mappingLangToXmlLang. - * - * @param mappingLangToXmlLang - * the mappingLangToXmlLang to set - */ - public void setMappingLangToXmlLang(boolean mappingLangToXmlLang) { - this.mappingLangToXmlLang = mappingLangToXmlLang ? AttributeName.HTML_LANG - : AttributeName.HTML; - } - - /** - * Sets the error handler. - * - * @see org.xml.sax.XMLReader#setErrorHandler(org.xml.sax.ErrorHandler) - */ - public void setErrorHandler(ErrorHandler eh) { - this.errorHandler = eh; - } - - public ErrorHandler getErrorHandler() { - return this.errorHandler; - } - - /** - * Sets the commentPolicy. - * - * @param commentPolicy - * the commentPolicy to set - */ - public void setCommentPolicy(XmlViolationPolicy commentPolicy) { - this.commentPolicy = commentPolicy; - } - - /** - * Sets the contentNonXmlCharPolicy. - * - * @param contentNonXmlCharPolicy - * the contentNonXmlCharPolicy to set - */ - public void setContentNonXmlCharPolicy( - XmlViolationPolicy contentNonXmlCharPolicy) { - if (contentNonXmlCharPolicy != XmlViolationPolicy.ALLOW) { - throw new IllegalArgumentException( - "Must use ErrorReportingTokenizer to set contentNonXmlCharPolicy to non-ALLOW."); - } - } - - /** - * Sets the contentSpacePolicy. - * - * @param contentSpacePolicy - * the contentSpacePolicy to set - */ - public void setContentSpacePolicy(XmlViolationPolicy contentSpacePolicy) { - this.contentSpacePolicy = contentSpacePolicy; - } - - /** - * Sets the xmlnsPolicy. - * - * @param xmlnsPolicy - * the xmlnsPolicy to set - */ - public void setXmlnsPolicy(XmlViolationPolicy xmlnsPolicy) { - if (xmlnsPolicy == XmlViolationPolicy.FATAL) { - throw new IllegalArgumentException("Can't use FATAL here."); - } - this.xmlnsPolicy = xmlnsPolicy; - } - - public void setNamePolicy(XmlViolationPolicy namePolicy) { - this.namePolicy = namePolicy; - } - - /** - * Sets the html4ModeCompatibleWithXhtml1Schemata. - * - * @param html4ModeCompatibleWithXhtml1Schemata - * the html4ModeCompatibleWithXhtml1Schemata to set - */ - public void setHtml4ModeCompatibleWithXhtml1Schemata( - boolean html4ModeCompatibleWithXhtml1Schemata) { - this.html4ModeCompatibleWithXhtml1Schemata = html4ModeCompatibleWithXhtml1Schemata; - } - - // ]NOCPP] - - // For the token handler to call - /** - * Sets the tokenizer state and the associated element name. This should - * only ever used to put the tokenizer into one of the states that have - * a special end tag expectation. - * - * @param specialTokenizerState - * the tokenizer state to set - */ - public void setState(int specialTokenizerState) { - this.stateSave = specialTokenizerState; - this.endTagExpectation = null; - this.endTagExpectationAsArray = null; - } - - // [NOCPP[ - - /** - * Sets the tokenizer state and the associated element name. This should - * only ever used to put the tokenizer into one of the states that have - * a special end tag expectation. For use from the tokenizer test harness. - * - * @param specialTokenizerState - * the tokenizer state to set - * @param endTagExpectation - * the expected end tag for transitioning back to normal - */ - public void setStateAndEndTagExpectation(int specialTokenizerState, - @Local String endTagExpectation) { - this.stateSave = specialTokenizerState; - if (specialTokenizerState == Tokenizer.DATA) { - return; - } - @Auto char[] asArray = Portability.newCharArrayFromLocal(endTagExpectation); - this.endTagExpectation = ElementName.elementNameByBuffer(asArray, 0, - asArray.length, interner); - endTagExpectationToArray(); - } - - // ]NOCPP] - - /** - * Sets the tokenizer state and the associated element name. This should - * only ever used to put the tokenizer into one of the states that have - * a special end tag expectation. - * - * @param specialTokenizerState - * the tokenizer state to set - * @param endTagExpectation - * the expected end tag for transitioning back to normal - */ - public void setStateAndEndTagExpectation(int specialTokenizerState, - ElementName endTagExpectation) { - this.stateSave = specialTokenizerState; - this.endTagExpectation = endTagExpectation; - endTagExpectationToArray(); - } - - private void endTagExpectationToArray() { - switch (endTagExpectation.getGroup()) { - case TreeBuilder.TITLE: - endTagExpectationAsArray = TITLE_ARR; - return; - case TreeBuilder.SCRIPT: - endTagExpectationAsArray = SCRIPT_ARR; - return; - case TreeBuilder.STYLE: - endTagExpectationAsArray = STYLE_ARR; - return; - case TreeBuilder.PLAINTEXT: - endTagExpectationAsArray = PLAINTEXT_ARR; - return; - case TreeBuilder.XMP: - endTagExpectationAsArray = XMP_ARR; - return; - case TreeBuilder.TEXTAREA: - endTagExpectationAsArray = TEXTAREA_ARR; - return; - case TreeBuilder.IFRAME: - endTagExpectationAsArray = IFRAME_ARR; - return; - case TreeBuilder.NOEMBED: - endTagExpectationAsArray = NOEMBED_ARR; - return; - case TreeBuilder.NOSCRIPT: - endTagExpectationAsArray = NOSCRIPT_ARR; - return; - case TreeBuilder.NOFRAMES: - endTagExpectationAsArray = NOFRAMES_ARR; - return; - default: - assert false: "Bad end tag expectation."; - return; - } - } - - /** - * For C++ use only. - */ - public void setLineNumber(int line) { - // CPPONLY: this.attributeLine = line; // XXX is this needed? - this.line = line; - } - - // start Locator impl - - /** - * @see org.xml.sax.Locator#getLineNumber() - */ - @Inline public int getLineNumber() { - return line; - } - - // [NOCPP[ - - /** - * @see org.xml.sax.Locator#getColumnNumber() - */ - @Inline public int getColumnNumber() { - return -1; - } - - /** - * @see org.xml.sax.Locator#getPublicId() - */ - public String getPublicId() { - return publicId; - } - - /** - * @see org.xml.sax.Locator#getSystemId() - */ - public String getSystemId() { - return systemId; - } - - // end Locator impl - - // end public API - - public void notifyAboutMetaBoundary() { - metaBoundaryPassed = true; - } - - void turnOnAdditionalHtml4Errors() { - html4 = true; - } - - // ]NOCPP] - - HtmlAttributes emptyAttributes() { - // [NOCPP[ - if (newAttributesEachTime) { - return new HtmlAttributes(mappingLangToXmlLang); - } else { - // ]NOCPP] - return HtmlAttributes.EMPTY_ATTRIBUTES; - // [NOCPP[ - } - // ]NOCPP] - } - - @Inline private void appendCharRefBuf(char c) { - // CPPONLY: assert charRefBufLen < charRefBuf.length: - // CPPONLY: "RELEASE: Attempted to overrun charRefBuf!"; - charRefBuf[charRefBufLen++] = c; - } - - private void emitOrAppendCharRefBuf(int returnState) throws SAXException { - if ((returnState & DATA_AND_RCDATA_MASK) != 0) { - appendCharRefBufToStrBuf(); - } else { - if (charRefBufLen > 0) { - tokenHandler.characters(charRefBuf, 0, charRefBufLen); - charRefBufLen = 0; - } - } - } - - @Inline private void clearStrBufAfterUse() { - strBufLen = 0; - } - - @Inline private void clearStrBufBeforeUse() { - assert strBufLen == 0: "strBufLen not reset after previous use!"; - strBufLen = 0; // no-op in the absence of bugs - } - - @Inline private void clearStrBufAfterOneHyphen() { - assert strBufLen == 1: "strBufLen length not one!"; - assert strBuf[0] == '-': "strBuf does not start with a hyphen!"; - strBufLen = 0; - } - - /** - * Appends to the buffer. - * - * @param c - * the UTF-16 code unit to append - */ - @Inline private void appendStrBuf(char c) { - // CPPONLY: assert strBufLen < strBuf.length: "Previous buffer length insufficient."; - // CPPONLY: if (strBufLen == strBuf.length) { - // CPPONLY: if (!EnsureBufferSpace(1)) { - // CPPONLY: assert false: "RELEASE: Unable to recover from buffer reallocation failure"; - // CPPONLY: } // TODO: Add telemetry when outer if fires but inner does not - // CPPONLY: } - strBuf[strBufLen++] = c; - } - - /** - * The buffer as a String. Currently only used for error reporting. - * - *

- * C++ memory note: The return value must be released. - * - * @return the buffer as a string - */ - protected String strBufToString() { - String str = Portability.newStringFromBuffer(strBuf, 0, strBufLen - // CPPONLY: , tokenHandler - ); - clearStrBufAfterUse(); - return str; - } - - /** - * Returns the buffer as a local name. The return value is released in - * emitDoctypeToken(). - * - * @return the buffer as local name - */ - private void strBufToDoctypeName() { - doctypeName = Portability.newLocalNameFromBuffer(strBuf, 0, strBufLen, - interner); - clearStrBufAfterUse(); - } - - /** - * Emits the buffer as character tokens. - * - * @throws SAXException - * if the token handler threw - */ - private void emitStrBuf() throws SAXException { - if (strBufLen > 0) { - tokenHandler.characters(strBuf, 0, strBufLen); - clearStrBufAfterUse(); - } - } - - @Inline private void appendSecondHyphenToBogusComment() throws SAXException { - // [NOCPP[ - switch (commentPolicy) { - case ALTER_INFOSET: - appendStrBuf(' '); - // FALLTHROUGH - case ALLOW: - warn("The document is not mappable to XML 1.0 due to two consecutive hyphens in a comment."); - // ]NOCPP] - appendStrBuf('-'); - // [NOCPP[ - break; - case FATAL: - fatal("The document is not mappable to XML 1.0 due to two consecutive hyphens in a comment."); - break; - } - // ]NOCPP] - } - - // [NOCPP[ - private void maybeAppendSpaceToBogusComment() throws SAXException { - switch (commentPolicy) { - case ALTER_INFOSET: - appendStrBuf(' '); - // FALLTHROUGH - case ALLOW: - warn("The document is not mappable to XML 1.0 due to a trailing hyphen in a comment."); - break; - case FATAL: - fatal("The document is not mappable to XML 1.0 due to a trailing hyphen in a comment."); - break; - } - } - - // ]NOCPP] - - @Inline private void adjustDoubleHyphenAndAppendToStrBufAndErr(char c) - throws SAXException { - errConsecutiveHyphens(); - // [NOCPP[ - switch (commentPolicy) { - case ALTER_INFOSET: - strBufLen--; - // WARNING!!! This expands the worst case of the buffer length - // given the length of input! - appendStrBuf(' '); - appendStrBuf('-'); - // FALLTHROUGH - case ALLOW: - warn("The document is not mappable to XML 1.0 due to two consecutive hyphens in a comment."); - // ]NOCPP] - appendStrBuf(c); - // [NOCPP[ - break; - case FATAL: - fatal("The document is not mappable to XML 1.0 due to two consecutive hyphens in a comment."); - break; - } - // ]NOCPP] - } - - private void appendStrBuf(@NoLength char[] buffer, int offset, int length) { - int newLen = strBufLen + length; - // CPPONLY: assert newLen <= strBuf.length: "Previous buffer length insufficient."; - // CPPONLY: if (strBuf.length < newLen) { - // CPPONLY: if (!EnsureBufferSpace(length)) { - // CPPONLY: assert false: "RELEASE: Unable to recover from buffer reallocation failure"; - // CPPONLY: } // TODO: Add telemetry when outer if fires but inner does not - // CPPONLY: } - System.arraycopy(buffer, offset, strBuf, strBufLen, length); - strBufLen = newLen; - } - - /** - * Append the contents of the char reference buffer to the main one. - */ - @Inline private void appendCharRefBufToStrBuf() { - appendStrBuf(charRefBuf, 0, charRefBufLen); - charRefBufLen = 0; - } - - /** - * Emits the current comment token. - * - * @param pos - * TODO - * - * @throws SAXException - */ - private void emitComment(int provisionalHyphens, int pos) - throws SAXException { - // [NOCPP[ - if (wantsComments) { - // ]NOCPP] - tokenHandler.comment(strBuf, 0, strBufLen - - provisionalHyphens); - // [NOCPP[ - } - // ]NOCPP] - clearStrBufAfterUse(); - cstart = pos + 1; - } - - /** - * Flushes coalesced character tokens. - * - * @param buf - * TODO - * @param pos - * TODO - * - * @throws SAXException - */ - protected void flushChars(@NoLength char[] buf, int pos) - throws SAXException { - if (pos > cstart) { - tokenHandler.characters(buf, cstart, pos - cstart); - } - cstart = Integer.MAX_VALUE; - } - - /** - * Reports an condition that would make the infoset incompatible with XML - * 1.0 as fatal. - * - * @param message - * the message - * @throws SAXException - * @throws SAXParseException - */ - public void fatal(String message) throws SAXException { - SAXParseException spe = new SAXParseException(message, this); - if (errorHandler != null) { - errorHandler.fatalError(spe); - } - throw spe; - } - - /** - * Reports a Parse Error. - * - * @param message - * the message - * @throws SAXException - */ - public void err(String message) throws SAXException { - if (errorHandler == null) { - return; - } - SAXParseException spe = new SAXParseException(message, this); - errorHandler.error(spe); - } - - public void errTreeBuilder(String message) throws SAXException { - ErrorHandler eh = null; - if (tokenHandler instanceof TreeBuilder) { - TreeBuilder treeBuilder = (TreeBuilder) tokenHandler; - eh = treeBuilder.getErrorHandler(); - } - if (eh == null) { - eh = errorHandler; - } - if (eh == null) { - return; - } - SAXParseException spe = new SAXParseException(message, this); - eh.error(spe); - } - - /** - * Reports a warning - * - * @param message - * the message - * @throws SAXException - */ - public void warn(String message) throws SAXException { - if (errorHandler == null) { - return; - } - SAXParseException spe = new SAXParseException(message, this); - errorHandler.warning(spe); - } - - private void strBufToElementNameString() { - tagName = ElementName.elementNameByBuffer(strBuf, 0, strBufLen, - interner); - clearStrBufAfterUse(); - } - - private int emitCurrentTagToken(boolean selfClosing, int pos) - throws SAXException { - cstart = pos + 1; - maybeErrSlashInEndTag(selfClosing); - stateSave = Tokenizer.DATA; - HtmlAttributes attrs = (attributes == null ? HtmlAttributes.EMPTY_ATTRIBUTES - : attributes); - if (endTag) { - /* - * When an end tag token is emitted, the content model flag must be - * switched to the PCDATA state. - */ - maybeErrAttributesOnEndTag(attrs); - // CPPONLY: if (!viewingXmlSource) { - tokenHandler.endTag(tagName); - // CPPONLY: } - // CPPONLY: if (newAttributesEachTime) { - // CPPONLY: Portability.delete(attributes); - // CPPONLY: attributes = null; - // CPPONLY: } - } else { - // CPPONLY: if (viewingXmlSource) { - // CPPONLY: assert newAttributesEachTime; - // CPPONLY: Portability.delete(attributes); - // CPPONLY: attributes = null; - // CPPONLY: } else { - tokenHandler.startTag(tagName, attrs, selfClosing); - // CPPONLY: } - } - tagName.release(); - tagName = null; - if (newAttributesEachTime) { - attributes = null; - } else { - attributes.clear(mappingLangToXmlLang); - } - /* - * The token handler may have called setStateAndEndTagExpectation - * and changed stateSave since the start of this method. - */ - return stateSave; - } - - private void attributeNameComplete() throws SAXException { - attributeName = AttributeName.nameByBuffer(strBuf, 0, strBufLen - // [NOCPP[ - , namePolicy != XmlViolationPolicy.ALLOW - // ]NOCPP] - , interner); - clearStrBufAfterUse(); - - if (attributes == null) { - attributes = new HtmlAttributes(mappingLangToXmlLang); - } - - /* - * When the user agent leaves the attribute name state (and before - * emitting the tag token, if appropriate), the complete attribute's - * name must be compared to the other attributes on the same token; if - * there is already an attribute on the token with the exact same name, - * then this is a parse error and the new attribute must be dropped, - * along with the value that gets associated with it (if any). - */ - if (attributes.contains(attributeName)) { - errDuplicateAttribute(); - attributeName.release(); - attributeName = null; - } - } - - private void addAttributeWithoutValue() throws SAXException { - noteAttributeWithoutValue(); - - // [NOCPP[ - if (metaBoundaryPassed && AttributeName.CHARSET == attributeName - && ElementName.META == tagName) { - err("A \u201Ccharset\u201D attribute on a \u201Cmeta\u201D element found after the first 512 bytes."); - } - // ]NOCPP] - if (attributeName != null) { - // [NOCPP[ - if (html4) { - if (attributeName.isBoolean()) { - if (html4ModeCompatibleWithXhtml1Schemata) { - attributes.addAttribute(attributeName, - attributeName.getLocal(AttributeName.HTML), - xmlnsPolicy); - } else { - attributes.addAttribute(attributeName, "", xmlnsPolicy); - } - } else { - if (AttributeName.BORDER != attributeName) { - err("Attribute value omitted for a non-boolean attribute. (HTML4-only error.)"); - attributes.addAttribute(attributeName, "", xmlnsPolicy); - } - } - } else { - if (AttributeName.SRC == attributeName - || AttributeName.HREF == attributeName) { - warn("Attribute \u201C" - + attributeName.getLocal(AttributeName.HTML) - + "\u201D without an explicit value seen. The attribute may be dropped by IE7."); - } - // ]NOCPP] - attributes.addAttribute(attributeName, - Portability.newEmptyString() - // [NOCPP[ - , xmlnsPolicy - // ]NOCPP] - // CPPONLY: , attributeLine - ); - // [NOCPP[ - } - // ]NOCPP] - attributeName = null; // attributeName has been adopted by the - // |attributes| object - } else { - clearStrBufAfterUse(); - } - } - - private void addAttributeWithValue() throws SAXException { - // [NOCPP[ - if (metaBoundaryPassed && ElementName.META == tagName - && AttributeName.CHARSET == attributeName) { - err("A \u201Ccharset\u201D attribute on a \u201Cmeta\u201D element found after the first 512 bytes."); - } - // ]NOCPP] - if (attributeName != null) { - String val = strBufToString(); // Ownership transferred to - // HtmlAttributes - // CPPONLY: if (mViewSource) { - // CPPONLY: mViewSource.MaybeLinkifyAttributeValue(attributeName, val); - // CPPONLY: } - // [NOCPP[ - if (!endTag && html4 && html4ModeCompatibleWithXhtml1Schemata - && attributeName.isCaseFolded()) { - val = newAsciiLowerCaseStringFromString(val); - } - // ]NOCPP] - attributes.addAttribute(attributeName, val - // [NOCPP[ - , xmlnsPolicy - // ]NOCPP] - // CPPONLY: , attributeLine - ); - attributeName = null; // attributeName has been adopted by the - // |attributes| object - } else { - // We have a duplicate attribute. Explicitly discard its value. - clearStrBufAfterUse(); - } - } - - // [NOCPP[ - - private static String newAsciiLowerCaseStringFromString(String str) { - if (str == null) { - return null; - } - char[] buf = new char[str.length()]; - for (int i = 0; i < str.length(); i++) { - char c = str.charAt(i); - if (c >= 'A' && c <= 'Z') { - c += 0x20; - } - buf[i] = c; - } - return new String(buf); - } - - protected void startErrorReporting() throws SAXException { - - } - - // ]NOCPP] - - public void start() throws SAXException { - initializeWithoutStarting(); - tokenHandler.startTokenization(this); - // [NOCPP[ - startErrorReporting(); - // ]NOCPP] - } - - public boolean tokenizeBuffer(UTF16Buffer buffer) throws SAXException { - int state = stateSave; - int returnState = returnStateSave; - char c = '\u0000'; - shouldSuspend = false; - lastCR = false; - - int start = buffer.getStart(); - int end = buffer.getEnd(); - - // In C++, the caller of tokenizeBuffer needs to do this explicitly. - // [NOCPP[ - ensureBufferSpace(end - start); - // ]NOCPP] - - /** - * The index of the last char read from buf. - */ - int pos = start - 1; - - /** - * The index of the first char in buf that is - * part of a coalesced run of character tokens or - * Integer.MAX_VALUE if there is not a current run being - * coalesced. - */ - switch (state) { - case DATA: - case RCDATA: - case SCRIPT_DATA: - case PLAINTEXT: - case RAWTEXT: - case CDATA_SECTION: - case SCRIPT_DATA_ESCAPED: - case SCRIPT_DATA_ESCAPE_START: - case SCRIPT_DATA_ESCAPE_START_DASH: - case SCRIPT_DATA_ESCAPED_DASH: - case SCRIPT_DATA_ESCAPED_DASH_DASH: - case SCRIPT_DATA_DOUBLE_ESCAPE_START: - case SCRIPT_DATA_DOUBLE_ESCAPED: - case SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN: - case SCRIPT_DATA_DOUBLE_ESCAPED_DASH: - case SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH: - case SCRIPT_DATA_DOUBLE_ESCAPE_END: - cstart = start; - break; - default: - cstart = Integer.MAX_VALUE; - break; - } - - /** - * The number of chars in buf that have - * meaning. (The rest of the array is garbage and should not be - * examined.) - */ - // CPPONLY: if (mViewSource) { - // CPPONLY: mViewSource.SetBuffer(buffer); - // CPPONLY: pos = stateLoop(state, c, pos, buffer.getBuffer(), false, returnState, buffer.getEnd()); - // CPPONLY: mViewSource.DropBuffer((pos == buffer.getEnd()) ? pos : pos + 1); - // CPPONLY: } else { - // CPPONLY: pos = stateLoop(state, c, pos, buffer.getBuffer(), false, returnState, buffer.getEnd()); - // CPPONLY: } - // [NOCPP[ - pos = stateLoop(state, c, pos, buffer.getBuffer(), false, returnState, - end); - // ]NOCPP] - if (pos == end) { - // exiting due to end of buffer - buffer.setStart(pos); - } else { - buffer.setStart(pos + 1); - } - return lastCR; - } - - // [NOCPP[ - private void ensureBufferSpace(int inputLength) throws SAXException { - // Add 2 to account for emissions of LT_GT, LT_SOLIDUS and RSQB_RSQB. - // Adding to the general worst case instead of only the - // TreeBuilder-exposed worst case to avoid re-introducing a bug when - // unifying the tokenizer and tree builder buffers in the future. - int worstCase = strBufLen + inputLength + charRefBufLen + 2; - tokenHandler.ensureBufferSpace(worstCase); - if (commentPolicy == XmlViolationPolicy.ALTER_INFOSET) { - // When altering infoset, if the comment contents are consecutive - // hyphens, each hyphen generates a space, too. These buffer - // contents never get emitted as characters() to the tokenHandler, - // which is why this calculation happens after the call to - // ensureBufferSpace on tokenHandler. - worstCase *= 2; - } - if (strBuf == null) { - // Add an arbitrary small value to avoid immediate reallocation - // once there are a few characters in the buffer. - strBuf = new char[worstCase + 128]; - } else if (worstCase > strBuf.length) { - // HotSpot reportedly allocates memory with 8-byte accuracy, so - // there's no point in trying to do math here to avoid slop. - // Maybe we should add some small constant to worstCase here - // but not doing that without profiling. In C++ with jemalloc, - // the corresponding method should do math to round up here - // to avoid slop. - char[] newBuf = new char[worstCase]; - System.arraycopy(strBuf, 0, newBuf, 0, strBufLen); - strBuf = newBuf; - } - } - // ]NOCPP] - - @SuppressWarnings("unused") private int stateLoop(int state, char c, - int pos, @NoLength char[] buf, boolean reconsume, int returnState, - int endPos) throws SAXException { - /* - * Idioms used in this code: - * - * - * Consuming the next input character - * - * To consume the next input character, the code does this: if (++pos == - * endPos) { break stateloop; } c = checkChar(buf, pos); - * - * - * Staying in a state - * - * When there's a state that the tokenizer may stay in over multiple - * input characters, the state has a wrapper |for(;;)| loop and staying - * in the state continues the loop. - * - * - * Switching to another state - * - * To switch to another state, the code sets the state variable to the - * magic number of the new state. Then it either continues stateloop or - * breaks out of the state's own wrapper loop if the target state is - * right after the current state in source order. (This is a partial - * workaround for Java's lack of goto.) - * - * - * Reconsume support - * - * The spec sometimes says that an input character is reconsumed in - * another state. If a state can ever be entered so that an input - * character can be reconsumed in it, the state's code starts with an - * |if (reconsume)| that sets reconsume to false and skips over the - * normal code for consuming a new character. - * - * To reconsume the current character in another state, the code sets - * |reconsume| to true and then switches to the other state. - * - * - * Emitting character tokens - * - * This method emits character tokens lazily. Whenever a new range of - * character tokens starts, the field cstart must be set to the start - * index of the range. The flushChars() method must be called at the end - * of a range to flush it. - * - * - * U+0000 handling - * - * The various states have to handle the replacement of U+0000 with - * U+FFFD. However, if U+0000 would be reconsumed in another state, the - * replacement doesn't need to happen, because it's handled by the - * reconsuming state. - * - * - * LF handling - * - * Every state needs to increment the line number upon LF unless the LF - * gets reconsumed by another state which increments the line number. - * - * - * CR handling - * - * Every state needs to handle CR unless the CR gets reconsumed and is - * handled by the reconsuming state. The CR needs to be handled as if it - * were and LF, the lastCR field must be set to true and then this - * method must return. The IO driver will then swallow the next - * character if it is an LF to coalesce CRLF. - */ - stateloop: for (;;) { - switch (state) { - case DATA: - dataloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - switch (c) { - case '&': - /* - * U+0026 AMPERSAND (&) Switch to the character - * reference in data state. - */ - flushChars(buf, pos); - assert charRefBufLen == 0: "charRefBufLen not reset after previous use!"; - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('\u0000'); - returnState = state; - state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos); - continue stateloop; - case '<': - /* - * U+003C LESS-THAN SIGN (<) Switch to the tag - * open state. - */ - flushChars(buf, pos); - - state = transition(state, Tokenizer.TAG_OPEN, reconsume, pos); - break dataloop; // FALL THROUGH continue - // stateloop; - case '\u0000': - emitReplacementCharacter(buf, pos); - continue; - case '\r': - emitCarriageReturn(buf, pos); - break stateloop; - case '\n': - silentLineFeed(); - default: - /* - * Anything else Emit the input character as a - * character token. - * - * Stay in the data state. - */ - continue; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case TAG_OPEN: - tagopenloop: for (;;) { - /* - * The behavior of this state depends on the content - * model flag. - */ - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * If the content model flag is set to the PCDATA state - * Consume the next input character: - */ - if (c >= 'A' && c <= 'Z') { - /* - * U+0041 LATIN CAPITAL LETTER A through to U+005A - * LATIN CAPITAL LETTER Z Create a new start tag - * token, - */ - endTag = false; - /* - * set its tag name to the lowercase version of the - * input character (add 0x0020 to the character's - * code point), - */ - clearStrBufBeforeUse(); - appendStrBuf((char) (c + 0x20)); - /* then switch to the tag name state. */ - state = transition(state, Tokenizer.TAG_NAME, reconsume, pos); - /* - * (Don't emit the token yet; further details will - * be filled in before it is emitted.) - */ - break tagopenloop; - // continue stateloop; - } else if (c >= 'a' && c <= 'z') { - /* - * U+0061 LATIN SMALL LETTER A through to U+007A - * LATIN SMALL LETTER Z Create a new start tag - * token, - */ - endTag = false; - /* - * set its tag name to the input character, - */ - clearStrBufBeforeUse(); - appendStrBuf(c); - /* then switch to the tag name state. */ - state = transition(state, Tokenizer.TAG_NAME, reconsume, pos); - /* - * (Don't emit the token yet; further details will - * be filled in before it is emitted.) - */ - break tagopenloop; - // continue stateloop; - } - switch (c) { - case '!': - /* - * U+0021 EXCLAMATION MARK (!) Switch to the - * markup declaration open state. - */ - state = transition(state, Tokenizer.MARKUP_DECLARATION_OPEN, reconsume, pos); - continue stateloop; - case '/': - /* - * U+002F SOLIDUS (/) Switch to the close tag - * open state. - */ - state = transition(state, Tokenizer.CLOSE_TAG_OPEN, reconsume, pos); - continue stateloop; - case '?': - // CPPONLY: if (viewingXmlSource) { - // CPPONLY: state = transition(state, - // CPPONLY: Tokenizer.PROCESSING_INSTRUCTION, - // CPPONLY: reconsume, - // CPPONLY: pos); - // CPPONLY: continue stateloop; - // CPPONLY: } - /* - * U+003F QUESTION MARK (?) Parse error. - */ - errProcessingInstruction(); - /* - * Switch to the bogus comment state. - */ - clearStrBufBeforeUse(); - appendStrBuf(c); - state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos); - continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Parse error. - */ - errLtGt(); - /* - * Emit a U+003C LESS-THAN SIGN character token - * and a U+003E GREATER-THAN SIGN character - * token. - */ - tokenHandler.characters(Tokenizer.LT_GT, 0, 2); - /* Switch to the data state. */ - cstart = pos + 1; - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - default: - /* - * Anything else Parse error. - */ - errBadCharAfterLt(c); - /* - * Emit a U+003C LESS-THAN SIGN character token - */ - tokenHandler.characters(Tokenizer.LT_GT, 0, 1); - /* - * and reconsume the current input character in - * the data state. - */ - cstart = pos; - reconsume = true; - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - } - } - // FALL THROUGH DON'T REORDER - case TAG_NAME: - tagnameloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - strBufToElementNameString(); - state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE - * Switch to the before attribute name state. - */ - strBufToElementNameString(); - state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos); - break tagnameloop; - // continue stateloop; - case '/': - /* - * U+002F SOLIDUS (/) Switch to the self-closing - * start tag state. - */ - strBufToElementNameString(); - state = transition(state, Tokenizer.SELF_CLOSING_START_TAG, reconsume, pos); - continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the current - * tag token. - */ - strBufToElementNameString(); - state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos); - if (shouldSuspend) { - break stateloop; - } - /* - * Switch to the data state. - */ - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - if (c >= 'A' && c <= 'Z') { - /* - * U+0041 LATIN CAPITAL LETTER A through to - * U+005A LATIN CAPITAL LETTER Z Append the - * lowercase version of the current input - * character (add 0x0020 to the character's - * code point) to the current tag token's - * tag name. - */ - c += 0x20; - } - /* - * Anything else Append the current input - * character to the current tag token's tag - * name. - */ - appendStrBuf(c); - /* - * Stay in the tag name state. - */ - continue; - } - } - // FALLTHRU DON'T REORDER - case BEFORE_ATTRIBUTE_NAME: - beforeattributenameloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay - * in the before attribute name state. - */ - continue; - case '/': - /* - * U+002F SOLIDUS (/) Switch to the self-closing - * start tag state. - */ - state = transition(state, Tokenizer.SELF_CLOSING_START_TAG, reconsume, pos); - continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the current - * tag token. - */ - state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos); - if (shouldSuspend) { - break stateloop; - } - /* - * Switch to the data state. - */ - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - case '\"': - case '\'': - case '<': - case '=': - /* - * U+0022 QUOTATION MARK (") U+0027 APOSTROPHE - * (') U+003C LESS-THAN SIGN (<) U+003D EQUALS - * SIGN (=) Parse error. - */ - errBadCharBeforeAttributeNameOrNull(c); - /* - * Treat it as per the "anything else" entry - * below. - */ - default: - /* - * Anything else Start a new attribute in the - * current tag token. - */ - if (c >= 'A' && c <= 'Z') { - /* - * U+0041 LATIN CAPITAL LETTER A through to - * U+005A LATIN CAPITAL LETTER Z Set that - * attribute's name to the lowercase version - * of the current input character (add - * 0x0020 to the character's code point) - */ - c += 0x20; - } - // CPPONLY: attributeLine = line; - /* - * Set that attribute's name to the current - * input character, - */ - clearStrBufBeforeUse(); - appendStrBuf(c); - /* - * and its value to the empty string. - */ - // Will do later. - /* - * Switch to the attribute name state. - */ - state = transition(state, Tokenizer.ATTRIBUTE_NAME, reconsume, pos); - break beforeattributenameloop; - // continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case ATTRIBUTE_NAME: - attributenameloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - attributeNameComplete(); - state = transition(state, Tokenizer.AFTER_ATTRIBUTE_NAME, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE - * Switch to the after attribute name state. - */ - attributeNameComplete(); - state = transition(state, Tokenizer.AFTER_ATTRIBUTE_NAME, reconsume, pos); - continue stateloop; - case '/': - /* - * U+002F SOLIDUS (/) Switch to the self-closing - * start tag state. - */ - attributeNameComplete(); - addAttributeWithoutValue(); - state = transition(state, Tokenizer.SELF_CLOSING_START_TAG, reconsume, pos); - continue stateloop; - case '=': - /* - * U+003D EQUALS SIGN (=) Switch to the before - * attribute value state. - */ - attributeNameComplete(); - state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_VALUE, reconsume, pos); - break attributenameloop; - // continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the current - * tag token. - */ - attributeNameComplete(); - addAttributeWithoutValue(); - state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos); - if (shouldSuspend) { - break stateloop; - } - /* - * Switch to the data state. - */ - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - case '\"': - case '\'': - case '<': - /* - * U+0022 QUOTATION MARK (") U+0027 APOSTROPHE - * (') U+003C LESS-THAN SIGN (<) Parse error. - */ - errQuoteOrLtInAttributeNameOrNull(c); - /* - * Treat it as per the "anything else" entry - * below. - */ - default: - if (c >= 'A' && c <= 'Z') { - /* - * U+0041 LATIN CAPITAL LETTER A through to - * U+005A LATIN CAPITAL LETTER Z Append the - * lowercase version of the current input - * character (add 0x0020 to the character's - * code point) to the current attribute's - * name. - */ - c += 0x20; - } - /* - * Anything else Append the current input - * character to the current attribute's name. - */ - appendStrBuf(c); - /* - * Stay in the attribute name state. - */ - continue; - } - } - // FALLTHRU DON'T REORDER - case BEFORE_ATTRIBUTE_VALUE: - beforeattributevalueloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay - * in the before attribute value state. - */ - continue; - case '"': - /* - * U+0022 QUOTATION MARK (") Switch to the - * attribute value (double-quoted) state. - */ - // CPPONLY: attributeLine = line; - clearStrBufBeforeUse(); - state = transition(state, Tokenizer.ATTRIBUTE_VALUE_DOUBLE_QUOTED, reconsume, pos); - break beforeattributevalueloop; - // continue stateloop; - case '&': - /* - * U+0026 AMPERSAND (&) Switch to the attribute - * value (unquoted) state and reconsume this - * input character. - */ - // CPPONLY: attributeLine = line; - clearStrBufBeforeUse(); - reconsume = true; - state = transition(state, Tokenizer.ATTRIBUTE_VALUE_UNQUOTED, reconsume, pos); - noteUnquotedAttributeValue(); - continue stateloop; - case '\'': - /* - * U+0027 APOSTROPHE (') Switch to the attribute - * value (single-quoted) state. - */ - // CPPONLY: attributeLine = line; - clearStrBufBeforeUse(); - state = transition(state, Tokenizer.ATTRIBUTE_VALUE_SINGLE_QUOTED, reconsume, pos); - continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Parse error. - */ - errAttributeValueMissing(); - /* - * Emit the current tag token. - */ - addAttributeWithoutValue(); - state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos); - if (shouldSuspend) { - break stateloop; - } - /* - * Switch to the data state. - */ - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - case '<': - case '=': - case '`': - /* - * U+003C LESS-THAN SIGN (<) U+003D EQUALS SIGN - * (=) U+0060 GRAVE ACCENT (`) - */ - errLtOrEqualsOrGraveInUnquotedAttributeOrNull(c); - /* - * Treat it as per the "anything else" entry - * below. - */ - default: - // [NOCPP[ - errHtml4NonNameInUnquotedAttribute(c); - // ]NOCPP] - /* - * Anything else Append the current input - * character to the current attribute's value. - */ - // CPPONLY: attributeLine = line; - clearStrBufBeforeUse(); - appendStrBuf(c); - /* - * Switch to the attribute value (unquoted) - * state. - */ - - state = transition(state, Tokenizer.ATTRIBUTE_VALUE_UNQUOTED, reconsume, pos); - noteUnquotedAttributeValue(); - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case ATTRIBUTE_VALUE_DOUBLE_QUOTED: - attributevaluedoublequotedloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - /* - * Consume the next input character: - */ - switch (c) { - case '"': - /* - * U+0022 QUOTATION MARK (") Switch to the after - * attribute value (quoted) state. - */ - addAttributeWithValue(); - - state = transition(state, Tokenizer.AFTER_ATTRIBUTE_VALUE_QUOTED, reconsume, pos); - break attributevaluedoublequotedloop; - // continue stateloop; - case '&': - /* - * U+0026 AMPERSAND (&) Switch to the character - * reference in attribute value state, with the - * additional allowed character being U+0022 - * QUOTATION MARK ("). - */ - assert charRefBufLen == 0: "charRefBufLen not reset after previous use!"; - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('\"'); - returnState = state; - state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos); - continue stateloop; - case '\r': - appendStrBufCarriageReturn(); - break stateloop; - case '\n': - appendStrBufLineFeed(); - continue; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * Anything else Append the current input - * character to the current attribute's value. - */ - appendStrBuf(c); - /* - * Stay in the attribute value (double-quoted) - * state. - */ - continue; - } - } - // FALLTHRU DON'T REORDER - case AFTER_ATTRIBUTE_VALUE_QUOTED: - afterattributevaluequotedloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE - * Switch to the before attribute name state. - */ - state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos); - continue stateloop; - case '/': - /* - * U+002F SOLIDUS (/) Switch to the self-closing - * start tag state. - */ - state = transition(state, Tokenizer.SELF_CLOSING_START_TAG, reconsume, pos); - break afterattributevaluequotedloop; - // continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the current - * tag token. - */ - state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos); - if (shouldSuspend) { - break stateloop; - } - /* - * Switch to the data state. - */ - continue stateloop; - default: - /* - * Anything else Parse error. - */ - errNoSpaceBetweenAttributes(); - /* - * Reconsume the character in the before - * attribute name state. - */ - reconsume = true; - state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos); - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case SELF_CLOSING_START_TAG: - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Set the self-closing - * flag of the current tag token. Emit the current - * tag token. - */ - // [NOCPP[ - errHtml4XmlVoidSyntax(); - // ]NOCPP] - state = transition(state, emitCurrentTagToken(true, pos), reconsume, pos); - if (shouldSuspend) { - break stateloop; - } - /* - * Switch to the data state. - */ - continue stateloop; - default: - /* Anything else Parse error. */ - errSlashNotFollowedByGt(); - /* - * Reconsume the character in the before attribute - * name state. - */ - reconsume = true; - state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos); - continue stateloop; - } - // XXX reorder point - case ATTRIBUTE_VALUE_UNQUOTED: - for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - addAttributeWithValue(); - state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE - * Switch to the before attribute name state. - */ - addAttributeWithValue(); - state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos); - continue stateloop; - case '&': - /* - * U+0026 AMPERSAND (&) Switch to the character - * reference in attribute value state, with the - * additional allowed character being U+003E - * GREATER-THAN SIGN (>) - */ - assert charRefBufLen == 0: "charRefBufLen not reset after previous use!"; - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('>'); - returnState = state; - state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos); - continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the current - * tag token. - */ - addAttributeWithValue(); - state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos); - if (shouldSuspend) { - break stateloop; - } - /* - * Switch to the data state. - */ - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - case '<': - case '\"': - case '\'': - case '=': - case '`': - /* - * U+0022 QUOTATION MARK (") U+0027 APOSTROPHE - * (') U+003C LESS-THAN SIGN (<) U+003D EQUALS - * SIGN (=) U+0060 GRAVE ACCENT (`) Parse error. - */ - errUnquotedAttributeValOrNull(c); - /* - * Treat it as per the "anything else" entry - * below. - */ - // fall through - default: - // [NOCPP] - errHtml4NonNameInUnquotedAttribute(c); - // ]NOCPP] - /* - * Anything else Append the current input - * character to the current attribute's value. - */ - appendStrBuf(c); - /* - * Stay in the attribute value (unquoted) state. - */ - continue; - } - } - // XXX reorder point - case AFTER_ATTRIBUTE_NAME: - for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay - * in the after attribute name state. - */ - continue; - case '/': - /* - * U+002F SOLIDUS (/) Switch to the self-closing - * start tag state. - */ - addAttributeWithoutValue(); - state = transition(state, Tokenizer.SELF_CLOSING_START_TAG, reconsume, pos); - continue stateloop; - case '=': - /* - * U+003D EQUALS SIGN (=) Switch to the before - * attribute value state. - */ - state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_VALUE, reconsume, pos); - continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the current - * tag token. - */ - addAttributeWithoutValue(); - state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos); - if (shouldSuspend) { - break stateloop; - } - /* - * Switch to the data state. - */ - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - case '\"': - case '\'': - case '<': - errQuoteOrLtInAttributeNameOrNull(c); - /* - * Treat it as per the "anything else" entry - * below. - */ - default: - addAttributeWithoutValue(); - /* - * Anything else Start a new attribute in the - * current tag token. - */ - if (c >= 'A' && c <= 'Z') { - /* - * U+0041 LATIN CAPITAL LETTER A through to - * U+005A LATIN CAPITAL LETTER Z Set that - * attribute's name to the lowercase version - * of the current input character (add - * 0x0020 to the character's code point) - */ - c += 0x20; - } - /* - * Set that attribute's name to the current - * input character, - */ - clearStrBufBeforeUse(); - appendStrBuf(c); - /* - * and its value to the empty string. - */ - // Will do later. - /* - * Switch to the attribute name state. - */ - state = transition(state, Tokenizer.ATTRIBUTE_NAME, reconsume, pos); - continue stateloop; - } - } - // XXX reorder point - case MARKUP_DECLARATION_OPEN: - markupdeclarationopenloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * If the next two characters are both U+002D - * HYPHEN-MINUS characters (-), consume those two - * characters, create a comment token whose data is the - * empty string, and switch to the comment start state. - * - * Otherwise, if the next seven characters are an ASCII - * case-insensitive match for the word "DOCTYPE", then - * consume those characters and switch to the DOCTYPE - * state. - * - * Otherwise, if the insertion mode is - * "in foreign content" and the current node is not an - * element in the HTML namespace and the next seven - * characters are an case-sensitive match for the string - * "[CDATA[" (the five uppercase letters "CDATA" with a - * U+005B LEFT SQUARE BRACKET character before and - * after), then consume those characters and switch to - * the CDATA section state. - * - * Otherwise, is is a parse error. Switch to the bogus - * comment state. The next character that is consumed, - * if any, is the first character that will be in the - * comment. - */ - switch (c) { - case '-': - clearStrBufBeforeUse(); - appendStrBuf(c); - state = transition(state, Tokenizer.MARKUP_DECLARATION_HYPHEN, reconsume, pos); - break markupdeclarationopenloop; - // continue stateloop; - case 'd': - case 'D': - clearStrBufBeforeUse(); - appendStrBuf(c); - index = 0; - state = transition(state, Tokenizer.MARKUP_DECLARATION_OCTYPE, reconsume, pos); - continue stateloop; - case '[': - if (tokenHandler.cdataSectionAllowed()) { - clearStrBufBeforeUse(); - appendStrBuf(c); - index = 0; - state = transition(state, Tokenizer.CDATA_START, reconsume, pos); - continue stateloop; - } - // else fall through - default: - errBogusComment(); - clearStrBufBeforeUse(); - reconsume = true; - state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos); - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case MARKUP_DECLARATION_HYPHEN: - markupdeclarationhyphenloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - switch (c) { - case '\u0000': - break stateloop; - case '-': - clearStrBufAfterOneHyphen(); - state = transition(state, Tokenizer.COMMENT_START, reconsume, pos); - break markupdeclarationhyphenloop; - // continue stateloop; - default: - errBogusComment(); - reconsume = true; - state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos); - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case COMMENT_START: - commentstartloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Comment start state - * - * - * Consume the next input character: - */ - switch (c) { - case '-': - /* - * U+002D HYPHEN-MINUS (-) Switch to the comment - * start dash state. - */ - appendStrBuf(c); - state = transition(state, Tokenizer.COMMENT_START_DASH, reconsume, pos); - continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Parse error. - */ - errPrematureEndOfComment(); - /* Emit the comment token. */ - emitComment(0, pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '\r': - appendStrBufCarriageReturn(); - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - break stateloop; - case '\n': - appendStrBufLineFeed(); - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - break commentstartloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * Anything else Append the input character to - * the comment token's data. - */ - appendStrBuf(c); - /* - * Switch to the comment state. - */ - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - break commentstartloop; - // continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case COMMENT: - commentloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Comment state Consume the next input character: - */ - switch (c) { - case '-': - /* - * U+002D HYPHEN-MINUS (-) Switch to the comment - * end dash state - */ - appendStrBuf(c); - state = transition(state, Tokenizer.COMMENT_END_DASH, reconsume, pos); - break commentloop; - // continue stateloop; - case '\r': - appendStrBufCarriageReturn(); - break stateloop; - case '\n': - appendStrBufLineFeed(); - continue; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * Anything else Append the input character to - * the comment token's data. - */ - appendStrBuf(c); - /* - * Stay in the comment state. - */ - continue; - } - } - // FALLTHRU DON'T REORDER - case COMMENT_END_DASH: - commentenddashloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Comment end dash state Consume the next input - * character: - */ - switch (c) { - case '-': - /* - * U+002D HYPHEN-MINUS (-) Switch to the comment - * end state - */ - appendStrBuf(c); - state = transition(state, Tokenizer.COMMENT_END, reconsume, pos); - break commentenddashloop; - // continue stateloop; - case '\r': - appendStrBufCarriageReturn(); - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - break stateloop; - case '\n': - appendStrBufLineFeed(); - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * Anything else Append a U+002D HYPHEN-MINUS - * (-) character and the input character to the - * comment token's data. - */ - appendStrBuf(c); - /* - * Switch to the comment state. - */ - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case COMMENT_END: - commentendloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Comment end dash state Consume the next input - * character: - */ - switch (c) { - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the comment - * token. - */ - emitComment(2, pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '-': - /* U+002D HYPHEN-MINUS (-) Parse error. */ - /* - * Append a U+002D HYPHEN-MINUS (-) character to - * the comment token's data. - */ - adjustDoubleHyphenAndAppendToStrBufAndErr(c); - /* - * Stay in the comment end state. - */ - continue; - case '\r': - adjustDoubleHyphenAndAppendToStrBufCarriageReturn(); - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - break stateloop; - case '\n': - adjustDoubleHyphenAndAppendToStrBufLineFeed(); - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - continue stateloop; - case '!': - errHyphenHyphenBang(); - appendStrBuf(c); - state = transition(state, Tokenizer.COMMENT_END_BANG, reconsume, pos); - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * Append two U+002D HYPHEN-MINUS (-) characters - * and the input character to the comment - * token's data. - */ - adjustDoubleHyphenAndAppendToStrBufAndErr(c); - /* - * Switch to the comment state. - */ - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - continue stateloop; - } - } - // XXX reorder point - case COMMENT_END_BANG: - for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Comment end bang state - * - * Consume the next input character: - */ - switch (c) { - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the comment - * token. - */ - emitComment(3, pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '-': - /* - * Append two U+002D HYPHEN-MINUS (-) characters - * and a U+0021 EXCLAMATION MARK (!) character - * to the comment token's data. - */ - appendStrBuf(c); - /* - * Switch to the comment end dash state. - */ - state = transition(state, Tokenizer.COMMENT_END_DASH, reconsume, pos); - continue stateloop; - case '\r': - appendStrBufCarriageReturn(); - break stateloop; - case '\n': - appendStrBufLineFeed(); - continue; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * Anything else Append two U+002D HYPHEN-MINUS - * (-) characters, a U+0021 EXCLAMATION MARK (!) - * character, and the input character to the - * comment token's data. Switch to the comment - * state. - */ - appendStrBuf(c); - /* - * Switch to the comment state. - */ - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - continue stateloop; - } - } - // XXX reorder point - case COMMENT_START_DASH: - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Comment start dash state - * - * Consume the next input character: - */ - switch (c) { - case '-': - /* - * U+002D HYPHEN-MINUS (-) Switch to the comment end - * state - */ - appendStrBuf(c); - state = transition(state, Tokenizer.COMMENT_END, reconsume, pos); - continue stateloop; - case '>': - errPrematureEndOfComment(); - /* Emit the comment token. */ - emitComment(1, pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '\r': - appendStrBufCarriageReturn(); - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - break stateloop; - case '\n': - appendStrBufLineFeed(); - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * Append a U+002D HYPHEN-MINUS character (-) and - * the current input character to the comment - * token's data. - */ - appendStrBuf(c); - /* - * Switch to the comment state. - */ - state = transition(state, Tokenizer.COMMENT, reconsume, pos); - continue stateloop; - } - // XXX reorder point - case CDATA_START: - for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - if (index < 6) { // CDATA_LSQB.length - if (c == Tokenizer.CDATA_LSQB[index]) { - appendStrBuf(c); - } else { - errBogusComment(); - reconsume = true; - state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos); - continue stateloop; - } - index++; - continue; - } else { - clearStrBufAfterUse(); - cstart = pos; // start coalescing - reconsume = true; - state = transition(state, Tokenizer.CDATA_SECTION, reconsume, pos); - break; // FALL THROUGH continue stateloop; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case CDATA_SECTION: - cdatasectionloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - switch (c) { - case ']': - flushChars(buf, pos); - state = transition(state, Tokenizer.CDATA_RSQB, reconsume, pos); - break cdatasectionloop; // FALL THROUGH - case '\u0000': - emitReplacementCharacter(buf, pos); - continue; - case '\r': - emitCarriageReturn(buf, pos); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - default: - continue; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case CDATA_RSQB: - cdatarsqb: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - switch (c) { - case ']': - state = transition(state, Tokenizer.CDATA_RSQB_RSQB, reconsume, pos); - break cdatarsqb; - default: - tokenHandler.characters(Tokenizer.RSQB_RSQB, 0, - 1); - cstart = pos; - reconsume = true; - state = transition(state, Tokenizer.CDATA_SECTION, reconsume, pos); - continue stateloop; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case CDATA_RSQB_RSQB: - cdatarsqbrsqb: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - switch (c) { - case ']': - // Saw a third ]. Emit one ] (logically the - // first one) and stay in this state to - // remember that the last two characters seen - // have been ]]. - tokenHandler.characters(Tokenizer.RSQB_RSQB, 0, 1); - continue; - case '>': - cstart = pos + 1; - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - default: - tokenHandler.characters(Tokenizer.RSQB_RSQB, 0, 2); - cstart = pos; - reconsume = true; - state = transition(state, Tokenizer.CDATA_SECTION, reconsume, pos); - continue stateloop; - } - } - // XXX reorder point - case ATTRIBUTE_VALUE_SINGLE_QUOTED: - attributevaluesinglequotedloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - /* - * Consume the next input character: - */ - switch (c) { - case '\'': - /* - * U+0027 APOSTROPHE (') Switch to the after - * attribute value (quoted) state. - */ - addAttributeWithValue(); - - state = transition(state, Tokenizer.AFTER_ATTRIBUTE_VALUE_QUOTED, reconsume, pos); - continue stateloop; - case '&': - /* - * U+0026 AMPERSAND (&) Switch to the character - * reference in attribute value state, with the - * + additional allowed character being U+0027 - * APOSTROPHE ('). - */ - assert charRefBufLen == 0: "charRefBufLen not reset after previous use!"; - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('\''); - returnState = state; - state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos); - break attributevaluesinglequotedloop; - // continue stateloop; - case '\r': - appendStrBufCarriageReturn(); - break stateloop; - case '\n': - appendStrBufLineFeed(); - continue; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * Anything else Append the current input - * character to the current attribute's value. - */ - appendStrBuf(c); - /* - * Stay in the attribute value (double-quoted) - * state. - */ - continue; - } - } - // FALLTHRU DON'T REORDER - case CONSUME_CHARACTER_REFERENCE: - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - if (c == '\u0000') { - break stateloop; - } - /* - * Unlike the definition is the spec, this state does not - * return a value and never requires the caller to - * backtrack. This state takes care of emitting characters - * or appending to the current attribute value. It also - * takes care of that in the case when consuming the - * character reference fails. - */ - /* - * This section defines how to consume a character - * reference. This definition is used when parsing character - * references in text and in attributes. - * - * The behavior depends on the identity of the next - * character (the one immediately after the U+0026 AMPERSAND - * character): - */ - switch (c) { - case ' ': - case '\t': - case '\n': - case '\r': // we'll reconsume! - case '\u000C': - case '<': - case '&': - emitOrAppendCharRefBuf(returnState); - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = pos; - } - reconsume = true; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - case '#': - /* - * U+0023 NUMBER SIGN (#) Consume the U+0023 NUMBER - * SIGN. - */ - appendCharRefBuf('#'); - state = transition(state, Tokenizer.CONSUME_NCR, reconsume, pos); - continue stateloop; - default: - if (c == additional) { - emitOrAppendCharRefBuf(returnState); - reconsume = true; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } - if (c >= 'a' && c <= 'z') { - firstCharKey = c - 'a' + 26; - } else if (c >= 'A' && c <= 'Z') { - firstCharKey = c - 'A'; - } else { - // No match - /* - * If no match can be made, then this is a parse - * error. - */ - errNoNamedCharacterMatch(); - emitOrAppendCharRefBuf(returnState); - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = pos; - } - reconsume = true; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } - // Didn't fail yet - appendCharRefBuf(c); - state = transition(state, Tokenizer.CHARACTER_REFERENCE_HILO_LOOKUP, reconsume, pos); - // FALL THROUGH continue stateloop; - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case CHARACTER_REFERENCE_HILO_LOOKUP: - { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - if (c == '\u0000') { - break stateloop; - } - /* - * The data structure is as follows: - * - * HILO_ACCEL is a two-dimensional int array whose major - * index corresponds to the second character of the - * character reference (code point as index) and the - * minor index corresponds to the first character of the - * character reference (packed so that A-Z runs from 0 - * to 25 and a-z runs from 26 to 51). This layout makes - * it easier to use the sparseness of the data structure - * to omit parts of it: The second dimension of the - * table is null when no character reference starts with - * the character corresponding to that row. - * - * The int value HILO_ACCEL (by these indeces) is zero - * if there exists no character reference starting with - * that two-letter prefix. Otherwise, the value is an - * int that packs two shorts so that the higher short is - * the index of the highest character reference name - * with that prefix in NAMES and the lower short - * corresponds to the index of the lowest character - * reference name with that prefix. (It happens that the - * first two character reference names share their - * prefix so the packed int cannot be 0 by packing the - * two shorts.) - * - * NAMES is an array of byte arrays where each byte - * array encodes the name of a character references as - * ASCII. The names omit the first two letters of the - * name. (Since storing the first two letters would be - * redundant with the data contained in HILO_ACCEL.) The - * entries are lexically sorted. - * - * For a given index in NAMES, the same index in VALUES - * contains the corresponding expansion as an array of - * two UTF-16 code units (either the character and - * U+0000 or a suggogate pair). - */ - int hilo = 0; - if (c <= 'z') { - @Const @NoLength int[] row = NamedCharactersAccel.HILO_ACCEL[c]; - if (row != null) { - hilo = row[firstCharKey]; - } - } - if (hilo == 0) { - /* - * If no match can be made, then this is a parse - * error. - */ - errNoNamedCharacterMatch(); - emitOrAppendCharRefBuf(returnState); - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = pos; - } - reconsume = true; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } - // Didn't fail yet - appendCharRefBuf(c); - lo = hilo & 0xFFFF; - hi = hilo >> 16; - entCol = -1; - candidate = -1; - charRefBufMark = 0; - state = transition(state, Tokenizer.CHARACTER_REFERENCE_TAIL, reconsume, pos); - // FALL THROUGH continue stateloop; - } - case CHARACTER_REFERENCE_TAIL: - outer: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - if (c == '\u0000') { - break stateloop; - } - entCol++; - /* - * Consume the maximum number of characters possible, - * with the consumed characters matching one of the - * identifiers in the first column of the named - * character references table (in a case-sensitive - * manner). - */ - loloop: for (;;) { - if (hi < lo) { - break outer; - } - if (entCol == NamedCharacters.NAMES[lo].length()) { - candidate = lo; - charRefBufMark = charRefBufLen; - lo++; - } else if (entCol > NamedCharacters.NAMES[lo].length()) { - break outer; - } else if (c > NamedCharacters.NAMES[lo].charAt(entCol)) { - lo++; - } else { - break loloop; - } - } - - hiloop: for (;;) { - if (hi < lo) { - break outer; - } - if (entCol == NamedCharacters.NAMES[hi].length()) { - break hiloop; - } - if (entCol > NamedCharacters.NAMES[hi].length()) { - break outer; - } else if (c < NamedCharacters.NAMES[hi].charAt(entCol)) { - hi--; - } else { - break hiloop; - } - } - - if (c == ';') { - // If we see a semicolon, there cannot be a - // longer match. Break the loop. However, before - // breaking, take the longest match so far as the - // candidate, if we are just about to complete a - // match. - if (entCol + 1 == NamedCharacters.NAMES[lo].length()) { - candidate = lo; - charRefBufMark = charRefBufLen; - } - break outer; - } - - if (hi < lo) { - break outer; - } - appendCharRefBuf(c); - continue; - } - - if (candidate == -1) { - // reconsume deals with CR, LF or nul - /* - * If no match can be made, then this is a parse error. - */ - errNoNamedCharacterMatch(); - emitOrAppendCharRefBuf(returnState); - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = pos; - } - reconsume = true; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } else { - // c can't be CR, LF or nul if we got here - @Const @CharacterName String candidateName = NamedCharacters.NAMES[candidate]; - if (candidateName.length() == 0 - || candidateName.charAt(candidateName.length() - 1) != ';') { - /* - * If the last character matched is not a U+003B - * SEMICOLON (;), there is a parse error. - */ - if ((returnState & DATA_AND_RCDATA_MASK) != 0) { - /* - * If the entity is being consumed as part of an - * attribute, and the last character matched is - * not a U+003B SEMICOLON (;), - */ - char ch; - if (charRefBufMark == charRefBufLen) { - ch = c; - } else { - ch = charRefBuf[charRefBufMark]; - } - if (ch == '=' || (ch >= '0' && ch <= '9') - || (ch >= 'A' && ch <= 'Z') - || (ch >= 'a' && ch <= 'z')) { - /* - * and the next character is either a U+003D - * EQUALS SIGN character (=) or in the range - * U+0030 DIGIT ZERO to U+0039 DIGIT NINE, - * U+0041 LATIN CAPITAL LETTER A to U+005A - * LATIN CAPITAL LETTER Z, or U+0061 LATIN - * SMALL LETTER A to U+007A LATIN SMALL - * LETTER Z, then, for historical reasons, - * all the characters that were matched - * after the U+0026 AMPERSAND (&) must be - * unconsumed, and nothing is returned. - */ - errNoNamedCharacterMatch(); - appendCharRefBufToStrBuf(); - reconsume = true; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } - } - if ((returnState & DATA_AND_RCDATA_MASK) != 0) { - errUnescapedAmpersandInterpretedAsCharacterReference(); - } else { - errNotSemicolonTerminated(); - } - } - - /* - * Otherwise, return a character token for the character - * corresponding to the entity name (as given by the - * second column of the named character references - * table). - */ - // CPPONLY: completedNamedCharacterReference(); - @Const @NoLength char[] val = NamedCharacters.VALUES[candidate]; - if ( - // [NOCPP[ - val.length == 1 - // ]NOCPP] - // CPPONLY: val[1] == 0 - ) { - emitOrAppendOne(val, returnState); - } else { - emitOrAppendTwo(val, returnState); - } - // this is so complicated! - if (charRefBufMark < charRefBufLen) { - if ((returnState & DATA_AND_RCDATA_MASK) != 0) { - appendStrBuf(charRefBuf, charRefBufMark, - charRefBufLen - charRefBufMark); - } else { - tokenHandler.characters(charRefBuf, charRefBufMark, - charRefBufLen - charRefBufMark); - } - } - // charRefBufLen will be zeroed below! - - // Check if we broke out early with c being the last - // character that matched as opposed to being the - // first one that didn't match. In the case of an - // early break, the next run on text should start - // *after* the current character and the current - // character shouldn't be reconsumed. - boolean earlyBreak = (c == ';' && charRefBufMark == charRefBufLen); - charRefBufLen = 0; - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = earlyBreak ? pos + 1 : pos; - } - reconsume = !earlyBreak; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - /* - * If the markup contains I'm ¬it; I tell you, the - * entity is parsed as "not", as in, I'm ¬it; I tell - * you. But if the markup was I'm ∉ I tell you, - * the entity would be parsed as "notin;", resulting in - * I'm ∉ I tell you. - */ - } - // XXX reorder point - case CONSUME_NCR: - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - value = 0; - seenDigits = false; - /* - * The behavior further depends on the character after the - * U+0023 NUMBER SIGN: - */ - switch (c) { - case 'x': - case 'X': - - /* - * U+0078 LATIN SMALL LETTER X U+0058 LATIN CAPITAL - * LETTER X Consume the X. - * - * Follow the steps below, but using the range of - * characters U+0030 DIGIT ZERO through to U+0039 - * DIGIT NINE, U+0061 LATIN SMALL LETTER A through - * to U+0066 LATIN SMALL LETTER F, and U+0041 LATIN - * CAPITAL LETTER A, through to U+0046 LATIN CAPITAL - * LETTER F (in other words, 0-9, A-F, a-f). - * - * When it comes to interpreting the number, - * interpret it as a hexadecimal number. - */ - appendCharRefBuf(c); - state = transition(state, Tokenizer.HEX_NCR_LOOP, reconsume, pos); - continue stateloop; - default: - /* - * Anything else Follow the steps below, but using - * the range of characters U+0030 DIGIT ZERO through - * to U+0039 DIGIT NINE (i.e. just 0-9). - * - * When it comes to interpreting the number, - * interpret it as a decimal number. - */ - reconsume = true; - state = transition(state, Tokenizer.DECIMAL_NRC_LOOP, reconsume, pos); - // FALL THROUGH continue stateloop; - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case DECIMAL_NRC_LOOP: - decimalloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - /* - * Consume as many characters as match the range of - * characters given above. - */ - assert value >= 0: "value must not become negative."; - if (c >= '0' && c <= '9') { - seenDigits = true; - // Avoid overflow - if (value <= 0x10FFFF) { - value *= 10; - value += c - '0'; - } - continue; - } else if (c == ';') { - if (seenDigits) { - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = pos + 1; - } - state = transition(state, Tokenizer.HANDLE_NCR_VALUE, reconsume, pos); - // FALL THROUGH continue stateloop; - break decimalloop; - } else { - errNoDigitsInNCR(); - appendCharRefBuf(';'); - emitOrAppendCharRefBuf(returnState); - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = pos + 1; - } - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } - } else { - /* - * If no characters match the range, then don't - * consume any characters (and unconsume the U+0023 - * NUMBER SIGN character and, if appropriate, the X - * character). This is a parse error; nothing is - * returned. - * - * Otherwise, if the next character is a U+003B - * SEMICOLON, consume that too. If it isn't, there - * is a parse error. - */ - if (!seenDigits) { - errNoDigitsInNCR(); - emitOrAppendCharRefBuf(returnState); - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = pos; - } - reconsume = true; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } else { - errCharRefLacksSemicolon(); - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = pos; - } - reconsume = true; - state = transition(state, Tokenizer.HANDLE_NCR_VALUE, reconsume, pos); - // FALL THROUGH continue stateloop; - break decimalloop; - } - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case HANDLE_NCR_VALUE: - // WARNING previous state sets reconsume - // We are not going to emit the contents of charRefBuf. - charRefBufLen = 0; - // XXX inline this case if the method size can take it - handleNcrValue(returnState); - state = transition(state, returnState, reconsume, pos); - continue stateloop; - // XXX reorder point - case HEX_NCR_LOOP: - for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume as many characters as match the range of - * characters given above. - */ - assert value >= 0: "value must not become negative."; - if (c >= '0' && c <= '9') { - seenDigits = true; - // Avoid overflow - if (value <= 0x10FFFF) { - value *= 16; - value += c - '0'; - } - continue; - } else if (c >= 'A' && c <= 'F') { - seenDigits = true; - // Avoid overflow - if (value <= 0x10FFFF) { - value *= 16; - value += c - 'A' + 10; - } - continue; - } else if (c >= 'a' && c <= 'f') { - seenDigits = true; - // Avoid overflow - if (value <= 0x10FFFF) { - value *= 16; - value += c - 'a' + 10; - } - continue; - } else if (c == ';') { - if (seenDigits) { - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = pos + 1; - } - state = transition(state, Tokenizer.HANDLE_NCR_VALUE, reconsume, pos); - continue stateloop; - } else { - errNoDigitsInNCR(); - appendCharRefBuf(';'); - emitOrAppendCharRefBuf(returnState); - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = pos + 1; - } - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } - } else { - /* - * If no characters match the range, then don't - * consume any characters (and unconsume the U+0023 - * NUMBER SIGN character and, if appropriate, the X - * character). This is a parse error; nothing is - * returned. - * - * Otherwise, if the next character is a U+003B - * SEMICOLON, consume that too. If it isn't, there - * is a parse error. - */ - if (!seenDigits) { - errNoDigitsInNCR(); - emitOrAppendCharRefBuf(returnState); - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = pos; - } - reconsume = true; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } else { - errCharRefLacksSemicolon(); - if ((returnState & DATA_AND_RCDATA_MASK) == 0) { - cstart = pos; - } - reconsume = true; - state = transition(state, Tokenizer.HANDLE_NCR_VALUE, reconsume, pos); - continue stateloop; - } - } - } - // XXX reorder point - case PLAINTEXT: - plaintextloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - switch (c) { - case '\u0000': - emitPlaintextReplacementCharacter(buf, pos); - continue; - case '\r': - emitCarriageReturn(buf, pos); - break stateloop; - case '\n': - silentLineFeed(); - default: - /* - * Anything else Emit the current input - * character as a character token. Stay in the - * RAWTEXT state. - */ - continue; - } - } - // XXX reorder point - case CLOSE_TAG_OPEN: - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Otherwise, if the content model flag is set to the PCDATA - * state, or if the next few characters do match that tag - * name, consume the next input character: - */ - switch (c) { - case '>': - /* U+003E GREATER-THAN SIGN (>) Parse error. */ - errLtSlashGt(); - /* - * Switch to the data state. - */ - cstart = pos + 1; - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '\r': - silentCarriageReturn(); - /* Anything else Parse error. */ - errGarbageAfterLtSlash(); - /* - * Switch to the bogus comment state. - */ - clearStrBufBeforeUse(); - appendStrBuf('\n'); - state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - /* Anything else Parse error. */ - errGarbageAfterLtSlash(); - /* - * Switch to the bogus comment state. - */ - clearStrBufBeforeUse(); - appendStrBuf(c); - state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos); - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - if (c >= 'A' && c <= 'Z') { - c += 0x20; - } - if (c >= 'a' && c <= 'z') { - /* - * U+0061 LATIN SMALL LETTER A through to U+007A - * LATIN SMALL LETTER Z Create a new end tag - * token, - */ - endTag = true; - /* - * set its tag name to the input character, - */ - clearStrBufBeforeUse(); - appendStrBuf(c); - /* - * then switch to the tag name state. (Don't - * emit the token yet; further details will be - * filled in before it is emitted.) - */ - state = transition(state, Tokenizer.TAG_NAME, reconsume, pos); - continue stateloop; - } else { - /* Anything else Parse error. */ - errGarbageAfterLtSlash(); - /* - * Switch to the bogus comment state. - */ - clearStrBufBeforeUse(); - appendStrBuf(c); - state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos); - continue stateloop; - } - } - // XXX reorder point - case RCDATA: - rcdataloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - switch (c) { - case '&': - /* - * U+0026 AMPERSAND (&) Switch to the character - * reference in RCDATA state. - */ - flushChars(buf, pos); - assert charRefBufLen == 0: "charRefBufLen not reset after previous use!"; - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('\u0000'); - returnState = state; - state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos); - continue stateloop; - case '<': - /* - * U+003C LESS-THAN SIGN (<) Switch to the - * RCDATA less-than sign state. - */ - flushChars(buf, pos); - - returnState = state; - state = transition(state, Tokenizer.RAWTEXT_RCDATA_LESS_THAN_SIGN, reconsume, pos); - continue stateloop; - case '\u0000': - emitReplacementCharacter(buf, pos); - continue; - case '\r': - emitCarriageReturn(buf, pos); - break stateloop; - case '\n': - silentLineFeed(); - default: - /* - * Emit the current input character as a - * character token. Stay in the RCDATA state. - */ - continue; - } - } - // XXX reorder point - case RAWTEXT: - rawtextloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - switch (c) { - case '<': - /* - * U+003C LESS-THAN SIGN (<) Switch to the - * RAWTEXT less-than sign state. - */ - flushChars(buf, pos); - - returnState = state; - state = transition(state, Tokenizer.RAWTEXT_RCDATA_LESS_THAN_SIGN, reconsume, pos); - break rawtextloop; - // FALL THRU continue stateloop; - case '\u0000': - emitReplacementCharacter(buf, pos); - continue; - case '\r': - emitCarriageReturn(buf, pos); - break stateloop; - case '\n': - silentLineFeed(); - default: - /* - * Emit the current input character as a - * character token. Stay in the RAWTEXT state. - */ - continue; - } - } - // XXX fallthru don't reorder - case RAWTEXT_RCDATA_LESS_THAN_SIGN: - rawtextrcdatalessthansignloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - switch (c) { - case '/': - /* - * U+002F SOLIDUS (/) Set the temporary buffer - * to the empty string. Switch to the script - * data end tag open state. - */ - index = 0; - clearStrBufBeforeUse(); - state = transition(state, Tokenizer.NON_DATA_END_TAG_NAME, reconsume, pos); - break rawtextrcdatalessthansignloop; - // FALL THRU continue stateloop; - default: - /* - * Otherwise, emit a U+003C LESS-THAN SIGN - * character token - */ - tokenHandler.characters(Tokenizer.LT_GT, 0, 1); - /* - * and reconsume the current input character in - * the data state. - */ - cstart = pos; - reconsume = true; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } - } - // XXX fall thru. don't reorder. - case NON_DATA_END_TAG_NAME: - for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * ASSERT! when entering this state, set index to 0 and - * call clearStrBufBeforeUse(); Let's implement the above - * without lookahead. strBuf is the 'temporary buffer'. - */ - if (endTagExpectationAsArray == null) { - tokenHandler.characters(Tokenizer.LT_SOLIDUS, - 0, 2); - cstart = pos; - reconsume = true; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } else if (index < endTagExpectationAsArray.length) { - char e = endTagExpectationAsArray[index]; - char folded = c; - if (c >= 'A' && c <= 'Z') { - folded += 0x20; - } - if (folded != e) { - // [NOCPP[ - errHtml4LtSlashInRcdata(folded); - // ]NOCPP] - tokenHandler.characters(Tokenizer.LT_SOLIDUS, - 0, 2); - emitStrBuf(); - cstart = pos; - reconsume = true; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } - appendStrBuf(c); - index++; - continue; - } else { - endTag = true; - // XXX replace contentModelElement with different - // type - tagName = endTagExpectation; - switch (c) { - case '\r': - silentCarriageReturn(); - clearStrBufAfterUse(); // strBuf not used - state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE - * FEED (LF) U+000C FORM FEED (FF) U+0020 - * SPACE If the current end tag token is an - * appropriate end tag token, then switch to - * the before attribute name state. - */ - clearStrBufAfterUse(); // strBuf not used - state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos); - continue stateloop; - case '/': - /* - * U+002F SOLIDUS (/) If the current end tag - * token is an appropriate end tag token, - * then switch to the self-closing start tag - * state. - */ - clearStrBufAfterUse(); // strBuf not used - state = transition(state, Tokenizer.SELF_CLOSING_START_TAG, reconsume, pos); - continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) If the - * current end tag token is an appropriate - * end tag token, then emit the current tag - * token and switch to the data state. - */ - clearStrBufAfterUse(); // strBuf not used - state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos); - if (shouldSuspend) { - break stateloop; - } - continue stateloop; - default: - /* - * Emit a U+003C LESS-THAN SIGN character - * token, a U+002F SOLIDUS character token, - * a character token for each of the - * characters in the temporary buffer (in - * the order they were added to the buffer), - * and reconsume the current input character - * in the RAWTEXT state. - */ - // [NOCPP[ - errWarnLtSlashInRcdata(); - // ]NOCPP] - tokenHandler.characters( - Tokenizer.LT_SOLIDUS, 0, 2); - emitStrBuf(); - cstart = pos; // don't drop the - // character - reconsume = true; - state = transition(state, returnState, reconsume, pos); - continue stateloop; - } - } - } - // XXX reorder point - // BEGIN HOTSPOT WORKAROUND - case BOGUS_COMMENT: - boguscommentloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - /* - * Consume every character up to and including the first - * U+003E GREATER-THAN SIGN character (>) or the end of - * the file (EOF), whichever comes first. Emit a comment - * token whose data is the concatenation of all the - * characters starting from and including the character - * that caused the state machine to switch into the - * bogus comment state, up to and including the - * character immediately before the last consumed - * character (i.e. up to the character just before the - * U+003E or EOF character). (If the comment was started - * by the end of the file (EOF), the token is empty.) - * - * Switch to the data state. - * - * If the end of the file was reached, reconsume the EOF - * character. - */ - switch (c) { - case '>': - emitComment(0, pos); - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '-': - appendStrBuf(c); - state = transition(state, Tokenizer.BOGUS_COMMENT_HYPHEN, reconsume, pos); - break boguscommentloop; - case '\r': - appendStrBufCarriageReturn(); - break stateloop; - case '\n': - appendStrBufLineFeed(); - continue; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - appendStrBuf(c); - continue; - } - } - // FALLTHRU DON'T REORDER - case BOGUS_COMMENT_HYPHEN: - boguscommenthyphenloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - switch (c) { - case '>': - // [NOCPP[ - maybeAppendSpaceToBogusComment(); - // ]NOCPP] - emitComment(0, pos); - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '-': - appendSecondHyphenToBogusComment(); - continue boguscommenthyphenloop; - case '\r': - appendStrBufCarriageReturn(); - state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos); - break stateloop; - case '\n': - appendStrBufLineFeed(); - state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos); - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - appendStrBuf(c); - state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos); - continue stateloop; - } - } - // XXX reorder point - case SCRIPT_DATA: - scriptdataloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - switch (c) { - case '<': - /* - * U+003C LESS-THAN SIGN (<) Switch to the - * script data less-than sign state. - */ - flushChars(buf, pos); - returnState = state; - state = transition(state, Tokenizer.SCRIPT_DATA_LESS_THAN_SIGN, reconsume, pos); - break scriptdataloop; // FALL THRU continue - // stateloop; - case '\u0000': - emitReplacementCharacter(buf, pos); - continue; - case '\r': - emitCarriageReturn(buf, pos); - break stateloop; - case '\n': - silentLineFeed(); - default: - /* - * Anything else Emit the current input - * character as a character token. Stay in the - * script data state. - */ - continue; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_LESS_THAN_SIGN: - scriptdatalessthansignloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - switch (c) { - case '/': - /* - * U+002F SOLIDUS (/) Set the temporary buffer - * to the empty string. Switch to the script - * data end tag open state. - */ - index = 0; - clearStrBufBeforeUse(); - state = transition(state, Tokenizer.NON_DATA_END_TAG_NAME, reconsume, pos); - continue stateloop; - case '!': - tokenHandler.characters(Tokenizer.LT_GT, 0, 1); - cstart = pos; - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPE_START, reconsume, pos); - break scriptdatalessthansignloop; // FALL THRU - // continue - // stateloop; - default: - /* - * Otherwise, emit a U+003C LESS-THAN SIGN - * character token - */ - tokenHandler.characters(Tokenizer.LT_GT, 0, 1); - /* - * and reconsume the current input character in - * the data state. - */ - cstart = pos; - reconsume = true; - state = transition(state, Tokenizer.SCRIPT_DATA, reconsume, pos); - continue stateloop; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_ESCAPE_START: - scriptdataescapestartloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '-': - /* - * U+002D HYPHEN-MINUS (-) Emit a U+002D - * HYPHEN-MINUS character token. Switch to the - * script data escape start dash state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPE_START_DASH, reconsume, pos); - break scriptdataescapestartloop; // FALL THRU - // continue - // stateloop; - default: - /* - * Anything else Reconsume the current input - * character in the script data state. - */ - reconsume = true; - state = transition(state, Tokenizer.SCRIPT_DATA, reconsume, pos); - continue stateloop; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_ESCAPE_START_DASH: - scriptdataescapestartdashloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '-': - /* - * U+002D HYPHEN-MINUS (-) Emit a U+002D - * HYPHEN-MINUS character token. Switch to the - * script data escaped dash dash state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED_DASH_DASH, reconsume, pos); - break scriptdataescapestartdashloop; - // continue stateloop; - default: - /* - * Anything else Reconsume the current input - * character in the script data state. - */ - reconsume = true; - state = transition(state, Tokenizer.SCRIPT_DATA, reconsume, pos); - continue stateloop; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_ESCAPED_DASH_DASH: - scriptdataescapeddashdashloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '-': - /* - * U+002D HYPHEN-MINUS (-) Emit a U+002D - * HYPHEN-MINUS character token. Stay in the - * script data escaped dash dash state. - */ - continue; - case '<': - /* - * U+003C LESS-THAN SIGN (<) Switch to the - * script data escaped less-than sign state. - */ - flushChars(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN, reconsume, pos); - continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit a U+003E - * GREATER-THAN SIGN character token. Switch to - * the script data state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA, reconsume, pos); - continue stateloop; - case '\u0000': - emitReplacementCharacter(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos); - break scriptdataescapeddashdashloop; - case '\r': - emitCarriageReturn(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - default: - /* - * Anything else Emit the current input - * character as a character token. Switch to the - * script data escaped state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos); - break scriptdataescapeddashdashloop; - // continue stateloop; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_ESCAPED: - scriptdataescapedloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - /* - * Consume the next input character: - */ - switch (c) { - case '-': - /* - * U+002D HYPHEN-MINUS (-) Emit a U+002D - * HYPHEN-MINUS character token. Switch to the - * script data escaped dash state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED_DASH, reconsume, pos); - break scriptdataescapedloop; // FALL THRU - // continue - // stateloop; - case '<': - /* - * U+003C LESS-THAN SIGN (<) Switch to the - * script data escaped less-than sign state. - */ - flushChars(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN, reconsume, pos); - continue stateloop; - case '\u0000': - emitReplacementCharacter(buf, pos); - continue; - case '\r': - emitCarriageReturn(buf, pos); - break stateloop; - case '\n': - silentLineFeed(); - default: - /* - * Anything else Emit the current input - * character as a character token. Stay in the - * script data escaped state. - */ - continue; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_ESCAPED_DASH: - scriptdataescapeddashloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '-': - /* - * U+002D HYPHEN-MINUS (-) Emit a U+002D - * HYPHEN-MINUS character token. Switch to the - * script data escaped dash dash state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED_DASH_DASH, reconsume, pos); - continue stateloop; - case '<': - /* - * U+003C LESS-THAN SIGN (<) Switch to the - * script data escaped less-than sign state. - */ - flushChars(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN, reconsume, pos); - break scriptdataescapeddashloop; - // continue stateloop; - case '\u0000': - emitReplacementCharacter(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos); - continue stateloop; - case '\r': - emitCarriageReturn(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - default: - /* - * Anything else Emit the current input - * character as a character token. Switch to the - * script data escaped state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos); - continue stateloop; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN: - scriptdataescapedlessthanloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '/': - /* - * U+002F SOLIDUS (/) Set the temporary buffer - * to the empty string. Switch to the script - * data escaped end tag open state. - */ - index = 0; - clearStrBufBeforeUse(); - returnState = Tokenizer.SCRIPT_DATA_ESCAPED; - state = transition(state, Tokenizer.NON_DATA_END_TAG_NAME, reconsume, pos); - continue stateloop; - case 'S': - case 's': - /* - * U+0041 LATIN CAPITAL LETTER A through to - * U+005A LATIN CAPITAL LETTER Z Emit a U+003C - * LESS-THAN SIGN character token and the - * current input character as a character token. - */ - tokenHandler.characters(Tokenizer.LT_GT, 0, 1); - cstart = pos; - index = 1; - /* - * Set the temporary buffer to the empty string. - * Append the lowercase version of the current - * input character (add 0x0020 to the - * character's code point) to the temporary - * buffer. Switch to the script data double - * escape start state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPE_START, reconsume, pos); - break scriptdataescapedlessthanloop; - // continue stateloop; - default: - /* - * Anything else Emit a U+003C LESS-THAN SIGN - * character token and reconsume the current - * input character in the script data escaped - * state. - */ - tokenHandler.characters(Tokenizer.LT_GT, 0, 1); - cstart = pos; - reconsume = true; - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos); - continue stateloop; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_DOUBLE_ESCAPE_START: - scriptdatadoubleescapestartloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - assert index > 0; - if (index < 6) { // SCRIPT_ARR.length - char folded = c; - if (c >= 'A' && c <= 'Z') { - folded += 0x20; - } - if (folded != Tokenizer.SCRIPT_ARR[index]) { - reconsume = true; - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos); - continue stateloop; - } - index++; - continue; - } - switch (c) { - case '\r': - emitCarriageReturn(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - case ' ': - case '\t': - case '\u000C': - case '/': - case '>': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE - * U+002F SOLIDUS (/) U+003E GREATER-THAN SIGN - * (>) Emit the current input character as a - * character token. If the temporary buffer is - * the string "script", then switch to the - * script data double escaped state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos); - break scriptdatadoubleescapestartloop; - // continue stateloop; - default: - /* - * Anything else Reconsume the current input - * character in the script data escaped state. - */ - reconsume = true; - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos); - continue stateloop; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_DOUBLE_ESCAPED: - scriptdatadoubleescapedloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - /* - * Consume the next input character: - */ - switch (c) { - case '-': - /* - * U+002D HYPHEN-MINUS (-) Emit a U+002D - * HYPHEN-MINUS character token. Switch to the - * script data double escaped dash state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED_DASH, reconsume, pos); - break scriptdatadoubleescapedloop; // FALL THRU - // continue - // stateloop; - case '<': - /* - * U+003C LESS-THAN SIGN (<) Emit a U+003C - * LESS-THAN SIGN character token. Switch to the - * script data double escaped less-than sign - * state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN, reconsume, pos); - continue stateloop; - case '\u0000': - emitReplacementCharacter(buf, pos); - continue; - case '\r': - emitCarriageReturn(buf, pos); - break stateloop; - case '\n': - silentLineFeed(); - default: - /* - * Anything else Emit the current input - * character as a character token. Stay in the - * script data double escaped state. - */ - continue; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_DOUBLE_ESCAPED_DASH: - scriptdatadoubleescapeddashloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '-': - /* - * U+002D HYPHEN-MINUS (-) Emit a U+002D - * HYPHEN-MINUS character token. Switch to the - * script data double escaped dash dash state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH, reconsume, pos); - break scriptdatadoubleescapeddashloop; - // continue stateloop; - case '<': - /* - * U+003C LESS-THAN SIGN (<) Emit a U+003C - * LESS-THAN SIGN character token. Switch to the - * script data double escaped less-than sign - * state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN, reconsume, pos); - continue stateloop; - case '\u0000': - emitReplacementCharacter(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos); - continue stateloop; - case '\r': - emitCarriageReturn(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - default: - /* - * Anything else Emit the current input - * character as a character token. Switch to the - * script data double escaped state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos); - continue stateloop; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH: - scriptdatadoubleescapeddashdashloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '-': - /* - * U+002D HYPHEN-MINUS (-) Emit a U+002D - * HYPHEN-MINUS character token. Stay in the - * script data double escaped dash dash state. - */ - continue; - case '<': - /* - * U+003C LESS-THAN SIGN (<) Emit a U+003C - * LESS-THAN SIGN character token. Switch to the - * script data double escaped less-than sign - * state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN, reconsume, pos); - break scriptdatadoubleescapeddashdashloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit a U+003E - * GREATER-THAN SIGN character token. Switch to - * the script data state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA, reconsume, pos); - continue stateloop; - case '\u0000': - emitReplacementCharacter(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos); - continue stateloop; - case '\r': - emitCarriageReturn(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - default: - /* - * Anything else Emit the current input - * character as a character token. Switch to the - * script data double escaped state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos); - continue stateloop; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN: - scriptdatadoubleescapedlessthanloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '/': - /* - * U+002F SOLIDUS (/) Emit a U+002F SOLIDUS - * character token. Set the temporary buffer to - * the empty string. Switch to the script data - * double escape end state. - */ - index = 0; - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPE_END, reconsume, pos); - break scriptdatadoubleescapedlessthanloop; - default: - /* - * Anything else Reconsume the current input - * character in the script data double escaped - * state. - */ - reconsume = true; - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos); - continue stateloop; - } - } - // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER - case SCRIPT_DATA_DOUBLE_ESCAPE_END: - scriptdatadoubleescapeendloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - if (index < 6) { // SCRIPT_ARR.length - char folded = c; - if (c >= 'A' && c <= 'Z') { - folded += 0x20; - } - if (folded != Tokenizer.SCRIPT_ARR[index]) { - reconsume = true; - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos); - continue stateloop; - } - index++; - continue; - } - switch (c) { - case '\r': - emitCarriageReturn(buf, pos); - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - case ' ': - case '\t': - case '\u000C': - case '/': - case '>': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE - * U+002F SOLIDUS (/) U+003E GREATER-THAN SIGN - * (>) Emit the current input character as a - * character token. If the temporary buffer is - * the string "script", then switch to the - * script data escaped state. - */ - state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos); - continue stateloop; - default: - /* - * Reconsume the current input character in the - * script data double escaped state. - */ - reconsume = true; - state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos); - continue stateloop; - } - } - // XXX reorder point - case MARKUP_DECLARATION_OCTYPE: - markupdeclarationdoctypeloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - if (index < 6) { // OCTYPE.length - char folded = c; - if (c >= 'A' && c <= 'Z') { - folded += 0x20; - } - if (folded == Tokenizer.OCTYPE[index]) { - appendStrBuf(c); - } else { - errBogusComment(); - reconsume = true; - state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos); - continue stateloop; - } - index++; - continue; - } else { - reconsume = true; - state = transition(state, Tokenizer.DOCTYPE, reconsume, pos); - break markupdeclarationdoctypeloop; - // continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case DOCTYPE: - doctypeloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - initDoctypeFields(); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - state = transition(state, Tokenizer.BEFORE_DOCTYPE_NAME, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE - * Switch to the before DOCTYPE name state. - */ - state = transition(state, Tokenizer.BEFORE_DOCTYPE_NAME, reconsume, pos); - break doctypeloop; - // continue stateloop; - default: - /* - * Anything else Parse error. - */ - errMissingSpaceBeforeDoctypeName(); - /* - * Reconsume the current character in the before - * DOCTYPE name state. - */ - reconsume = true; - state = transition(state, Tokenizer.BEFORE_DOCTYPE_NAME, reconsume, pos); - break doctypeloop; - // continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case BEFORE_DOCTYPE_NAME: - beforedoctypenameloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay - * in the before DOCTYPE name state. - */ - continue; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Parse error. - */ - errNamelessDoctype(); - /* - * Create a new DOCTYPE token. Set its - * force-quirks flag to on. - */ - forceQuirks = true; - /* - * Emit the token. - */ - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - if (c >= 'A' && c <= 'Z') { - /* - * U+0041 LATIN CAPITAL LETTER A through to - * U+005A LATIN CAPITAL LETTER Z Create a - * new DOCTYPE token. Set the token's name - * to the lowercase version of the input - * character (add 0x0020 to the character's - * code point). - */ - c += 0x20; - } - /* Anything else Create a new DOCTYPE token. */ - /* - * Set the token's name name to the current - * input character. - */ - clearStrBufBeforeUse(); - appendStrBuf(c); - /* - * Switch to the DOCTYPE name state. - */ - state = transition(state, Tokenizer.DOCTYPE_NAME, reconsume, pos); - break beforedoctypenameloop; - // continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case DOCTYPE_NAME: - doctypenameloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - strBufToDoctypeName(); - state = transition(state, Tokenizer.AFTER_DOCTYPE_NAME, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE - * Switch to the after DOCTYPE name state. - */ - strBufToDoctypeName(); - state = transition(state, Tokenizer.AFTER_DOCTYPE_NAME, reconsume, pos); - break doctypenameloop; - // continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the current - * DOCTYPE token. - */ - strBufToDoctypeName(); - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * U+0041 LATIN CAPITAL LETTER A through to - * U+005A LATIN CAPITAL LETTER Z Append the - * lowercase version of the input character (add - * 0x0020 to the character's code point) to the - * current DOCTYPE token's name. - */ - if (c >= 'A' && c <= 'Z') { - c += 0x0020; - } - /* - * Anything else Append the current input - * character to the current DOCTYPE token's - * name. - */ - appendStrBuf(c); - /* - * Stay in the DOCTYPE name state. - */ - continue; - } - } - // FALLTHRU DON'T REORDER - case AFTER_DOCTYPE_NAME: - afterdoctypenameloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay - * in the after DOCTYPE name state. - */ - continue; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the current - * DOCTYPE token. - */ - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case 'p': - case 'P': - index = 0; - state = transition(state, Tokenizer.DOCTYPE_UBLIC, reconsume, pos); - break afterdoctypenameloop; - // continue stateloop; - case 's': - case 'S': - index = 0; - state = transition(state, Tokenizer.DOCTYPE_YSTEM, reconsume, pos); - continue stateloop; - default: - /* - * Otherwise, this is the parse error. - */ - bogusDoctype(); - - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - // done by bogusDoctype(); - /* - * Switch to the bogus DOCTYPE state. - */ - state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos); - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case DOCTYPE_UBLIC: - doctypeublicloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * If the six characters starting from the current input - * character are an ASCII case-insensitive match for the - * word "PUBLIC", then consume those characters and - * switch to the before DOCTYPE public identifier state. - */ - if (index < 5) { // UBLIC.length - char folded = c; - if (c >= 'A' && c <= 'Z') { - folded += 0x20; - } - if (folded != Tokenizer.UBLIC[index]) { - bogusDoctype(); - // forceQuirks = true; - reconsume = true; - state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos); - continue stateloop; - } - index++; - continue; - } else { - reconsume = true; - state = transition(state, Tokenizer.AFTER_DOCTYPE_PUBLIC_KEYWORD, reconsume, pos); - break doctypeublicloop; - // continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case AFTER_DOCTYPE_PUBLIC_KEYWORD: - afterdoctypepublickeywordloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - state = transition(state, Tokenizer.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE - * Switch to the before DOCTYPE public - * identifier state. - */ - state = transition(state, Tokenizer.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER, reconsume, pos); - break afterdoctypepublickeywordloop; - // FALL THROUGH continue stateloop - case '"': - /* - * U+0022 QUOTATION MARK (") Parse Error. - */ - errNoSpaceBetweenDoctypePublicKeywordAndQuote(); - /* - * Set the DOCTYPE token's public identifier to - * the empty string (not missing), - */ - clearStrBufBeforeUse(); - /* - * then switch to the DOCTYPE public identifier - * (double-quoted) state. - */ - state = transition(state, Tokenizer.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos); - continue stateloop; - case '\'': - /* - * U+0027 APOSTROPHE (') Parse Error. - */ - errNoSpaceBetweenDoctypePublicKeywordAndQuote(); - /* - * Set the DOCTYPE token's public identifier to - * the empty string (not missing), - */ - clearStrBufBeforeUse(); - /* - * then switch to the DOCTYPE public identifier - * (single-quoted) state. - */ - state = transition(state, Tokenizer.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED, reconsume, pos); - continue stateloop; - case '>': - /* U+003E GREATER-THAN SIGN (>) Parse error. */ - errExpectedPublicId(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - default: - bogusDoctype(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - // done by bogusDoctype(); - /* - * Switch to the bogus DOCTYPE state. - */ - state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos); - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case BEFORE_DOCTYPE_PUBLIC_IDENTIFIER: - beforedoctypepublicidentifierloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay - * in the before DOCTYPE public identifier - * state. - */ - continue; - case '"': - /* - * U+0022 QUOTATION MARK (") Set the DOCTYPE - * token's public identifier to the empty string - * (not missing), - */ - clearStrBufBeforeUse(); - /* - * then switch to the DOCTYPE public identifier - * (double-quoted) state. - */ - state = transition(state, Tokenizer.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos); - break beforedoctypepublicidentifierloop; - // continue stateloop; - case '\'': - /* - * U+0027 APOSTROPHE (') Set the DOCTYPE token's - * public identifier to the empty string (not - * missing), - */ - clearStrBufBeforeUse(); - /* - * then switch to the DOCTYPE public identifier - * (single-quoted) state. - */ - state = transition(state, Tokenizer.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED, reconsume, pos); - continue stateloop; - case '>': - /* U+003E GREATER-THAN SIGN (>) Parse error. */ - errExpectedPublicId(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - default: - bogusDoctype(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - // done by bogusDoctype(); - /* - * Switch to the bogus DOCTYPE state. - */ - state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos); - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED: - doctypepublicidentifierdoublequotedloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '"': - /* - * U+0022 QUOTATION MARK (") Switch to the after - * DOCTYPE public identifier state. - */ - publicIdentifier = strBufToString(); - state = transition(state, Tokenizer.AFTER_DOCTYPE_PUBLIC_IDENTIFIER, reconsume, pos); - break doctypepublicidentifierdoublequotedloop; - // continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Parse error. - */ - errGtInPublicId(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - publicIdentifier = strBufToString(); - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '\r': - appendStrBufCarriageReturn(); - break stateloop; - case '\n': - appendStrBufLineFeed(); - continue; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * Anything else Append the current input - * character to the current DOCTYPE token's - * public identifier. - */ - appendStrBuf(c); - /* - * Stay in the DOCTYPE public identifier - * (double-quoted) state. - */ - continue; - } - } - // FALLTHRU DON'T REORDER - case AFTER_DOCTYPE_PUBLIC_IDENTIFIER: - afterdoctypepublicidentifierloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - state = transition(state, Tokenizer.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE - * Switch to the between DOCTYPE public and - * system identifiers state. - */ - state = transition(state, Tokenizer.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS, reconsume, pos); - break afterdoctypepublicidentifierloop; - // continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the current - * DOCTYPE token. - */ - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '"': - /* - * U+0022 QUOTATION MARK (") Parse error. - */ - errNoSpaceBetweenPublicAndSystemIds(); - /* - * Set the DOCTYPE token's system identifier to - * the empty string (not missing), - */ - clearStrBufBeforeUse(); - /* - * then switch to the DOCTYPE system identifier - * (double-quoted) state. - */ - state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos); - continue stateloop; - case '\'': - /* - * U+0027 APOSTROPHE (') Parse error. - */ - errNoSpaceBetweenPublicAndSystemIds(); - /* - * Set the DOCTYPE token's system identifier to - * the empty string (not missing), - */ - clearStrBufBeforeUse(); - /* - * then switch to the DOCTYPE system identifier - * (single-quoted) state. - */ - state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED, reconsume, pos); - continue stateloop; - default: - bogusDoctype(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - // done by bogusDoctype(); - /* - * Switch to the bogus DOCTYPE state. - */ - state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos); - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS: - betweendoctypepublicandsystemidentifiersloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay - * in the between DOCTYPE public and system - * identifiers state. - */ - continue; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the current - * DOCTYPE token. - */ - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '"': - /* - * U+0022 QUOTATION MARK (") Set the DOCTYPE - * token's system identifier to the empty string - * (not missing), - */ - clearStrBufBeforeUse(); - /* - * then switch to the DOCTYPE system identifier - * (double-quoted) state. - */ - state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos); - break betweendoctypepublicandsystemidentifiersloop; - // continue stateloop; - case '\'': - /* - * U+0027 APOSTROPHE (') Set the DOCTYPE token's - * system identifier to the empty string (not - * missing), - */ - clearStrBufBeforeUse(); - /* - * then switch to the DOCTYPE system identifier - * (single-quoted) state. - */ - state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED, reconsume, pos); - continue stateloop; - default: - bogusDoctype(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - // done by bogusDoctype(); - /* - * Switch to the bogus DOCTYPE state. - */ - state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos); - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED: - doctypesystemidentifierdoublequotedloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '"': - /* - * U+0022 QUOTATION MARK (") Switch to the after - * DOCTYPE system identifier state. - */ - systemIdentifier = strBufToString(); - state = transition(state, Tokenizer.AFTER_DOCTYPE_SYSTEM_IDENTIFIER, reconsume, pos); - continue stateloop; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Parse error. - */ - errGtInSystemId(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - systemIdentifier = strBufToString(); - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '\r': - appendStrBufCarriageReturn(); - break stateloop; - case '\n': - appendStrBufLineFeed(); - continue; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * Anything else Append the current input - * character to the current DOCTYPE token's - * system identifier. - */ - appendStrBuf(c); - /* - * Stay in the DOCTYPE system identifier - * (double-quoted) state. - */ - continue; - } - } - // FALLTHRU DON'T REORDER - case AFTER_DOCTYPE_SYSTEM_IDENTIFIER: - afterdoctypesystemidentifierloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay - * in the after DOCTYPE system identifier state. - */ - continue; - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit the current - * DOCTYPE token. - */ - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - default: - /* - * Switch to the bogus DOCTYPE state. (This does - * not set the DOCTYPE token's force-quirks flag - * to on.) - */ - bogusDoctypeWithoutQuirks(); - state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos); - break afterdoctypesystemidentifierloop; - // continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case BOGUS_DOCTYPE: - for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - /* - * Consume the next input character: - */ - switch (c) { - case '>': - /* - * U+003E GREATER-THAN SIGN (>) Emit that - * DOCTYPE token. - */ - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '\r': - silentCarriageReturn(); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - default: - /* - * Anything else Stay in the bogus DOCTYPE - * state. - */ - continue; - } - } - // XXX reorder point - case DOCTYPE_YSTEM: - doctypeystemloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Otherwise, if the six characters starting from the - * current input character are an ASCII case-insensitive - * match for the word "SYSTEM", then consume those - * characters and switch to the before DOCTYPE system - * identifier state. - */ - if (index < 5) { // YSTEM.length - char folded = c; - if (c >= 'A' && c <= 'Z') { - folded += 0x20; - } - if (folded != Tokenizer.YSTEM[index]) { - bogusDoctype(); - reconsume = true; - state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos); - continue stateloop; - } - index++; - continue stateloop; - } else { - reconsume = true; - state = transition(state, Tokenizer.AFTER_DOCTYPE_SYSTEM_KEYWORD, reconsume, pos); - break doctypeystemloop; - // continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case AFTER_DOCTYPE_SYSTEM_KEYWORD: - afterdoctypesystemkeywordloop: for (;;) { - if (reconsume) { - reconsume = false; - } else { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - } - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - state = transition(state, Tokenizer.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER, reconsume, pos); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE - * Switch to the before DOCTYPE public - * identifier state. - */ - state = transition(state, Tokenizer.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER, reconsume, pos); - break afterdoctypesystemkeywordloop; - // FALL THROUGH continue stateloop - case '"': - /* - * U+0022 QUOTATION MARK (") Parse Error. - */ - errNoSpaceBetweenDoctypeSystemKeywordAndQuote(); - /* - * Set the DOCTYPE token's system identifier to - * the empty string (not missing), - */ - clearStrBufBeforeUse(); - /* - * then switch to the DOCTYPE public identifier - * (double-quoted) state. - */ - state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos); - continue stateloop; - case '\'': - /* - * U+0027 APOSTROPHE (') Parse Error. - */ - errNoSpaceBetweenDoctypeSystemKeywordAndQuote(); - /* - * Set the DOCTYPE token's public identifier to - * the empty string (not missing), - */ - clearStrBufBeforeUse(); - /* - * then switch to the DOCTYPE public identifier - * (single-quoted) state. - */ - state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED, reconsume, pos); - continue stateloop; - case '>': - /* U+003E GREATER-THAN SIGN (>) Parse error. */ - errExpectedPublicId(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - default: - bogusDoctype(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - // done by bogusDoctype(); - /* - * Switch to the bogus DOCTYPE state. - */ - state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos); - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case BEFORE_DOCTYPE_SYSTEM_IDENTIFIER: - beforedoctypesystemidentifierloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\r': - silentCarriageReturn(); - break stateloop; - case '\n': - silentLineFeed(); - // fall thru - case ' ': - case '\t': - case '\u000C': - /* - * U+0009 CHARACTER TABULATION U+000A LINE FEED - * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay - * in the before DOCTYPE system identifier - * state. - */ - continue; - case '"': - /* - * U+0022 QUOTATION MARK (") Set the DOCTYPE - * token's system identifier to the empty string - * (not missing), - */ - clearStrBufBeforeUse(); - /* - * then switch to the DOCTYPE system identifier - * (double-quoted) state. - */ - state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos); - continue stateloop; - case '\'': - /* - * U+0027 APOSTROPHE (') Set the DOCTYPE token's - * system identifier to the empty string (not - * missing), - */ - clearStrBufBeforeUse(); - /* - * then switch to the DOCTYPE system identifier - * (single-quoted) state. - */ - state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED, reconsume, pos); - break beforedoctypesystemidentifierloop; - // continue stateloop; - case '>': - /* U+003E GREATER-THAN SIGN (>) Parse error. */ - errExpectedSystemId(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - default: - bogusDoctype(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - // done by bogusDoctype(); - /* - * Switch to the bogus DOCTYPE state. - */ - state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos); - continue stateloop; - } - } - // FALLTHRU DON'T REORDER - case DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED: - for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\'': - /* - * U+0027 APOSTROPHE (') Switch to the after - * DOCTYPE system identifier state. - */ - systemIdentifier = strBufToString(); - state = transition(state, Tokenizer.AFTER_DOCTYPE_SYSTEM_IDENTIFIER, reconsume, pos); - continue stateloop; - case '>': - errGtInSystemId(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - systemIdentifier = strBufToString(); - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '\r': - appendStrBufCarriageReturn(); - break stateloop; - case '\n': - appendStrBufLineFeed(); - continue; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * Anything else Append the current input - * character to the current DOCTYPE token's - * system identifier. - */ - appendStrBuf(c); - /* - * Stay in the DOCTYPE system identifier - * (double-quoted) state. - */ - continue; - } - } - // XXX reorder point - case DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED: - for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - /* - * Consume the next input character: - */ - switch (c) { - case '\'': - /* - * U+0027 APOSTROPHE (') Switch to the after - * DOCTYPE public identifier state. - */ - publicIdentifier = strBufToString(); - state = transition(state, Tokenizer.AFTER_DOCTYPE_PUBLIC_IDENTIFIER, reconsume, pos); - continue stateloop; - case '>': - errGtInPublicId(); - /* - * Set the DOCTYPE token's force-quirks flag to - * on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - publicIdentifier = strBufToString(); - emitDoctypeToken(pos); - /* - * Switch to the data state. - */ - state = transition(state, Tokenizer.DATA, reconsume, pos); - continue stateloop; - case '\r': - appendStrBufCarriageReturn(); - break stateloop; - case '\n': - appendStrBufLineFeed(); - continue; - case '\u0000': - c = '\uFFFD'; - // fall thru - default: - /* - * Anything else Append the current input - * character to the current DOCTYPE token's - * public identifier. - */ - appendStrBuf(c); - /* - * Stay in the DOCTYPE public identifier - * (single-quoted) state. - */ - continue; - } - } - // XXX reorder point - case PROCESSING_INSTRUCTION: - processinginstructionloop: for (;;) { - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - switch (c) { - case '?': - state = transition( - state, - Tokenizer.PROCESSING_INSTRUCTION_QUESTION_MARK, - reconsume, pos); - break processinginstructionloop; - // continue stateloop; - default: - continue; - } - } - case PROCESSING_INSTRUCTION_QUESTION_MARK: - if (++pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - switch (c) { - case '>': - state = transition(state, Tokenizer.DATA, - reconsume, pos); - continue stateloop; - default: - state = transition(state, - Tokenizer.PROCESSING_INSTRUCTION, - reconsume, pos); - continue stateloop; - } - // END HOTSPOT WORKAROUND - } - } - flushChars(buf, pos); - /* - * if (prevCR && pos != endPos) { // why is this needed? pos--; col--; } - */ - // Save locals - stateSave = state; - returnStateSave = returnState; - return pos; - } - - // HOTSPOT WORKAROUND INSERTION POINT - - // [NOCPP[ - - protected int transition(int from, int to, boolean reconsume, int pos) throws SAXException { - return to; - } - - // ]NOCPP] - - private void initDoctypeFields() { - // Discard the characters "DOCTYPE" accumulated as a potential bogus - // comment into strBuf. - clearStrBufAfterUse(); - doctypeName = ""; - if (systemIdentifier != null) { - Portability.releaseString(systemIdentifier); - systemIdentifier = null; - } - if (publicIdentifier != null) { - Portability.releaseString(publicIdentifier); - publicIdentifier = null; - } - forceQuirks = false; - } - - @Inline private void adjustDoubleHyphenAndAppendToStrBufCarriageReturn() - throws SAXException { - silentCarriageReturn(); - adjustDoubleHyphenAndAppendToStrBufAndErr('\n'); - } - - @Inline private void adjustDoubleHyphenAndAppendToStrBufLineFeed() - throws SAXException { - silentLineFeed(); - adjustDoubleHyphenAndAppendToStrBufAndErr('\n'); - } - - @Inline private void appendStrBufLineFeed() { - silentLineFeed(); - appendStrBuf('\n'); - } - - @Inline private void appendStrBufCarriageReturn() { - silentCarriageReturn(); - appendStrBuf('\n'); - } - - @Inline protected void silentCarriageReturn() { - ++line; - lastCR = true; - } - - @Inline protected void silentLineFeed() { - ++line; - } - - private void emitCarriageReturn(@NoLength char[] buf, int pos) - throws SAXException { - silentCarriageReturn(); - flushChars(buf, pos); - tokenHandler.characters(Tokenizer.LF, 0, 1); - cstart = Integer.MAX_VALUE; - } - - private void emitReplacementCharacter(@NoLength char[] buf, int pos) - throws SAXException { - flushChars(buf, pos); - tokenHandler.zeroOriginatingReplacementCharacter(); - cstart = pos + 1; - } - - private void emitPlaintextReplacementCharacter(@NoLength char[] buf, int pos) - throws SAXException { - flushChars(buf, pos); - tokenHandler.characters(REPLACEMENT_CHARACTER, 0, 1); - cstart = pos + 1; - } - - private void setAdditionalAndRememberAmpersandLocation(char add) { - additional = add; - // [NOCPP[ - ampersandLocation = new LocatorImpl(this); - // ]NOCPP] - } - - private void bogusDoctype() throws SAXException { - errBogusDoctype(); - forceQuirks = true; - } - - private void bogusDoctypeWithoutQuirks() throws SAXException { - errBogusDoctype(); - forceQuirks = false; - } - - private void handleNcrValue(int returnState) throws SAXException { - /* - * If one or more characters match the range, then take them all and - * interpret the string of characters as a number (either hexadecimal or - * decimal as appropriate). - */ - if (value <= 0xFFFF) { - if (value >= 0x80 && value <= 0x9f) { - /* - * If that number is one of the numbers in the first column of - * the following table, then this is a parse error. - */ - errNcrInC1Range(); - /* - * Find the row with that number in the first column, and return - * a character token for the Unicode character given in the - * second column of that row. - */ - @NoLength char[] val = NamedCharacters.WINDOWS_1252[value - 0x80]; - emitOrAppendOne(val, returnState); - // [NOCPP[ - } else if (value == 0xC - && contentSpacePolicy != XmlViolationPolicy.ALLOW) { - if (contentSpacePolicy == XmlViolationPolicy.ALTER_INFOSET) { - emitOrAppendOne(Tokenizer.SPACE, returnState); - } else if (contentSpacePolicy == XmlViolationPolicy.FATAL) { - fatal("A character reference expanded to a form feed which is not legal XML 1.0 white space."); - } - // ]NOCPP] - } else if (value == 0x0) { - errNcrZero(); - emitOrAppendOne(Tokenizer.REPLACEMENT_CHARACTER, returnState); - } else if ((value & 0xF800) == 0xD800) { - errNcrSurrogate(); - emitOrAppendOne(Tokenizer.REPLACEMENT_CHARACTER, returnState); - } else { - /* - * Otherwise, return a character token for the Unicode character - * whose code point is that number. - */ - char ch = (char) value; - // [NOCPP[ - if (value == 0x0D) { - errNcrCr(); - } else if ((value <= 0x0008) || (value == 0x000B) - || (value >= 0x000E && value <= 0x001F)) { - ch = errNcrControlChar(ch); - } else if (value >= 0xFDD0 && value <= 0xFDEF) { - errNcrUnassigned(); - } else if ((value & 0xFFFE) == 0xFFFE) { - ch = errNcrNonCharacter(ch); - } else if (value >= 0x007F && value <= 0x009F) { - errNcrControlChar(); - } else { - maybeWarnPrivateUse(ch); - } - // ]NOCPP] - bmpChar[0] = ch; - emitOrAppendOne(bmpChar, returnState); - } - } else if (value <= 0x10FFFF) { - // [NOCPP[ - maybeWarnPrivateUseAstral(); - if ((value & 0xFFFE) == 0xFFFE) { - errAstralNonCharacter(value); - } - // ]NOCPP] - astralChar[0] = (char) (Tokenizer.LEAD_OFFSET + (value >> 10)); - astralChar[1] = (char) (0xDC00 + (value & 0x3FF)); - emitOrAppendTwo(astralChar, returnState); - } else { - errNcrOutOfRange(); - emitOrAppendOne(Tokenizer.REPLACEMENT_CHARACTER, returnState); - } - } - - public void eof() throws SAXException { - int state = stateSave; - int returnState = returnStateSave; - - eofloop: for (;;) { - switch (state) { - case SCRIPT_DATA_LESS_THAN_SIGN: - case SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN: - /* - * Otherwise, emit a U+003C LESS-THAN SIGN character token - */ - tokenHandler.characters(Tokenizer.LT_GT, 0, 1); - /* - * and reconsume the current input character in the data - * state. - */ - break eofloop; - case TAG_OPEN: - /* - * The behavior of this state depends on the content model - * flag. - */ - /* - * Anything else Parse error. - */ - errEofAfterLt(); - /* - * Emit a U+003C LESS-THAN SIGN character token - */ - tokenHandler.characters(Tokenizer.LT_GT, 0, 1); - /* - * and reconsume the current input character in the data - * state. - */ - break eofloop; - case RAWTEXT_RCDATA_LESS_THAN_SIGN: - /* - * Emit a U+003C LESS-THAN SIGN character token - */ - tokenHandler.characters(Tokenizer.LT_GT, 0, 1); - /* - * and reconsume the current input character in the RCDATA - * state. - */ - break eofloop; - case NON_DATA_END_TAG_NAME: - /* - * Emit a U+003C LESS-THAN SIGN character token, a U+002F - * SOLIDUS character token, - */ - tokenHandler.characters(Tokenizer.LT_SOLIDUS, 0, 2); - /* - * a character token for each of the characters in the - * temporary buffer (in the order they were added to the - * buffer), - */ - emitStrBuf(); - /* - * and reconsume the current input character in the RCDATA - * state. - */ - break eofloop; - case CLOSE_TAG_OPEN: - /* EOF Parse error. */ - errEofAfterLt(); - /* - * Emit a U+003C LESS-THAN SIGN character token and a U+002F - * SOLIDUS character token. - */ - tokenHandler.characters(Tokenizer.LT_SOLIDUS, 0, 2); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case TAG_NAME: - /* - * EOF Parse error. - */ - errEofInTagName(); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case BEFORE_ATTRIBUTE_NAME: - case AFTER_ATTRIBUTE_VALUE_QUOTED: - case SELF_CLOSING_START_TAG: - /* EOF Parse error. */ - errEofWithoutGt(); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case ATTRIBUTE_NAME: - /* - * EOF Parse error. - */ - errEofInAttributeName(); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case AFTER_ATTRIBUTE_NAME: - case BEFORE_ATTRIBUTE_VALUE: - /* EOF Parse error. */ - errEofWithoutGt(); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case ATTRIBUTE_VALUE_DOUBLE_QUOTED: - case ATTRIBUTE_VALUE_SINGLE_QUOTED: - case ATTRIBUTE_VALUE_UNQUOTED: - /* EOF Parse error. */ - errEofInAttributeValue(); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case BOGUS_COMMENT: - emitComment(0, 0); - break eofloop; - case BOGUS_COMMENT_HYPHEN: - // [NOCPP[ - maybeAppendSpaceToBogusComment(); - // ]NOCPP] - emitComment(0, 0); - break eofloop; - case MARKUP_DECLARATION_OPEN: - errBogusComment(); - emitComment(0, 0); - break eofloop; - case MARKUP_DECLARATION_HYPHEN: - errBogusComment(); - emitComment(0, 0); - break eofloop; - case MARKUP_DECLARATION_OCTYPE: - if (index < 6) { - errBogusComment(); - emitComment(0, 0); - } else { - /* EOF Parse error. */ - errEofInDoctype(); - /* - * Create a new DOCTYPE token. Set its force-quirks flag - * to on. - */ - doctypeName = ""; - if (systemIdentifier != null) { - Portability.releaseString(systemIdentifier); - systemIdentifier = null; - } - if (publicIdentifier != null) { - Portability.releaseString(publicIdentifier); - publicIdentifier = null; - } - forceQuirks = true; - /* - * Emit the token. - */ - emitDoctypeToken(0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - } - break eofloop; - case COMMENT_START: - case COMMENT: - /* - * EOF Parse error. - */ - errEofInComment(); - /* Emit the comment token. */ - emitComment(0, 0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case COMMENT_END: - errEofInComment(); - /* Emit the comment token. */ - emitComment(2, 0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case COMMENT_END_DASH: - case COMMENT_START_DASH: - errEofInComment(); - /* Emit the comment token. */ - emitComment(1, 0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case COMMENT_END_BANG: - errEofInComment(); - /* Emit the comment token. */ - emitComment(3, 0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case DOCTYPE: - case BEFORE_DOCTYPE_NAME: - errEofInDoctype(); - /* - * Create a new DOCTYPE token. Set its force-quirks flag to - * on. - */ - forceQuirks = true; - /* - * Emit the token. - */ - emitDoctypeToken(0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case DOCTYPE_NAME: - errEofInDoctype(); - strBufToDoctypeName(); - /* - * Set the DOCTYPE token's force-quirks flag to on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - emitDoctypeToken(0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case DOCTYPE_UBLIC: - case DOCTYPE_YSTEM: - case AFTER_DOCTYPE_NAME: - case AFTER_DOCTYPE_PUBLIC_KEYWORD: - case AFTER_DOCTYPE_SYSTEM_KEYWORD: - case BEFORE_DOCTYPE_PUBLIC_IDENTIFIER: - errEofInDoctype(); - /* - * Set the DOCTYPE token's force-quirks flag to on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - emitDoctypeToken(0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED: - case DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED: - /* EOF Parse error. */ - errEofInPublicId(); - /* - * Set the DOCTYPE token's force-quirks flag to on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - publicIdentifier = strBufToString(); - emitDoctypeToken(0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case AFTER_DOCTYPE_PUBLIC_IDENTIFIER: - case BEFORE_DOCTYPE_SYSTEM_IDENTIFIER: - case BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS: - errEofInDoctype(); - /* - * Set the DOCTYPE token's force-quirks flag to on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - emitDoctypeToken(0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED: - case DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED: - /* EOF Parse error. */ - errEofInSystemId(); - /* - * Set the DOCTYPE token's force-quirks flag to on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - systemIdentifier = strBufToString(); - emitDoctypeToken(0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case AFTER_DOCTYPE_SYSTEM_IDENTIFIER: - errEofInDoctype(); - /* - * Set the DOCTYPE token's force-quirks flag to on. - */ - forceQuirks = true; - /* - * Emit that DOCTYPE token. - */ - emitDoctypeToken(0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case BOGUS_DOCTYPE: - /* - * Emit that DOCTYPE token. - */ - emitDoctypeToken(0); - /* - * Reconsume the EOF character in the data state. - */ - break eofloop; - case CONSUME_CHARACTER_REFERENCE: - /* - * Unlike the definition is the spec, this state does not - * return a value and never requires the caller to - * backtrack. This state takes care of emitting characters - * or appending to the current attribute value. It also - * takes care of that in the case when consuming the entity - * fails. - */ - /* - * This section defines how to consume an entity. This - * definition is used when parsing entities in text and in - * attributes. - * - * The behavior depends on the identity of the next - * character (the one immediately after the U+0026 AMPERSAND - * character): - */ - - emitOrAppendCharRefBuf(returnState); - state = returnState; - continue; - case CHARACTER_REFERENCE_HILO_LOOKUP: - errNoNamedCharacterMatch(); - emitOrAppendCharRefBuf(returnState); - state = returnState; - continue; - case CHARACTER_REFERENCE_TAIL: - outer: for (;;) { - char c = '\u0000'; - entCol++; - /* - * Consume the maximum number of characters possible, - * with the consumed characters matching one of the - * identifiers in the first column of the named - * character references table (in a case-sensitive - * manner). - */ - hiloop: for (;;) { - if (hi == -1) { - break hiloop; - } - if (entCol == NamedCharacters.NAMES[hi].length()) { - break hiloop; - } - if (entCol > NamedCharacters.NAMES[hi].length()) { - break outer; - } else if (c < NamedCharacters.NAMES[hi].charAt(entCol)) { - hi--; - } else { - break hiloop; - } - } - - loloop: for (;;) { - if (hi < lo) { - break outer; - } - if (entCol == NamedCharacters.NAMES[lo].length()) { - candidate = lo; - charRefBufMark = charRefBufLen; - lo++; - } else if (entCol > NamedCharacters.NAMES[lo].length()) { - break outer; - } else if (c > NamedCharacters.NAMES[lo].charAt(entCol)) { - lo++; - } else { - break loloop; - } - } - if (hi < lo) { - break outer; - } - continue; - } - - if (candidate == -1) { - /* - * If no match can be made, then this is a parse error. - */ - errNoNamedCharacterMatch(); - emitOrAppendCharRefBuf(returnState); - state = returnState; - continue eofloop; - } else { - @Const @CharacterName String candidateName = NamedCharacters.NAMES[candidate]; - if (candidateName.length() == 0 - || candidateName.charAt(candidateName.length() - 1) != ';') { - /* - * If the last character matched is not a U+003B - * SEMICOLON (;), there is a parse error. - */ - if ((returnState & DATA_AND_RCDATA_MASK) != 0) { - /* - * If the entity is being consumed as part of an - * attribute, and the last character matched is - * not a U+003B SEMICOLON (;), - */ - char ch; - if (charRefBufMark == charRefBufLen) { - ch = '\u0000'; - } else { - ch = charRefBuf[charRefBufMark]; - } - if ((ch >= '0' && ch <= '9') - || (ch >= 'A' && ch <= 'Z') - || (ch >= 'a' && ch <= 'z')) { - /* - * and the next character is in the range - * U+0030 DIGIT ZERO to U+0039 DIGIT NINE, - * U+0041 LATIN CAPITAL LETTER A to U+005A - * LATIN CAPITAL LETTER Z, or U+0061 LATIN - * SMALL LETTER A to U+007A LATIN SMALL - * LETTER Z, then, for historical reasons, - * all the characters that were matched - * after the U+0026 AMPERSAND (&) must be - * unconsumed, and nothing is returned. - */ - errNoNamedCharacterMatch(); - appendCharRefBufToStrBuf(); - state = returnState; - continue eofloop; - } - } - if ((returnState & DATA_AND_RCDATA_MASK) != 0) { - errUnescapedAmpersandInterpretedAsCharacterReference(); - } else { - errNotSemicolonTerminated(); - } - } - - /* - * Otherwise, return a character token for the character - * corresponding to the entity name (as given by the - * second column of the named character references - * table). - */ - @Const @NoLength char[] val = NamedCharacters.VALUES[candidate]; - if ( - // [NOCPP[ - val.length == 1 - // ]NOCPP] - // CPPONLY: val[1] == 0 - ) { - emitOrAppendOne(val, returnState); - } else { - emitOrAppendTwo(val, returnState); - } - // this is so complicated! - if (charRefBufMark < charRefBufLen) { - if ((returnState & DATA_AND_RCDATA_MASK) != 0) { - appendStrBuf(charRefBuf, charRefBufMark, - charRefBufLen - charRefBufMark); - } else { - tokenHandler.characters(charRefBuf, charRefBufMark, - charRefBufLen - charRefBufMark); - } - } - charRefBufLen = 0; - state = returnState; - continue eofloop; - /* - * If the markup contains I'm ¬it; I tell you, the - * entity is parsed as "not", as in, I'm ¬it; I tell - * you. But if the markup was I'm ∉ I tell you, - * the entity would be parsed as "notin;", resulting in - * I'm ∉ I tell you. - */ - } - case CONSUME_NCR: - case DECIMAL_NRC_LOOP: - case HEX_NCR_LOOP: - /* - * If no characters match the range, then don't consume any - * characters (and unconsume the U+0023 NUMBER SIGN - * character and, if appropriate, the X character). This is - * a parse error; nothing is returned. - * - * Otherwise, if the next character is a U+003B SEMICOLON, - * consume that too. If it isn't, there is a parse error. - */ - if (!seenDigits) { - errNoDigitsInNCR(); - emitOrAppendCharRefBuf(returnState); - state = returnState; - continue; - } else { - errCharRefLacksSemicolon(); - } - // WARNING previous state sets reconsume - handleNcrValue(returnState); - state = returnState; - continue; - case CDATA_RSQB: - tokenHandler.characters(Tokenizer.RSQB_RSQB, 0, 1); - break eofloop; - case CDATA_RSQB_RSQB: - tokenHandler.characters(Tokenizer.RSQB_RSQB, 0, 2); - break eofloop; - case DATA: - default: - break eofloop; - } - } - // case DATA: - /* - * EOF Emit an end-of-file token. - */ - tokenHandler.eof(); - return; - } - - private void emitDoctypeToken(int pos) throws SAXException { - cstart = pos + 1; - tokenHandler.doctype(doctypeName, publicIdentifier, systemIdentifier, - forceQuirks); - // It is OK and sufficient to release these here, since - // there's no way out of the doctype states than through paths - // that call this method. - doctypeName = null; - Portability.releaseString(publicIdentifier); - publicIdentifier = null; - Portability.releaseString(systemIdentifier); - systemIdentifier = null; - } - - @Inline protected char checkChar(@NoLength char[] buf, int pos) - throws SAXException { - return buf[pos]; - } - - public boolean internalEncodingDeclaration(String internalCharset) - throws SAXException { - if (encodingDeclarationHandler != null) { - return encodingDeclarationHandler.internalEncodingDeclaration(internalCharset); - } - return false; - } - - /** - * @param val - * @throws SAXException - */ - private void emitOrAppendTwo(@Const @NoLength char[] val, int returnState) - throws SAXException { - if ((returnState & DATA_AND_RCDATA_MASK) != 0) { - appendStrBuf(val[0]); - appendStrBuf(val[1]); - } else { - tokenHandler.characters(val, 0, 2); - } - } - - private void emitOrAppendOne(@Const @NoLength char[] val, int returnState) - throws SAXException { - if ((returnState & DATA_AND_RCDATA_MASK) != 0) { - appendStrBuf(val[0]); - } else { - tokenHandler.characters(val, 0, 1); - } - } - - public void end() throws SAXException { - strBuf = null; - doctypeName = null; - if (systemIdentifier != null) { - Portability.releaseString(systemIdentifier); - systemIdentifier = null; - } - if (publicIdentifier != null) { - Portability.releaseString(publicIdentifier); - publicIdentifier = null; - } - if (tagName != null) { - tagName.release(); - tagName = null; - } - if (attributeName != null) { - attributeName.release(); - attributeName = null; - } - tokenHandler.endTokenization(); - if (attributes != null) { - // [NOCPP[ - attributes = null; - // ]NOCPP] - // CPPONLY: attributes.clear(mappingLangToXmlLang); - } - } - - public void requestSuspension() { - shouldSuspend = true; - } - - // [NOCPP[ - - public void becomeConfident() { - confident = true; - } - - /** - * Returns the nextCharOnNewLine. - * - * @return the nextCharOnNewLine - */ - public boolean isNextCharOnNewLine() { - return false; - } - - public boolean isPrevCR() { - return lastCR; - } - - /** - * Returns the line. - * - * @return the line - */ - public int getLine() { - return -1; - } - - /** - * Returns the col. - * - * @return the col - */ - public int getCol() { - return -1; - } - - // ]NOCPP] - - public boolean isInDataState() { - return (stateSave == DATA); - } - - public void resetToDataState() { - clearStrBufAfterUse(); - charRefBufLen = 0; - stateSave = Tokenizer.DATA; - // line = 1; XXX line numbers - lastCR = false; - index = 0; - forceQuirks = false; - additional = '\u0000'; - entCol = -1; - firstCharKey = -1; - lo = 0; - hi = 0; // will always be overwritten before use anyway - candidate = -1; - charRefBufMark = 0; - value = 0; - seenDigits = false; - endTag = false; - shouldSuspend = false; - initDoctypeFields(); - if (tagName != null) { - tagName.release(); - tagName = null; - } - if (attributeName != null) { - attributeName.release(); - attributeName = null; - } - if (newAttributesEachTime) { - if (attributes != null) { - Portability.delete(attributes); - attributes = null; - } - } - } - - public void loadState(Tokenizer other) throws SAXException { - strBufLen = other.strBufLen; - if (strBufLen > strBuf.length) { - strBuf = new char[strBufLen]; - } - System.arraycopy(other.strBuf, 0, strBuf, 0, strBufLen); - - charRefBufLen = other.charRefBufLen; - System.arraycopy(other.charRefBuf, 0, charRefBuf, 0, charRefBufLen); - - stateSave = other.stateSave; - returnStateSave = other.returnStateSave; - endTagExpectation = other.endTagExpectation; - endTagExpectationAsArray = other.endTagExpectationAsArray; - // line = 1; XXX line numbers - lastCR = other.lastCR; - index = other.index; - forceQuirks = other.forceQuirks; - additional = other.additional; - entCol = other.entCol; - firstCharKey = other.firstCharKey; - lo = other.lo; - hi = other.hi; - candidate = other.candidate; - charRefBufMark = other.charRefBufMark; - value = other.value; - seenDigits = other.seenDigits; - endTag = other.endTag; - shouldSuspend = false; - - if (other.doctypeName == null) { - doctypeName = null; - } else { - doctypeName = Portability.newLocalFromLocal(other.doctypeName, - interner); - } - - Portability.releaseString(systemIdentifier); - if (other.systemIdentifier == null) { - systemIdentifier = null; - } else { - systemIdentifier = Portability.newStringFromString(other.systemIdentifier); - } - - Portability.releaseString(publicIdentifier); - if (other.publicIdentifier == null) { - publicIdentifier = null; - } else { - publicIdentifier = Portability.newStringFromString(other.publicIdentifier); - } - - if (tagName != null) { - tagName.release(); - } - if (other.tagName == null) { - tagName = null; - } else { - tagName = other.tagName.cloneElementName(interner); - } - - if (attributeName != null) { - attributeName.release(); - } - if (other.attributeName == null) { - attributeName = null; - } else { - attributeName = other.attributeName.cloneAttributeName(interner); - } - - Portability.delete(attributes); - if (other.attributes == null) { - attributes = null; - } else { - attributes = other.attributes.cloneAttributes(interner); - } - } - - public void initializeWithoutStarting() throws SAXException { - confident = false; - strBuf = null; - line = 1; - // CPPONLY: attributeLine = 1; - // [NOCPP[ - html4 = false; - metaBoundaryPassed = false; - wantsComments = tokenHandler.wantsComments(); - if (!newAttributesEachTime) { - attributes = new HtmlAttributes(mappingLangToXmlLang); - } - // ]NOCPP] - resetToDataState(); - } - - protected void errGarbageAfterLtSlash() throws SAXException { - } - - protected void errLtSlashGt() throws SAXException { - } - - protected void errWarnLtSlashInRcdata() throws SAXException { - } - - protected void errHtml4LtSlashInRcdata(char folded) throws SAXException { - } - - protected void errCharRefLacksSemicolon() throws SAXException { - } - - protected void errNoDigitsInNCR() throws SAXException { - } - - protected void errGtInSystemId() throws SAXException { - } - - protected void errGtInPublicId() throws SAXException { - } - - protected void errNamelessDoctype() throws SAXException { - } - - protected void errConsecutiveHyphens() throws SAXException { - } - - protected void errPrematureEndOfComment() throws SAXException { - } - - protected void errBogusComment() throws SAXException { - } - - protected void errUnquotedAttributeValOrNull(char c) throws SAXException { - } - - protected void errSlashNotFollowedByGt() throws SAXException { - } - - protected void errHtml4XmlVoidSyntax() throws SAXException { - } - - protected void errNoSpaceBetweenAttributes() throws SAXException { - } - - protected void errHtml4NonNameInUnquotedAttribute(char c) - throws SAXException { - } - - protected void errLtOrEqualsOrGraveInUnquotedAttributeOrNull(char c) - throws SAXException { - } - - protected void errAttributeValueMissing() throws SAXException { - } - - protected void errBadCharBeforeAttributeNameOrNull(char c) - throws SAXException { - } - - protected void errEqualsSignBeforeAttributeName() throws SAXException { - } - - protected void errBadCharAfterLt(char c) throws SAXException { - } - - protected void errLtGt() throws SAXException { - } - - protected void errProcessingInstruction() throws SAXException { - } - - protected void errUnescapedAmpersandInterpretedAsCharacterReference() - throws SAXException { - } - - protected void errNotSemicolonTerminated() throws SAXException { - } - - protected void errNoNamedCharacterMatch() throws SAXException { - } - - protected void errQuoteBeforeAttributeName(char c) throws SAXException { - } - - protected void errQuoteOrLtInAttributeNameOrNull(char c) - throws SAXException { - } - - protected void errExpectedPublicId() throws SAXException { - } - - protected void errBogusDoctype() throws SAXException { - } - - protected void maybeWarnPrivateUseAstral() throws SAXException { - } - - protected void maybeWarnPrivateUse(char ch) throws SAXException { - } - - protected void maybeErrAttributesOnEndTag(HtmlAttributes attrs) - throws SAXException { - } - - protected void maybeErrSlashInEndTag(boolean selfClosing) - throws SAXException { - } - - protected char errNcrNonCharacter(char ch) throws SAXException { - return ch; - } - - protected void errAstralNonCharacter(int ch) throws SAXException { - } - - protected void errNcrSurrogate() throws SAXException { - } - - protected char errNcrControlChar(char ch) throws SAXException { - return ch; - } - - protected void errNcrCr() throws SAXException { - } - - protected void errNcrInC1Range() throws SAXException { - } - - protected void errEofInPublicId() throws SAXException { - } - - protected void errEofInComment() throws SAXException { - } - - protected void errEofInDoctype() throws SAXException { - } - - protected void errEofInAttributeValue() throws SAXException { - } - - protected void errEofInAttributeName() throws SAXException { - } - - protected void errEofWithoutGt() throws SAXException { - } - - protected void errEofInTagName() throws SAXException { - } - - protected void errEofInEndTag() throws SAXException { - } - - protected void errEofAfterLt() throws SAXException { - } - - protected void errNcrOutOfRange() throws SAXException { - } - - protected void errNcrUnassigned() throws SAXException { - } - - protected void errDuplicateAttribute() throws SAXException { - } - - protected void errEofInSystemId() throws SAXException { - } - - protected void errExpectedSystemId() throws SAXException { - } - - protected void errMissingSpaceBeforeDoctypeName() throws SAXException { - } - - protected void errHyphenHyphenBang() throws SAXException { - } - - protected void errNcrControlChar() throws SAXException { - } - - protected void errNcrZero() throws SAXException { - } - - protected void errNoSpaceBetweenDoctypeSystemKeywordAndQuote() - throws SAXException { - } - - protected void errNoSpaceBetweenPublicAndSystemIds() throws SAXException { - } - - protected void errNoSpaceBetweenDoctypePublicKeywordAndQuote() - throws SAXException { - } - - protected void noteAttributeWithoutValue() throws SAXException { - } - - protected void noteUnquotedAttributeValue() throws SAXException { - } - - /** - * Sets the encodingDeclarationHandler. - * - * @param encodingDeclarationHandler - * the encodingDeclarationHandler to set - */ - public void setEncodingDeclarationHandler( - EncodingDeclarationHandler encodingDeclarationHandler) { - this.encodingDeclarationHandler = encodingDeclarationHandler; - } - - void destructor() { - // The translator will write refcount tracing stuff here - Portability.delete(attributes); - attributes = null; - } - - // [NOCPP[ - - /** - * Sets an offset to be added to the position reported to - * TransitionHandler. - * - * @param offset the offset - */ - public void setTransitionBaseOffset(int offset) { - - } - - // ]NOCPP] - -} diff --git a/parser/html/javasrc/TreeBuilder.java b/parser/html/javasrc/TreeBuilder.java deleted file mode 100644 index 58074086d..000000000 --- a/parser/html/javasrc/TreeBuilder.java +++ /dev/null @@ -1,6552 +0,0 @@ -/* - * Copyright (c) 2007 Henri Sivonen - * Copyright (c) 2007-2015 Mozilla Foundation - * Copyright (c) 2018-2019 Moonchild Productions - * Portions of comments Copyright 2004-2008 Apple Computer, Inc., Mozilla - * Foundation, and Opera Software ASA. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -/* - * The comments following this one that use the same comment syntax as this - * comment are quotes from the WHATWG HTML 5 spec as of 27 June 2007 - * amended as of June 28 2007. - * That document came with this statement: - * "© Copyright 2004-2007 Apple Computer, Inc., Mozilla Foundation, and - * Opera Software ASA. You are granted a license to use, reproduce and - * create derivative works of this document." - */ - -package nu.validator.htmlparser.impl; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import org.xml.sax.ErrorHandler; -import org.xml.sax.Locator; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; - -import nu.validator.htmlparser.annotation.Auto; -import nu.validator.htmlparser.annotation.Const; -import nu.validator.htmlparser.annotation.IdType; -import nu.validator.htmlparser.annotation.Inline; -import nu.validator.htmlparser.annotation.Literal; -import nu.validator.htmlparser.annotation.Local; -import nu.validator.htmlparser.annotation.NoLength; -import nu.validator.htmlparser.annotation.NsUri; -import nu.validator.htmlparser.common.DoctypeExpectation; -import nu.validator.htmlparser.common.DocumentMode; -import nu.validator.htmlparser.common.DocumentModeHandler; -import nu.validator.htmlparser.common.Interner; -import nu.validator.htmlparser.common.TokenHandler; -import nu.validator.htmlparser.common.XmlViolationPolicy; - -public abstract class TreeBuilder implements TokenHandler, - TreeBuilderState { - - /** - * Array version of U+FFFD. - */ - private static final @NoLength char[] REPLACEMENT_CHARACTER = { '\uFFFD' }; - - // Start dispatch groups - - final static int OTHER = 0; - - final static int A = 1; - - final static int BASE = 2; - - final static int BODY = 3; - - final static int BR = 4; - - final static int BUTTON = 5; - - final static int CAPTION = 6; - - final static int COL = 7; - - final static int COLGROUP = 8; - - final static int FORM = 9; - - final static int FRAME = 10; - - final static int FRAMESET = 11; - - final static int IMAGE = 12; - - final static int INPUT = 13; - - final static int ISINDEX = 14; - - final static int LI = 15; - - final static int LINK_OR_BASEFONT_OR_BGSOUND = 16; - - final static int MATH = 17; - - final static int META = 18; - - final static int SVG = 19; - - final static int HEAD = 20; - - final static int HR = 22; - - final static int HTML = 23; - - final static int NOBR = 24; - - final static int NOFRAMES = 25; - - final static int NOSCRIPT = 26; - - final static int OPTGROUP = 27; - - final static int OPTION = 28; - - final static int P = 29; - - final static int PLAINTEXT = 30; - - final static int SCRIPT = 31; - - final static int SELECT = 32; - - final static int STYLE = 33; - - final static int TABLE = 34; - - final static int TEXTAREA = 35; - - final static int TITLE = 36; - - final static int TR = 37; - - final static int XMP = 38; - - final static int TBODY_OR_THEAD_OR_TFOOT = 39; - - final static int TD_OR_TH = 40; - - final static int DD_OR_DT = 41; - - final static int H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 = 42; - - final static int MARQUEE_OR_APPLET = 43; - - final static int PRE_OR_LISTING = 44; - - final static int B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U = 45; - - final static int UL_OR_OL_OR_DL = 46; - - final static int IFRAME = 47; - - final static int EMBED = 48; - - final static int AREA_OR_WBR = 49; - - final static int DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU = 50; - - final static int ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY = 51; - - final static int RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR = 52; - - final static int RB_OR_RTC = 53; - - final static int PARAM_OR_SOURCE_OR_TRACK = 55; - - final static int MGLYPH_OR_MALIGNMARK = 56; - - final static int MI_MO_MN_MS_MTEXT = 57; - - final static int ANNOTATION_XML = 58; - - final static int FOREIGNOBJECT_OR_DESC = 59; - - final static int NOEMBED = 60; - - final static int FIELDSET = 61; - - final static int OUTPUT = 62; - - final static int OBJECT = 63; - - final static int FONT = 64; - - final static int KEYGEN = 65; - - final static int MENUITEM = 66; - - final static int TEMPLATE = 67; - - final static int IMG = 68; - - final static int RT_OR_RP = 69; - - // start insertion modes - - private static final int IN_ROW = 0; - - private static final int IN_TABLE_BODY = 1; - - private static final int IN_TABLE = 2; - - private static final int IN_CAPTION = 3; - - private static final int IN_CELL = 4; - - private static final int FRAMESET_OK = 5; - - private static final int IN_BODY = 6; - - private static final int IN_HEAD = 7; - - private static final int IN_HEAD_NOSCRIPT = 8; - - // no fall-through - - private static final int IN_COLUMN_GROUP = 9; - - // no fall-through - - private static final int IN_SELECT_IN_TABLE = 10; - - private static final int IN_SELECT = 11; - - // no fall-through - - private static final int AFTER_BODY = 12; - - // no fall-through - - private static final int IN_FRAMESET = 13; - - private static final int AFTER_FRAMESET = 14; - - // no fall-through - - private static final int INITIAL = 15; - - // could add fall-through - - private static final int BEFORE_HTML = 16; - - // could add fall-through - - private static final int BEFORE_HEAD = 17; - - // no fall-through - - private static final int AFTER_HEAD = 18; - - // no fall-through - - private static final int AFTER_AFTER_BODY = 19; - - // no fall-through - - private static final int AFTER_AFTER_FRAMESET = 20; - - // no fall-through - - private static final int TEXT = 21; - - private static final int IN_TEMPLATE = 22; - - // start charset states - - private static final int CHARSET_INITIAL = 0; - - private static final int CHARSET_C = 1; - - private static final int CHARSET_H = 2; - - private static final int CHARSET_A = 3; - - private static final int CHARSET_R = 4; - - private static final int CHARSET_S = 5; - - private static final int CHARSET_E = 6; - - private static final int CHARSET_T = 7; - - private static final int CHARSET_EQUALS = 8; - - private static final int CHARSET_SINGLE_QUOTED = 9; - - private static final int CHARSET_DOUBLE_QUOTED = 10; - - private static final int CHARSET_UNQUOTED = 11; - - // end pseudo enums - - // [NOCPP[ - - private final static String[] HTML4_PUBLIC_IDS = { - "-//W3C//DTD HTML 4.0 Frameset//EN", - "-//W3C//DTD HTML 4.0 Transitional//EN", - "-//W3C//DTD HTML 4.0//EN", "-//W3C//DTD HTML 4.01 Frameset//EN", - "-//W3C//DTD HTML 4.01 Transitional//EN", - "-//W3C//DTD HTML 4.01//EN" }; - - // ]NOCPP] - - @Literal private final static String[] QUIRKY_PUBLIC_IDS = { - "+//silmaril//dtd html pro v0r11 19970101//", - "-//advasoft ltd//dtd html 3.0 aswedit + extensions//", - "-//as//dtd html 3.0 aswedit + extensions//", - "-//ietf//dtd html 2.0 level 1//", - "-//ietf//dtd html 2.0 level 2//", - "-//ietf//dtd html 2.0 strict level 1//", - "-//ietf//dtd html 2.0 strict level 2//", - "-//ietf//dtd html 2.0 strict//", - "-//ietf//dtd html 2.0//", - "-//ietf//dtd html 2.1e//", - "-//ietf//dtd html 3.0//", - "-//ietf//dtd html 3.2 final//", - "-//ietf//dtd html 3.2//", - "-//ietf//dtd html 3//", - "-//ietf//dtd html level 0//", - "-//ietf//dtd html level 1//", - "-//ietf//dtd html level 2//", - "-//ietf//dtd html level 3//", - "-//ietf//dtd html strict level 0//", - "-//ietf//dtd html strict level 1//", - "-//ietf//dtd html strict level 2//", - "-//ietf//dtd html strict level 3//", - "-//ietf//dtd html strict//", - "-//ietf//dtd html//", - "-//metrius//dtd metrius presentational//", - "-//microsoft//dtd internet explorer 2.0 html strict//", - "-//microsoft//dtd internet explorer 2.0 html//", - "-//microsoft//dtd internet explorer 2.0 tables//", - "-//microsoft//dtd internet explorer 3.0 html strict//", - "-//microsoft//dtd internet explorer 3.0 html//", - "-//microsoft//dtd internet explorer 3.0 tables//", - "-//netscape comm. corp.//dtd html//", - "-//netscape comm. corp.//dtd strict html//", - "-//o'reilly and associates//dtd html 2.0//", - "-//o'reilly and associates//dtd html extended 1.0//", - "-//o'reilly and associates//dtd html extended relaxed 1.0//", - "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//", - "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//", - "-//spyglass//dtd html 2.0 extended//", - "-//sq//dtd html 2.0 hotmetal + extensions//", - "-//sun microsystems corp.//dtd hotjava html//", - "-//sun microsystems corp.//dtd hotjava strict html//", - "-//w3c//dtd html 3 1995-03-24//", "-//w3c//dtd html 3.2 draft//", - "-//w3c//dtd html 3.2 final//", "-//w3c//dtd html 3.2//", - "-//w3c//dtd html 3.2s draft//", "-//w3c//dtd html 4.0 frameset//", - "-//w3c//dtd html 4.0 transitional//", - "-//w3c//dtd html experimental 19960712//", - "-//w3c//dtd html experimental 970421//", "-//w3c//dtd w3 html//", - "-//w3o//dtd w3 html 3.0//", "-//webtechs//dtd mozilla html 2.0//", - "-//webtechs//dtd mozilla html//" }; - - private static final int NOT_FOUND_ON_STACK = Integer.MAX_VALUE; - - // [NOCPP[ - - private static final @Local String HTML_LOCAL = "html"; - - // ]NOCPP] - - private int mode = INITIAL; - - private int originalMode = INITIAL; - - /** - * Used only when moving back to IN_BODY. - */ - private boolean framesetOk = true; - - protected Tokenizer tokenizer; - - // [NOCPP[ - - protected ErrorHandler errorHandler; - - private DocumentModeHandler documentModeHandler; - - private DoctypeExpectation doctypeExpectation = DoctypeExpectation.HTML; - - private LocatorImpl firstCommentLocation; - - // ]NOCPP] - - private boolean scriptingEnabled = false; - - private boolean needToDropLF; - - // [NOCPP[ - - private boolean wantingComments; - - // ]NOCPP] - - private boolean fragment; - - private @Local String contextName; - - private @NsUri String contextNamespace; - - private T contextNode; - - /** - * Stack of template insertion modes - */ - private @Auto int[] templateModeStack; - - /** - * Current template mode stack pointer. - */ - private int templateModePtr = -1; - - private @Auto StackNode[] stack; - - private int currentPtr = -1; - - private @Auto StackNode[] listOfActiveFormattingElements; - - private int listPtr = -1; - - private T formPointer; - - private T headPointer; - - /** - * Used to work around Gecko limitations. Not used in Java. - */ - private T deepTreeSurrogateParent; - - protected @Auto char[] charBuffer; - - protected int charBufferLen = 0; - - private boolean quirks = false; - - private boolean isSrcdocDocument = false; - - // [NOCPP[ - - private boolean reportingDoctype = true; - - private XmlViolationPolicy namePolicy = XmlViolationPolicy.ALTER_INFOSET; - - private final Map idLocations = new HashMap(); - - private boolean html4; - - // ]NOCPP] - - protected TreeBuilder() { - fragment = false; - } - - /** - * Reports an condition that would make the infoset incompatible with XML - * 1.0 as fatal. - * - * @throws SAXException - * @throws SAXParseException - */ - protected void fatal() throws SAXException { - } - - // [NOCPP[ - - protected final void fatal(Exception e) throws SAXException { - SAXParseException spe = new SAXParseException(e.getMessage(), - tokenizer, e); - if (errorHandler != null) { - errorHandler.fatalError(spe); - } - throw spe; - } - - final void fatal(String s) throws SAXException { - SAXParseException spe = new SAXParseException(s, tokenizer); - if (errorHandler != null) { - errorHandler.fatalError(spe); - } - throw spe; - } - - /** - * Reports a Parse Error. - * - * @param message - * the message - * @throws SAXException - */ - final void err(String message) throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck(message); - } - - /** - * Reports a Parse Error without checking if an error handler is present. - * - * @param message - * the message - * @throws SAXException - */ - final void errNoCheck(String message) throws SAXException { - SAXParseException spe = new SAXParseException(message, tokenizer); - errorHandler.error(spe); - } - - private void errListUnclosedStartTags(int eltPos) throws SAXException { - if (currentPtr != -1) { - for (int i = currentPtr; i > eltPos; i--) { - reportUnclosedElementNameAndLocation(i); - } - } - } - - /** - * Reports the name and location of an unclosed element. - * - * @throws SAXException - */ - private final void reportUnclosedElementNameAndLocation(int pos) throws SAXException { - StackNode node = stack[pos]; - if (node.isOptionalEndTag()) { - return; - } - TaintableLocatorImpl locator = node.getLocator(); - if (locator.isTainted()) { - return; - } - locator.markTainted(); - SAXParseException spe = new SAXParseException( - "Unclosed element \u201C" + node.popName + "\u201D.", locator); - errorHandler.error(spe); - } - - /** - * Reports a warning - * - * @param message - * the message - * @throws SAXException - */ - final void warn(String message) throws SAXException { - if (errorHandler == null) { - return; - } - SAXParseException spe = new SAXParseException(message, tokenizer); - errorHandler.warning(spe); - } - - /** - * Reports a warning with an explicit locator - * - * @param message - * the message - * @throws SAXException - */ - final void warn(String message, Locator locator) throws SAXException { - if (errorHandler == null) { - return; - } - SAXParseException spe = new SAXParseException(message, locator); - errorHandler.warning(spe); - } - - // ]NOCPP] - - @SuppressWarnings("unchecked") public final void startTokenization(Tokenizer self) throws SAXException { - tokenizer = self; - stack = new StackNode[64]; - templateModeStack = new int[64]; - listOfActiveFormattingElements = new StackNode[64]; - needToDropLF = false; - originalMode = INITIAL; - templateModePtr = -1; - currentPtr = -1; - listPtr = -1; - formPointer = null; - headPointer = null; - deepTreeSurrogateParent = null; - // [NOCPP[ - html4 = false; - idLocations.clear(); - wantingComments = wantsComments(); - firstCommentLocation = null; - // ]NOCPP] - start(fragment); - charBufferLen = 0; - charBuffer = null; - framesetOk = true; - if (fragment) { - T elt; - if (contextNode != null) { - elt = contextNode; - } else { - elt = createHtmlElementSetAsRoot(tokenizer.emptyAttributes()); - } - // When the context node is not in the HTML namespace, contrary - // to the spec, the first node on the stack is not set to "html" - // in the HTML namespace. Instead, it is set to a node that has - // the characteristics of the appropriate "adjusted current node". - // This way, there is no need to perform "adjusted current node" - // checks during tree construction. Instead, it's sufficient to - // just look at the current node. However, this also means that it - // is not safe to treat "html" in the HTML namespace as a sentinel - // that ends stack popping. Instead, stack popping loops that are - // meant not to pop the first element on the stack need to check - // for currentPos becoming zero. - if (contextNamespace == "http://www.w3.org/2000/svg") { - ElementName elementName = ElementName.SVG; - if ("title" == contextName || "desc" == contextName - || "foreignObject" == contextName) { - // These elements are all alike and we don't care about - // the exact name. - elementName = ElementName.FOREIGNOBJECT; - } - // This is the SVG variant of the StackNode constructor. - StackNode node = new StackNode(elementName, - elementName.camelCaseName, elt - // [NOCPP[ - , errorHandler == null ? null - : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); - currentPtr++; - stack[currentPtr] = node; - tokenizer.setState(Tokenizer.DATA); - // The frameset-ok flag is set even though never - // ends up being allowed as HTML frameset in the fragment case. - mode = FRAMESET_OK; - } else if (contextNamespace == "http://www.w3.org/1998/Math/MathML") { - ElementName elementName = ElementName.MATH; - if ("mi" == contextName || "mo" == contextName - || "mn" == contextName || "ms" == contextName - || "mtext" == contextName) { - // These elements are all alike and we don't care about - // the exact name. - elementName = ElementName.MTEXT; - } else if ("annotation-xml" == contextName) { - elementName = ElementName.ANNOTATION_XML; - // Blink does not check the encoding attribute of the - // annotation-xml element innerHTML is being set on. - // Let's do the same at least until - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=26783 - // is resolved. - } - // This is the MathML variant of the StackNode constructor. - StackNode node = new StackNode(elementName, elt, - elementName.name, false - // [NOCPP[ - , errorHandler == null ? null - : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); - currentPtr++; - stack[currentPtr] = node; - tokenizer.setState(Tokenizer.DATA); - // The frameset-ok flag is set even though never - // ends up being allowed as HTML frameset in the fragment case. - mode = FRAMESET_OK; - } else { // html - StackNode node = new StackNode(ElementName.HTML, elt - // [NOCPP[ - , errorHandler == null ? null - : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); - currentPtr++; - stack[currentPtr] = node; - if ("template" == contextName) { - pushTemplateMode(IN_TEMPLATE); - } - resetTheInsertionMode(); - formPointer = getFormPointerForContext(contextNode); - if ("title" == contextName || "textarea" == contextName) { - tokenizer.setState(Tokenizer.RCDATA); - } else if ("style" == contextName || "xmp" == contextName - || "iframe" == contextName || "noembed" == contextName - || "noframes" == contextName - || (scriptingEnabled && "noscript" == contextName)) { - tokenizer.setState(Tokenizer.RAWTEXT); - } else if ("plaintext" == contextName) { - tokenizer.setState(Tokenizer.PLAINTEXT); - } else if ("script" == contextName) { - tokenizer.setState(Tokenizer.SCRIPT_DATA); - } else { - tokenizer.setState(Tokenizer.DATA); - } - } - contextName = null; - contextNode = null; - } else { - mode = INITIAL; - // If we are viewing XML source, put a foreign element permanently - // on the stack so that cdataSectionAllowed() returns true. - // CPPONLY: if (tokenizer.isViewingXmlSource()) { - // CPPONLY: T elt = createElement("http://www.w3.org/2000/svg", - // CPPONLY: "svg", - // CPPONLY: tokenizer.emptyAttributes(), null); - // CPPONLY: StackNode node = new StackNode(ElementName.SVG, - // CPPONLY: "svg", - // CPPONLY: elt); - // CPPONLY: currentPtr++; - // CPPONLY: stack[currentPtr] = node; - // CPPONLY: } - } - } - - public final void doctype(@Local String name, String publicIdentifier, - String systemIdentifier, boolean forceQuirks) throws SAXException { - needToDropLF = false; - if (!isInForeign() && mode == INITIAL) { - // [NOCPP[ - if (reportingDoctype) { - // ]NOCPP] - String emptyString = Portability.newEmptyString(); - appendDoctypeToDocument(name == null ? "" : name, - publicIdentifier == null ? emptyString - : publicIdentifier, - systemIdentifier == null ? emptyString - : systemIdentifier); - Portability.releaseString(emptyString); - // [NOCPP[ - } - switch (doctypeExpectation) { - case HTML: - // ]NOCPP] - if (isQuirky(name, publicIdentifier, systemIdentifier, - forceQuirks)) { - errQuirkyDoctype(); - documentModeInternal(DocumentMode.QUIRKS_MODE, - publicIdentifier, systemIdentifier, false); - } else if (isAlmostStandards(publicIdentifier, - systemIdentifier)) { - // [NOCPP[ - if (firstCommentLocation != null) { - warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", - firstCommentLocation); - } - // ]NOCPP] - errAlmostStandardsDoctype(); - documentModeInternal( - DocumentMode.ALMOST_STANDARDS_MODE, - publicIdentifier, systemIdentifier, false); - } else { - // [NOCPP[ - if (firstCommentLocation != null) { - warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", - firstCommentLocation); - } - if ((Portability.literalEqualsString( - "-//W3C//DTD HTML 4.0//EN", publicIdentifier) && (systemIdentifier == null || Portability.literalEqualsString( - "http://www.w3.org/TR/REC-html40/strict.dtd", - systemIdentifier))) - || (Portability.literalEqualsString( - "-//W3C//DTD HTML 4.01//EN", - publicIdentifier) && (systemIdentifier == null || Portability.literalEqualsString( - "http://www.w3.org/TR/html4/strict.dtd", - systemIdentifier))) - || (Portability.literalEqualsString( - "-//W3C//DTD XHTML 1.0 Strict//EN", - publicIdentifier) && Portability.literalEqualsString( - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd", - systemIdentifier)) - || (Portability.literalEqualsString( - "-//W3C//DTD XHTML 1.1//EN", - publicIdentifier) && Portability.literalEqualsString( - "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd", - systemIdentifier)) - - ) { - warn("Obsolete doctype. Expected \u201C\u201D."); - } else if (!((systemIdentifier == null || Portability.literalEqualsString( - "about:legacy-compat", systemIdentifier)) && publicIdentifier == null)) { - err("Legacy doctype. Expected \u201C\u201D."); - } - // ]NOCPP] - documentModeInternal(DocumentMode.STANDARDS_MODE, - publicIdentifier, systemIdentifier, false); - } - // [NOCPP[ - break; - case HTML401_STRICT: - html4 = true; - tokenizer.turnOnAdditionalHtml4Errors(); - if (isQuirky(name, publicIdentifier, systemIdentifier, - forceQuirks)) { - err("Quirky doctype. Expected \u201C\u201D."); - documentModeInternal(DocumentMode.QUIRKS_MODE, - publicIdentifier, systemIdentifier, true); - } else if (isAlmostStandards(publicIdentifier, - systemIdentifier)) { - if (firstCommentLocation != null) { - warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", - firstCommentLocation); - } - err("Almost standards mode doctype. Expected \u201C\u201D."); - documentModeInternal( - DocumentMode.ALMOST_STANDARDS_MODE, - publicIdentifier, systemIdentifier, true); - } else { - if (firstCommentLocation != null) { - warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", - firstCommentLocation); - } - if ("-//W3C//DTD HTML 4.01//EN".equals(publicIdentifier)) { - if (!"http://www.w3.org/TR/html4/strict.dtd".equals(systemIdentifier)) { - warn("The doctype did not contain the system identifier prescribed by the HTML 4.01 specification. Expected \u201C\u201D."); - } - } else { - err("The doctype was not the HTML 4.01 Strict doctype. Expected \u201C\u201D."); - } - documentModeInternal(DocumentMode.STANDARDS_MODE, - publicIdentifier, systemIdentifier, true); - } - break; - case HTML401_TRANSITIONAL: - html4 = true; - tokenizer.turnOnAdditionalHtml4Errors(); - if (isQuirky(name, publicIdentifier, systemIdentifier, - forceQuirks)) { - err("Quirky doctype. Expected \u201C\u201D."); - documentModeInternal(DocumentMode.QUIRKS_MODE, - publicIdentifier, systemIdentifier, true); - } else if (isAlmostStandards(publicIdentifier, - systemIdentifier)) { - if (firstCommentLocation != null) { - warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", - firstCommentLocation); - } - if ("-//W3C//DTD HTML 4.01 Transitional//EN".equals(publicIdentifier) - && systemIdentifier != null) { - if (!"http://www.w3.org/TR/html4/loose.dtd".equals(systemIdentifier)) { - warn("The doctype did not contain the system identifier prescribed by the HTML 4.01 specification. Expected \u201C\u201D."); - } - } else { - err("The doctype was not a non-quirky HTML 4.01 Transitional doctype. Expected \u201C\u201D."); - } - documentModeInternal( - DocumentMode.ALMOST_STANDARDS_MODE, - publicIdentifier, systemIdentifier, true); - } else { - if (firstCommentLocation != null) { - warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", - firstCommentLocation); - } - err("The doctype was not the HTML 4.01 Transitional doctype. Expected \u201C\u201D."); - documentModeInternal(DocumentMode.STANDARDS_MODE, - publicIdentifier, systemIdentifier, true); - } - break; - case AUTO: - html4 = isHtml4Doctype(publicIdentifier); - if (html4) { - tokenizer.turnOnAdditionalHtml4Errors(); - } - if (isQuirky(name, publicIdentifier, systemIdentifier, - forceQuirks)) { - err("Quirky doctype. Expected e.g. \u201C\u201D."); - documentModeInternal(DocumentMode.QUIRKS_MODE, - publicIdentifier, systemIdentifier, html4); - } else if (isAlmostStandards(publicIdentifier, - systemIdentifier)) { - if (firstCommentLocation != null) { - warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", - firstCommentLocation); - } - if ("-//W3C//DTD HTML 4.01 Transitional//EN".equals(publicIdentifier)) { - if (!"http://www.w3.org/TR/html4/loose.dtd".equals(systemIdentifier)) { - warn("The doctype did not contain the system identifier prescribed by the HTML 4.01 specification. Expected \u201C\u201D."); - } - } else { - err("Almost standards mode doctype. Expected e.g. \u201C\u201D."); - } - documentModeInternal( - DocumentMode.ALMOST_STANDARDS_MODE, - publicIdentifier, systemIdentifier, html4); - } else { - if (firstCommentLocation != null) { - warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", - firstCommentLocation); - } - if ("-//W3C//DTD HTML 4.01//EN".equals(publicIdentifier)) { - if (!"http://www.w3.org/TR/html4/strict.dtd".equals(systemIdentifier)) { - warn("The doctype did not contain the system identifier prescribed by the HTML 4.01 specification. Expected \u201C\u201D."); - } - } else if ("-//W3C//DTD XHTML 1.0 Strict//EN".equals(publicIdentifier)) { - if (!"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd".equals(systemIdentifier)) { - warn("The doctype did not contain the system identifier prescribed by the XHTML 1.0 specification. Expected \u201C\u201D."); - } - } else if ("//W3C//DTD XHTML 1.1//EN".equals(publicIdentifier)) { - if (!"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd".equals(systemIdentifier)) { - warn("The doctype did not contain the system identifier prescribed by the XHTML 1.1 specification. Expected \u201C\u201D."); - } - } else if (!((systemIdentifier == null || Portability.literalEqualsString( - "about:legacy-compat", systemIdentifier)) && publicIdentifier == null)) { - err("Unexpected doctype. Expected, e.g., \u201C\u201D."); - } - documentModeInternal(DocumentMode.STANDARDS_MODE, - publicIdentifier, systemIdentifier, html4); - } - break; - case NO_DOCTYPE_ERRORS: - if (isQuirky(name, publicIdentifier, systemIdentifier, - forceQuirks)) { - documentModeInternal(DocumentMode.QUIRKS_MODE, - publicIdentifier, systemIdentifier, false); - } else if (isAlmostStandards(publicIdentifier, - systemIdentifier)) { - documentModeInternal( - DocumentMode.ALMOST_STANDARDS_MODE, - publicIdentifier, systemIdentifier, false); - } else { - documentModeInternal(DocumentMode.STANDARDS_MODE, - publicIdentifier, systemIdentifier, false); - } - break; - } - // ]NOCPP] - - /* - * - * Then, switch to the root element mode of the tree construction - * stage. - */ - mode = BEFORE_HTML; - return; - } - /* - * A DOCTYPE token Parse error. - */ - errStrayDoctype(); - /* - * Ignore the token. - */ - return; - } - - // [NOCPP[ - - private boolean isHtml4Doctype(String publicIdentifier) { - if (publicIdentifier != null - && (Arrays.binarySearch(TreeBuilder.HTML4_PUBLIC_IDS, - publicIdentifier) > -1)) { - return true; - } - return false; - } - - // ]NOCPP] - - public final void comment(@NoLength char[] buf, int start, int length) - throws SAXException { - needToDropLF = false; - // [NOCPP[ - if (firstCommentLocation == null) { - firstCommentLocation = new LocatorImpl(tokenizer); - } - if (!wantingComments) { - return; - } - // ]NOCPP] - if (!isInForeign()) { - switch (mode) { - case INITIAL: - case BEFORE_HTML: - case AFTER_AFTER_BODY: - case AFTER_AFTER_FRAMESET: - /* - * A comment token Append a Comment node to the Document - * object with the data attribute set to the data given in - * the comment token. - */ - appendCommentToDocument(buf, start, length); - return; - case AFTER_BODY: - /* - * A comment token Append a Comment node to the first - * element in the stack of open elements (the html element), - * with the data attribute set to the data given in the - * comment token. - */ - flushCharacters(); - appendComment(stack[0].node, buf, start, length); - return; - default: - break; - } - } - /* - * A comment token Append a Comment node to the current node with the - * data attribute set to the data given in the comment token. - */ - flushCharacters(); - appendComment(stack[currentPtr].node, buf, start, length); - return; - } - - /** - * @see nu.validator.htmlparser.common.TokenHandler#characters(char[], int, - * int) - */ - public final void characters(@Const @NoLength char[] buf, int start, int length) - throws SAXException { - // Note: Can't attach error messages to EOF in C++ yet - - // CPPONLY: if (tokenizer.isViewingXmlSource()) { - // CPPONLY: return; - // CPPONLY: } - if (needToDropLF) { - needToDropLF = false; - if (buf[start] == '\n') { - start++; - length--; - if (length == 0) { - return; - } - } - } - - // optimize the most common case - switch (mode) { - case IN_BODY: - case IN_CELL: - case IN_CAPTION: - if (!isInForeignButNotHtmlOrMathTextIntegrationPoint()) { - reconstructTheActiveFormattingElements(); - } - // fall through - case TEXT: - accumulateCharacters(buf, start, length); - return; - case IN_TABLE: - case IN_TABLE_BODY: - case IN_ROW: - accumulateCharactersForced(buf, start, length); - return; - default: - int end = start + length; - charactersloop: for (int i = start; i < end; i++) { - switch (buf[i]) { - case ' ': - case '\t': - case '\n': - case '\r': - case '\u000C': - /* - * A character token that is one of one of U+0009 - * CHARACTER TABULATION, U+000A LINE FEED (LF), - * U+000C FORM FEED (FF), or U+0020 SPACE - */ - switch (mode) { - case INITIAL: - case BEFORE_HTML: - case BEFORE_HEAD: - /* - * Ignore the token. - */ - start = i + 1; - continue; - case IN_HEAD: - case IN_HEAD_NOSCRIPT: - case AFTER_HEAD: - case IN_COLUMN_GROUP: - case IN_FRAMESET: - case AFTER_FRAMESET: - /* - * Append the character to the current node. - */ - continue; - case FRAMESET_OK: - case IN_TEMPLATE: - case IN_BODY: - case IN_CELL: - case IN_CAPTION: - if (start < i) { - accumulateCharacters(buf, start, i - - start); - start = i; - } - - /* - * Reconstruct the active formatting - * elements, if any. - */ - if (!isInForeignButNotHtmlOrMathTextIntegrationPoint()) { - flushCharacters(); - reconstructTheActiveFormattingElements(); - } - /* - * Append the token's character to the - * current node. - */ - break charactersloop; - case IN_SELECT: - case IN_SELECT_IN_TABLE: - break charactersloop; - case IN_TABLE: - case IN_TABLE_BODY: - case IN_ROW: - accumulateCharactersForced(buf, i, 1); - start = i + 1; - continue; - case AFTER_BODY: - case AFTER_AFTER_BODY: - case AFTER_AFTER_FRAMESET: - if (start < i) { - accumulateCharacters(buf, start, i - - start); - start = i; - } - /* - * Reconstruct the active formatting - * elements, if any. - */ - flushCharacters(); - reconstructTheActiveFormattingElements(); - /* - * Append the token's character to the - * current node. - */ - continue; - } - default: - /* - * A character token that is not one of one of - * U+0009 CHARACTER TABULATION, U+000A LINE FEED - * (LF), U+000C FORM FEED (FF), or U+0020 SPACE - */ - switch (mode) { - case INITIAL: - /* - * Parse error. - */ - // [NOCPP[ - switch (doctypeExpectation) { - case AUTO: - err("Non-space characters found without seeing a doctype first. Expected e.g. \u201C\u201D."); - break; - case HTML: - // XXX figure out a way to report this in the Gecko View Source case - err("Non-space characters found without seeing a doctype first. Expected \u201C\u201D."); - break; - case HTML401_STRICT: - err("Non-space characters found without seeing a doctype first. Expected \u201C\u201D."); - break; - case HTML401_TRANSITIONAL: - err("Non-space characters found without seeing a doctype first. Expected \u201C\u201D."); - break; - case NO_DOCTYPE_ERRORS: - } - // ]NOCPP] - /* - * - * Set the document to quirks mode. - */ - documentModeInternal( - DocumentMode.QUIRKS_MODE, null, - null, false); - /* - * Then, switch to the root element mode of - * the tree construction stage - */ - mode = BEFORE_HTML; - /* - * and reprocess the current token. - */ - i--; - continue; - case BEFORE_HTML: - /* - * Create an HTMLElement node with the tag - * name html, in the HTML namespace. Append - * it to the Document object. - */ - // No need to flush characters here, - // because there's nothing to flush. - appendHtmlElementToDocumentAndPush(); - /* Switch to the main mode */ - mode = BEFORE_HEAD; - /* - * reprocess the current token. - */ - i--; - continue; - case BEFORE_HEAD: - if (start < i) { - accumulateCharacters(buf, start, i - - start); - start = i; - } - /* - * /Act as if a start tag token with the tag - * name "head" and no attributes had been - * seen, - */ - flushCharacters(); - appendToCurrentNodeAndPushHeadElement(HtmlAttributes.EMPTY_ATTRIBUTES); - mode = IN_HEAD; - /* - * then reprocess the current token. - * - * This will result in an empty head element - * being generated, with the current token - * being reprocessed in the "after head" - * insertion mode. - */ - i--; - continue; - case IN_HEAD: - if (start < i) { - accumulateCharacters(buf, start, i - - start); - start = i; - } - /* - * Act as if an end tag token with the tag - * name "head" had been seen, - */ - flushCharacters(); - pop(); - mode = AFTER_HEAD; - /* - * and reprocess the current token. - */ - i--; - continue; - case IN_HEAD_NOSCRIPT: - if (start < i) { - accumulateCharacters(buf, start, i - - start); - start = i; - } - /* - * Parse error. Act as if an end tag with - * the tag name "noscript" had been seen - */ - errNonSpaceInNoscriptInHead(); - flushCharacters(); - pop(); - mode = IN_HEAD; - /* - * and reprocess the current token. - */ - i--; - continue; - case AFTER_HEAD: - if (start < i) { - accumulateCharacters(buf, start, i - - start); - start = i; - } - /* - * Act as if a start tag token with the tag - * name "body" and no attributes had been - * seen, - */ - flushCharacters(); - appendToCurrentNodeAndPushBodyElement(); - mode = FRAMESET_OK; - /* - * and then reprocess the current token. - */ - i--; - continue; - case FRAMESET_OK: - framesetOk = false; - mode = IN_BODY; - i--; - continue; - case IN_TEMPLATE: - case IN_BODY: - case IN_CELL: - case IN_CAPTION: - if (start < i) { - accumulateCharacters(buf, start, i - - start); - start = i; - } - /* - * Reconstruct the active formatting - * elements, if any. - */ - if (!isInForeignButNotHtmlOrMathTextIntegrationPoint()) { - flushCharacters(); - reconstructTheActiveFormattingElements(); - } - /* - * Append the token's character to the - * current node. - */ - break charactersloop; - case IN_TABLE: - case IN_TABLE_BODY: - case IN_ROW: - accumulateCharactersForced(buf, i, 1); - start = i + 1; - continue; - case IN_COLUMN_GROUP: - if (start < i) { - accumulateCharacters(buf, start, i - - start); - start = i; - } - /* - * Act as if an end tag with the tag name - * "colgroup" had been seen, and then, if - * that token wasn't ignored, reprocess the - * current token. - */ - if (currentPtr == 0 || stack[currentPtr].getGroup() == - TreeBuilder.TEMPLATE) { - errNonSpaceInColgroupInFragment(); - start = i + 1; - continue; - } - flushCharacters(); - pop(); - mode = IN_TABLE; - i--; - continue; - case IN_SELECT: - case IN_SELECT_IN_TABLE: - break charactersloop; - case AFTER_BODY: - errNonSpaceAfterBody(); - fatal(); - mode = framesetOk ? FRAMESET_OK : IN_BODY; - i--; - continue; - case IN_FRAMESET: - if (start < i) { - accumulateCharacters(buf, start, i - - start); - // start index is adjusted below. - } - /* - * Parse error. - */ - errNonSpaceInFrameset(); - /* - * Ignore the token. - */ - start = i + 1; - continue; - case AFTER_FRAMESET: - if (start < i) { - accumulateCharacters(buf, start, i - - start); - // start index is adjusted below. - } - /* - * Parse error. - */ - errNonSpaceAfterFrameset(); - /* - * Ignore the token. - */ - start = i + 1; - continue; - case AFTER_AFTER_BODY: - /* - * Parse error. - */ - errNonSpaceInTrailer(); - /* - * Switch back to the main mode and - * reprocess the token. - */ - mode = framesetOk ? FRAMESET_OK : IN_BODY; - i--; - continue; - case AFTER_AFTER_FRAMESET: - if (start < i) { - accumulateCharacters(buf, start, i - - start); - // start index is adjusted below. - } - /* - * Parse error. - */ - errNonSpaceInTrailer(); - /* - * Ignore the token. - */ - start = i + 1; - continue; - } - } - } - if (start < end) { - accumulateCharacters(buf, start, end - start); - } - } - } - - /** - * @see nu.validator.htmlparser.common.TokenHandler#zeroOriginatingReplacementCharacter() - */ - public void zeroOriginatingReplacementCharacter() throws SAXException { - if (mode == TEXT) { - accumulateCharacters(REPLACEMENT_CHARACTER, 0, 1); - return; - } - if (currentPtr >= 0) { - if (isSpecialParentInForeign(stack[currentPtr])) { - return; - } - accumulateCharacters(REPLACEMENT_CHARACTER, 0, 1); - } - } - - public final void eof() throws SAXException { - flushCharacters(); - // Note: Can't attach error messages to EOF in C++ yet - eofloop: for (;;) { - switch (mode) { - case INITIAL: - /* - * Parse error. - */ - // [NOCPP[ - switch (doctypeExpectation) { - case AUTO: - err("End of file seen without seeing a doctype first. Expected e.g. \u201C\u201D."); - break; - case HTML: - err("End of file seen without seeing a doctype first. Expected \u201C\u201D."); - break; - case HTML401_STRICT: - err("End of file seen without seeing a doctype first. Expected \u201C\u201D."); - break; - case HTML401_TRANSITIONAL: - err("End of file seen without seeing a doctype first. Expected \u201C\u201D."); - break; - case NO_DOCTYPE_ERRORS: - } - // ]NOCPP] - /* - * - * Set the document to quirks mode. - */ - documentModeInternal(DocumentMode.QUIRKS_MODE, null, null, - false); - /* - * Then, switch to the root element mode of the tree - * construction stage - */ - mode = BEFORE_HTML; - /* - * and reprocess the current token. - */ - continue; - case BEFORE_HTML: - /* - * Create an HTMLElement node with the tag name html, in the - * HTML namespace. Append it to the Document object. - */ - appendHtmlElementToDocumentAndPush(); - // XXX application cache manifest - /* Switch to the main mode */ - mode = BEFORE_HEAD; - /* - * reprocess the current token. - */ - continue; - case BEFORE_HEAD: - appendToCurrentNodeAndPushHeadElement(HtmlAttributes.EMPTY_ATTRIBUTES); - mode = IN_HEAD; - continue; - case IN_HEAD: - // [NOCPP[ - if (errorHandler != null && currentPtr > 1) { - errEofWithUnclosedElements(); - } - // ]NOCPP] - while (currentPtr > 0) { - popOnEof(); - } - mode = AFTER_HEAD; - continue; - case IN_HEAD_NOSCRIPT: - // [NOCPP[ - errEofWithUnclosedElements(); - // ]NOCPP] - while (currentPtr > 1) { - popOnEof(); - } - mode = IN_HEAD; - continue; - case AFTER_HEAD: - appendToCurrentNodeAndPushBodyElement(); - mode = IN_BODY; - continue; - case IN_TABLE_BODY: - case IN_ROW: - case IN_TABLE: - case IN_SELECT_IN_TABLE: - case IN_SELECT: - case IN_COLUMN_GROUP: - case FRAMESET_OK: - case IN_CAPTION: - case IN_CELL: - case IN_BODY: - // [NOCPP[ - // i > 0 to stop in time in the foreign fragment case. - openelementloop: for (int i = currentPtr; i > 0; i--) { - int group = stack[i].getGroup(); - switch (group) { - case DD_OR_DT: - case LI: - case P: - case TBODY_OR_THEAD_OR_TFOOT: - case TD_OR_TH: - case BODY: - case HTML: - break; - default: - errEofWithUnclosedElements(); - break openelementloop; - } - } - // ]NOCPP] - - if (isTemplateModeStackEmpty()) { - break eofloop; - } - - // fall through to IN_TEMPLATE - case IN_TEMPLATE: - int eltPos = findLast("template"); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - assert fragment; - break eofloop; - } - if (errorHandler != null) { - errUnclosedElements(eltPos, "template"); - } - while (currentPtr >= eltPos) { - pop(); - } - clearTheListOfActiveFormattingElementsUpToTheLastMarker(); - popTemplateMode(); - resetTheInsertionMode(); - - // Reprocess token. - continue; - case TEXT: - // [NOCPP[ - if (errorHandler != null) { - errNoCheck("End of file seen when expecting text or an end tag."); - errListUnclosedStartTags(0); - } - // ]NOCPP] - // XXX mark script as already executed - if (originalMode == AFTER_HEAD) { - popOnEof(); - } - popOnEof(); - mode = originalMode; - continue; - case IN_FRAMESET: - // [NOCPP[ - if (errorHandler != null && currentPtr > 0) { - errEofWithUnclosedElements(); - } - // ]NOCPP] - break eofloop; - case AFTER_BODY: - case AFTER_FRAMESET: - case AFTER_AFTER_BODY: - case AFTER_AFTER_FRAMESET: - default: - // [NOCPP[ - if (currentPtr == 0) { // This silliness is here to poison - // buggy compiler optimizations in - // GWT - System.currentTimeMillis(); - } - // ]NOCPP] - break eofloop; - } - } - while (currentPtr > 0) { - popOnEof(); - } - if (!fragment) { - popOnEof(); - } - /* Stop parsing. */ - } - - /** - * @see nu.validator.htmlparser.common.TokenHandler#endTokenization() - */ - public final void endTokenization() throws SAXException { - formPointer = null; - headPointer = null; - deepTreeSurrogateParent = null; - templateModeStack = null; - if (stack != null) { - while (currentPtr > -1) { - stack[currentPtr].release(); - currentPtr--; - } - stack = null; - } - if (listOfActiveFormattingElements != null) { - while (listPtr > -1) { - if (listOfActiveFormattingElements[listPtr] != null) { - listOfActiveFormattingElements[listPtr].release(); - } - listPtr--; - } - listOfActiveFormattingElements = null; - } - // [NOCPP[ - idLocations.clear(); - // ]NOCPP] - charBuffer = null; - end(); - } - - public final void startTag(ElementName elementName, - HtmlAttributes attributes, boolean selfClosing) throws SAXException { - flushCharacters(); - - // [NOCPP[ - if (errorHandler != null) { - // ID uniqueness - @IdType String id = attributes.getId(); - if (id != null) { - LocatorImpl oldLoc = idLocations.get(id); - if (oldLoc != null) { - err("Duplicate ID \u201C" + id + "\u201D."); - errorHandler.warning(new SAXParseException( - "The first occurrence of ID \u201C" + id - + "\u201D was here.", oldLoc)); - } else { - idLocations.put(id, new LocatorImpl(tokenizer)); - } - } - } - // ]NOCPP] - - int eltPos; - needToDropLF = false; - starttagloop: for (;;) { - int group = elementName.getGroup(); - @Local String name = elementName.name; - if (isInForeign()) { - StackNode currentNode = stack[currentPtr]; - @NsUri String currNs = currentNode.ns; - if (!(currentNode.isHtmlIntegrationPoint() || (currNs == "http://www.w3.org/1998/Math/MathML" && ((currentNode.getGroup() == MI_MO_MN_MS_MTEXT && group != MGLYPH_OR_MALIGNMARK) || (currentNode.getGroup() == ANNOTATION_XML && group == SVG))))) { - switch (group) { - case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U: - case DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU: - case BODY: - case BR: - case RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR: - case DD_OR_DT: - case UL_OR_OL_OR_DL: - case EMBED: - case IMG: - case H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6: - case HEAD: - case HR: - case LI: - case META: - case NOBR: - case P: - case PRE_OR_LISTING: - case TABLE: - case FONT: - // re-check FONT to deal with the special case - if (!(group == FONT && !(attributes.contains(AttributeName.COLOR) - || attributes.contains(AttributeName.FACE) || attributes.contains(AttributeName.SIZE)))) { - errHtmlStartTagInForeignContext(name); - if (!fragment) { - while (!isSpecialParentInForeign(stack[currentPtr])) { - pop(); - } - continue starttagloop; - } // else fall thru - } - // else fall thru - default: - if ("http://www.w3.org/2000/svg" == currNs) { - attributes.adjustForSvg(); - if (selfClosing) { - appendVoidElementToCurrentMayFosterSVG( - elementName, attributes); - selfClosing = false; - } else { - appendToCurrentNodeAndPushElementMayFosterSVG( - elementName, attributes); - } - attributes = null; // CPP - break starttagloop; - } else { - attributes.adjustForMath(); - if (selfClosing) { - appendVoidElementToCurrentMayFosterMathML( - elementName, attributes); - selfClosing = false; - } else { - appendToCurrentNodeAndPushElementMayFosterMathML( - elementName, attributes); - } - attributes = null; // CPP - break starttagloop; - } - } // switch - } // foreignObject / annotation-xml - } - switch (mode) { - case IN_TEMPLATE: - switch (group) { - case COL: - popTemplateMode(); - pushTemplateMode(IN_COLUMN_GROUP); - mode = IN_COLUMN_GROUP; - // Reprocess token. - continue; - case CAPTION: - case COLGROUP: - case TBODY_OR_THEAD_OR_TFOOT: - popTemplateMode(); - pushTemplateMode(IN_TABLE); - mode = IN_TABLE; - // Reprocess token. - continue; - case TR: - popTemplateMode(); - pushTemplateMode(IN_TABLE_BODY); - mode = IN_TABLE_BODY; - // Reprocess token. - continue; - case TD_OR_TH: - popTemplateMode(); - pushTemplateMode(IN_ROW); - mode = IN_ROW; - // Reprocess token. - continue; - case META: - checkMetaCharset(attributes); - appendVoidElementToCurrentMayFoster( - elementName, - attributes); - selfClosing = false; - attributes = null; // CPP - break starttagloop; - case TITLE: - startTagTitleInHead(elementName, attributes); - attributes = null; // CPP - break starttagloop; - case BASE: - case LINK_OR_BASEFONT_OR_BGSOUND: - appendVoidElementToCurrentMayFoster( - elementName, - attributes); - selfClosing = false; - attributes = null; // CPP - break starttagloop; - case SCRIPT: - startTagScriptInHead(elementName, attributes); - attributes = null; // CPP - break starttagloop; - case NOFRAMES: - case STYLE: - startTagGenericRawText(elementName, attributes); - attributes = null; // CPP - break starttagloop; - case TEMPLATE: - startTagTemplateInHead(elementName, attributes); - attributes = null; // CPP - break starttagloop; - default: - popTemplateMode(); - pushTemplateMode(IN_BODY); - mode = IN_BODY; - // Reprocess token. - continue; - } - case IN_ROW: - switch (group) { - case TD_OR_TH: - clearStackBackTo(findLastOrRoot(TreeBuilder.TR)); - appendToCurrentNodeAndPushElement( - elementName, - attributes); - mode = IN_CELL; - insertMarker(); - attributes = null; // CPP - break starttagloop; - case CAPTION: - case COL: - case COLGROUP: - case TBODY_OR_THEAD_OR_TFOOT: - case TR: - eltPos = findLastOrRoot(TreeBuilder.TR); - if (eltPos == 0) { - assert fragment || isTemplateContents(); - errNoTableRowToClose(); - break starttagloop; - } - clearStackBackTo(eltPos); - pop(); - mode = IN_TABLE_BODY; - continue; - default: - // fall through to IN_TABLE - } - case IN_TABLE_BODY: - switch (group) { - case TR: - clearStackBackTo(findLastInTableScopeOrRootTemplateTbodyTheadTfoot()); - appendToCurrentNodeAndPushElement( - elementName, - attributes); - mode = IN_ROW; - attributes = null; // CPP - break starttagloop; - case TD_OR_TH: - errStartTagInTableBody(name); - clearStackBackTo(findLastInTableScopeOrRootTemplateTbodyTheadTfoot()); - appendToCurrentNodeAndPushElement( - ElementName.TR, - HtmlAttributes.EMPTY_ATTRIBUTES); - mode = IN_ROW; - continue; - case CAPTION: - case COL: - case COLGROUP: - case TBODY_OR_THEAD_OR_TFOOT: - eltPos = findLastInTableScopeOrRootTemplateTbodyTheadTfoot(); - if (eltPos == 0 || stack[eltPos].getGroup() == TEMPLATE) { - assert fragment || isTemplateContents(); - errStrayStartTag(name); - break starttagloop; - } else { - clearStackBackTo(eltPos); - pop(); - mode = IN_TABLE; - continue; - } - default: - // fall through to IN_TABLE - } - case IN_TABLE: - intableloop: for (;;) { - switch (group) { - case CAPTION: - clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE)); - insertMarker(); - appendToCurrentNodeAndPushElement( - elementName, - attributes); - mode = IN_CAPTION; - attributes = null; // CPP - break starttagloop; - case COLGROUP: - clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE)); - appendToCurrentNodeAndPushElement( - elementName, - attributes); - mode = IN_COLUMN_GROUP; - attributes = null; // CPP - break starttagloop; - case COL: - clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE)); - appendToCurrentNodeAndPushElement( - ElementName.COLGROUP, - HtmlAttributes.EMPTY_ATTRIBUTES); - mode = IN_COLUMN_GROUP; - continue starttagloop; - case TBODY_OR_THEAD_OR_TFOOT: - clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE)); - appendToCurrentNodeAndPushElement( - elementName, - attributes); - mode = IN_TABLE_BODY; - attributes = null; // CPP - break starttagloop; - case TR: - case TD_OR_TH: - clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE)); - appendToCurrentNodeAndPushElement( - ElementName.TBODY, - HtmlAttributes.EMPTY_ATTRIBUTES); - mode = IN_TABLE_BODY; - continue starttagloop; - case TEMPLATE: - // fall through to IN_HEAD - break intableloop; - case TABLE: - errTableSeenWhileTableOpen(); - eltPos = findLastInTableScope(name); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - assert fragment || isTemplateContents(); - break starttagloop; - } - generateImpliedEndTags(); - if (errorHandler != null && !isCurrent("table")) { - errNoCheckUnclosedElementsOnStack(); - } - while (currentPtr >= eltPos) { - pop(); - } - resetTheInsertionMode(); - continue starttagloop; - case SCRIPT: - // XXX need to manage much more stuff - // here if - // supporting - // document.write() - appendToCurrentNodeAndPushElement( - elementName, - attributes); - originalMode = mode; - mode = TEXT; - tokenizer.setStateAndEndTagExpectation( - Tokenizer.SCRIPT_DATA, elementName); - attributes = null; // CPP - break starttagloop; - case STYLE: - appendToCurrentNodeAndPushElement( - elementName, - attributes); - originalMode = mode; - mode = TEXT; - tokenizer.setStateAndEndTagExpectation( - Tokenizer.RAWTEXT, elementName); - attributes = null; // CPP - break starttagloop; - case INPUT: - errStartTagInTable(name); - if (!Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "hidden", - attributes.getValue(AttributeName.TYPE))) { - break intableloop; - } - appendVoidElementToCurrent( - name, attributes, - formPointer); - selfClosing = false; - attributes = null; // CPP - break starttagloop; - case FORM: - if (formPointer != null || isTemplateContents()) { - errFormWhenFormOpen(); - break starttagloop; - } else { - errStartTagInTable(name); - appendVoidFormToCurrent(attributes); - attributes = null; // CPP - break starttagloop; - } - default: - errStartTagInTable(name); - // fall through to IN_BODY - break intableloop; - } - } - case IN_CAPTION: - switch (group) { - case CAPTION: - case COL: - case COLGROUP: - case TBODY_OR_THEAD_OR_TFOOT: - case TR: - case TD_OR_TH: - errStrayStartTag(name); - eltPos = findLastInTableScope("caption"); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - break starttagloop; - } - generateImpliedEndTags(); - if (errorHandler != null && currentPtr != eltPos) { - errNoCheckUnclosedElementsOnStack(); - } - while (currentPtr >= eltPos) { - pop(); - } - clearTheListOfActiveFormattingElementsUpToTheLastMarker(); - mode = IN_TABLE; - continue; - default: - // fall through to IN_BODY - } - case IN_CELL: - switch (group) { - case CAPTION: - case COL: - case COLGROUP: - case TBODY_OR_THEAD_OR_TFOOT: - case TR: - case TD_OR_TH: - eltPos = findLastInTableScopeTdTh(); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - errNoCellToClose(); - break starttagloop; - } else { - closeTheCell(eltPos); - continue; - } - default: - // fall through to IN_BODY - } - case FRAMESET_OK: - switch (group) { - case FRAMESET: - if (mode == FRAMESET_OK) { - if (currentPtr == 0 || stack[1].getGroup() != BODY) { - assert fragment || isTemplateContents(); - errStrayStartTag(name); - break starttagloop; - } else { - errFramesetStart(); - detachFromParent(stack[1].node); - while (currentPtr > 0) { - pop(); - } - appendToCurrentNodeAndPushElement( - elementName, - attributes); - mode = IN_FRAMESET; - attributes = null; // CPP - break starttagloop; - } - } else { - errStrayStartTag(name); - break starttagloop; - } - // NOT falling through! - case PRE_OR_LISTING: - case LI: - case DD_OR_DT: - case BUTTON: - case MARQUEE_OR_APPLET: - case OBJECT: - case TABLE: - case AREA_OR_WBR: - case BR: - case EMBED: - case IMG: - case INPUT: - case KEYGEN: - case HR: - case TEXTAREA: - case XMP: - case IFRAME: - case SELECT: - if (mode == FRAMESET_OK - && !(group == INPUT && Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "hidden", - attributes.getValue(AttributeName.TYPE)))) { - framesetOk = false; - mode = IN_BODY; - } - // fall through to IN_BODY - default: - // fall through to IN_BODY - } - case IN_BODY: - inbodyloop: for (;;) { - switch (group) { - case HTML: - errStrayStartTag(name); - if (!fragment && !isTemplateContents()) { - addAttributesToHtml(attributes); - attributes = null; // CPP - } - break starttagloop; - case BASE: - case LINK_OR_BASEFONT_OR_BGSOUND: - case META: - case STYLE: - case SCRIPT: - case TITLE: - case TEMPLATE: - // Fall through to IN_HEAD - break inbodyloop; - case BODY: - if (currentPtr == 0 || stack[1].getGroup() != BODY || isTemplateContents()) { - assert fragment || isTemplateContents(); - errStrayStartTag(name); - break starttagloop; - } - errFooSeenWhenFooOpen(name); - framesetOk = false; - if (mode == FRAMESET_OK) { - mode = IN_BODY; - } - if (addAttributesToBody(attributes)) { - attributes = null; // CPP - } - break starttagloop; - case P: - case DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU: - case UL_OR_OL_OR_DL: - case ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY: - implicitlyCloseP(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - case H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6: - implicitlyCloseP(); - if (stack[currentPtr].getGroup() == H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6) { - errHeadingWhenHeadingOpen(); - pop(); - } - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - case FIELDSET: - implicitlyCloseP(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes, formPointer); - attributes = null; // CPP - break starttagloop; - case PRE_OR_LISTING: - implicitlyCloseP(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - needToDropLF = true; - attributes = null; // CPP - break starttagloop; - case FORM: - if (formPointer != null && !isTemplateContents()) { - errFormWhenFormOpen(); - break starttagloop; - } else { - implicitlyCloseP(); - appendToCurrentNodeAndPushFormElementMayFoster(attributes); - attributes = null; // CPP - break starttagloop; - } - case LI: - case DD_OR_DT: - eltPos = currentPtr; - for (;;) { - StackNode node = stack[eltPos]; // weak - // ref - if (node.getGroup() == group) { // LI or - // DD_OR_DT - generateImpliedEndTagsExceptFor(node.name); - if (errorHandler != null - && eltPos != currentPtr) { - errUnclosedElementsImplied(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - break; - } else if (eltPos == 0 || (node.isSpecial() - && (node.ns != "http://www.w3.org/1999/xhtml" - || (node.name != "p" - && node.name != "address" - && node.name != "div")))) { - break; - } - eltPos--; - } - implicitlyCloseP(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - case PLAINTEXT: - implicitlyCloseP(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - tokenizer.setStateAndEndTagExpectation( - Tokenizer.PLAINTEXT, elementName); - attributes = null; // CPP - break starttagloop; - case A: - int activeAPos = findInListOfActiveFormattingElementsContainsBetweenEndAndLastMarker("a"); - if (activeAPos != -1) { - errFooSeenWhenFooOpen(name); - StackNode activeA = listOfActiveFormattingElements[activeAPos]; - activeA.retain(); - adoptionAgencyEndTag("a"); - removeFromStack(activeA); - activeAPos = findInListOfActiveFormattingElements(activeA); - if (activeAPos != -1) { - removeFromListOfActiveFormattingElements(activeAPos); - } - activeA.release(); - } - reconstructTheActiveFormattingElements(); - appendToCurrentNodeAndPushFormattingElementMayFoster( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U: - case FONT: - reconstructTheActiveFormattingElements(); - maybeForgetEarlierDuplicateFormattingElement(elementName.name, attributes); - appendToCurrentNodeAndPushFormattingElementMayFoster( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - case NOBR: - reconstructTheActiveFormattingElements(); - if (TreeBuilder.NOT_FOUND_ON_STACK != findLastInScope("nobr")) { - errFooSeenWhenFooOpen(name); - adoptionAgencyEndTag("nobr"); - reconstructTheActiveFormattingElements(); - } - appendToCurrentNodeAndPushFormattingElementMayFoster( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - case BUTTON: - eltPos = findLastInScope(name); - if (eltPos != TreeBuilder.NOT_FOUND_ON_STACK) { - errFooSeenWhenFooOpen(name); - generateImpliedEndTags(); - if (errorHandler != null - && !isCurrent(name)) { - errUnclosedElementsImplied(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - continue starttagloop; - } else { - reconstructTheActiveFormattingElements(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes, formPointer); - attributes = null; // CPP - break starttagloop; - } - case OBJECT: - reconstructTheActiveFormattingElements(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes, formPointer); - insertMarker(); - attributes = null; // CPP - break starttagloop; - case MARQUEE_OR_APPLET: - reconstructTheActiveFormattingElements(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - insertMarker(); - attributes = null; // CPP - break starttagloop; - case TABLE: - // The only quirk. Blame Hixie and - // Acid2. - if (!quirks) { - implicitlyCloseP(); - } - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - mode = IN_TABLE; - attributes = null; // CPP - break starttagloop; - case BR: - case EMBED: - case AREA_OR_WBR: - reconstructTheActiveFormattingElements(); - // FALL THROUGH to PARAM_OR_SOURCE_OR_TRACK - // CPPONLY: case MENUITEM: - case PARAM_OR_SOURCE_OR_TRACK: - appendVoidElementToCurrentMayFoster( - elementName, - attributes); - selfClosing = false; - attributes = null; // CPP - break starttagloop; - case HR: - implicitlyCloseP(); - appendVoidElementToCurrentMayFoster( - elementName, - attributes); - selfClosing = false; - attributes = null; // CPP - break starttagloop; - case IMAGE: - errImage(); - elementName = ElementName.IMG; - continue starttagloop; - case IMG: - case KEYGEN: - case INPUT: - reconstructTheActiveFormattingElements(); - appendVoidElementToCurrentMayFoster( - name, attributes, - formPointer); - selfClosing = false; - attributes = null; // CPP - break starttagloop; - case ISINDEX: - errIsindex(); - if (formPointer != null && !isTemplateContents()) { - break starttagloop; - } - implicitlyCloseP(); - HtmlAttributes formAttrs = new HtmlAttributes(0); - int actionIndex = attributes.getIndex(AttributeName.ACTION); - if (actionIndex > -1) { - formAttrs.addAttribute( - AttributeName.ACTION, - attributes.getValueNoBoundsCheck(actionIndex) - // [NOCPP[ - , XmlViolationPolicy.ALLOW - // ]NOCPP] - // CPPONLY: , attributes.getLineNoBoundsCheck(actionIndex) - ); - } - appendToCurrentNodeAndPushFormElementMayFoster(formAttrs); - appendVoidElementToCurrentMayFoster( - ElementName.HR, - HtmlAttributes.EMPTY_ATTRIBUTES); - appendToCurrentNodeAndPushElementMayFoster( - ElementName.LABEL, - HtmlAttributes.EMPTY_ATTRIBUTES); - int promptIndex = attributes.getIndex(AttributeName.PROMPT); - if (promptIndex > -1) { - @Auto char[] prompt = Portability.newCharArrayFromString(attributes.getValueNoBoundsCheck(promptIndex)); - appendCharacters(stack[currentPtr].node, - prompt, 0, prompt.length); - } else { - appendIsindexPrompt(stack[currentPtr].node); - } - HtmlAttributes inputAttributes = new HtmlAttributes( - 0); - inputAttributes.addAttribute( - AttributeName.NAME, - Portability.newStringFromLiteral("isindex") - // [NOCPP[ - , XmlViolationPolicy.ALLOW - // ]NOCPP] - // CPPONLY: , tokenizer.getLineNumber() - ); - for (int i = 0; i < attributes.getLength(); i++) { - AttributeName attributeQName = attributes.getAttributeNameNoBoundsCheck(i); - if (AttributeName.NAME == attributeQName - || AttributeName.PROMPT == attributeQName) { - attributes.releaseValue(i); - } else if (AttributeName.ACTION != attributeQName) { - inputAttributes.addAttribute( - attributeQName, - attributes.getValueNoBoundsCheck(i) - // [NOCPP[ - , XmlViolationPolicy.ALLOW - // ]NOCPP] - // CPPONLY: , attributes.getLineNoBoundsCheck(i) - ); - } - } - attributes.clearWithoutReleasingContents(); - appendVoidElementToCurrentMayFoster( - "input", - inputAttributes, formPointer); - pop(); // label - appendVoidElementToCurrentMayFoster( - ElementName.HR, - HtmlAttributes.EMPTY_ATTRIBUTES); - pop(); // form - - if (!isTemplateContents()) { - formPointer = null; - } - - selfClosing = false; - // Portability.delete(formAttrs); - // Portability.delete(inputAttributes); - // Don't delete attributes, they are deleted - // later - break starttagloop; - case TEXTAREA: - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes, formPointer); - tokenizer.setStateAndEndTagExpectation( - Tokenizer.RCDATA, elementName); - originalMode = mode; - mode = TEXT; - needToDropLF = true; - attributes = null; // CPP - break starttagloop; - case XMP: - implicitlyCloseP(); - reconstructTheActiveFormattingElements(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - originalMode = mode; - mode = TEXT; - tokenizer.setStateAndEndTagExpectation( - Tokenizer.RAWTEXT, elementName); - attributes = null; // CPP - break starttagloop; - case NOSCRIPT: - if (!scriptingEnabled) { - reconstructTheActiveFormattingElements(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - } else { - // fall through - } - case NOFRAMES: - case IFRAME: - case NOEMBED: - startTagGenericRawText(elementName, attributes); - attributes = null; // CPP - break starttagloop; - case SELECT: - reconstructTheActiveFormattingElements(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes, formPointer); - switch (mode) { - case IN_TABLE: - case IN_CAPTION: - case IN_COLUMN_GROUP: - case IN_TABLE_BODY: - case IN_ROW: - case IN_CELL: - mode = IN_SELECT_IN_TABLE; - break; - default: - mode = IN_SELECT; - break; - } - attributes = null; // CPP - break starttagloop; - case OPTGROUP: - case OPTION: - if (isCurrent("option")) { - pop(); - } - reconstructTheActiveFormattingElements(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - case RB_OR_RTC: - eltPos = findLastInScope("ruby"); - if (eltPos != NOT_FOUND_ON_STACK) { - generateImpliedEndTags(); - } - if (eltPos != currentPtr) { - if (eltPos == NOT_FOUND_ON_STACK) { - errStartTagSeenWithoutRuby(name); - } else { - errUnclosedChildrenInRuby(); - } - } - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - case RT_OR_RP: - eltPos = findLastInScope("ruby"); - if (eltPos != NOT_FOUND_ON_STACK) { - generateImpliedEndTagsExceptFor("rtc"); - } - if (eltPos != currentPtr) { - if (!isCurrent("rtc")) { - if (eltPos == NOT_FOUND_ON_STACK) { - errStartTagSeenWithoutRuby(name); - } else { - errUnclosedChildrenInRuby(); - } - } - } - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - case MATH: - reconstructTheActiveFormattingElements(); - attributes.adjustForMath(); - if (selfClosing) { - appendVoidElementToCurrentMayFosterMathML( - elementName, attributes); - selfClosing = false; - } else { - appendToCurrentNodeAndPushElementMayFosterMathML( - elementName, attributes); - } - attributes = null; // CPP - break starttagloop; - case SVG: - reconstructTheActiveFormattingElements(); - attributes.adjustForSvg(); - if (selfClosing) { - appendVoidElementToCurrentMayFosterSVG( - elementName, - attributes); - selfClosing = false; - } else { - appendToCurrentNodeAndPushElementMayFosterSVG( - elementName, attributes); - } - attributes = null; // CPP - break starttagloop; - case CAPTION: - case COL: - case COLGROUP: - case TBODY_OR_THEAD_OR_TFOOT: - case TR: - case TD_OR_TH: - case FRAME: - case FRAMESET: - case HEAD: - errStrayStartTag(name); - break starttagloop; - case OUTPUT: - reconstructTheActiveFormattingElements(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes, formPointer); - attributes = null; // CPP - break starttagloop; - default: - reconstructTheActiveFormattingElements(); - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - } - } - case IN_HEAD: - inheadloop: for (;;) { - switch (group) { - case HTML: - errStrayStartTag(name); - if (!fragment && !isTemplateContents()) { - addAttributesToHtml(attributes); - attributes = null; // CPP - } - break starttagloop; - case BASE: - case LINK_OR_BASEFONT_OR_BGSOUND: - appendVoidElementToCurrentMayFoster( - elementName, - attributes); - selfClosing = false; - attributes = null; // CPP - break starttagloop; - case META: - // Fall through to IN_HEAD_NOSCRIPT - break inheadloop; - case TITLE: - startTagTitleInHead(elementName, attributes); - attributes = null; // CPP - break starttagloop; - case NOSCRIPT: - if (scriptingEnabled) { - appendToCurrentNodeAndPushElement( - elementName, - attributes); - originalMode = mode; - mode = TEXT; - tokenizer.setStateAndEndTagExpectation( - Tokenizer.RAWTEXT, elementName); - } else { - appendToCurrentNodeAndPushElementMayFoster( - elementName, - attributes); - mode = IN_HEAD_NOSCRIPT; - } - attributes = null; // CPP - break starttagloop; - case SCRIPT: - startTagScriptInHead(elementName, attributes); - attributes = null; // CPP - break starttagloop; - case STYLE: - case NOFRAMES: - startTagGenericRawText(elementName, attributes); - attributes = null; // CPP - break starttagloop; - case HEAD: - /* Parse error. */ - errFooSeenWhenFooOpen(name); - /* Ignore the token. */ - break starttagloop; - case TEMPLATE: - startTagTemplateInHead(elementName, attributes); - attributes = null; // CPP - break starttagloop; - default: - pop(); - mode = AFTER_HEAD; - continue starttagloop; - } - } - case IN_HEAD_NOSCRIPT: - switch (group) { - case HTML: - // XXX did Hixie really mean to omit "base" - // here? - errStrayStartTag(name); - if (!fragment && !isTemplateContents()) { - addAttributesToHtml(attributes); - attributes = null; // CPP - } - break starttagloop; - case LINK_OR_BASEFONT_OR_BGSOUND: - appendVoidElementToCurrentMayFoster( - elementName, - attributes); - selfClosing = false; - attributes = null; // CPP - break starttagloop; - case META: - checkMetaCharset(attributes); - appendVoidElementToCurrentMayFoster( - elementName, - attributes); - selfClosing = false; - attributes = null; // CPP - break starttagloop; - case STYLE: - case NOFRAMES: - appendToCurrentNodeAndPushElement( - elementName, - attributes); - originalMode = mode; - mode = TEXT; - tokenizer.setStateAndEndTagExpectation( - Tokenizer.RAWTEXT, elementName); - attributes = null; // CPP - break starttagloop; - case HEAD: - errFooSeenWhenFooOpen(name); - break starttagloop; - case NOSCRIPT: - errFooSeenWhenFooOpen(name); - break starttagloop; - default: - errBadStartTagInHead(name); - pop(); - mode = IN_HEAD; - continue; - } - case IN_COLUMN_GROUP: - switch (group) { - case HTML: - errStrayStartTag(name); - if (!fragment && !isTemplateContents()) { - addAttributesToHtml(attributes); - attributes = null; // CPP - } - break starttagloop; - case COL: - appendVoidElementToCurrentMayFoster( - elementName, - attributes); - selfClosing = false; - attributes = null; // CPP - break starttagloop; - case TEMPLATE: - startTagTemplateInHead(elementName, attributes); - attributes = null; // CPP - break starttagloop; - default: - if (currentPtr == 0 || stack[currentPtr].getGroup() == TEMPLATE) { - assert fragment || isTemplateContents(); - errGarbageInColgroup(); - break starttagloop; - } - pop(); - mode = IN_TABLE; - continue; - } - case IN_SELECT_IN_TABLE: - switch (group) { - case CAPTION: - case TBODY_OR_THEAD_OR_TFOOT: - case TR: - case TD_OR_TH: - case TABLE: - errStartTagWithSelectOpen(name); - eltPos = findLastInTableScope("select"); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - assert fragment; - break starttagloop; // http://www.w3.org/Bugs/Public/show_bug.cgi?id=8375 - } - while (currentPtr >= eltPos) { - pop(); - } - resetTheInsertionMode(); - continue; - default: - // fall through to IN_SELECT - } - case IN_SELECT: - switch (group) { - case HTML: - errStrayStartTag(name); - if (!fragment) { - addAttributesToHtml(attributes); - attributes = null; // CPP - } - break starttagloop; - case OPTION: - if (isCurrent("option")) { - pop(); - } - appendToCurrentNodeAndPushElement( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - case OPTGROUP: - if (isCurrent("option")) { - pop(); - } - if (isCurrent("optgroup")) { - pop(); - } - appendToCurrentNodeAndPushElement( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - case SELECT: - errStartSelectWhereEndSelectExpected(); - eltPos = findLastInTableScope(name); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - assert fragment; - errNoSelectInTableScope(); - break starttagloop; - } else { - while (currentPtr >= eltPos) { - pop(); - } - resetTheInsertionMode(); - break starttagloop; - } - case INPUT: - case TEXTAREA: - case KEYGEN: - errStartTagWithSelectOpen(name); - eltPos = findLastInTableScope("select"); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - assert fragment; - break starttagloop; - } - while (currentPtr >= eltPos) { - pop(); - } - resetTheInsertionMode(); - continue; - case SCRIPT: - startTagScriptInHead(elementName, attributes); - attributes = null; // CPP - break starttagloop; - case TEMPLATE: - startTagTemplateInHead(elementName, attributes); - attributes = null; // CPP - break starttagloop; - default: - errStrayStartTag(name); - break starttagloop; - } - case AFTER_BODY: - switch (group) { - case HTML: - errStrayStartTag(name); - if (!fragment && !isTemplateContents()) { - addAttributesToHtml(attributes); - attributes = null; // CPP - } - break starttagloop; - default: - errStrayStartTag(name); - mode = framesetOk ? FRAMESET_OK : IN_BODY; - continue; - } - case IN_FRAMESET: - switch (group) { - case FRAMESET: - appendToCurrentNodeAndPushElement( - elementName, - attributes); - attributes = null; // CPP - break starttagloop; - case FRAME: - appendVoidElementToCurrentMayFoster( - elementName, - attributes); - selfClosing = false; - attributes = null; // CPP - break starttagloop; - default: - // fall through to AFTER_FRAMESET - } - case AFTER_FRAMESET: - switch (group) { - case HTML: - errStrayStartTag(name); - if (!fragment && !isTemplateContents()) { - addAttributesToHtml(attributes); - attributes = null; // CPP - } - break starttagloop; - case NOFRAMES: - appendToCurrentNodeAndPushElement( - elementName, - attributes); - originalMode = mode; - mode = TEXT; - tokenizer.setStateAndEndTagExpectation( - Tokenizer.RAWTEXT, elementName); - attributes = null; // CPP - break starttagloop; - default: - errStrayStartTag(name); - break starttagloop; - } - case INITIAL: - /* - * Parse error. - */ - // [NOCPP[ - switch (doctypeExpectation) { - case AUTO: - err("Start tag seen without seeing a doctype first. Expected e.g. \u201C\u201D."); - break; - case HTML: - // ]NOCPP] - errStartTagWithoutDoctype(); - // [NOCPP[ - break; - case HTML401_STRICT: - err("Start tag seen without seeing a doctype first. Expected \u201C\u201D."); - break; - case HTML401_TRANSITIONAL: - err("Start tag seen without seeing a doctype first. Expected \u201C\u201D."); - break; - case NO_DOCTYPE_ERRORS: - } - // ]NOCPP] - /* - * - * Set the document to quirks mode. - */ - documentModeInternal(DocumentMode.QUIRKS_MODE, null, null, - false); - /* - * Then, switch to the root element mode of the tree - * construction stage - */ - mode = BEFORE_HTML; - /* - * and reprocess the current token. - */ - continue; - case BEFORE_HTML: - switch (group) { - case HTML: - // optimize error check and streaming SAX by - // hoisting - // "html" handling here. - if (attributes == HtmlAttributes.EMPTY_ATTRIBUTES) { - // This has the right magic side effect - // that - // it - // makes attributes in SAX Tree mutable. - appendHtmlElementToDocumentAndPush(); - } else { - appendHtmlElementToDocumentAndPush(attributes); - } - // XXX application cache should fire here - mode = BEFORE_HEAD; - attributes = null; // CPP - break starttagloop; - default: - /* - * Create an HTMLElement node with the tag name - * html, in the HTML namespace. Append it to the - * Document object. - */ - appendHtmlElementToDocumentAndPush(); - /* Switch to the main mode */ - mode = BEFORE_HEAD; - /* - * reprocess the current token. - */ - continue; - } - case BEFORE_HEAD: - switch (group) { - case HTML: - errStrayStartTag(name); - if (!fragment && !isTemplateContents()) { - addAttributesToHtml(attributes); - attributes = null; // CPP - } - break starttagloop; - case HEAD: - /* - * A start tag whose tag name is "head" - * - * Create an element for the token. - * - * Set the head element pointer to this new element - * node. - * - * Append the new element to the current node and - * push it onto the stack of open elements. - */ - appendToCurrentNodeAndPushHeadElement(attributes); - /* - * Change the insertion mode to "in head". - */ - mode = IN_HEAD; - attributes = null; // CPP - break starttagloop; - default: - /* - * Any other start tag token - * - * Act as if a start tag token with the tag name - * "head" and no attributes had been seen, - */ - appendToCurrentNodeAndPushHeadElement(HtmlAttributes.EMPTY_ATTRIBUTES); - mode = IN_HEAD; - /* - * then reprocess the current token. - * - * This will result in an empty head element being - * generated, with the current token being - * reprocessed in the "after head" insertion mode. - */ - continue; - } - case AFTER_HEAD: - switch (group) { - case HTML: - errStrayStartTag(name); - if (!fragment && !isTemplateContents()) { - addAttributesToHtml(attributes); - attributes = null; // CPP - } - break starttagloop; - case BODY: - if (attributes.getLength() == 0) { - // This has the right magic side effect - // that - // it - // makes attributes in SAX Tree mutable. - appendToCurrentNodeAndPushBodyElement(); - } else { - appendToCurrentNodeAndPushBodyElement(attributes); - } - framesetOk = false; - mode = IN_BODY; - attributes = null; // CPP - break starttagloop; - case FRAMESET: - appendToCurrentNodeAndPushElement( - elementName, - attributes); - mode = IN_FRAMESET; - attributes = null; // CPP - break starttagloop; - case TEMPLATE: - errFooBetweenHeadAndBody(name); - pushHeadPointerOntoStack(); - StackNode headOnStack = stack[currentPtr]; - startTagTemplateInHead(elementName, attributes); - removeFromStack(headOnStack); - attributes = null; // CPP - break starttagloop; - case BASE: - case LINK_OR_BASEFONT_OR_BGSOUND: - errFooBetweenHeadAndBody(name); - pushHeadPointerOntoStack(); - appendVoidElementToCurrentMayFoster( - elementName, - attributes); - selfClosing = false; - pop(); // head - attributes = null; // CPP - break starttagloop; - case META: - errFooBetweenHeadAndBody(name); - checkMetaCharset(attributes); - pushHeadPointerOntoStack(); - appendVoidElementToCurrentMayFoster( - elementName, - attributes); - selfClosing = false; - pop(); // head - attributes = null; // CPP - break starttagloop; - case SCRIPT: - errFooBetweenHeadAndBody(name); - pushHeadPointerOntoStack(); - appendToCurrentNodeAndPushElement( - elementName, - attributes); - originalMode = mode; - mode = TEXT; - tokenizer.setStateAndEndTagExpectation( - Tokenizer.SCRIPT_DATA, elementName); - attributes = null; // CPP - break starttagloop; - case STYLE: - case NOFRAMES: - errFooBetweenHeadAndBody(name); - pushHeadPointerOntoStack(); - appendToCurrentNodeAndPushElement( - elementName, - attributes); - originalMode = mode; - mode = TEXT; - tokenizer.setStateAndEndTagExpectation( - Tokenizer.RAWTEXT, elementName); - attributes = null; // CPP - break starttagloop; - case TITLE: - errFooBetweenHeadAndBody(name); - pushHeadPointerOntoStack(); - appendToCurrentNodeAndPushElement( - elementName, - attributes); - originalMode = mode; - mode = TEXT; - tokenizer.setStateAndEndTagExpectation( - Tokenizer.RCDATA, elementName); - attributes = null; // CPP - break starttagloop; - case HEAD: - errStrayStartTag(name); - break starttagloop; - default: - appendToCurrentNodeAndPushBodyElement(); - mode = FRAMESET_OK; - continue; - } - case AFTER_AFTER_BODY: - switch (group) { - case HTML: - errStrayStartTag(name); - if (!fragment && !isTemplateContents()) { - addAttributesToHtml(attributes); - attributes = null; // CPP - } - break starttagloop; - default: - errStrayStartTag(name); - fatal(); - mode = framesetOk ? FRAMESET_OK : IN_BODY; - continue; - } - case AFTER_AFTER_FRAMESET: - switch (group) { - case HTML: - errStrayStartTag(name); - if (!fragment && !isTemplateContents()) { - addAttributesToHtml(attributes); - attributes = null; // CPP - } - break starttagloop; - case NOFRAMES: - startTagGenericRawText(elementName, attributes); - attributes = null; // CPP - break starttagloop; - default: - errStrayStartTag(name); - break starttagloop; - } - case TEXT: - assert false; - break starttagloop; // Avoid infinite loop if the assertion - // fails - } - } - if (selfClosing) { - errSelfClosing(); - } - // CPPONLY: if (mBuilder == null && attributes != HtmlAttributes.EMPTY_ATTRIBUTES) { - // CPPONLY: Portability.delete(attributes); - // CPPONLY: } - } - - private void startTagTitleInHead(ElementName elementName, HtmlAttributes attributes) throws SAXException { - appendToCurrentNodeAndPushElementMayFoster(elementName, attributes); - originalMode = mode; - mode = TEXT; - tokenizer.setStateAndEndTagExpectation(Tokenizer.RCDATA, elementName); - } - - private void startTagGenericRawText(ElementName elementName, HtmlAttributes attributes) throws SAXException { - appendToCurrentNodeAndPushElementMayFoster(elementName, attributes); - originalMode = mode; - mode = TEXT; - tokenizer.setStateAndEndTagExpectation(Tokenizer.RAWTEXT, elementName); - } - - private void startTagScriptInHead(ElementName elementName, HtmlAttributes attributes) throws SAXException { - // XXX need to manage much more stuff here if supporting document.write() - appendToCurrentNodeAndPushElementMayFoster(elementName, attributes); - originalMode = mode; - mode = TEXT; - tokenizer.setStateAndEndTagExpectation(Tokenizer.SCRIPT_DATA, elementName); - } - - private void startTagTemplateInHead(ElementName elementName, HtmlAttributes attributes) throws SAXException { - appendToCurrentNodeAndPushElement(elementName, attributes); - insertMarker(); - framesetOk = false; - originalMode = mode; - mode = IN_TEMPLATE; - pushTemplateMode(IN_TEMPLATE); - } - - private boolean isTemplateContents() { - return TreeBuilder.NOT_FOUND_ON_STACK != findLast("template"); - } - - private boolean isTemplateModeStackEmpty() { - return templateModePtr == -1; - } - - private boolean isSpecialParentInForeign(StackNode stackNode) { - @NsUri String ns = stackNode.ns; - return ("http://www.w3.org/1999/xhtml" == ns) - || (stackNode.isHtmlIntegrationPoint()) - || (("http://www.w3.org/1998/Math/MathML" == ns) && (stackNode.getGroup() == MI_MO_MN_MS_MTEXT)); - } - - /** - * - *

- * C++ memory note: The return value must be released. - * - * @return - * @throws SAXException - * @throws StopSniffingException - */ - public static String extractCharsetFromContent(String attributeValue - // CPPONLY: , TreeBuilder tb - ) { - // This is a bit ugly. Converting the string to char array in order to - // make the portability layer smaller. - int charsetState = CHARSET_INITIAL; - int start = -1; - int end = -1; - @Auto char[] buffer = Portability.newCharArrayFromString(attributeValue); - - charsetloop: for (int i = 0; i < buffer.length; i++) { - char c = buffer[i]; - switch (charsetState) { - case CHARSET_INITIAL: - switch (c) { - case 'c': - case 'C': - charsetState = CHARSET_C; - continue; - default: - continue; - } - case CHARSET_C: - switch (c) { - case 'h': - case 'H': - charsetState = CHARSET_H; - continue; - default: - charsetState = CHARSET_INITIAL; - continue; - } - case CHARSET_H: - switch (c) { - case 'a': - case 'A': - charsetState = CHARSET_A; - continue; - default: - charsetState = CHARSET_INITIAL; - continue; - } - case CHARSET_A: - switch (c) { - case 'r': - case 'R': - charsetState = CHARSET_R; - continue; - default: - charsetState = CHARSET_INITIAL; - continue; - } - case CHARSET_R: - switch (c) { - case 's': - case 'S': - charsetState = CHARSET_S; - continue; - default: - charsetState = CHARSET_INITIAL; - continue; - } - case CHARSET_S: - switch (c) { - case 'e': - case 'E': - charsetState = CHARSET_E; - continue; - default: - charsetState = CHARSET_INITIAL; - continue; - } - case CHARSET_E: - switch (c) { - case 't': - case 'T': - charsetState = CHARSET_T; - continue; - default: - charsetState = CHARSET_INITIAL; - continue; - } - case CHARSET_T: - switch (c) { - case '\t': - case '\n': - case '\u000C': - case '\r': - case ' ': - continue; - case '=': - charsetState = CHARSET_EQUALS; - continue; - default: - return null; - } - case CHARSET_EQUALS: - switch (c) { - case '\t': - case '\n': - case '\u000C': - case '\r': - case ' ': - continue; - case '\'': - start = i + 1; - charsetState = CHARSET_SINGLE_QUOTED; - continue; - case '\"': - start = i + 1; - charsetState = CHARSET_DOUBLE_QUOTED; - continue; - default: - start = i; - charsetState = CHARSET_UNQUOTED; - continue; - } - case CHARSET_SINGLE_QUOTED: - switch (c) { - case '\'': - end = i; - break charsetloop; - default: - continue; - } - case CHARSET_DOUBLE_QUOTED: - switch (c) { - case '\"': - end = i; - break charsetloop; - default: - continue; - } - case CHARSET_UNQUOTED: - switch (c) { - case '\t': - case '\n': - case '\u000C': - case '\r': - case ' ': - case ';': - end = i; - break charsetloop; - default: - continue; - } - } - } - String charset = null; - if (start != -1) { - if (end == -1) { - end = buffer.length; - } - charset = Portability.newStringFromBuffer(buffer, start, end - - start - // CPPONLY: , tb - ); - } - return charset; - } - - private void checkMetaCharset(HtmlAttributes attributes) - throws SAXException { - String charset = attributes.getValue(AttributeName.CHARSET); - if (charset != null) { - if (tokenizer.internalEncodingDeclaration(charset)) { - requestSuspension(); - return; - } - return; - } - if (!Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "content-type", - attributes.getValue(AttributeName.HTTP_EQUIV))) { - return; - } - String content = attributes.getValue(AttributeName.CONTENT); - if (content != null) { - String extract = TreeBuilder.extractCharsetFromContent(content - // CPPONLY: , this - ); - // remember not to return early without releasing the string - if (extract != null) { - if (tokenizer.internalEncodingDeclaration(extract)) { - requestSuspension(); - } - } - Portability.releaseString(extract); - } - } - - public final void endTag(ElementName elementName) throws SAXException { - flushCharacters(); - needToDropLF = false; - int eltPos; - int group = elementName.getGroup(); - @Local String name = elementName.name; - endtagloop: for (;;) { - if (isInForeign()) { - if (stack[currentPtr].name != name) { - if (currentPtr == 0) { - errStrayEndTag(name); - } else { - errEndTagDidNotMatchCurrentOpenElement(name, stack[currentPtr].popName); - } - } - eltPos = currentPtr; - for (;;) { - if (eltPos == 0) { - assert fragment: "We can get this close to the root of the stack in foreign content only in the fragment case."; - break endtagloop; - } - if (stack[eltPos].name == name) { - while (currentPtr >= eltPos) { - pop(); - } - break endtagloop; - } - if (stack[--eltPos].ns == "http://www.w3.org/1999/xhtml") { - break; - } - } - } - switch (mode) { - case IN_TEMPLATE: - switch (group) { - case TEMPLATE: - // fall through to IN_HEAD - break; - default: - errStrayEndTag(name); - break endtagloop; - } - case IN_ROW: - switch (group) { - case TR: - eltPos = findLastOrRoot(TreeBuilder.TR); - if (eltPos == 0) { - assert fragment || isTemplateContents(); - errNoTableRowToClose(); - break endtagloop; - } - clearStackBackTo(eltPos); - pop(); - mode = IN_TABLE_BODY; - break endtagloop; - case TABLE: - eltPos = findLastOrRoot(TreeBuilder.TR); - if (eltPos == 0) { - assert fragment || isTemplateContents(); - errNoTableRowToClose(); - break endtagloop; - } - clearStackBackTo(eltPos); - pop(); - mode = IN_TABLE_BODY; - continue; - case TBODY_OR_THEAD_OR_TFOOT: - if (findLastInTableScope(name) == TreeBuilder.NOT_FOUND_ON_STACK) { - errStrayEndTag(name); - break endtagloop; - } - eltPos = findLastOrRoot(TreeBuilder.TR); - if (eltPos == 0) { - assert fragment || isTemplateContents(); - errNoTableRowToClose(); - break endtagloop; - } - clearStackBackTo(eltPos); - pop(); - mode = IN_TABLE_BODY; - continue; - case BODY: - case CAPTION: - case COL: - case COLGROUP: - case HTML: - case TD_OR_TH: - errStrayEndTag(name); - break endtagloop; - default: - // fall through to IN_TABLE - } - case IN_TABLE_BODY: - switch (group) { - case TBODY_OR_THEAD_OR_TFOOT: - eltPos = findLastOrRoot(name); - if (eltPos == 0) { - errStrayEndTag(name); - break endtagloop; - } - clearStackBackTo(eltPos); - pop(); - mode = IN_TABLE; - break endtagloop; - case TABLE: - eltPos = findLastInTableScopeOrRootTemplateTbodyTheadTfoot(); - if (eltPos == 0 || stack[eltPos].getGroup() == TEMPLATE) { - assert fragment || isTemplateContents(); - errStrayEndTag(name); - break endtagloop; - } - clearStackBackTo(eltPos); - pop(); - mode = IN_TABLE; - continue; - case BODY: - case CAPTION: - case COL: - case COLGROUP: - case HTML: - case TD_OR_TH: - case TR: - errStrayEndTag(name); - break endtagloop; - default: - // fall through to IN_TABLE - } - case IN_TABLE: - switch (group) { - case TABLE: - eltPos = findLast("table"); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - assert fragment || isTemplateContents(); - errStrayEndTag(name); - break endtagloop; - } - while (currentPtr >= eltPos) { - pop(); - } - resetTheInsertionMode(); - break endtagloop; - case BODY: - case CAPTION: - case COL: - case COLGROUP: - case HTML: - case TBODY_OR_THEAD_OR_TFOOT: - case TD_OR_TH: - case TR: - errStrayEndTag(name); - break endtagloop; - case TEMPLATE: - // fall through to IN_HEAD - break; - default: - errStrayEndTag(name); - // fall through to IN_BODY - } - case IN_CAPTION: - switch (group) { - case CAPTION: - eltPos = findLastInTableScope("caption"); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - break endtagloop; - } - generateImpliedEndTags(); - if (errorHandler != null && currentPtr != eltPos) { - errUnclosedElements(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - clearTheListOfActiveFormattingElementsUpToTheLastMarker(); - mode = IN_TABLE; - break endtagloop; - case TABLE: - errTableClosedWhileCaptionOpen(); - eltPos = findLastInTableScope("caption"); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - break endtagloop; - } - generateImpliedEndTags(); - if (errorHandler != null && currentPtr != eltPos) { - errUnclosedElements(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - clearTheListOfActiveFormattingElementsUpToTheLastMarker(); - mode = IN_TABLE; - continue; - case BODY: - case COL: - case COLGROUP: - case HTML: - case TBODY_OR_THEAD_OR_TFOOT: - case TD_OR_TH: - case TR: - errStrayEndTag(name); - break endtagloop; - default: - // fall through to IN_BODY - } - case IN_CELL: - switch (group) { - case TD_OR_TH: - eltPos = findLastInTableScope(name); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - errStrayEndTag(name); - break endtagloop; - } - generateImpliedEndTags(); - if (errorHandler != null && !isCurrent(name)) { - errUnclosedElements(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - clearTheListOfActiveFormattingElementsUpToTheLastMarker(); - mode = IN_ROW; - break endtagloop; - case TABLE: - case TBODY_OR_THEAD_OR_TFOOT: - case TR: - if (findLastInTableScope(name) == TreeBuilder.NOT_FOUND_ON_STACK) { - assert name == "tbody" || name == "tfoot" || name == "thead" || fragment || isTemplateContents(); - errStrayEndTag(name); - break endtagloop; - } - closeTheCell(findLastInTableScopeTdTh()); - continue; - case BODY: - case CAPTION: - case COL: - case COLGROUP: - case HTML: - errStrayEndTag(name); - break endtagloop; - default: - // fall through to IN_BODY - } - case FRAMESET_OK: - case IN_BODY: - switch (group) { - case BODY: - if (!isSecondOnStackBody()) { - assert fragment || isTemplateContents(); - errStrayEndTag(name); - break endtagloop; - } - assert currentPtr >= 1; - if (errorHandler != null) { - uncloseloop1: for (int i = 2; i <= currentPtr; i++) { - switch (stack[i].getGroup()) { - case DD_OR_DT: - case LI: - case OPTGROUP: - case OPTION: // is this possible? - case P: - case RB_OR_RTC: - case RT_OR_RP: - case TD_OR_TH: - case TBODY_OR_THEAD_OR_TFOOT: - break; - default: - errEndWithUnclosedElements(name); - break uncloseloop1; - } - } - } - mode = AFTER_BODY; - break endtagloop; - case HTML: - if (!isSecondOnStackBody()) { - assert fragment || isTemplateContents(); - errStrayEndTag(name); - break endtagloop; - } - if (errorHandler != null) { - uncloseloop2: for (int i = 0; i <= currentPtr; i++) { - switch (stack[i].getGroup()) { - case DD_OR_DT: - case LI: - case P: - case RB_OR_RTC: - case RT_OR_RP: - case TBODY_OR_THEAD_OR_TFOOT: - case TD_OR_TH: - case BODY: - case HTML: - break; - default: - errEndWithUnclosedElements(name); - break uncloseloop2; - } - } - } - mode = AFTER_BODY; - continue; - case DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU: - case UL_OR_OL_OR_DL: - case PRE_OR_LISTING: - case FIELDSET: - case BUTTON: - case ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY: - eltPos = findLastInScope(name); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - errStrayEndTag(name); - } else { - generateImpliedEndTags(); - if (errorHandler != null && !isCurrent(name)) { - errUnclosedElements(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - } - break endtagloop; - case FORM: - if (!isTemplateContents()) { - if (formPointer == null) { - errStrayEndTag(name); - break endtagloop; - } - formPointer = null; - eltPos = findLastInScope(name); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - errStrayEndTag(name); - break endtagloop; - } - generateImpliedEndTags(); - if (errorHandler != null && !isCurrent(name)) { - errUnclosedElements(eltPos, name); - } - removeFromStack(eltPos); - break endtagloop; - } else { - eltPos = findLastInScope(name); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - errStrayEndTag(name); - break endtagloop; - } - generateImpliedEndTags(); - if (errorHandler != null && !isCurrent(name)) { - errUnclosedElements(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - break endtagloop; - } - case P: - eltPos = findLastInButtonScope("p"); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - errNoElementToCloseButEndTagSeen("p"); - // XXX Can the 'in foreign' case happen anymore? - if (isInForeign()) { - errHtmlStartTagInForeignContext(name); - // Check for currentPtr for the fragment - // case. - while (currentPtr >= 0 && stack[currentPtr].ns != "http://www.w3.org/1999/xhtml") { - pop(); - } - } - appendVoidElementToCurrentMayFoster( - elementName, - HtmlAttributes.EMPTY_ATTRIBUTES); - break endtagloop; - } - generateImpliedEndTagsExceptFor("p"); - assert eltPos != TreeBuilder.NOT_FOUND_ON_STACK; - if (errorHandler != null && eltPos != currentPtr) { - errUnclosedElements(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - break endtagloop; - case LI: - eltPos = findLastInListScope(name); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - errNoElementToCloseButEndTagSeen(name); - } else { - generateImpliedEndTagsExceptFor(name); - if (errorHandler != null - && eltPos != currentPtr) { - errUnclosedElements(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - } - break endtagloop; - case DD_OR_DT: - eltPos = findLastInScope(name); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - errNoElementToCloseButEndTagSeen(name); - } else { - generateImpliedEndTagsExceptFor(name); - if (errorHandler != null - && eltPos != currentPtr) { - errUnclosedElements(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - } - break endtagloop; - case H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6: - eltPos = findLastInScopeHn(); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - errStrayEndTag(name); - } else { - generateImpliedEndTags(); - if (errorHandler != null && !isCurrent(name)) { - errUnclosedElements(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - } - break endtagloop; - case OBJECT: - case MARQUEE_OR_APPLET: - eltPos = findLastInScope(name); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - errStrayEndTag(name); - } else { - generateImpliedEndTags(); - if (errorHandler != null && !isCurrent(name)) { - errUnclosedElements(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - clearTheListOfActiveFormattingElementsUpToTheLastMarker(); - } - break endtagloop; - case BR: - errEndTagBr(); - if (isInForeign()) { - // XXX can this happen anymore? - errHtmlStartTagInForeignContext(name); - // Check for currentPtr for the fragment - // case. - while (currentPtr >= 0 && stack[currentPtr].ns != "http://www.w3.org/1999/xhtml") { - pop(); - } - } - reconstructTheActiveFormattingElements(); - appendVoidElementToCurrentMayFoster( - elementName, - HtmlAttributes.EMPTY_ATTRIBUTES); - break endtagloop; - case TEMPLATE: - // fall through to IN_HEAD; - break; - case AREA_OR_WBR: - // CPPONLY: case MENUITEM: - case PARAM_OR_SOURCE_OR_TRACK: - case EMBED: - case IMG: - case IMAGE: - case INPUT: - case KEYGEN: // XXX?? - case HR: - case ISINDEX: - case IFRAME: - case NOEMBED: // XXX??? - case NOFRAMES: // XXX?? - case SELECT: - case TABLE: - case TEXTAREA: // XXX?? - errStrayEndTag(name); - break endtagloop; - case NOSCRIPT: - if (scriptingEnabled) { - errStrayEndTag(name); - break endtagloop; - } else { - // fall through - } - case A: - case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U: - case FONT: - case NOBR: - if (adoptionAgencyEndTag(name)) { - break endtagloop; - } - // else handle like any other tag - default: - if (isCurrent(name)) { - pop(); - break endtagloop; - } - - eltPos = currentPtr; - for (;;) { - StackNode node = stack[eltPos]; - if (node.ns == "http://www.w3.org/1999/xhtml" && node.name == name) { - generateImpliedEndTags(); - if (errorHandler != null - && !isCurrent(name)) { - errUnclosedElements(eltPos, name); - } - while (currentPtr >= eltPos) { - pop(); - } - break endtagloop; - } else if (eltPos == 0 || node.isSpecial()) { - errStrayEndTag(name); - break endtagloop; - } - eltPos--; - } - } - case IN_HEAD: - switch (group) { - case HEAD: - pop(); - mode = AFTER_HEAD; - break endtagloop; - case BR: - case HTML: - case BODY: - pop(); - mode = AFTER_HEAD; - continue; - case TEMPLATE: - endTagTemplateInHead(); - break endtagloop; - default: - errStrayEndTag(name); - break endtagloop; - } - case IN_HEAD_NOSCRIPT: - switch (group) { - case NOSCRIPT: - pop(); - mode = IN_HEAD; - break endtagloop; - case BR: - errStrayEndTag(name); - pop(); - mode = IN_HEAD; - continue; - default: - errStrayEndTag(name); - break endtagloop; - } - case IN_COLUMN_GROUP: - switch (group) { - case COLGROUP: - if (currentPtr == 0 || stack[currentPtr].getGroup() == - TreeBuilder.TEMPLATE) { - assert fragment || isTemplateContents(); - errGarbageInColgroup(); - break endtagloop; - } - pop(); - mode = IN_TABLE; - break endtagloop; - case COL: - errStrayEndTag(name); - break endtagloop; - case TEMPLATE: - endTagTemplateInHead(); - break endtagloop; - default: - if (currentPtr == 0 || stack[currentPtr].getGroup() == - TreeBuilder.TEMPLATE) { - assert fragment || isTemplateContents(); - errGarbageInColgroup(); - break endtagloop; - } - pop(); - mode = IN_TABLE; - continue; - } - case IN_SELECT_IN_TABLE: - switch (group) { - case CAPTION: - case TABLE: - case TBODY_OR_THEAD_OR_TFOOT: - case TR: - case TD_OR_TH: - errEndTagSeenWithSelectOpen(name); - if (findLastInTableScope(name) != TreeBuilder.NOT_FOUND_ON_STACK) { - eltPos = findLastInTableScope("select"); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - assert fragment; - break endtagloop; // http://www.w3.org/Bugs/Public/show_bug.cgi?id=8375 - } - while (currentPtr >= eltPos) { - pop(); - } - resetTheInsertionMode(); - continue; - } else { - break endtagloop; - } - default: - // fall through to IN_SELECT - } - case IN_SELECT: - switch (group) { - case OPTION: - if (isCurrent("option")) { - pop(); - break endtagloop; - } else { - errStrayEndTag(name); - break endtagloop; - } - case OPTGROUP: - if (isCurrent("option") - && "optgroup" == stack[currentPtr - 1].name) { - pop(); - } - if (isCurrent("optgroup")) { - pop(); - } else { - errStrayEndTag(name); - } - break endtagloop; - case SELECT: - eltPos = findLastInTableScope("select"); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - assert fragment; - errStrayEndTag(name); - break endtagloop; - } - while (currentPtr >= eltPos) { - pop(); - } - resetTheInsertionMode(); - break endtagloop; - case TEMPLATE: - endTagTemplateInHead(); - break endtagloop; - default: - errStrayEndTag(name); - break endtagloop; - } - case AFTER_BODY: - switch (group) { - case HTML: - if (fragment) { - errStrayEndTag(name); - break endtagloop; - } else { - mode = AFTER_AFTER_BODY; - break endtagloop; - } - default: - errEndTagAfterBody(); - mode = framesetOk ? FRAMESET_OK : IN_BODY; - continue; - } - case IN_FRAMESET: - switch (group) { - case FRAMESET: - if (currentPtr == 0) { - assert fragment; - errStrayEndTag(name); - break endtagloop; - } - pop(); - if ((!fragment) && !isCurrent("frameset")) { - mode = AFTER_FRAMESET; - } - break endtagloop; - default: - errStrayEndTag(name); - break endtagloop; - } - case AFTER_FRAMESET: - switch (group) { - case HTML: - mode = AFTER_AFTER_FRAMESET; - break endtagloop; - default: - errStrayEndTag(name); - break endtagloop; - } - case INITIAL: - /* - * Parse error. - */ - // [NOCPP[ - switch (doctypeExpectation) { - case AUTO: - err("End tag seen without seeing a doctype first. Expected e.g. \u201C\u201D."); - break; - case HTML: - // ]NOCPP] - errEndTagSeenWithoutDoctype(); - // [NOCPP[ - break; - case HTML401_STRICT: - err("End tag seen without seeing a doctype first. Expected \u201C\u201D."); - break; - case HTML401_TRANSITIONAL: - err("End tag seen without seeing a doctype first. Expected \u201C\u201D."); - break; - case NO_DOCTYPE_ERRORS: - } - // ]NOCPP] - /* - * - * Set the document to quirks mode. - */ - documentModeInternal(DocumentMode.QUIRKS_MODE, null, null, - false); - /* - * Then, switch to the root element mode of the tree - * construction stage - */ - mode = BEFORE_HTML; - /* - * and reprocess the current token. - */ - continue; - case BEFORE_HTML: - switch (group) { - case HEAD: - case BR: - case HTML: - case BODY: - /* - * Create an HTMLElement node with the tag name - * html, in the HTML namespace. Append it to the - * Document object. - */ - appendHtmlElementToDocumentAndPush(); - /* Switch to the main mode */ - mode = BEFORE_HEAD; - /* - * reprocess the current token. - */ - continue; - default: - errStrayEndTag(name); - break endtagloop; - } - case BEFORE_HEAD: - switch (group) { - case HEAD: - case BR: - case HTML: - case BODY: - appendToCurrentNodeAndPushHeadElement(HtmlAttributes.EMPTY_ATTRIBUTES); - mode = IN_HEAD; - continue; - default: - errStrayEndTag(name); - break endtagloop; - } - case AFTER_HEAD: - switch (group) { - case TEMPLATE: - endTagTemplateInHead(); - break endtagloop; - case HTML: - case BODY: - case BR: - appendToCurrentNodeAndPushBodyElement(); - mode = FRAMESET_OK; - continue; - default: - errStrayEndTag(name); - break endtagloop; - } - case AFTER_AFTER_BODY: - errStrayEndTag(name); - mode = framesetOk ? FRAMESET_OK : IN_BODY; - continue; - case AFTER_AFTER_FRAMESET: - errStrayEndTag(name); - break endtagloop; - case TEXT: - // XXX need to manage insertion point here - pop(); - if (originalMode == AFTER_HEAD) { - silentPop(); - } - mode = originalMode; - break endtagloop; - } - } // endtagloop - } - - private void endTagTemplateInHead() throws SAXException { - int eltPos = findLast("template"); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - errStrayEndTag("template"); - return; - } - generateImpliedEndTags(); - if (errorHandler != null && !isCurrent("template")) { - errUnclosedElements(eltPos, "template"); - } - while (currentPtr >= eltPos) { - pop(); - } - clearTheListOfActiveFormattingElementsUpToTheLastMarker(); - popTemplateMode(); - resetTheInsertionMode(); - } - - private int findLastInTableScopeOrRootTemplateTbodyTheadTfoot() { - for (int i = currentPtr; i > 0; i--) { - if (stack[i].getGroup() == TreeBuilder.TBODY_OR_THEAD_OR_TFOOT || - stack[i].getGroup() == TreeBuilder.TEMPLATE) { - return i; - } - } - return 0; - } - - private int findLast(@Local String name) { - for (int i = currentPtr; i > 0; i--) { - if (stack[i].ns == "http://www.w3.org/1999/xhtml" && stack[i].name == name) { - return i; - } - } - return TreeBuilder.NOT_FOUND_ON_STACK; - } - - private int findLastInTableScope(@Local String name) { - for (int i = currentPtr; i > 0; i--) { - if (stack[i].ns == "http://www.w3.org/1999/xhtml") { - if (stack[i].name == name) { - return i; - } else if (stack[i].name == "table" || stack[i].name == "template") { - return TreeBuilder.NOT_FOUND_ON_STACK; - } - } - } - return TreeBuilder.NOT_FOUND_ON_STACK; - } - - private int findLastInButtonScope(@Local String name) { - for (int i = currentPtr; i > 0; i--) { - if (stack[i].ns == "http://www.w3.org/1999/xhtml") { - if (stack[i].name == name) { - return i; - } else if (stack[i].name == "button") { - return TreeBuilder.NOT_FOUND_ON_STACK; - } - } - - if (stack[i].isScoping()) { - return TreeBuilder.NOT_FOUND_ON_STACK; - } - } - return TreeBuilder.NOT_FOUND_ON_STACK; - } - - private int findLastInScope(@Local String name) { - for (int i = currentPtr; i > 0; i--) { - if (stack[i].ns == "http://www.w3.org/1999/xhtml" && stack[i].name == name) { - return i; - } else if (stack[i].isScoping()) { - return TreeBuilder.NOT_FOUND_ON_STACK; - } - } - return TreeBuilder.NOT_FOUND_ON_STACK; - } - - private int findLastInListScope(@Local String name) { - for (int i = currentPtr; i > 0; i--) { - if (stack[i].ns == "http://www.w3.org/1999/xhtml") { - if (stack[i].name == name) { - return i; - } else if (stack[i].name == "ul" || stack[i].name == "ol") { - return TreeBuilder.NOT_FOUND_ON_STACK; - } - } - - if (stack[i].isScoping()) { - return TreeBuilder.NOT_FOUND_ON_STACK; - } - } - return TreeBuilder.NOT_FOUND_ON_STACK; - } - - private int findLastInScopeHn() { - for (int i = currentPtr; i > 0; i--) { - if (stack[i].getGroup() == TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6) { - return i; - } else if (stack[i].isScoping()) { - return TreeBuilder.NOT_FOUND_ON_STACK; - } - } - return TreeBuilder.NOT_FOUND_ON_STACK; - } - - private void generateImpliedEndTagsExceptFor(@Local String name) - throws SAXException { - for (;;) { - StackNode node = stack[currentPtr]; - switch (node.getGroup()) { - case P: - case LI: - case DD_OR_DT: - case OPTION: - case OPTGROUP: - case RB_OR_RTC: - case RT_OR_RP: - if (node.ns == "http://www.w3.org/1999/xhtml" && node.name == name) { - return; - } - pop(); - continue; - default: - return; - } - } - } - - private void generateImpliedEndTags() throws SAXException { - for (;;) { - switch (stack[currentPtr].getGroup()) { - case P: - case LI: - case DD_OR_DT: - case OPTION: - case OPTGROUP: - case RB_OR_RTC: - case RT_OR_RP: - pop(); - continue; - default: - return; - } - } - } - - private boolean isSecondOnStackBody() { - return currentPtr >= 1 && stack[1].getGroup() == TreeBuilder.BODY; - } - - private void documentModeInternal(DocumentMode m, String publicIdentifier, - String systemIdentifier, boolean html4SpecificAdditionalErrorChecks) - throws SAXException { - - if (isSrcdocDocument) { - // Srcdoc documents are always rendered in standards mode. - quirks = false; - if (documentModeHandler != null) { - documentModeHandler.documentMode( - DocumentMode.STANDARDS_MODE - // [NOCPP[ - , null, null, false - // ]NOCPP] - ); - } - return; - } - - quirks = (m == DocumentMode.QUIRKS_MODE); - if (documentModeHandler != null) { - documentModeHandler.documentMode( - m - // [NOCPP[ - , publicIdentifier, systemIdentifier, - html4SpecificAdditionalErrorChecks - // ]NOCPP] - ); - } - // [NOCPP[ - documentMode(m, publicIdentifier, systemIdentifier, - html4SpecificAdditionalErrorChecks); - // ]NOCPP] - } - - private boolean isAlmostStandards(String publicIdentifier, - String systemIdentifier) { - if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "-//w3c//dtd xhtml 1.0 transitional//en", publicIdentifier)) { - return true; - } - if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "-//w3c//dtd xhtml 1.0 frameset//en", publicIdentifier)) { - return true; - } - if (systemIdentifier != null) { - if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "-//w3c//dtd html 4.01 transitional//en", publicIdentifier)) { - return true; - } - if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "-//w3c//dtd html 4.01 frameset//en", publicIdentifier)) { - return true; - } - } - return false; - } - - private boolean isQuirky(@Local String name, String publicIdentifier, - String systemIdentifier, boolean forceQuirks) { - if (forceQuirks) { - return true; - } - if (name != HTML_LOCAL) { - return true; - } - if (publicIdentifier != null) { - for (int i = 0; i < TreeBuilder.QUIRKY_PUBLIC_IDS.length; i++) { - if (Portability.lowerCaseLiteralIsPrefixOfIgnoreAsciiCaseString( - TreeBuilder.QUIRKY_PUBLIC_IDS[i], publicIdentifier)) { - return true; - } - } - if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "-//w3o//dtd w3 html strict 3.0//en//", publicIdentifier) - || Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "-/w3c/dtd html 4.0 transitional/en", - publicIdentifier) - || Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "html", publicIdentifier)) { - return true; - } - } - if (systemIdentifier == null) { - if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "-//w3c//dtd html 4.01 transitional//en", publicIdentifier)) { - return true; - } else if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "-//w3c//dtd html 4.01 frameset//en", publicIdentifier)) { - return true; - } - } else if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd", - systemIdentifier)) { - return true; - } - return false; - } - - private void closeTheCell(int eltPos) throws SAXException { - generateImpliedEndTags(); - if (errorHandler != null && eltPos != currentPtr) { - errUnclosedElementsCell(eltPos); - } - while (currentPtr >= eltPos) { - pop(); - } - clearTheListOfActiveFormattingElementsUpToTheLastMarker(); - mode = IN_ROW; - return; - } - - private int findLastInTableScopeTdTh() { - for (int i = currentPtr; i > 0; i--) { - @Local String name = stack[i].name; - if (stack[i].ns == "http://www.w3.org/1999/xhtml") { - if ("td" == name || "th" == name) { - return i; - } else if (name == "table" || name == "template") { - return TreeBuilder.NOT_FOUND_ON_STACK; - } - } - } - return TreeBuilder.NOT_FOUND_ON_STACK; - } - - private void clearStackBackTo(int eltPos) throws SAXException { - int eltGroup = stack[eltPos].getGroup(); - while (currentPtr > eltPos) { // > not >= intentional - if (stack[currentPtr].ns == "http://www.w3.org/1999/xhtml" - && stack[currentPtr].getGroup() == TEMPLATE - && (eltGroup == TABLE || eltGroup == TBODY_OR_THEAD_OR_TFOOT|| eltGroup == TR || eltPos == 0)) { - return; - } - pop(); - } - } - - private void resetTheInsertionMode() { - StackNode node; - @Local String name; - @NsUri String ns; - for (int i = currentPtr; i >= 0; i--) { - node = stack[i]; - name = node.name; - ns = node.ns; - if (i == 0) { - if (!(contextNamespace == "http://www.w3.org/1999/xhtml" && (contextName == "td" || contextName == "th"))) { - if (fragment) { - // Make sure we are parsing a fragment otherwise the context element doesn't make sense. - name = contextName; - ns = contextNamespace; - } - } else { - mode = framesetOk ? FRAMESET_OK : IN_BODY; // XXX from Hixie's email - return; - } - } - if ("select" == name) { - int ancestorIndex = i; - while (ancestorIndex > 0) { - StackNode ancestor = stack[ancestorIndex--]; - if ("http://www.w3.org/1999/xhtml" == ancestor.ns) { - if ("template" == ancestor.name) { - break; - } - if ("table" == ancestor.name) { - mode = IN_SELECT_IN_TABLE; - return; - } - } - } - mode = IN_SELECT; - return; - } else if ("td" == name || "th" == name) { - mode = IN_CELL; - return; - } else if ("tr" == name) { - mode = IN_ROW; - return; - } else if ("tbody" == name || "thead" == name || "tfoot" == name) { - mode = IN_TABLE_BODY; - return; - } else if ("caption" == name) { - mode = IN_CAPTION; - return; - } else if ("colgroup" == name) { - mode = IN_COLUMN_GROUP; - return; - } else if ("table" == name) { - mode = IN_TABLE; - return; - } else if ("http://www.w3.org/1999/xhtml" != ns) { - mode = framesetOk ? FRAMESET_OK : IN_BODY; - return; - } else if ("template" == name) { - assert templateModePtr >= 0; - mode = templateModeStack[templateModePtr]; - return; - } else if ("head" == name) { - if (name == contextName) { - mode = framesetOk ? FRAMESET_OK : IN_BODY; // really - } else { - mode = IN_HEAD; - } - return; - } else if ("body" == name) { - mode = framesetOk ? FRAMESET_OK : IN_BODY; - return; - } else if ("frameset" == name) { - // TODO: Fragment case. Add error reporting. - mode = IN_FRAMESET; - return; - } else if ("html" == name) { - if (headPointer == null) { - // TODO: Fragment case. Add error reporting. - mode = BEFORE_HEAD; - } else { - mode = AFTER_HEAD; - } - return; - } else if (i == 0) { - mode = framesetOk ? FRAMESET_OK : IN_BODY; - return; - } - } - } - - /** - * @throws SAXException - * - */ - private void implicitlyCloseP() throws SAXException { - int eltPos = findLastInButtonScope("p"); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - return; - } - generateImpliedEndTagsExceptFor("p"); - if (errorHandler != null && eltPos != currentPtr) { - errUnclosedElementsImplied(eltPos, "p"); - } - while (currentPtr >= eltPos) { - pop(); - } - } - - private boolean debugOnlyClearLastStackSlot() { - stack[currentPtr] = null; - return true; - } - - private boolean debugOnlyClearLastListSlot() { - listOfActiveFormattingElements[listPtr] = null; - return true; - } - - private void pushTemplateMode(int mode) { - templateModePtr++; - if (templateModePtr == templateModeStack.length) { - int[] newStack = new int[templateModeStack.length + 64]; - System.arraycopy(templateModeStack, 0, newStack, 0, templateModeStack.length); - templateModeStack = newStack; - } - templateModeStack[templateModePtr] = mode; - } - - @SuppressWarnings("unchecked") private void push(StackNode node) throws SAXException { - currentPtr++; - if (currentPtr == stack.length) { - StackNode[] newStack = new StackNode[stack.length + 64]; - System.arraycopy(stack, 0, newStack, 0, stack.length); - stack = newStack; - } - stack[currentPtr] = node; - elementPushed(node.ns, node.popName, node.node); - } - - @SuppressWarnings("unchecked") private void silentPush(StackNode node) throws SAXException { - currentPtr++; - if (currentPtr == stack.length) { - StackNode[] newStack = new StackNode[stack.length + 64]; - System.arraycopy(stack, 0, newStack, 0, stack.length); - stack = newStack; - } - stack[currentPtr] = node; - } - - @SuppressWarnings("unchecked") private void append(StackNode node) { - listPtr++; - if (listPtr == listOfActiveFormattingElements.length) { - StackNode[] newList = new StackNode[listOfActiveFormattingElements.length + 64]; - System.arraycopy(listOfActiveFormattingElements, 0, newList, 0, - listOfActiveFormattingElements.length); - listOfActiveFormattingElements = newList; - } - listOfActiveFormattingElements[listPtr] = node; - } - - @Inline private void insertMarker() { - append(null); - } - - private void clearTheListOfActiveFormattingElementsUpToTheLastMarker() { - while (listPtr > -1) { - if (listOfActiveFormattingElements[listPtr] == null) { - --listPtr; - return; - } - listOfActiveFormattingElements[listPtr].release(); - --listPtr; - } - } - - @Inline private boolean isCurrent(@Local String name) { - return stack[currentPtr].ns == "http://www.w3.org/1999/xhtml" && - name == stack[currentPtr].name; - } - - private void removeFromStack(int pos) throws SAXException { - if (currentPtr == pos) { - pop(); - } else { - fatal(); - stack[pos].release(); - System.arraycopy(stack, pos + 1, stack, pos, currentPtr - pos); - assert debugOnlyClearLastStackSlot(); - currentPtr--; - } - } - - private void removeFromStack(StackNode node) throws SAXException { - if (stack[currentPtr] == node) { - pop(); - } else { - int pos = currentPtr - 1; - while (pos >= 0 && stack[pos] != node) { - pos--; - } - if (pos == -1) { - // dead code? - return; - } - fatal(); - node.release(); - System.arraycopy(stack, pos + 1, stack, pos, currentPtr - pos); - currentPtr--; - } - } - - private void removeFromListOfActiveFormattingElements(int pos) { - assert listOfActiveFormattingElements[pos] != null; - listOfActiveFormattingElements[pos].release(); - if (pos == listPtr) { - assert debugOnlyClearLastListSlot(); - listPtr--; - return; - } - assert pos < listPtr; - System.arraycopy(listOfActiveFormattingElements, pos + 1, - listOfActiveFormattingElements, pos, listPtr - pos); - assert debugOnlyClearLastListSlot(); - listPtr--; - } - - /** - * Adoption agency algorithm. - * - * @param name subject as described in the specified algorithm. - * @return Returns true if the algorithm has completed and there is nothing remaining to - * be done. Returns false if the algorithm needs to "act as described in the 'any other - * end tag' entry" as described in the specified algorithm. - * @throws SAXException - */ - private boolean adoptionAgencyEndTag(@Local String name) throws SAXException { - // This check intends to ensure that for properly nested tags, closing tags will match - // against the stack instead of the listOfActiveFormattingElements. - if (stack[currentPtr].ns == "http://www.w3.org/1999/xhtml" && - stack[currentPtr].name == name && - findInListOfActiveFormattingElements(stack[currentPtr]) == -1) { - // If the current element matches the name but isn't on the list of active - // formatting elements, then it is possible that the list was mangled by the Noah's Ark - // clause. In this case, we want to match the end tag against the stack instead of - // proceeding with the AAA algorithm that may match against the list of - // active formatting elements (and possibly mangle the tree in unexpected ways). - pop(); - return true; - } - - // If you crash around here, perhaps some stack node variable claimed to - // be a weak ref isn't. - for (int i = 0; i < 8; ++i) { - int formattingEltListPos = listPtr; - while (formattingEltListPos > -1) { - StackNode listNode = listOfActiveFormattingElements[formattingEltListPos]; // weak ref - if (listNode == null) { - formattingEltListPos = -1; - break; - } else if (listNode.name == name) { - break; - } - formattingEltListPos--; - } - if (formattingEltListPos == -1) { - return false; - } - // this *looks* like a weak ref to the list of formatting elements - StackNode formattingElt = listOfActiveFormattingElements[formattingEltListPos]; - int formattingEltStackPos = currentPtr; - boolean inScope = true; - while (formattingEltStackPos > -1) { - StackNode node = stack[formattingEltStackPos]; // weak ref - if (node == formattingElt) { - break; - } else if (node.isScoping()) { - inScope = false; - } - formattingEltStackPos--; - } - if (formattingEltStackPos == -1) { - errNoElementToCloseButEndTagSeen(name); - removeFromListOfActiveFormattingElements(formattingEltListPos); - return true; - } - if (!inScope) { - errNoElementToCloseButEndTagSeen(name); - return true; - } - // stackPos now points to the formatting element and it is in scope - if (formattingEltStackPos != currentPtr) { - errEndTagViolatesNestingRules(name); - } - int furthestBlockPos = formattingEltStackPos + 1; - while (furthestBlockPos <= currentPtr) { - StackNode node = stack[furthestBlockPos]; // weak ref - assert furthestBlockPos > 0: "How is formattingEltStackPos + 1 not > 0?"; - if (node.isSpecial()) { - break; - } - furthestBlockPos++; - } - if (furthestBlockPos > currentPtr) { - // no furthest block - while (currentPtr >= formattingEltStackPos) { - pop(); - } - removeFromListOfActiveFormattingElements(formattingEltListPos); - return true; - } - StackNode commonAncestor = stack[formattingEltStackPos - 1]; // weak ref - StackNode furthestBlock = stack[furthestBlockPos]; // weak ref - // detachFromParent(furthestBlock.node); XXX AAA CHANGE - int bookmark = formattingEltListPos; - int nodePos = furthestBlockPos; - StackNode lastNode = furthestBlock; // weak ref - int j = 0; - for (;;) { - ++j; - nodePos--; - if (nodePos == formattingEltStackPos) { - break; - } - StackNode node = stack[nodePos]; // weak ref - int nodeListPos = findInListOfActiveFormattingElements(node); - - if (j > 3 && nodeListPos != -1) { - removeFromListOfActiveFormattingElements(nodeListPos); - - // Adjust the indices into the list to account - // for the removal of nodeListPos. - if (nodeListPos <= formattingEltListPos) { - formattingEltListPos--; - } - if (nodeListPos <= bookmark) { - bookmark--; - } - - // Update position to reflect removal from list. - nodeListPos = -1; - } - - if (nodeListPos == -1) { - assert formattingEltStackPos < nodePos; - assert bookmark < nodePos; - assert furthestBlockPos > nodePos; - removeFromStack(nodePos); // node is now a bad pointer in C++ - furthestBlockPos--; - continue; - } - // now node is both on stack and in the list - if (nodePos == furthestBlockPos) { - bookmark = nodeListPos + 1; - } - // if (hasChildren(node.node)) { XXX AAA CHANGE - assert node == listOfActiveFormattingElements[nodeListPos]; - assert node == stack[nodePos]; - T clone = createElement("http://www.w3.org/1999/xhtml", - node.name, node.attributes.cloneAttributes(null), commonAncestor.node); - StackNode newNode = new StackNode(node.getFlags(), node.ns, - node.name, clone, node.popName, node.attributes - // [NOCPP[ - , node.getLocator() - // ]NOCPP] - ); // creation ownership goes to stack - node.dropAttributes(); // adopt ownership to newNode - stack[nodePos] = newNode; - newNode.retain(); // retain for list - listOfActiveFormattingElements[nodeListPos] = newNode; - node.release(); // release from stack - node.release(); // release from list - node = newNode; - // } XXX AAA CHANGE - detachFromParent(lastNode.node); - appendElement(lastNode.node, node.node); - lastNode = node; - } - if (commonAncestor.isFosterParenting()) { - fatal(); - detachFromParent(lastNode.node); - insertIntoFosterParent(lastNode.node); - } else { - detachFromParent(lastNode.node); - appendElement(lastNode.node, commonAncestor.node); - } - T clone = createElement("http://www.w3.org/1999/xhtml", - formattingElt.name, - formattingElt.attributes.cloneAttributes(null), furthestBlock.node); - StackNode formattingClone = new StackNode( - formattingElt.getFlags(), formattingElt.ns, - formattingElt.name, clone, formattingElt.popName, - formattingElt.attributes - // [NOCPP[ - , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); // Ownership transfers to stack below - formattingElt.dropAttributes(); // transfer ownership to - // formattingClone - appendChildrenToNewParent(furthestBlock.node, clone); - appendElement(clone, furthestBlock.node); - removeFromListOfActiveFormattingElements(formattingEltListPos); - insertIntoListOfActiveFormattingElements(formattingClone, bookmark); - assert formattingEltStackPos < furthestBlockPos; - removeFromStack(formattingEltStackPos); - // furthestBlockPos is now off by one and points to the slot after - // it - insertIntoStack(formattingClone, furthestBlockPos); - } - return true; - } - - private void insertIntoStack(StackNode node, int position) - throws SAXException { - assert currentPtr + 1 < stack.length; - assert position <= currentPtr + 1; - if (position == currentPtr + 1) { - push(node); - } else { - System.arraycopy(stack, position, stack, position + 1, - (currentPtr - position) + 1); - currentPtr++; - stack[position] = node; - } - } - - private void insertIntoListOfActiveFormattingElements( - StackNode formattingClone, int bookmark) { - formattingClone.retain(); - assert listPtr + 1 < listOfActiveFormattingElements.length; - if (bookmark <= listPtr) { - System.arraycopy(listOfActiveFormattingElements, bookmark, - listOfActiveFormattingElements, bookmark + 1, - (listPtr - bookmark) + 1); - } - listPtr++; - listOfActiveFormattingElements[bookmark] = formattingClone; - } - - private int findInListOfActiveFormattingElements(StackNode node) { - for (int i = listPtr; i >= 0; i--) { - if (node == listOfActiveFormattingElements[i]) { - return i; - } - } - return -1; - } - - private int findInListOfActiveFormattingElementsContainsBetweenEndAndLastMarker( - @Local String name) { - for (int i = listPtr; i >= 0; i--) { - StackNode node = listOfActiveFormattingElements[i]; - if (node == null) { - return -1; - } else if (node.name == name) { - return i; - } - } - return -1; - } - - - private void maybeForgetEarlierDuplicateFormattingElement( - @Local String name, HtmlAttributes attributes) throws SAXException { - int candidate = -1; - int count = 0; - for (int i = listPtr; i >= 0; i--) { - StackNode node = listOfActiveFormattingElements[i]; - if (node == null) { - break; - } - if (node.name == name && node.attributes.equalsAnother(attributes)) { - candidate = i; - ++count; - } - } - if (count >= 3) { - removeFromListOfActiveFormattingElements(candidate); - } - } - - private int findLastOrRoot(@Local String name) { - for (int i = currentPtr; i > 0; i--) { - if (stack[i].ns == "http://www.w3.org/1999/xhtml" && stack[i].name == name) { - return i; - } - } - return 0; - } - - private int findLastOrRoot(int group) { - for (int i = currentPtr; i > 0; i--) { - if (stack[i].getGroup() == group) { - return i; - } - } - return 0; - } - - /** - * Attempt to add attribute to the body element. - * @param attributes the attributes - * @return true iff the attributes were added - * @throws SAXException - */ - private boolean addAttributesToBody(HtmlAttributes attributes) - throws SAXException { - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - // ]NOCPP] - if (currentPtr >= 1) { - StackNode body = stack[1]; - if (body.getGroup() == TreeBuilder.BODY) { - addAttributesToElement(body.node, attributes); - return true; - } - } - return false; - } - - private void addAttributesToHtml(HtmlAttributes attributes) - throws SAXException { - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - // ]NOCPP] - addAttributesToElement(stack[0].node, attributes); - } - - private void pushHeadPointerOntoStack() throws SAXException { - assert headPointer != null; - assert mode == AFTER_HEAD; - fatal(); - silentPush(new StackNode(ElementName.HEAD, headPointer - // [NOCPP[ - , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - )); - } - - /** - * @throws SAXException - * - */ - private void reconstructTheActiveFormattingElements() throws SAXException { - if (listPtr == -1) { - return; - } - StackNode mostRecent = listOfActiveFormattingElements[listPtr]; - if (mostRecent == null || isInStack(mostRecent)) { - return; - } - int entryPos = listPtr; - for (;;) { - entryPos--; - if (entryPos == -1) { - break; - } - if (listOfActiveFormattingElements[entryPos] == null) { - break; - } - if (isInStack(listOfActiveFormattingElements[entryPos])) { - break; - } - } - while (entryPos < listPtr) { - entryPos++; - StackNode entry = listOfActiveFormattingElements[entryPos]; - StackNode currentNode = stack[currentPtr]; - - T clone; - if (currentNode.isFosterParenting()) { - clone = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", entry.name, - entry.attributes.cloneAttributes(null)); - } else { - clone = createElement("http://www.w3.org/1999/xhtml", entry.name, - entry.attributes.cloneAttributes(null), currentNode.node); - appendElement(clone, currentNode.node); - } - - StackNode entryClone = new StackNode(entry.getFlags(), - entry.ns, entry.name, clone, entry.popName, - entry.attributes - // [NOCPP[ - , entry.getLocator() - // ]NOCPP] - ); - - entry.dropAttributes(); // transfer ownership to entryClone - - push(entryClone); - // stack takes ownership of the local variable - listOfActiveFormattingElements[entryPos] = entryClone; - // overwriting the old entry on the list, so release & retain - entry.release(); - entryClone.retain(); - } - } - - private void insertIntoFosterParent(T child) throws SAXException { - int tablePos = findLastOrRoot(TreeBuilder.TABLE); - int templatePos = findLastOrRoot(TreeBuilder.TEMPLATE); - - if (templatePos >= tablePos) { - appendElement(child, stack[templatePos].node); - return; - } - - StackNode node = stack[tablePos]; - insertFosterParentedChild(child, node.node, stack[tablePos - 1].node); - } - - private T createAndInsertFosterParentedElement(@NsUri String ns, @Local String name, - HtmlAttributes attributes) throws SAXException { - return createAndInsertFosterParentedElement(ns, name, attributes, null); - } - - private T createAndInsertFosterParentedElement(@NsUri String ns, @Local String name, - HtmlAttributes attributes, T form) throws SAXException { - int tablePos = findLastOrRoot(TreeBuilder.TABLE); - int templatePos = findLastOrRoot(TreeBuilder.TEMPLATE); - - if (templatePos >= tablePos) { - T child = createElement(ns, name, attributes, form, stack[templatePos].node); - appendElement(child, stack[templatePos].node); - return child; - } - - StackNode node = stack[tablePos]; - return createAndInsertFosterParentedElement(ns, name, attributes, form, node.node, stack[tablePos - 1].node); - } - - private boolean isInStack(StackNode node) { - for (int i = currentPtr; i >= 0; i--) { - if (stack[i] == node) { - return true; - } - } - return false; - } - - private void popTemplateMode() { - templateModePtr--; - } - - private void pop() throws SAXException { - StackNode node = stack[currentPtr]; - assert debugOnlyClearLastStackSlot(); - currentPtr--; - elementPopped(node.ns, node.popName, node.node); - node.release(); - } - - private void silentPop() throws SAXException { - StackNode node = stack[currentPtr]; - assert debugOnlyClearLastStackSlot(); - currentPtr--; - node.release(); - } - - private void popOnEof() throws SAXException { - StackNode node = stack[currentPtr]; - assert debugOnlyClearLastStackSlot(); - currentPtr--; - markMalformedIfScript(node.node); - elementPopped(node.ns, node.popName, node.node); - node.release(); - } - - // [NOCPP[ - private void checkAttributes(HtmlAttributes attributes, @NsUri String ns) - throws SAXException { - if (errorHandler != null) { - int len = attributes.getXmlnsLength(); - for (int i = 0; i < len; i++) { - AttributeName name = attributes.getXmlnsAttributeName(i); - if (name == AttributeName.XMLNS) { - if (html4) { - err("Attribute \u201Cxmlns\u201D not allowed here. (HTML4-only error.)"); - } else { - String xmlns = attributes.getXmlnsValue(i); - if (!ns.equals(xmlns)) { - err("Bad value \u201C" - + xmlns - + "\u201D for the attribute \u201Cxmlns\u201D (only \u201C" - + ns + "\u201D permitted here)."); - switch (namePolicy) { - case ALTER_INFOSET: - // fall through - case ALLOW: - warn("Attribute \u201Cxmlns\u201D is not serializable as XML 1.0."); - break; - case FATAL: - fatal("Attribute \u201Cxmlns\u201D is not serializable as XML 1.0."); - break; - } - } - } - } else if (ns != "http://www.w3.org/1999/xhtml" - && name == AttributeName.XMLNS_XLINK) { - String xmlns = attributes.getXmlnsValue(i); - if (!"http://www.w3.org/1999/xlink".equals(xmlns)) { - err("Bad value \u201C" - + xmlns - + "\u201D for the attribute \u201Cxmlns:link\u201D (only \u201Chttp://www.w3.org/1999/xlink\u201D permitted here)."); - switch (namePolicy) { - case ALTER_INFOSET: - // fall through - case ALLOW: - warn("Attribute \u201Cxmlns:xlink\u201D with a value other than \u201Chttp://www.w3.org/1999/xlink\u201D is not serializable as XML 1.0 without changing document semantics."); - break; - case FATAL: - fatal("Attribute \u201Cxmlns:xlink\u201D with a value other than \u201Chttp://www.w3.org/1999/xlink\u201D is not serializable as XML 1.0 without changing document semantics."); - break; - } - } - } else { - err("Attribute \u201C" + attributes.getXmlnsLocalName(i) - + "\u201D not allowed here."); - switch (namePolicy) { - case ALTER_INFOSET: - // fall through - case ALLOW: - warn("Attribute with the local name \u201C" - + attributes.getXmlnsLocalName(i) - + "\u201D is not serializable as XML 1.0."); - break; - case FATAL: - fatal("Attribute with the local name \u201C" - + attributes.getXmlnsLocalName(i) - + "\u201D is not serializable as XML 1.0."); - break; - } - } - } - } - attributes.processNonNcNames(this, namePolicy); - } - - private String checkPopName(@Local String name) throws SAXException { - if (NCName.isNCName(name)) { - return name; - } else { - switch (namePolicy) { - case ALLOW: - warn("Element name \u201C" + name - + "\u201D cannot be represented as XML 1.0."); - return name; - case ALTER_INFOSET: - warn("Element name \u201C" + name - + "\u201D cannot be represented as XML 1.0."); - return NCName.escapeName(name); - case FATAL: - fatal("Element name \u201C" + name - + "\u201D cannot be represented as XML 1.0."); - } - } - return null; // keep compiler happy - } - - // ]NOCPP] - - private void appendHtmlElementToDocumentAndPush(HtmlAttributes attributes) - throws SAXException { - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - // ]NOCPP] - T elt = createHtmlElementSetAsRoot(attributes); - StackNode node = new StackNode(ElementName.HTML, - elt - // [NOCPP[ - , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); - push(node); - } - - private void appendHtmlElementToDocumentAndPush() throws SAXException { - appendHtmlElementToDocumentAndPush(tokenizer.emptyAttributes()); - } - - private void appendToCurrentNodeAndPushHeadElement(HtmlAttributes attributes) - throws SAXException { - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - // ]NOCPP] - T currentNode = stack[currentPtr].node; - T elt = createElement("http://www.w3.org/1999/xhtml", "head", attributes, currentNode); - appendElement(elt, currentNode); - headPointer = elt; - StackNode node = new StackNode(ElementName.HEAD, - elt - // [NOCPP[ - , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); - push(node); - } - - private void appendToCurrentNodeAndPushBodyElement(HtmlAttributes attributes) - throws SAXException { - appendToCurrentNodeAndPushElement(ElementName.BODY, - attributes); - } - - private void appendToCurrentNodeAndPushBodyElement() throws SAXException { - appendToCurrentNodeAndPushBodyElement(tokenizer.emptyAttributes()); - } - - private void appendToCurrentNodeAndPushFormElementMayFoster( - HtmlAttributes attributes) throws SAXException { - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - // ]NOCPP] - - T elt; - StackNode current = stack[currentPtr]; - if (current.isFosterParenting()) { - fatal(); - elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", "form", attributes); - } else { - elt = createElement("http://www.w3.org/1999/xhtml", "form", attributes, current.node); - appendElement(elt, current.node); - } - - if (!isTemplateContents()) { - formPointer = elt; - } - - StackNode node = new StackNode(ElementName.FORM, - elt - // [NOCPP[ - , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); - push(node); - } - - private void appendToCurrentNodeAndPushFormattingElementMayFoster( - ElementName elementName, HtmlAttributes attributes) - throws SAXException { - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - // ]NOCPP] - // This method can't be called for custom elements - HtmlAttributes clone = attributes.cloneAttributes(null); - // Attributes must not be read after calling createElement, because - // createElement may delete attributes in C++. - T elt; - StackNode current = stack[currentPtr]; - if (current.isFosterParenting()) { - fatal(); - elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", elementName.name, attributes); - } else { - elt = createElement("http://www.w3.org/1999/xhtml", elementName.name, attributes, current.node); - appendElement(elt, current.node); - } - StackNode node = new StackNode(elementName, elt, clone - // [NOCPP[ - , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); - push(node); - append(node); - node.retain(); // append doesn't retain itself - } - - private void appendToCurrentNodeAndPushElement(ElementName elementName, - HtmlAttributes attributes) - throws SAXException { - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - // ]NOCPP] - // This method can't be called for custom elements - T currentNode = stack[currentPtr].node; - T elt = createElement("http://www.w3.org/1999/xhtml", elementName.name, attributes, currentNode); - appendElement(elt, currentNode); - if (ElementName.TEMPLATE == elementName) { - elt = getDocumentFragmentForTemplate(elt); - } - StackNode node = new StackNode(elementName, elt - // [NOCPP[ - , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); - push(node); - } - - private void appendToCurrentNodeAndPushElementMayFoster(ElementName elementName, - HtmlAttributes attributes) - throws SAXException { - @Local String popName = elementName.name; - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - if (elementName.isCustom()) { - popName = checkPopName(popName); - } - // ]NOCPP] - T elt; - StackNode current = stack[currentPtr]; - if (current.isFosterParenting()) { - fatal(); - elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", popName, attributes); - } else { - elt = createElement("http://www.w3.org/1999/xhtml", popName, attributes, current.node); - appendElement(elt, current.node); - } - StackNode node = new StackNode(elementName, elt, popName - // [NOCPP[ - , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); - push(node); - } - - private void appendToCurrentNodeAndPushElementMayFosterMathML( - ElementName elementName, HtmlAttributes attributes) - throws SAXException { - @Local String popName = elementName.name; - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1998/Math/MathML"); - if (elementName.isCustom()) { - popName = checkPopName(popName); - } - // ]NOCPP] - boolean markAsHtmlIntegrationPoint = false; - if (ElementName.ANNOTATION_XML == elementName - && annotationXmlEncodingPermitsHtml(attributes)) { - markAsHtmlIntegrationPoint = true; - } - // Attributes must not be read after calling createElement(), since - // createElement may delete the object in C++. - T elt; - StackNode current = stack[currentPtr]; - if (current.isFosterParenting()) { - fatal(); - elt = createAndInsertFosterParentedElement("http://www.w3.org/1998/Math/MathML", popName, attributes); - } else { - elt = createElement("http://www.w3.org/1998/Math/MathML", popName, attributes, current.node); - appendElement(elt, current.node); - } - StackNode node = new StackNode(elementName, elt, popName, - markAsHtmlIntegrationPoint - // [NOCPP[ - , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); - push(node); - } - - // [NOCPP[ - T getDocumentFragmentForTemplate(T template) { - return template; - } - - T getFormPointerForContext(T context) { - return null; - } - // ]NOCPP] - - private boolean annotationXmlEncodingPermitsHtml(HtmlAttributes attributes) { - String encoding = attributes.getValue(AttributeName.ENCODING); - if (encoding == null) { - return false; - } - return Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "application/xhtml+xml", encoding) - || Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( - "text/html", encoding); - } - - private void appendToCurrentNodeAndPushElementMayFosterSVG( - ElementName elementName, HtmlAttributes attributes) - throws SAXException { - @Local String popName = elementName.camelCaseName; - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/2000/svg"); - if (elementName.isCustom()) { - popName = checkPopName(popName); - } - // ]NOCPP] - T elt; - StackNode current = stack[currentPtr]; - if (current.isFosterParenting()) { - fatal(); - elt = createAndInsertFosterParentedElement("http://www.w3.org/2000/svg", popName, attributes); - } else { - elt = createElement("http://www.w3.org/2000/svg", popName, attributes, current.node); - appendElement(elt, current.node); - } - StackNode node = new StackNode(elementName, popName, elt - // [NOCPP[ - , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); - push(node); - } - - private void appendToCurrentNodeAndPushElementMayFoster(ElementName elementName, - HtmlAttributes attributes, T form) - throws SAXException { - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - // ]NOCPP] - // Can't be called for custom elements - T elt; - T formOwner = form == null || fragment || isTemplateContents() ? null : form; - StackNode current = stack[currentPtr]; - if (current.isFosterParenting()) { - fatal(); - elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", elementName.name, - attributes, formOwner); - } else { - elt = createElement("http://www.w3.org/1999/xhtml", elementName.name, - attributes, formOwner, current.node); - appendElement(elt, current.node); - } - StackNode node = new StackNode(elementName, elt - // [NOCPP[ - , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) - // ]NOCPP] - ); - push(node); - } - - private void appendVoidElementToCurrentMayFoster( - @Local String name, HtmlAttributes attributes, T form) throws SAXException { - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - // ]NOCPP] - // Can't be called for custom elements - T elt; - T formOwner = form == null || fragment || isTemplateContents() ? null : form; - StackNode current = stack[currentPtr]; - if (current.isFosterParenting()) { - fatal(); - elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", name, - attributes, formOwner); - } else { - elt = createElement("http://www.w3.org/1999/xhtml", name, - attributes, formOwner, current.node); - appendElement(elt, current.node); - } - elementPushed("http://www.w3.org/1999/xhtml", name, elt); - elementPopped("http://www.w3.org/1999/xhtml", name, elt); - } - - private void appendVoidElementToCurrentMayFoster( - ElementName elementName, HtmlAttributes attributes) - throws SAXException { - @Local String popName = elementName.name; - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - if (elementName.isCustom()) { - popName = checkPopName(popName); - } - // ]NOCPP] - T elt; - StackNode current = stack[currentPtr]; - if (current.isFosterParenting()) { - fatal(); - elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", popName, attributes); - } else { - elt = createElement("http://www.w3.org/1999/xhtml", popName, attributes, current.node); - appendElement(elt, current.node); - } - elementPushed("http://www.w3.org/1999/xhtml", popName, elt); - elementPopped("http://www.w3.org/1999/xhtml", popName, elt); - } - - private void appendVoidElementToCurrentMayFosterSVG( - ElementName elementName, HtmlAttributes attributes) - throws SAXException { - @Local String popName = elementName.camelCaseName; - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/2000/svg"); - if (elementName.isCustom()) { - popName = checkPopName(popName); - } - // ]NOCPP] - T elt; - StackNode current = stack[currentPtr]; - if (current.isFosterParenting()) { - fatal(); - elt = createAndInsertFosterParentedElement("http://www.w3.org/2000/svg", popName, attributes); - } else { - elt = createElement("http://www.w3.org/2000/svg", popName, attributes, current.node); - appendElement(elt, current.node); - } - elementPushed("http://www.w3.org/2000/svg", popName, elt); - elementPopped("http://www.w3.org/2000/svg", popName, elt); - } - - private void appendVoidElementToCurrentMayFosterMathML( - ElementName elementName, HtmlAttributes attributes) - throws SAXException { - @Local String popName = elementName.name; - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1998/Math/MathML"); - if (elementName.isCustom()) { - popName = checkPopName(popName); - } - // ]NOCPP] - T elt; - StackNode current = stack[currentPtr]; - if (current.isFosterParenting()) { - fatal(); - elt = createAndInsertFosterParentedElement("http://www.w3.org/1998/Math/MathML", popName, attributes); - } else { - elt = createElement("http://www.w3.org/1998/Math/MathML", popName, attributes, current.node); - appendElement(elt, current.node); - } - elementPushed("http://www.w3.org/1998/Math/MathML", popName, elt); - elementPopped("http://www.w3.org/1998/Math/MathML", popName, elt); - } - - private void appendVoidElementToCurrent( - @Local String name, HtmlAttributes attributes, T form) throws SAXException { - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - // ]NOCPP] - // Can't be called for custom elements - T currentNode = stack[currentPtr].node; - T elt = createElement("http://www.w3.org/1999/xhtml", name, attributes, - form == null || fragment || isTemplateContents() ? null : form, currentNode); - appendElement(elt, currentNode); - elementPushed("http://www.w3.org/1999/xhtml", name, elt); - elementPopped("http://www.w3.org/1999/xhtml", name, elt); - } - - private void appendVoidFormToCurrent(HtmlAttributes attributes) throws SAXException { - // [NOCPP[ - checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); - // ]NOCPP] - T currentNode = stack[currentPtr].node; - T elt = createElement("http://www.w3.org/1999/xhtml", "form", - attributes, currentNode); - formPointer = elt; - // ownership transferred to form pointer - appendElement(elt, currentNode); - elementPushed("http://www.w3.org/1999/xhtml", "form", elt); - elementPopped("http://www.w3.org/1999/xhtml", "form", elt); - } - - // [NOCPP[ - - private final void accumulateCharactersForced(@Const @NoLength char[] buf, - int start, int length) throws SAXException { - System.arraycopy(buf, start, charBuffer, charBufferLen, length); - charBufferLen += length; - } - - @Override public void ensureBufferSpace(int inputLength) - throws SAXException { - // TODO: Unify Tokenizer.strBuf and TreeBuilder.charBuffer so that - // this method becomes unnecessary. - int worstCase = charBufferLen + inputLength; - if (charBuffer == null) { - // Add an arbitrary small value to avoid immediate reallocation - // once there are a few characters in the buffer. - charBuffer = new char[worstCase + 128]; - } else if (worstCase > charBuffer.length) { - // HotSpot reportedly allocates memory with 8-byte accuracy, so - // there's no point in trying to do math here to avoid slop. - // Maybe we should add some small constant to worstCase here - // but not doing that without profiling. In C++ with jemalloc, - // the corresponding method should do math to round up here - // to avoid slop. - char[] newBuf = new char[worstCase]; - System.arraycopy(charBuffer, 0, newBuf, 0, charBufferLen); - charBuffer = newBuf; - } - } - - // ]NOCPP] - - protected void accumulateCharacters(@Const @NoLength char[] buf, int start, - int length) throws SAXException { - appendCharacters(stack[currentPtr].node, buf, start, length); - } - - // ------------------------------- // - - protected final void requestSuspension() { - tokenizer.requestSuspension(); - } - - protected abstract T createElement(@NsUri String ns, @Local String name, - HtmlAttributes attributes, T intendedParent) throws SAXException; - - protected T createElement(@NsUri String ns, @Local String name, - HtmlAttributes attributes, T form, T intendedParent) throws SAXException { - return createElement("http://www.w3.org/1999/xhtml", name, attributes, intendedParent); - } - - protected abstract T createHtmlElementSetAsRoot(HtmlAttributes attributes) - throws SAXException; - - protected abstract void detachFromParent(T element) throws SAXException; - - protected abstract boolean hasChildren(T element) throws SAXException; - - protected abstract void appendElement(T child, T newParent) - throws SAXException; - - protected abstract void appendChildrenToNewParent(T oldParent, T newParent) - throws SAXException; - - protected abstract void insertFosterParentedChild(T child, T table, - T stackParent) throws SAXException; - - // We don't generate CPP code for this method because it is not used in generated CPP - // code. Instead, the form owner version of this method is called with a null form owner. - // [NOCPP[ - - protected abstract T createAndInsertFosterParentedElement(@NsUri String ns, @Local String name, - HtmlAttributes attributes, T table, T stackParent) throws SAXException; - - // ]NOCPP] - - protected T createAndInsertFosterParentedElement(@NsUri String ns, @Local String name, - HtmlAttributes attributes, T form, T table, T stackParent) throws SAXException { - return createAndInsertFosterParentedElement(ns, name, attributes, table, stackParent); - }; - - protected abstract void insertFosterParentedCharacters( - @NoLength char[] buf, int start, int length, T table, T stackParent) - throws SAXException; - - protected abstract void appendCharacters(T parent, @NoLength char[] buf, - int start, int length) throws SAXException; - - protected abstract void appendIsindexPrompt(T parent) throws SAXException; - - protected abstract void appendComment(T parent, @NoLength char[] buf, - int start, int length) throws SAXException; - - protected abstract void appendCommentToDocument(@NoLength char[] buf, - int start, int length) throws SAXException; - - protected abstract void addAttributesToElement(T element, - HtmlAttributes attributes) throws SAXException; - - protected void markMalformedIfScript(T elt) throws SAXException { - - } - - protected void start(boolean fragmentMode) throws SAXException { - - } - - protected void end() throws SAXException { - - } - - protected void appendDoctypeToDocument(@Local String name, - String publicIdentifier, String systemIdentifier) - throws SAXException { - - } - - protected void elementPushed(@NsUri String ns, @Local String name, T node) - throws SAXException { - - } - - protected void elementPopped(@NsUri String ns, @Local String name, T node) - throws SAXException { - - } - - // [NOCPP[ - - protected void documentMode(DocumentMode m, String publicIdentifier, - String systemIdentifier, boolean html4SpecificAdditionalErrorChecks) - throws SAXException { - - } - - /** - * @see nu.validator.htmlparser.common.TokenHandler#wantsComments() - */ - public boolean wantsComments() { - return wantingComments; - } - - public void setIgnoringComments(boolean ignoreComments) { - wantingComments = !ignoreComments; - } - - /** - * Sets the errorHandler. - * - * @param errorHandler - * the errorHandler to set - */ - public final void setErrorHandler(ErrorHandler errorHandler) { - this.errorHandler = errorHandler; - } - - /** - * Returns the errorHandler. - * - * @return the errorHandler - */ - public ErrorHandler getErrorHandler() { - return errorHandler; - } - - /** - * The argument MUST be an interned string or null. - * - * @param context - */ - public final void setFragmentContext(@Local String context) { - this.contextName = context; - this.contextNamespace = "http://www.w3.org/1999/xhtml"; - this.contextNode = null; - this.fragment = (contextName != null); - this.quirks = false; - } - - // ]NOCPP] - - /** - * @see nu.validator.htmlparser.common.TokenHandler#cdataSectionAllowed() - */ - @Inline public boolean cdataSectionAllowed() throws SAXException { - return isInForeign(); - } - - private boolean isInForeign() { - return currentPtr >= 0 - && stack[currentPtr].ns != "http://www.w3.org/1999/xhtml"; - } - - private boolean isInForeignButNotHtmlOrMathTextIntegrationPoint() { - if (currentPtr < 0) { - return false; - } - return !isSpecialParentInForeign(stack[currentPtr]); - } - - /** - * The argument MUST be an interned string or null. - * - * @param context - */ - public final void setFragmentContext(@Local String context, - @NsUri String ns, T node, boolean quirks) { - // [NOCPP[ - if (!((context == null && ns == null) - || "http://www.w3.org/1999/xhtml" == ns - || "http://www.w3.org/2000/svg" == ns || "http://www.w3.org/1998/Math/MathML" == ns)) { - throw new IllegalArgumentException( - "The namespace must be the HTML, SVG or MathML namespace (or null when the local name is null). Got: " - + ns); - } - // ]NOCPP] - this.contextName = context; - this.contextNamespace = ns; - this.contextNode = node; - this.fragment = (contextName != null); - this.quirks = quirks; - } - - protected final T currentNode() { - return stack[currentPtr].node; - } - - /** - * Returns the scriptingEnabled. - * - * @return the scriptingEnabled - */ - public boolean isScriptingEnabled() { - return scriptingEnabled; - } - - /** - * Sets the scriptingEnabled. - * - * @param scriptingEnabled - * the scriptingEnabled to set - */ - public void setScriptingEnabled(boolean scriptingEnabled) { - this.scriptingEnabled = scriptingEnabled; - } - - public void setIsSrcdocDocument(boolean isSrcdocDocument) { - this.isSrcdocDocument = isSrcdocDocument; - } - - // [NOCPP[ - - /** - * Sets the doctypeExpectation. - * - * @param doctypeExpectation - * the doctypeExpectation to set - */ - public void setDoctypeExpectation(DoctypeExpectation doctypeExpectation) { - this.doctypeExpectation = doctypeExpectation; - } - - public void setNamePolicy(XmlViolationPolicy namePolicy) { - this.namePolicy = namePolicy; - } - - /** - * Sets the documentModeHandler. - * - * @param documentModeHandler - * the documentModeHandler to set - */ - public void setDocumentModeHandler(DocumentModeHandler documentModeHandler) { - this.documentModeHandler = documentModeHandler; - } - - /** - * Sets the reportingDoctype. - * - * @param reportingDoctype - * the reportingDoctype to set - */ - public void setReportingDoctype(boolean reportingDoctype) { - this.reportingDoctype = reportingDoctype; - } - - // ]NOCPP] - - /** - * Flushes the pending characters. Public for document.write use cases only. - * @throws SAXException - */ - public final void flushCharacters() throws SAXException { - if (charBufferLen > 0) { - if ((mode == IN_TABLE || mode == IN_TABLE_BODY || mode == IN_ROW) - && charBufferContainsNonWhitespace()) { - errNonSpaceInTable(); - reconstructTheActiveFormattingElements(); - if (!stack[currentPtr].isFosterParenting()) { - // reconstructing gave us a new current node - appendCharacters(currentNode(), charBuffer, 0, - charBufferLen); - charBufferLen = 0; - return; - } - - int tablePos = findLastOrRoot(TreeBuilder.TABLE); - int templatePos = findLastOrRoot(TreeBuilder.TEMPLATE); - - if (templatePos >= tablePos) { - appendCharacters(stack[templatePos].node, charBuffer, 0, charBufferLen); - charBufferLen = 0; - return; - } - - StackNode tableElt = stack[tablePos]; - insertFosterParentedCharacters(charBuffer, 0, charBufferLen, - tableElt.node, stack[tablePos - 1].node); - charBufferLen = 0; - return; - } - appendCharacters(currentNode(), charBuffer, 0, charBufferLen); - charBufferLen = 0; - } - } - - private boolean charBufferContainsNonWhitespace() { - for (int i = 0; i < charBufferLen; i++) { - switch (charBuffer[i]) { - case ' ': - case '\t': - case '\n': - case '\r': - case '\u000C': - continue; - default: - return true; - } - } - return false; - } - - /** - * Creates a comparable snapshot of the tree builder state. Snapshot - * creation is only supported immediately after a script end tag has been - * processed. In C++ the caller is responsible for calling - * delete on the returned object. - * - * @return a snapshot. - * @throws SAXException - */ - @SuppressWarnings("unchecked") public TreeBuilderState newSnapshot() - throws SAXException { - StackNode[] listCopy = new StackNode[listPtr + 1]; - for (int i = 0; i < listCopy.length; i++) { - StackNode node = listOfActiveFormattingElements[i]; - if (node != null) { - StackNode newNode = new StackNode(node.getFlags(), node.ns, - node.name, node.node, node.popName, - node.attributes.cloneAttributes(null) - // [NOCPP[ - , node.getLocator() - // ]NOCPP] - ); - listCopy[i] = newNode; - } else { - listCopy[i] = null; - } - } - StackNode[] stackCopy = new StackNode[currentPtr + 1]; - for (int i = 0; i < stackCopy.length; i++) { - StackNode node = stack[i]; - int listIndex = findInListOfActiveFormattingElements(node); - if (listIndex == -1) { - StackNode newNode = new StackNode(node.getFlags(), node.ns, - node.name, node.node, node.popName, - null - // [NOCPP[ - , node.getLocator() - // ]NOCPP] - ); - stackCopy[i] = newNode; - } else { - stackCopy[i] = listCopy[listIndex]; - stackCopy[i].retain(); - } - } - int[] templateModeStackCopy = new int[templateModePtr + 1]; - System.arraycopy(templateModeStack, 0, templateModeStackCopy, 0, - templateModeStackCopy.length); - return new StateSnapshot(stackCopy, listCopy, templateModeStackCopy, formPointer, - headPointer, deepTreeSurrogateParent, mode, originalMode, framesetOk, - needToDropLF, quirks); - } - - public boolean snapshotMatches(TreeBuilderState snapshot) { - StackNode[] stackCopy = snapshot.getStack(); - int stackLen = snapshot.getStackLength(); - StackNode[] listCopy = snapshot.getListOfActiveFormattingElements(); - int listLen = snapshot.getListOfActiveFormattingElementsLength(); - int[] templateModeStackCopy = snapshot.getTemplateModeStack(); - int templateModeStackLen = snapshot.getTemplateModeStackLength(); - - if (stackLen != currentPtr + 1 - || listLen != listPtr + 1 - || templateModeStackLen != templateModePtr + 1 - || formPointer != snapshot.getFormPointer() - || headPointer != snapshot.getHeadPointer() - || deepTreeSurrogateParent != snapshot.getDeepTreeSurrogateParent() - || mode != snapshot.getMode() - || originalMode != snapshot.getOriginalMode() - || framesetOk != snapshot.isFramesetOk() - || needToDropLF != snapshot.isNeedToDropLF() - || quirks != snapshot.isQuirks()) { // maybe just assert quirks - return false; - } - for (int i = listLen - 1; i >= 0; i--) { - if (listCopy[i] == null - && listOfActiveFormattingElements[i] == null) { - continue; - } else if (listCopy[i] == null - || listOfActiveFormattingElements[i] == null) { - return false; - } - if (listCopy[i].node != listOfActiveFormattingElements[i].node) { - return false; // it's possible that this condition is overly - // strict - } - } - for (int i = stackLen - 1; i >= 0; i--) { - if (stackCopy[i].node != stack[i].node) { - return false; - } - } - for (int i = templateModeStackLen - 1; i >=0; i--) { - if (templateModeStackCopy[i] != templateModeStack[i]) { - return false; - } - } - return true; - } - - @SuppressWarnings("unchecked") public void loadState( - TreeBuilderState snapshot, Interner interner) - throws SAXException { - StackNode[] stackCopy = snapshot.getStack(); - int stackLen = snapshot.getStackLength(); - StackNode[] listCopy = snapshot.getListOfActiveFormattingElements(); - int listLen = snapshot.getListOfActiveFormattingElementsLength(); - int[] templateModeStackCopy = snapshot.getTemplateModeStack(); - int templateModeStackLen = snapshot.getTemplateModeStackLength(); - - for (int i = 0; i <= listPtr; i++) { - if (listOfActiveFormattingElements[i] != null) { - listOfActiveFormattingElements[i].release(); - } - } - if (listOfActiveFormattingElements.length < listLen) { - listOfActiveFormattingElements = new StackNode[listLen]; - } - listPtr = listLen - 1; - - for (int i = 0; i <= currentPtr; i++) { - stack[i].release(); - } - if (stack.length < stackLen) { - stack = new StackNode[stackLen]; - } - currentPtr = stackLen - 1; - - if (templateModeStack.length < templateModeStackLen) { - templateModeStack = new int[templateModeStackLen]; - } - templateModePtr = templateModeStackLen - 1; - - for (int i = 0; i < listLen; i++) { - StackNode node = listCopy[i]; - if (node != null) { - StackNode newNode = new StackNode(node.getFlags(), node.ns, - Portability.newLocalFromLocal(node.name, interner), node.node, - Portability.newLocalFromLocal(node.popName, interner), - node.attributes.cloneAttributes(null) - // [NOCPP[ - , node.getLocator() - // ]NOCPP] - ); - listOfActiveFormattingElements[i] = newNode; - } else { - listOfActiveFormattingElements[i] = null; - } - } - for (int i = 0; i < stackLen; i++) { - StackNode node = stackCopy[i]; - int listIndex = findInArray(node, listCopy); - if (listIndex == -1) { - StackNode newNode = new StackNode(node.getFlags(), node.ns, - Portability.newLocalFromLocal(node.name, interner), node.node, - Portability.newLocalFromLocal(node.popName, interner), - null - // [NOCPP[ - , node.getLocator() - // ]NOCPP] - ); - stack[i] = newNode; - } else { - stack[i] = listOfActiveFormattingElements[listIndex]; - stack[i].retain(); - } - } - System.arraycopy(templateModeStackCopy, 0, templateModeStack, 0, templateModeStackLen); - formPointer = snapshot.getFormPointer(); - headPointer = snapshot.getHeadPointer(); - deepTreeSurrogateParent = snapshot.getDeepTreeSurrogateParent(); - mode = snapshot.getMode(); - originalMode = snapshot.getOriginalMode(); - framesetOk = snapshot.isFramesetOk(); - needToDropLF = snapshot.isNeedToDropLF(); - quirks = snapshot.isQuirks(); - } - - private int findInArray(StackNode node, StackNode[] arr) { - for (int i = listPtr; i >= 0; i--) { - if (node == arr[i]) { - return i; - } - } - return -1; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getFormPointer() - */ - public T getFormPointer() { - return formPointer; - } - - /** - * Returns the headPointer. - * - * @return the headPointer - */ - public T getHeadPointer() { - return headPointer; - } - - /** - * Returns the deepTreeSurrogateParent. - * - * @return the deepTreeSurrogateParent - */ - public T getDeepTreeSurrogateParent() { - return deepTreeSurrogateParent; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getListOfActiveFormattingElements() - */ - public StackNode[] getListOfActiveFormattingElements() { - return listOfActiveFormattingElements; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getStack() - */ - public StackNode[] getStack() { - return stack; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getTemplateModeStack() - */ - public int[] getTemplateModeStack() { - return templateModeStack; - } - - /** - * Returns the mode. - * - * @return the mode - */ - public int getMode() { - return mode; - } - - /** - * Returns the originalMode. - * - * @return the originalMode - */ - public int getOriginalMode() { - return originalMode; - } - - /** - * Returns the framesetOk. - * - * @return the framesetOk - */ - public boolean isFramesetOk() { - return framesetOk; - } - - /** - * Returns the needToDropLF. - * - * @return the needToDropLF - */ - public boolean isNeedToDropLF() { - return needToDropLF; - } - - /** - * Returns the quirks. - * - * @return the quirks - */ - public boolean isQuirks() { - return quirks; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getListOfActiveFormattingElementsLength() - */ - public int getListOfActiveFormattingElementsLength() { - return listPtr + 1; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getStackLength() - */ - public int getStackLength() { - return currentPtr + 1; - } - - /** - * @see nu.validator.htmlparser.impl.TreeBuilderState#getTemplateModeStackLength() - */ - public int getTemplateModeStackLength() { - return templateModePtr + 1; - } - - /** - * Reports a stray start tag. - * @param name the name of the stray tag - * - * @throws SAXException - */ - private void errStrayStartTag(@Local String name) throws SAXException { - err("Stray start tag \u201C" + name + "\u201D."); - } - - /** - * Reports a stray end tag. - * @param name the name of the stray tag - * - * @throws SAXException - */ - private void errStrayEndTag(@Local String name) throws SAXException { - err("Stray end tag \u201C" + name + "\u201D."); - } - - /** - * Reports a state when elements expected to be closed were not. - * - * @param eltPos the position of the start tag on the stack of the element - * being closed. - * @param name the name of the end tag - * - * @throws SAXException - */ - private void errUnclosedElements(int eltPos, @Local String name) throws SAXException { - errNoCheck("End tag \u201C" + name + "\u201D seen, but there were open elements."); - errListUnclosedStartTags(eltPos); - } - - /** - * Reports a state when elements expected to be closed ahead of an implied - * end tag but were not. - * - * @param eltPos the position of the start tag on the stack of the element - * being closed. - * @param name the name of the end tag - * - * @throws SAXException - */ - private void errUnclosedElementsImplied(int eltPos, String name) throws SAXException { - errNoCheck("End tag \u201C" + name + "\u201D implied, but there were open elements."); - errListUnclosedStartTags(eltPos); - } - - /** - * Reports a state when elements expected to be closed ahead of an implied - * table cell close. - * - * @param eltPos the position of the start tag on the stack of the element - * being closed. - * @throws SAXException - */ - private void errUnclosedElementsCell(int eltPos) throws SAXException { - errNoCheck("A table cell was implicitly closed, but there were open elements."); - errListUnclosedStartTags(eltPos); - } - - private void errStrayDoctype() throws SAXException { - err("Stray doctype."); - } - - private void errAlmostStandardsDoctype() throws SAXException { - if (!isSrcdocDocument) { - err("Almost standards mode doctype. Expected \u201C\u201D."); - } - } - - private void errQuirkyDoctype() throws SAXException { - if (!isSrcdocDocument) { - err("Quirky doctype. Expected \u201C\u201D."); - } - } - - private void errNonSpaceInTrailer() throws SAXException { - err("Non-space character in page trailer."); - } - - private void errNonSpaceAfterFrameset() throws SAXException { - err("Non-space after \u201Cframeset\u201D."); - } - - private void errNonSpaceInFrameset() throws SAXException { - err("Non-space in \u201Cframeset\u201D."); - } - - private void errNonSpaceAfterBody() throws SAXException { - err("Non-space character after body."); - } - - private void errNonSpaceInColgroupInFragment() throws SAXException { - err("Non-space in \u201Ccolgroup\u201D when parsing fragment."); - } - - private void errNonSpaceInNoscriptInHead() throws SAXException { - err("Non-space character inside \u201Cnoscript\u201D inside \u201Chead\u201D."); - } - - private void errFooBetweenHeadAndBody(@Local String name) throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("\u201C" + name + "\u201D element between \u201Chead\u201D and \u201Cbody\u201D."); - } - - private void errStartTagWithoutDoctype() throws SAXException { - if (!isSrcdocDocument) { - err("Start tag seen without seeing a doctype first. Expected \u201C\u201D."); - } - } - - private void errNoSelectInTableScope() throws SAXException { - err("No \u201Cselect\u201D in table scope."); - } - - private void errStartSelectWhereEndSelectExpected() throws SAXException { - err("\u201Cselect\u201D start tag where end tag expected."); - } - - private void errStartTagWithSelectOpen(@Local String name) - throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("\u201C" + name - + "\u201D start tag with \u201Cselect\u201D open."); - } - - private void errBadStartTagInHead(@Local String name) throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("Bad start tag in \u201C" + name - + "\u201D in \u201Chead\u201D."); - } - - private void errImage() throws SAXException { - err("Saw a start tag \u201Cimage\u201D."); - } - - private void errIsindex() throws SAXException { - err("\u201Cisindex\u201D seen."); - } - - private void errFooSeenWhenFooOpen(@Local String name) throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("An \u201C" + name + "\u201D start tag seen but an element of the same type was already open."); - } - - private void errHeadingWhenHeadingOpen() throws SAXException { - err("Heading cannot be a child of another heading."); - } - - private void errFramesetStart() throws SAXException { - err("\u201Cframeset\u201D start tag seen."); - } - - private void errNoCellToClose() throws SAXException { - err("No cell to close."); - } - - private void errStartTagInTable(@Local String name) throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("Start tag \u201C" + name - + "\u201D seen in \u201Ctable\u201D."); - } - - private void errFormWhenFormOpen() throws SAXException { - err("Saw a \u201Cform\u201D start tag, but there was already an active \u201Cform\u201D element. Nested forms are not allowed. Ignoring the tag."); - } - - private void errTableSeenWhileTableOpen() throws SAXException { - err("Start tag for \u201Ctable\u201D seen but the previous \u201Ctable\u201D is still open."); - } - - private void errStartTagInTableBody(@Local String name) throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("\u201C" + name + "\u201D start tag in table body."); - } - - private void errEndTagSeenWithoutDoctype() throws SAXException { - if (!isSrcdocDocument) { - err("End tag seen without seeing a doctype first. Expected \u201C\u201D."); - } - } - - private void errEndTagAfterBody() throws SAXException { - err("Saw an end tag after \u201Cbody\u201D had been closed."); - } - - private void errEndTagSeenWithSelectOpen(@Local String name) throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("\u201C" + name - + "\u201D end tag with \u201Cselect\u201D open."); - } - - private void errGarbageInColgroup() throws SAXException { - err("Garbage in \u201Ccolgroup\u201D fragment."); - } - - private void errEndTagBr() throws SAXException { - err("End tag \u201Cbr\u201D."); - } - - private void errNoElementToCloseButEndTagSeen(@Local String name) - throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("No \u201C" + name + "\u201D element in scope but a \u201C" - + name + "\u201D end tag seen."); - } - - private void errHtmlStartTagInForeignContext(@Local String name) - throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("HTML start tag \u201C" + name - + "\u201D in a foreign namespace context."); - } - - private void errTableClosedWhileCaptionOpen() throws SAXException { - err("\u201Ctable\u201D closed but \u201Ccaption\u201D was still open."); - } - - private void errNoTableRowToClose() throws SAXException { - err("No table row to close."); - } - - private void errNonSpaceInTable() throws SAXException { - err("Misplaced non-space characters insided a table."); - } - - private void errUnclosedChildrenInRuby() throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("Unclosed children in \u201Cruby\u201D."); - } - - private void errStartTagSeenWithoutRuby(@Local String name) throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("Start tag \u201C" - + name - + "\u201D seen without a \u201Cruby\u201D element being open."); - } - - private void errSelfClosing() throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("Self-closing syntax (\u201C/>\u201D) used on a non-void HTML element. Ignoring the slash and treating as a start tag."); - } - - private void errNoCheckUnclosedElementsOnStack() throws SAXException { - errNoCheck("Unclosed elements on stack."); - } - - private void errEndTagDidNotMatchCurrentOpenElement(@Local String name, - @Local String currOpenName) throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("End tag \u201C" - + name - + "\u201D did not match the name of the current open element (\u201C" - + currOpenName + "\u201D)."); - } - - private void errEndTagViolatesNestingRules(@Local String name) throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("End tag \u201C" + name + "\u201D violates nesting rules."); - } - - private void errEofWithUnclosedElements() throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("End of file seen and there were open elements."); - // just report all remaining unclosed elements - errListUnclosedStartTags(0); - } - - /** - * Reports arriving at/near end of document with unclosed elements remaining. - * - * @param message - * the message - * @throws SAXException - */ - private void errEndWithUnclosedElements(@Local String name) throws SAXException { - if (errorHandler == null) { - return; - } - errNoCheck("End tag for \u201C" - + name - + "\u201D seen, but there were unclosed elements."); - // just report all remaining unclosed elements - errListUnclosedStartTags(0); - } -} diff --git a/parser/html/javasrc/UTF16Buffer.java b/parser/html/javasrc/UTF16Buffer.java deleted file mode 100644 index ec79185ec..000000000 --- a/parser/html/javasrc/UTF16Buffer.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2008-2010 Mozilla Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -package nu.validator.htmlparser.impl; - -import nu.validator.htmlparser.annotation.NoLength; - -/** - * An UTF-16 buffer that knows the start and end indeces of its unconsumed - * content. - * - * @version $Id$ - * @author hsivonen - */ -public final class UTF16Buffer { - - /** - * The backing store of the buffer. May be larger than the logical content - * of this UTF16Buffer. - */ - private final @NoLength char[] buffer; - - /** - * The index of the first unconsumed character in the backing buffer. - */ - private int start; - - /** - * The index of the slot immediately after the last character in the backing - * buffer that is part of the logical content of this - * UTF16Buffer. - */ - private int end; - - //[NOCPP[ - - /** - * Constructor for wrapping an existing UTF-16 code unit array. - * - * @param buffer - * the backing buffer - * @param start - * the index of the first character to consume - * @param end - * the index immediately after the last character to consume - */ - public UTF16Buffer(@NoLength char[] buffer, int start, int end) { - this.buffer = buffer; - this.start = start; - this.end = end; - } - - // ]NOCPP] - - /** - * Returns the start index. - * - * @return the start index - */ - public int getStart() { - return start; - } - - /** - * Sets the start index. - * - * @param start - * the start index - */ - public void setStart(int start) { - this.start = start; - } - - /** - * Returns the backing buffer. - * - * @return the backing buffer - */ - public @NoLength char[] getBuffer() { - return buffer; - } - - /** - * Returns the end index. - * - * @return the end index - */ - public int getEnd() { - return end; - } - - /** - * Checks if the buffer has data left. - * - * @return true if there's data left - */ - public boolean hasMore() { - return start < end; - } - - /** - * Returns end - start. - * - * @return end - start - */ - public int getLength() { - return end - start; - } - - /** - * Adjusts the start index to skip over the first character if it is a line - * feed and the previous character was a carriage return. - * - * @param lastWasCR - * whether the previous character was a carriage return - */ - public void adjust(boolean lastWasCR) { - if (lastWasCR && buffer[start] == '\n') { - start++; - } - } - - /** - * Sets the end index. - * - * @param end - * the end index - */ - public void setEnd(int end) { - this.end = end; - } -} diff --git a/parser/html/nsHtml5AttributeName.cpp b/parser/html/nsHtml5AttributeName.cpp index 8c7db5689..2dee33220 100644 --- a/parser/html/nsHtml5AttributeName.cpp +++ b/parser/html/nsHtml5AttributeName.cpp @@ -21,11 +21,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit AttributeName.java instead and regenerate. - */ - #define nsHtml5AttributeName_cpp__ #include "nsIAtom.h" diff --git a/parser/html/nsHtml5AttributeName.h b/parser/html/nsHtml5AttributeName.h index 9d00e2da0..12e4da3b2 100644 --- a/parser/html/nsHtml5AttributeName.h +++ b/parser/html/nsHtml5AttributeName.h @@ -21,11 +21,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit AttributeName.java instead and regenerate. - */ - #ifndef nsHtml5AttributeName_h #define nsHtml5AttributeName_h diff --git a/parser/html/nsHtml5ElementName.cpp b/parser/html/nsHtml5ElementName.cpp index 95adda073..74b0450ee 100644 --- a/parser/html/nsHtml5ElementName.cpp +++ b/parser/html/nsHtml5ElementName.cpp @@ -21,11 +21,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit ElementName.java instead and regenerate. - */ - #define nsHtml5ElementName_cpp__ #include "nsIAtom.h" diff --git a/parser/html/nsHtml5ElementName.h b/parser/html/nsHtml5ElementName.h index 4aba0d3e1..252716990 100644 --- a/parser/html/nsHtml5ElementName.h +++ b/parser/html/nsHtml5ElementName.h @@ -21,11 +21,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit ElementName.java instead and regenerate. - */ - #ifndef nsHtml5ElementName_h #define nsHtml5ElementName_h diff --git a/parser/html/nsHtml5HtmlAttributes.cpp b/parser/html/nsHtml5HtmlAttributes.cpp index 132d33da1..fc4c33cde 100644 --- a/parser/html/nsHtml5HtmlAttributes.cpp +++ b/parser/html/nsHtml5HtmlAttributes.cpp @@ -22,11 +22,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit HtmlAttributes.java instead and regenerate. - */ - #define nsHtml5HtmlAttributes_cpp__ #include "nsIAtom.h" diff --git a/parser/html/nsHtml5HtmlAttributes.h b/parser/html/nsHtml5HtmlAttributes.h index ba3992788..e4423f152 100644 --- a/parser/html/nsHtml5HtmlAttributes.h +++ b/parser/html/nsHtml5HtmlAttributes.h @@ -22,11 +22,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit HtmlAttributes.java instead and regenerate. - */ - #ifndef nsHtml5HtmlAttributes_h #define nsHtml5HtmlAttributes_h diff --git a/parser/html/nsHtml5MetaScanner.cpp b/parser/html/nsHtml5MetaScanner.cpp index 3449026f1..b9c426c19 100644 --- a/parser/html/nsHtml5MetaScanner.cpp +++ b/parser/html/nsHtml5MetaScanner.cpp @@ -22,11 +22,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit MetaScanner.java instead and regenerate. - */ - #define nsHtml5MetaScanner_cpp__ #include "nsIAtom.h" diff --git a/parser/html/nsHtml5MetaScanner.h b/parser/html/nsHtml5MetaScanner.h index 08a771860..43f107454 100644 --- a/parser/html/nsHtml5MetaScanner.h +++ b/parser/html/nsHtml5MetaScanner.h @@ -22,11 +22,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit MetaScanner.java instead and regenerate. - */ - #ifndef nsHtml5MetaScanner_h #define nsHtml5MetaScanner_h diff --git a/parser/html/nsHtml5Portability.h b/parser/html/nsHtml5Portability.h index 17004fb5d..a0143940b 100644 --- a/parser/html/nsHtml5Portability.h +++ b/parser/html/nsHtml5Portability.h @@ -21,11 +21,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit Portability.java instead and regenerate. - */ - #ifndef nsHtml5Portability_h #define nsHtml5Portability_h diff --git a/parser/html/nsHtml5StackNode.cpp b/parser/html/nsHtml5StackNode.cpp index f38510f4c..3f527c656 100644 --- a/parser/html/nsHtml5StackNode.cpp +++ b/parser/html/nsHtml5StackNode.cpp @@ -22,11 +22,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit StackNode.java instead and regenerate. - */ - #define nsHtml5StackNode_cpp__ #include "nsIAtom.h" diff --git a/parser/html/nsHtml5StackNode.h b/parser/html/nsHtml5StackNode.h index 1f39cd0b5..a2fb57911 100644 --- a/parser/html/nsHtml5StackNode.h +++ b/parser/html/nsHtml5StackNode.h @@ -22,11 +22,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit StackNode.java instead and regenerate. - */ - #ifndef nsHtml5StackNode_h #define nsHtml5StackNode_h diff --git a/parser/html/nsHtml5StateSnapshot.cpp b/parser/html/nsHtml5StateSnapshot.cpp index 2c2b0ec41..a943503b0 100644 --- a/parser/html/nsHtml5StateSnapshot.cpp +++ b/parser/html/nsHtml5StateSnapshot.cpp @@ -21,11 +21,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit StateSnapshot.java instead and regenerate. - */ - #define nsHtml5StateSnapshot_cpp__ #include "nsIAtom.h" diff --git a/parser/html/nsHtml5StateSnapshot.h b/parser/html/nsHtml5StateSnapshot.h index 3594c8cd6..be2bce158 100644 --- a/parser/html/nsHtml5StateSnapshot.h +++ b/parser/html/nsHtml5StateSnapshot.h @@ -21,11 +21,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit StateSnapshot.java instead and regenerate. - */ - #ifndef nsHtml5StateSnapshot_h #define nsHtml5StateSnapshot_h diff --git a/parser/html/nsHtml5Tokenizer.cpp b/parser/html/nsHtml5Tokenizer.cpp index 8aae5613a..4c815b0c0 100644 --- a/parser/html/nsHtml5Tokenizer.cpp +++ b/parser/html/nsHtml5Tokenizer.cpp @@ -24,11 +24,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit Tokenizer.java instead and regenerate. - */ - #define nsHtml5Tokenizer_cpp__ #include "nsIAtom.h" diff --git a/parser/html/nsHtml5Tokenizer.h b/parser/html/nsHtml5Tokenizer.h index 11a9bab1b..37f0eae17 100644 --- a/parser/html/nsHtml5Tokenizer.h +++ b/parser/html/nsHtml5Tokenizer.h @@ -24,11 +24,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit Tokenizer.java instead and regenerate. - */ - #ifndef nsHtml5Tokenizer_h #define nsHtml5Tokenizer_h diff --git a/parser/html/nsHtml5TreeBuilder.cpp b/parser/html/nsHtml5TreeBuilder.cpp index e5040d050..a02626e47 100644 --- a/parser/html/nsHtml5TreeBuilder.cpp +++ b/parser/html/nsHtml5TreeBuilder.cpp @@ -24,11 +24,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit TreeBuilder.java instead and regenerate. - */ - #define nsHtml5TreeBuilder_cpp__ #include "nsContentUtils.h" diff --git a/parser/html/nsHtml5TreeBuilder.h b/parser/html/nsHtml5TreeBuilder.h index 15c9d2628..208402d36 100644 --- a/parser/html/nsHtml5TreeBuilder.h +++ b/parser/html/nsHtml5TreeBuilder.h @@ -24,11 +24,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit TreeBuilder.java instead and regenerate. - */ - #ifndef nsHtml5TreeBuilder_h #define nsHtml5TreeBuilder_h diff --git a/parser/html/nsHtml5UTF16Buffer.cpp b/parser/html/nsHtml5UTF16Buffer.cpp index e6db76374..e829accb0 100644 --- a/parser/html/nsHtml5UTF16Buffer.cpp +++ b/parser/html/nsHtml5UTF16Buffer.cpp @@ -21,11 +21,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit UTF16Buffer.java instead and regenerate. - */ - #define nsHtml5UTF16Buffer_cpp__ #include "nsIAtom.h" diff --git a/parser/html/nsHtml5UTF16Buffer.h b/parser/html/nsHtml5UTF16Buffer.h index cc38f6daf..e783c28d3 100644 --- a/parser/html/nsHtml5UTF16Buffer.h +++ b/parser/html/nsHtml5UTF16Buffer.h @@ -21,11 +21,6 @@ * DEALINGS IN THE SOFTWARE. */ -/* - * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. - * Please edit UTF16Buffer.java instead and regenerate. - */ - #ifndef nsHtml5UTF16Buffer_h #define nsHtml5UTF16Buffer_h -- cgit v1.2.3 From 4266966eeef44dfce422b9703692e519678610c6 Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Wed, 4 Sep 2019 17:55:04 +0200 Subject: Kill newly-spawned threads if we're shutting down. --- xpcom/threads/nsThreadPool.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xpcom/threads/nsThreadPool.cpp b/xpcom/threads/nsThreadPool.cpp index 241fad39d..eb967870a 100644 --- a/xpcom/threads/nsThreadPool.cpp +++ b/xpcom/threads/nsThreadPool.cpp @@ -112,7 +112,9 @@ nsThreadPool::PutEvent(already_AddRefed aEvent, uint32_t aFlags) bool killThread = false; { MutexAutoLock lock(mMutex); - if (mThreads.Count() < (int32_t)mThreadLimit) { + if (mShutdown) { + killThread = true; // we're in shutdown, kill the thread + } else if (mThreads.Count() < (int32_t)mThreadLimit) { mThreads.AppendObject(thread); } else { killThread = true; // okay, we don't need this thread anymore -- cgit v1.2.3 From 4d4924e5105dbdfdcc3ff821671fba0dfeb11b11 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 4 Sep 2019 21:04:07 +0200 Subject: Fix a crash in IndexedDB. --- dom/indexedDB/IDBCursor.cpp | 8 ++++---- dom/indexedDB/IDBFactory.cpp | 4 ++-- dom/indexedDB/IDBKeyRange.cpp | 2 +- dom/indexedDB/IDBObjectStore.cpp | 15 ++++++++------- dom/indexedDB/Key.cpp | 38 +++++++++++++++++++++++++++----------- dom/indexedDB/Key.h | 15 +++++++++++---- dom/indexedDB/KeyPath.cpp | 22 +++++++++++++++------- dom/indexedDB/KeyPath.h | 12 +++++++++--- js/src/jsapi.cpp | 22 ++++++++++++++++++++++ js/src/jsapi.h | 3 +++ 10 files changed, 102 insertions(+), 39 deletions(-) diff --git a/dom/indexedDB/IDBCursor.cpp b/dom/indexedDB/IDBCursor.cpp index af88742f0..be0295dc7 100644 --- a/dom/indexedDB/IDBCursor.cpp +++ b/dom/indexedDB/IDBCursor.cpp @@ -430,7 +430,7 @@ IDBCursor::Continue(JSContext* aCx, } Key key; - aRv = key.SetFromJSVal(aCx, aKey); + aRv = key.SetFromJSVal(aCx, aKey, /* aCallGetters */ true); if (aRv.Failed()) { return; } @@ -536,7 +536,7 @@ IDBCursor::ContinuePrimaryKey(JSContext* aCx, } Key key; - aRv = key.SetFromJSVal(aCx, aKey); + aRv = key.SetFromJSVal(aCx, aKey, /* aCallGetters */ true); if (aRv.Failed()) { return; } @@ -558,7 +558,7 @@ IDBCursor::ContinuePrimaryKey(JSContext* aCx, } Key primaryKey; - aRv = primaryKey.SetFromJSVal(aCx, aPrimaryKey); + aRv = primaryKey.SetFromJSVal(aCx, aPrimaryKey, /* aCallGetters */ true); if (aRv.Failed()) { return; } @@ -718,7 +718,7 @@ IDBCursor::Update(JSContext* aCx, JS::Handle aValue, const KeyPath& keyPath = objectStore->GetKeyPath(); Key key; - aRv = keyPath.ExtractKey(aCx, aValue, key); + aRv = keyPath.ExtractKey(aCx, aValue, key, /* aCallGetters */ false); if (aRv.Failed()) { return nullptr; } diff --git a/dom/indexedDB/IDBFactory.cpp b/dom/indexedDB/IDBFactory.cpp index c1ef6353d..66471fe24 100644 --- a/dom/indexedDB/IDBFactory.cpp +++ b/dom/indexedDB/IDBFactory.cpp @@ -482,13 +482,13 @@ IDBFactory::Cmp(JSContext* aCx, JS::Handle aFirst, JS::Handle aSecond, ErrorResult& aRv) { Key first, second; - nsresult rv = first.SetFromJSVal(aCx, aFirst); + nsresult rv = first.SetFromJSVal(aCx, aFirst, /* aCallGetters */ true); if (NS_FAILED(rv)) { aRv.Throw(rv); return 0; } - rv = second.SetFromJSVal(aCx, aSecond); + rv = second.SetFromJSVal(aCx, aSecond, /* aCallGetters */ true); if (NS_FAILED(rv)) { aRv.Throw(rv); return 0; diff --git a/dom/indexedDB/IDBKeyRange.cpp b/dom/indexedDB/IDBKeyRange.cpp index e61c80617..168fb4a5a 100644 --- a/dom/indexedDB/IDBKeyRange.cpp +++ b/dom/indexedDB/IDBKeyRange.cpp @@ -24,7 +24,7 @@ GetKeyFromJSVal(JSContext* aCx, JS::Handle aVal, Key& aKey) { - nsresult rv = aKey.SetFromJSVal(aCx, aVal); + nsresult rv = aKey.SetFromJSVal(aCx, aVal, /* aCallGetters */ true); if (NS_FAILED(rv)) { MOZ_ASSERT(NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_DOM_INDEXEDDB); return rv; diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index 756792741..f86c619a7 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -1084,7 +1084,7 @@ IDBObjectStore::AppendIndexUpdateInfo( if (!aMultiEntry) { Key key; - rv = aKeyPath.ExtractKey(aCx, aVal, key); + rv = aKeyPath.ExtractKey(aCx, aVal, key, /* aCallGetters */ false); // If an index's keyPath doesn't match an object, we ignore that object. if (rv == NS_ERROR_DOM_INDEXEDDB_DATA_ERR || key.IsUnset()) { @@ -1128,13 +1128,13 @@ IDBObjectStore::AppendIndexUpdateInfo( for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) { JS::Rooted arrayItem(aCx); - if (NS_WARN_IF(!JS_GetElement(aCx, array, arrayIndex, &arrayItem))) { + if (NS_WARN_IF(!JS_GetOwnElement(aCx, array, arrayIndex, &arrayItem))) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } Key value; - if (NS_FAILED(value.SetFromJSVal(aCx, arrayItem)) || + if (NS_FAILED(value.SetFromJSVal(aCx, arrayItem, /* aCallGetters */ false)) || value.IsUnset()) { // Not a value we can do anything with, ignore it. continue; @@ -1153,7 +1153,7 @@ IDBObjectStore::AppendIndexUpdateInfo( } else { Key value; - if (NS_FAILED(value.SetFromJSVal(aCx, val)) || + if (NS_FAILED(value.SetFromJSVal(aCx, val, /* aCallGetters */ false)) || value.IsUnset()) { // Not a value we can do anything with, ignore it. return NS_OK; @@ -1324,12 +1324,12 @@ IDBObjectStore::GetAddInfo(JSContext* aCx, if (!HasValidKeyPath()) { // Out-of-line keys must be passed in. - rv = aKey.SetFromJSVal(aCx, aKeyVal); + rv = aKey.SetFromJSVal(aCx, aKeyVal, /* aCallGetters */ true); if (NS_FAILED(rv)) { return rv; } } else if (!isAutoIncrement) { - rv = GetKeyPath().ExtractKey(aCx, aValue, aKey); + rv = GetKeyPath().ExtractKey(aCx, aValue, aKey, /* aCallGetters */ false); if (NS_FAILED(rv)) { return rv; } @@ -1368,7 +1368,8 @@ IDBObjectStore::GetAddInfo(JSContext* aCx, aValue, aKey, &GetAddInfoCallback, - &data); + &data, + /* aCallGetters */ false); } else { rv = GetAddInfoCallback(aCx, &data); } diff --git a/dom/indexedDB/Key.cpp b/dom/indexedDB/Key.cpp index 0f693b2c6..575734af2 100644 --- a/dom/indexedDB/Key.cpp +++ b/dom/indexedDB/Key.cpp @@ -201,8 +201,11 @@ Key::ToLocaleBasedKey(Key& aTarget, const nsCString& aLocale) const } nsresult -Key::EncodeJSValInternal(JSContext* aCx, JS::Handle aVal, - uint8_t aTypeOffset, uint16_t aRecursionDepth) +Key::EncodeJSValInternal(JSContext* aCx, + JS::Handle aVal, + uint8_t aTypeOffset, + uint16_t aRecursionDepth, + bool aCallGetters) { static_assert(eMaxType * kMaxArrayCollapse < 256, "Unable to encode jsvals."); @@ -257,13 +260,18 @@ Key::EncodeJSValInternal(JSContext* aCx, JS::Handle aVal, for (uint32_t index = 0; index < length; index++) { JS::Rooted val(aCx); - if (!JS_GetElement(aCx, obj, index, &val)) { + bool ok = aCallGetters ? JS_GetElement(aCx, obj, index, &val) + : JS_GetOwnElement(aCx, obj, index, &val); + if (!ok) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } - nsresult rv = EncodeJSValInternal(aCx, val, aTypeOffset, - aRecursionDepth + 1); + nsresult rv = EncodeJSValInternal(aCx, + val, + aTypeOffset, + aRecursionDepth + 1, + aCallGetters); if (NS_FAILED(rv)) { return rv; } @@ -406,9 +414,10 @@ Key::DecodeJSValInternal(const unsigned char*& aPos, const unsigned char* aEnd, nsresult Key::EncodeJSVal(JSContext* aCx, JS::Handle aVal, - uint8_t aTypeOffset) + uint8_t aTypeOffset, + bool aCallGetters) { - return EncodeJSValInternal(aCx, aVal, aTypeOffset, 0); + return EncodeJSValInternal(aCx, aVal, aTypeOffset, 0, aCallGetters); } void @@ -741,7 +750,8 @@ Key::SetFromValueArray(mozIStorageValueArray* aValues, nsresult Key::SetFromJSVal(JSContext* aCx, - JS::Handle aVal) + JS::Handle aVal, + bool aCallGetters) { mBuffer.Truncate(); @@ -750,7 +760,7 @@ Key::SetFromJSVal(JSContext* aCx, return NS_OK; } - nsresult rv = EncodeJSVal(aCx, aVal, 0); + nsresult rv = EncodeJSVal(aCx, aVal, 0, aCallGetters); if (NS_FAILED(rv)) { Unset(); return rv; @@ -793,9 +803,15 @@ Key::ToJSVal(JSContext* aCx, } nsresult -Key::AppendItem(JSContext* aCx, bool aFirstOfArray, JS::Handle aVal) +Key::AppendItem(JSContext* aCx, + bool aFirstOfArray, + JS::Handle aVal, + bool aCallGetters) { - nsresult rv = EncodeJSVal(aCx, aVal, aFirstOfArray ? eMaxType : 0); + nsresult rv = EncodeJSVal(aCx, + aVal, + aFirstOfArray ? eMaxType : 0, + aCallGetters); if (NS_FAILED(rv)) { Unset(); return rv; diff --git a/dom/indexedDB/Key.h b/dom/indexedDB/Key.h index 9d70ce6ad..a4fb65b48 100644 --- a/dom/indexedDB/Key.h +++ b/dom/indexedDB/Key.h @@ -203,7 +203,7 @@ public: } nsresult - SetFromJSVal(JSContext* aCx, JS::Handle aVal); + SetFromJSVal(JSContext* aCx, JS::Handle aVal, bool aCallGetters); nsresult ToJSVal(JSContext* aCx, JS::MutableHandle aVal) const; @@ -212,7 +212,10 @@ public: ToJSVal(JSContext* aCx, JS::Heap& aVal) const; nsresult - AppendItem(JSContext* aCx, bool aFirstOfArray, JS::Handle aVal); + AppendItem(JSContext* aCx, + bool aFirstOfArray, + JS::Handle aVal, + bool aCallGetters); nsresult ToLocaleBasedKey(Key& aTarget, const nsCString& aLocale) const; @@ -283,7 +286,10 @@ private: // Encoding functions. These append the encoded value to the end of mBuffer nsresult - EncodeJSVal(JSContext* aCx, JS::Handle aVal, uint8_t aTypeOffset); + EncodeJSVal(JSContext* aCx, + JS::Handle aVal, + uint8_t aTypeOffset, + bool aCallGetters); void EncodeString(const nsAString& aString, uint8_t aTypeOffset); @@ -331,7 +337,8 @@ private: EncodeJSValInternal(JSContext* aCx, JS::Handle aVal, uint8_t aTypeOffset, - uint16_t aRecursionDepth); + uint16_t aRecursionDepth, + bool aCallGetters); static nsresult DecodeJSValInternal(const unsigned char*& aPos, diff --git a/dom/indexedDB/KeyPath.cpp b/dom/indexedDB/KeyPath.cpp index 30edd8cd7..0221c9450 100644 --- a/dom/indexedDB/KeyPath.cpp +++ b/dom/indexedDB/KeyPath.cpp @@ -372,11 +372,13 @@ KeyPath::AppendStringWithValidation(const nsAString& aString) } nsresult -KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue, Key& aKey) const +KeyPath::ExtractKey(JSContext* aCx, + const JS::Value& aValue, + Key& aKey, + bool aCallGetters) const { uint32_t len = mStrings.Length(); JS::Rooted value(aCx); - aKey.Unset(); for (uint32_t i = 0; i < len; ++i) { @@ -388,7 +390,10 @@ KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue, Key& aKey) const return rv; } - if (NS_FAILED(aKey.AppendItem(aCx, IsArray() && i == 0, value))) { + if (NS_FAILED(aKey.AppendItem(aCx, + IsArray() && i == 0, + value, + aCallGetters))) { NS_ASSERTION(aKey.IsUnset(), "Encoding error should unset"); return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } @@ -437,9 +442,12 @@ KeyPath::ExtractKeyAsJSVal(JSContext* aCx, const JS::Value& aValue, } nsresult -KeyPath::ExtractOrCreateKey(JSContext* aCx, const JS::Value& aValue, - Key& aKey, ExtractOrCreateKeyCallback aCallback, - void* aClosure) const +KeyPath::ExtractOrCreateKey(JSContext* aCx, + const JS::Value& aValue, + Key& aKey, + ExtractOrCreateKeyCallback aCallback, + void* aClosure, + bool aCallGetters) const { NS_ASSERTION(IsString(), "This doesn't make sense!"); @@ -455,7 +463,7 @@ KeyPath::ExtractOrCreateKey(JSContext* aCx, const JS::Value& aValue, return rv; } - if (NS_FAILED(aKey.AppendItem(aCx, false, value))) { + if (NS_FAILED(aKey.AppendItem(aCx, false, value, aCallGetters))) { NS_ASSERTION(aKey.IsUnset(), "Should be unset"); return value.isUndefined() ? NS_OK : NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } diff --git a/dom/indexedDB/KeyPath.h b/dom/indexedDB/KeyPath.h index c133cdc4a..e6e5f57d4 100644 --- a/dom/indexedDB/KeyPath.h +++ b/dom/indexedDB/KeyPath.h @@ -72,7 +72,10 @@ public: Parse(const Nullable& aValue, KeyPath* aKeyPath); nsresult - ExtractKey(JSContext* aCx, const JS::Value& aValue, Key& aKey) const; + ExtractKey(JSContext* aCx, + const JS::Value& aValue, + Key& aKey, + bool aCallGetters) const; nsresult ExtractKeyAsJSVal(JSContext* aCx, const JS::Value& aValue, @@ -82,9 +85,12 @@ public: (*ExtractOrCreateKeyCallback)(JSContext* aCx, void* aClosure); nsresult - ExtractOrCreateKey(JSContext* aCx, const JS::Value& aValue, Key& aKey, + ExtractOrCreateKey(JSContext* aCx, + const JS::Value& aValue, + Key& aKey, ExtractOrCreateKeyCallback aCallback, - void* aClosure) const; + void* aClosure, + bool aCallGetters) const; inline bool IsValid() const { return mType != NONEXISTENT; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 84a315587..cad0840e0 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -2010,6 +2010,28 @@ JS_GetOwnUCPropertyDescriptor(JSContext* cx, HandleObject obj, const char16_t* n return JS_GetOwnPropertyDescriptorById(cx, obj, id, desc); } +JS_PUBLIC_API(bool) +JS_GetOwnElement(JSContext* cx, JS::HandleObject obj, uint32_t index, JS::MutableHandleValue vp) +{ + RootedId id(cx); + if (!IndexToId(cx, index, &id)) { + return false; + } + + Rooted desc(cx); + if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, &desc)) { + return false; + } + + if (desc.object() && desc.isDataDescriptor()) { + vp.set(desc.value()); + } else { + vp.setUndefined(); + } + + return true; +} + JS_PUBLIC_API(bool) JS_GetPropertyDescriptorById(JSContext* cx, HandleObject obj, HandleId id, MutableHandle desc) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 67b3d4267..dc00c650d 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2922,6 +2922,9 @@ extern JS_PUBLIC_API(bool) JS_GetOwnUCPropertyDescriptor(JSContext* cx, JS::HandleObject obj, const char16_t* name, size_t namelen, JS::MutableHandle desc); +extern JS_PUBLIC_API(bool) +JS_GetOwnElement(JSContext* cx, JS::HandleObject obj, uint32_t index, JS::MutableHandleValue vp); + /** * Like JS_GetOwnPropertyDescriptorById, but also searches the prototype chain * if no own property is found directly on obj. The object on which the -- cgit v1.2.3 From 334a892eb5f94c9adc5253101a213c8b8b492df8 Mon Sep 17 00:00:00 2001 From: Sebastian Streich Date: Thu, 5 Sep 2019 09:48:51 +0200 Subject: Add checks to respect CSP-wildcard + Ports. --- dom/security/nsCSPUtils.cpp | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp index 71c8e3433..d07ad7945 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -641,13 +641,22 @@ nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected // just a specific scheme, the parser should generate a nsCSPSchemeSource. NS_ASSERTION((!mHost.IsEmpty()), "host can not be the empty string"); + // Before we can check if the host matches, we have to + // extract the host part from aUri. + nsAutoCString uriHost; + nsresult rv = aUri->GetAsciiHost(uriHost); + NS_ENSURE_SUCCESS(rv, false); + + nsString decodedUriHost; + CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost); + // 2) host matching: Enforce a single * if (mHost.EqualsASCII("*")) { // The single ASTERISK character (*) does not match a URI's scheme of a type // designating a globally unique identifier (such as blob:, data:, or filesystem:) - // At the moment firefox does not support filesystem; but for future compatibility + // At the moment UXP does not support "filesystem:" but for future compatibility // we support it in CSP according to the spec, see: 4.2.2 Matching Source Expressions - // Note, that whitelisting any of these schemes would call nsCSPSchemeSrc::permits(). + // Note: whitelisting any of these schemes would call nsCSPSchemeSrc::permits(). bool isBlobScheme = (NS_SUCCEEDED(aUri->SchemeIs("blob", &isBlobScheme)) && isBlobScheme); bool isDataScheme = @@ -658,20 +667,15 @@ nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected if (isBlobScheme || isDataScheme || isFileScheme) { return false; } - return true; - } - - // Before we can check if the host matches, we have to - // extract the host part from aUri. - nsAutoCString uriHost; - nsresult rv = aUri->GetAsciiHost(uriHost); - NS_ENSURE_SUCCESS(rv, false); - - nsString decodedUriHost; - CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost); + // If no scheme is present there also won't be a port and folder to check + // which means we can return early. + if (mScheme.IsEmpty()) { + return true; + } + } // 4.5) host matching: Check if the allowed host starts with a wilcard. - if (mHost.First() == '*') { + else if (mHost.First() == '*') { NS_ASSERTION(mHost[1] == '.', "Second character needs to be '.' whenever host starts with '*'"); // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before checking -- cgit v1.2.3 From d90dd7b0c60e7950b668a08d415c0395c92db535 Mon Sep 17 00:00:00 2001 From: Olli Pettay Date: Thu, 5 Sep 2019 11:29:47 +0200 Subject: Ensure the right body element is used throughout the method call. --- dom/html/ImageDocument.cpp | 2 +- dom/html/PluginDocument.cpp | 2 +- dom/html/VideoDocument.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dom/html/ImageDocument.cpp b/dom/html/ImageDocument.cpp index f83a804be..451d989c3 100644 --- a/dom/html/ImageDocument.cpp +++ b/dom/html/ImageDocument.cpp @@ -659,7 +659,7 @@ ImageDocument::CreateSyntheticDocument() NS_ENSURE_SUCCESS(rv, rv); // Add the image element - Element* body = GetBodyElement(); + RefPtr body = GetBodyElement(); if (!body) { NS_WARNING("no body on image document!"); return NS_ERROR_FAILURE; diff --git a/dom/html/PluginDocument.cpp b/dom/html/PluginDocument.cpp index 1c923ecc6..f6be8a915 100644 --- a/dom/html/PluginDocument.cpp +++ b/dom/html/PluginDocument.cpp @@ -206,7 +206,7 @@ PluginDocument::CreateSyntheticPluginDocument() NS_ENSURE_SUCCESS(rv, rv); // then attach our plugin - Element* body = GetBodyElement(); + RefPtr body = GetBodyElement(); if (!body) { NS_WARNING("no body on plugin document!"); return NS_ERROR_FAILURE; diff --git a/dom/html/VideoDocument.cpp b/dom/html/VideoDocument.cpp index 1bd898564..76b2e326f 100644 --- a/dom/html/VideoDocument.cpp +++ b/dom/html/VideoDocument.cpp @@ -90,7 +90,7 @@ VideoDocument::CreateSyntheticVideoDocument(nsIChannel* aChannel, nsresult rv = MediaDocument::CreateSyntheticDocument(); NS_ENSURE_SUCCESS(rv, rv); - Element* body = GetBodyElement(); + RefPtr body = GetBodyElement(); if (!body) { NS_WARNING("no body on video document!"); return NS_ERROR_FAILURE; -- cgit v1.2.3 From 2b223cce089bb8cbfb1a463fdd42e09eee63c7b2 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 5 Sep 2019 13:03:09 +0200 Subject: Use the correct group for JIT constraints. This fixes a rare crash/CTD in JS. This adds information about the constraints to a new RAII class so we can finish all constraints at the end. Based on changes in BZ 1568397 --- js/src/jit/IonAnalysis.cpp | 32 ++++++++++++----- js/src/jit/IonAnalysis.h | 7 ++-- js/src/vm/TypeInference.cpp | 84 +++++++++++++++++++++++++++++++++++++++++---- js/src/vm/TypeInference.h | 59 ++++++++++++++++++++++++++++++- 4 files changed, 164 insertions(+), 18 deletions(-) diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 5fc624fb1..ace6cd81e 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -4005,7 +4005,7 @@ jit::ConvertLinearInequality(TempAllocator& alloc, MBasicBlock* block, const Lin } static bool -AnalyzePoppedThis(JSContext* cx, ObjectGroup* group, +AnalyzePoppedThis(JSContext* cx, DPAConstraintInfo& constraintInfo, ObjectGroup* group, MDefinition* thisValue, MInstruction* ins, bool definitelyExecuted, HandlePlainObject baseobj, Vector* initializerList, @@ -4046,7 +4046,12 @@ AnalyzePoppedThis(JSContext* cx, ObjectGroup* group, return true; RootedId id(cx, NameToId(setprop->name())); - if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, group, id)) { + bool added = false; + if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, constraintInfo, + group, id, &added)) { + return false; + } + if (!added) { // The prototype chain already contains a getter/setter for this // property, or type information is too imprecise. return true; @@ -4106,7 +4111,12 @@ AnalyzePoppedThis(JSContext* cx, ObjectGroup* group, if (!baseobj->lookup(cx, id) && !accessedProperties->append(get->name())) return false; - if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, group, id)) { + bool added = false; + if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, constraintInfo, + group, id, &added)) { + return false; + } + if (!added) { // The |this| value can escape if any property reads it does go // through a getter. return true; @@ -4132,8 +4142,11 @@ CmpInstructions(const void* a, const void* b) } bool -jit::AnalyzeNewScriptDefiniteProperties(JSContext* cx, HandleFunction fun, - ObjectGroup* group, HandlePlainObject baseobj, +jit::AnalyzeNewScriptDefiniteProperties(JSContext* cx, + DPAConstraintInfo& constraintInfo, + HandleFunction fun, + ObjectGroup* group, + HandlePlainObject baseobj, Vector* initializerList) { MOZ_ASSERT(cx->zone()->types.activeAnalysis); @@ -4293,7 +4306,7 @@ jit::AnalyzeNewScriptDefiniteProperties(JSContext* cx, HandleFunction fun, bool handled = false; size_t slotSpan = baseobj->slotSpan(); - if (!AnalyzePoppedThis(cx, group, thisValue, ins, definitelyExecuted, + if (!AnalyzePoppedThis(cx, constraintInfo, group, thisValue, ins, definitelyExecuted, baseobj, initializerList, &accessedProperties, &handled)) { return false; @@ -4312,7 +4325,6 @@ jit::AnalyzeNewScriptDefiniteProperties(JSContext* cx, HandleFunction fun, // contingent on the correct frames being inlined. Add constraints to // invalidate the definite properties if additional functions could be // called at the inline frame sites. - Vector exitBlocks(cx); for (MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) { // Inlining decisions made after the last new property was added to // the object don't need to be frozen. @@ -4320,9 +4332,11 @@ jit::AnalyzeNewScriptDefiniteProperties(JSContext* cx, HandleFunction fun, break; if (MResumePoint* rp = block->callerResumePoint()) { if (block->numPredecessors() == 1 && block->getPredecessor(0) == rp->block()) { - JSScript* script = rp->block()->info().script(); - if (!AddClearDefiniteFunctionUsesInScript(cx, group, script, block->info().script())) + JSScript* caller = rp->block()->info().script(); + JSScript* callee = block->info().script(); + if (!constraintInfo.addInliningConstraint(caller, callee)) { return false; + } } } } diff --git a/js/src/jit/IonAnalysis.h b/js/src/jit/IonAnalysis.h index efd31415b..49bc0b591 100644 --- a/js/src/jit/IonAnalysis.h +++ b/js/src/jit/IonAnalysis.h @@ -196,8 +196,11 @@ MCompare* ConvertLinearInequality(TempAllocator& alloc, MBasicBlock* block, const LinearSum& sum); MOZ_MUST_USE bool -AnalyzeNewScriptDefiniteProperties(JSContext* cx, HandleFunction fun, - ObjectGroup* group, HandlePlainObject baseobj, +AnalyzeNewScriptDefiniteProperties(JSContext* cx, + DPAConstraintInfo& constraintInfo, + HandleFunction fun, + ObjectGroup* group, + HandlePlainObject baseobj, Vector* initializerList); MOZ_MUST_USE bool diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index 7c2c0194e..88327b47e 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -3084,29 +3084,39 @@ class TypeConstraintClearDefiniteGetterSetter : public TypeConstraint }; bool -js::AddClearDefiniteGetterSetterForPrototypeChain(JSContext* cx, ObjectGroup* group, HandleId id) +js::AddClearDefiniteGetterSetterForPrototypeChain(JSContext* cx, + DPAConstraintInfo& constraintInfo, + ObjectGroup* group, + HandleId id, + bool* added) { /* * Ensure that if the properties named here could have a getter, setter or * a permanent property in any transitive prototype, the definite * properties get cleared from the group. */ + + *added = false; + RootedObject proto(cx, group->proto().toObjectOrNull()); while (proto) { ObjectGroup* protoGroup = JSObject::getGroup(cx, proto); if (!protoGroup) { - cx->recoverFromOutOfMemory(); return false; } if (protoGroup->unknownProperties()) - return false; + return true; HeapTypeSet* protoTypes = protoGroup->getProperty(cx, proto, id); - if (!protoTypes || protoTypes->nonDataProperty() || protoTypes->nonWritableProperty()) + if (!protoTypes) return false; - if (!protoTypes->addConstraint(cx, cx->typeLifoAlloc().new_(group))) + if (protoTypes->nonDataProperty() || protoTypes->nonWritableProperty()) + return true; + if (!constraintInfo.addProtoConstraint(proto, id)) return false; proto = proto->staticPrototype(); } + + *added = true; return true; } @@ -3612,6 +3622,43 @@ struct DestroyTypeNewScript } // namespace +bool DPAConstraintInfo::finishConstraints(JSContext* cx, ObjectGroup* group) { + for (const ProtoConstraint& constraint : protoConstraints_) { + ObjectGroup* protoGroup = constraint.proto->group(); + + // Note: we rely on the group's type information being unchanged since + // AddClearDefiniteGetterSetterForPrototypeChain. + + bool unknownProperties = protoGroup->unknownProperties(); + MOZ_RELEASE_ASSERT(!unknownProperties); + + HeapTypeSet* protoTypes = + protoGroup->getProperty(cx, constraint.proto, constraint.id); + MOZ_RELEASE_ASSERT(protoTypes); + + MOZ_ASSERT(!protoTypes->nonDataProperty()); + MOZ_ASSERT(!protoTypes->nonWritableProperty()); + + if (!protoTypes->addConstraint( + cx, + cx->typeLifoAlloc().new_( + group))) { + ReportOutOfMemory(cx); + return false; + } + } + + for (const InliningConstraint& constraint : inliningConstraints_) { + if (!AddClearDefiniteFunctionUsesInScript(cx, group, constraint.caller, + constraint.callee)) { + ReportOutOfMemory(cx); + return false; + } + } + + return true; +} + bool TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, bool force) { @@ -3715,10 +3762,17 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, return false; Vector initializerVector(cx); + + DPAConstraintInfo constraintInfo(cx); RootedPlainObject templateRoot(cx, templateObject()); RootedFunction fun(cx, function()); - if (!jit::AnalyzeNewScriptDefiniteProperties(cx, fun, group, templateRoot, &initializerVector)) + if (!jit::AnalyzeNewScriptDefiniteProperties(cx, + constraintInfo, + fun, + group, + templateRoot, + &initializerVector)) return false; if (!group->newScript()) @@ -3772,6 +3826,14 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, // The definite properties analysis found exactly the properties that // are held in common by the preliminary objects. No further analysis // is needed. + + if (!constraintInfo.finishConstraints(cx, group)) { + return false; + } + if (!group->newScript()) { + return true; + } + group->addDefiniteProperties(cx, templateObject()->lastProperty()); destroyNewScript.group = nullptr; @@ -3792,6 +3854,16 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, initialFlags); if (!initialGroup) return false; + + // Add the constraints. Use the initialGroup as group referenced by the + // constraints because that's the group that will have the TypeNewScript + // associated with it. See the detachNewScript and setNewScript calls below. + if (!constraintInfo.finishConstraints(cx, initialGroup)) { + return false; + } + if (!group->newScript()) { + return true; + } initialGroup->addDefiniteProperties(cx, templateObject()->lastProperty()); group->addDefiniteProperties(cx, prefixShape); diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h index 94ce7e871..fd021fc96 100644 --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -789,8 +789,65 @@ class TemporaryTypeSet : public TypeSet TypedArraySharedness* sharedness); }; +// Stack class to record information about constraints that need to be added +// after finishing the Definite Properties Analysis. When the analysis succeeds, +// the |finishConstraints| method must be called to add the constraints to the +// TypeSets. +// +// There are two constraint types managed here: +// +// 1. Proto constraints for HeapTypeSets, to guard against things like getters +// and setters on the proto chain. +// +// 2. Inlining constraints for StackTypeSets, to invalidate when additional +// functions could be called at call sites where we inlined a function. +// +// This class uses bare GC-thing pointers because GC is suppressed when the +// analysis runs. +class MOZ_RAII DPAConstraintInfo { + struct ProtoConstraint { + JSObject* proto; + jsid id; + ProtoConstraint(JSObject* proto, jsid id) : proto(proto), id(id) {} + }; + struct InliningConstraint { + JSScript* caller; + JSScript* callee; + InliningConstraint(JSScript* caller, JSScript* callee) + : caller(caller), callee(callee) {} + }; + + JS::AutoCheckCannotGC nogc_; + Vector protoConstraints_; + Vector inliningConstraints_; + +public: + explicit DPAConstraintInfo(JSContext* cx) + : nogc_(cx) + , protoConstraints_(cx) + , inliningConstraints_(cx) + { + } + + DPAConstraintInfo(const DPAConstraintInfo&) = delete; + void operator=(const DPAConstraintInfo&) = delete; + + MOZ_MUST_USE bool addProtoConstraint(JSObject* proto, jsid id) { + return protoConstraints_.emplaceBack(proto, id); + } + MOZ_MUST_USE bool addInliningConstraint(JSScript* caller, JSScript* callee) { + return inliningConstraints_.emplaceBack(caller, callee); + } + + MOZ_MUST_USE bool finishConstraints(JSContext* cx, ObjectGroup* group); +}; + bool -AddClearDefiniteGetterSetterForPrototypeChain(JSContext* cx, ObjectGroup* group, HandleId id); +AddClearDefiniteGetterSetterForPrototypeChain(JSContext* cx, + DPAConstraintInfo& constraintInfo, + ObjectGroup* group, + HandleId id, + bool* added); bool AddClearDefiniteFunctionUsesInScript(JSContext* cx, ObjectGroup* group, -- cgit v1.2.3 From e3c13af9761895a19fb1f58abf920190aa739348 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 5 Sep 2019 15:30:32 +0200 Subject: Properly implement various HSTS states. Previously, HSTS preload list values could be overridden temporarily due to counter-intuitive behavior of the API's removeState function. This adds an explicit flag to the API for writing knockout values to the Site Security Service, with the default resetting to whatever the preload list state is. --- security/manager/ssl/nsISiteSecurityService.idl | 12 +++++++--- security/manager/ssl/nsSiteSecurityService.cpp | 31 ++++++++++++++----------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/security/manager/ssl/nsISiteSecurityService.idl b/security/manager/ssl/nsISiteSecurityService.idl index 753f32b57..b61577152 100644 --- a/security/manager/ssl/nsISiteSecurityService.idl +++ b/security/manager/ssl/nsISiteSecurityService.idl @@ -23,7 +23,7 @@ namespace mozilla [ref] native nsCStringTArrayRef(nsTArray); [ref] native mozillaPkixTime(mozilla::pkix::Time); -[scriptable, uuid(275127f8-dbd7-4681-afbf-6df0c6587a01)] +[scriptable, uuid(233908bd-6741-4474-a6e1-f298c6ce9eaf)] interface nsISiteSecurityService : nsISupports { const uint32_t HEADER_HSTS = 0; @@ -98,15 +98,21 @@ interface nsISiteSecurityService : nsISupports * Given a header type, removes state relating to that header of a host, * including the includeSubdomains state that would affect subdomains. * This essentially removes the state for the domain tree rooted at this - * host. + * host. If any preloaded information is present for that host, that + * information will then be used instead of any other previously existing + * state, unless the force parameter is set. + * * @param aType the type of security state in question * @param aURI the URI of the target host * @param aFlags options for this request as defined in nsISocketProvider: * NO_PERMANENT_STORAGE + * @param force if set, forces no-HSTS state by writing a knockout value, + * overriding any preload list state */ void removeState(in uint32_t aType, in nsIURI aURI, - in uint32_t aFlags); + in uint32_t aFlags, + [optional] in boolean force); /** * See isSecureURI diff --git a/security/manager/ssl/nsSiteSecurityService.cpp b/security/manager/ssl/nsSiteSecurityService.cpp index cfee79d8d..44ee7dcc0 100644 --- a/security/manager/ssl/nsSiteSecurityService.cpp +++ b/security/manager/ssl/nsSiteSecurityService.cpp @@ -330,21 +330,22 @@ nsSiteSecurityService::SetHSTSState(uint32_t aType, uint32_t flags, SecurityPropertyState aHSTSState) { - // If max-age is zero, that's an indication to immediately remove the - // security state, so here's a shortcut. - if (!maxage) { - return RemoveState(aType, aSourceURI, flags); + // Exit early if STS not enabled + if (!mUseStsService) { + return NS_OK; + } + + // If max-age is zero, the host is no longer considered HSTS. If the host was + // preloaded, we store an entry indicating that this host is not HSTS, causing + // the preloaded information to be ignored. + if (maxage == 0) { + return RemoveState(aType, aSourceURI, flags, true); } MOZ_ASSERT((aHSTSState == SecurityPropertySet || aHSTSState == SecurityPropertyNegative), "HSTS State must be SecurityPropertySet or SecurityPropertyNegative"); - // Exit early if STS not enabled - if (!mUseStsService) { - return NS_OK; - } - int64_t expiretime = ExpireTimeFromMaxAge(maxage); SiteHSTSState siteState(expiretime, aHSTSState, includeSubdomains); nsAutoCString stateString; @@ -367,7 +368,7 @@ nsSiteSecurityService::SetHSTSState(uint32_t aType, NS_IMETHODIMP nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI, - uint32_t aFlags) + uint32_t aFlags, bool force = false) { // Child processes are not allowed direct access to this. if (!XRE_IsParentProcess()) { @@ -387,8 +388,9 @@ nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI, mozilla::DataStorageType storageType = isPrivate ? mozilla::DataStorage_Private : mozilla::DataStorage_Persistent; - // If this host is in the preload list, we have to store a knockout entry. - if (GetPreloadListEntry(hostname.get())) { + // If this host is in the preload list, we have to store a knockout entry + // if it's explicitly forced to not be HSTS anymore + if (force && GetPreloadListEntry(hostname.get())) { SSSLOG(("SSS: storing knockout entry for %s", hostname.get())); SiteHSTSState siteState(0, SecurityPropertyKnockout, false); nsAutoCString stateString; @@ -769,7 +771,10 @@ nsSiteSecurityService::ProcessPKPHeader(nsIURI* aSourceURI, return NS_ERROR_FAILURE; } - // if maxAge == 0 we must delete all state, for now no hole-punching + // If maxAge == 0, we remove dynamic HPKP state for this host. Due to + // architectural constraints, if this host was preloaded, any future lookups + // will use the preloaded state (i.e. we can't store a "this host is not HPKP" + // entry like we can for HSTS). if (maxAge == 0) { return RemoveState(aType, aSourceURI, aFlags); } -- cgit v1.2.3 From 6db06749e2037029adc96660aafa5339ed609e60 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 5 Sep 2019 18:42:49 +0200 Subject: Fix whitelisting of JavaScript-uris by CSP hash. --- dom/security/nsCSPContext.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp index 65be02809..56a119e1a 100644 --- a/dom/security/nsCSPContext.cpp +++ b/dom/security/nsCSPContext.cpp @@ -513,8 +513,19 @@ nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType, for (uint32_t i = 0; i < mPolicies.Length(); i++) { bool allowed = mPolicies[i]->allows(aContentType, CSP_UNSAFE_INLINE, EmptyString(), aParserCreated) || - mPolicies[i]->allows(aContentType, CSP_NONCE, aNonce, aParserCreated) || - mPolicies[i]->allows(aContentType, CSP_HASH, aContent, aParserCreated); + mPolicies[i]->allows(aContentType, CSP_NONCE, aNonce, aParserCreated); + + // If the inlined script or style is allowed by either unsafe-inline or the + // nonce, go ahead and shortcut this loop. + if (allowed) { + continue; + } + + // Check if the csp-hash matches against the hash of the script. + // If we don't have any content to check, block the script. + if (!aContent.IsEmpty()) { + allowed = mPolicies[i]->allows(aContentType, CSP_HASH, aContent, aParserCreated); + } if (!allowed) { // policy is violoated: deny the load unless policy is report only and -- cgit v1.2.3 From b8a1f577034d16a928997f9368907c9f3b1b1bba Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Tue, 10 Sep 2019 10:44:10 +0200 Subject: Don't assume Intel architecture for compiler optimizations on Linux/gcc. This only adds SSE2 flags when the CPU architecture is correct for it. Resolves #1226 --- build/autoconf/compiler-opts.m4 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/build/autoconf/compiler-opts.m4 b/build/autoconf/compiler-opts.m4 index 82d0b43fc..0848d78d9 100644 --- a/build/autoconf/compiler-opts.m4 +++ b/build/autoconf/compiler-opts.m4 @@ -176,8 +176,13 @@ if test "$GNU_CC"; then CFLAGS="$CFLAGS -ffunction-sections -fdata-sections" CXXFLAGS="$CXXFLAGS -ffunction-sections -fdata-sections" fi - CFLAGS="$CFLAGS -fno-math-errno -msse2 -mfpmath=sse" - CXXFLAGS="$CXXFLAGS -fno-exceptions -fno-math-errno -msse2 -mfpmath=sse" + CFLAGS="$CFLAGS -fno-math-errno" + CXXFLAGS="$CXXFLAGS -fno-exceptions -fno-math-errno" + + if test "$CPU_ARCH" = x86; then + CFLAGS="$CFLAGS -msse2 -mfpmath=sse" + CXXFLAGS="$CXXFLAGS -msse2 -mfpmath=sse" + fi if test -z "$CLANG_CC"; then case "$CC_VERSION" in -- cgit v1.2.3 From 18a2244f5b1e28c2d38dd95bf93d02cef0173178 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Tue, 10 Sep 2019 19:07:28 +0200 Subject: Issue #1226 - Explicitly enable sse2 on x86_64 also. Although it's enabled by default in gcc (should be, anyway!), we're being explicit here for 64-bit x86 platforms here also. This matches the old behavior. --- build/autoconf/compiler-opts.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/autoconf/compiler-opts.m4 b/build/autoconf/compiler-opts.m4 index 0848d78d9..77c2e85b5 100644 --- a/build/autoconf/compiler-opts.m4 +++ b/build/autoconf/compiler-opts.m4 @@ -179,7 +179,7 @@ if test "$GNU_CC"; then CFLAGS="$CFLAGS -fno-math-errno" CXXFLAGS="$CXXFLAGS -fno-exceptions -fno-math-errno" - if test "$CPU_ARCH" = x86; then + if test "$CPU_ARCH" = "x86" -o "$CPU_ARCH" = "x86_64"; then CFLAGS="$CFLAGS -msse2 -mfpmath=sse" CXXFLAGS="$CXXFLAGS -msse2 -mfpmath=sse" fi -- cgit v1.2.3 From d59ef877425d39d4ef13f13e9c6ba034bfd6b483 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Tue, 17 Sep 2019 11:04:22 +0200 Subject: No issue - Add 360 Safeguard to DLL blocklist 360 Safeguard/360 Total Security (Qihoo) causes crashes in a11y components. This adds the offending dll to the injection blocklist. See BZ bug 1536227 for details. --- mozglue/build/WindowsDllBlocklist.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mozglue/build/WindowsDllBlocklist.cpp b/mozglue/build/WindowsDllBlocklist.cpp index c7d14041d..0686b64cb 100644 --- a/mozglue/build/WindowsDllBlocklist.cpp +++ b/mozglue/build/WindowsDllBlocklist.cpp @@ -227,6 +227,9 @@ static DllBlockInfo sWindowsDllBlocklist[] = { // Comodo IS old versions, startup crash on 64-bit, bug 1140397 { "guard64.dll", MAKE_VERSION(6, 3, 0, 0) }, + // 360 Safeguard/360 Total Security causes a11y crashes, bug 1536227. + { "safemon64.dll", ALL_VERSIONS }, + { nullptr, 0 } }; -- cgit v1.2.3 From 7422dd4fe9111e822d358cc160ce2deee08ab6c6 Mon Sep 17 00:00:00 2001 From: adeshkp Date: Tue, 24 Sep 2019 13:51:29 -0400 Subject: Issue #1231 - Stop using ICC profiles on Linux. General consensus seems to be that color management on Linux desktops is not mature enough to enable by default. --- modules/libpref/init/all.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index e69a985ce..b64c157c5 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -732,12 +732,19 @@ pref("gfx.layerscope.port", 23456); // This should be use to quickly find which slow paths are used by test cases. pref("gfx.perf-warnings.enabled", false); -// 0 = Off, 1 = Full, 2 = Tagged Images Only. +// 0 = Off, 1 = All Images, 2 = Tagged Images Only. // See eCMSMode in gfx/thebes/gfxPlatform.h +#ifdef XP_WIN pref("gfx.color_management.mode", 2); pref("gfx.color_management.display_profile", ""); pref("gfx.color_management.rendering_intent", 0); pref("gfx.color_management.enablev4", true); +#else +pref("gfx.color_management.mode", 0); +pref("gfx.color_management.display_profile", ""); +pref("gfx.color_management.rendering_intent", 0); +pref("gfx.color_management.enablev4", false); +#endif pref("gfx.downloadable_fonts.enabled", true); pref("gfx.downloadable_fonts.fallback_delay", 3000); -- cgit v1.2.3 From 870375faa3f8c8af30d7fd8c2eb6c20dc6f325b1 Mon Sep 17 00:00:00 2001 From: Dmitry Grigoryev Date: Sat, 28 Sep 2019 10:00:36 +0200 Subject: Added missing libwebp NEON files --- media/libwebp/dsp/alpha_processing_neon.c | 191 +++++++++++++++++ media/libwebp/dsp/filters_neon.c | 329 ++++++++++++++++++++++++++++++ media/libwebp/dsp/moz.build | 6 + media/libwebp/dsp/yuv_neon.c | 288 ++++++++++++++++++++++++++ 4 files changed, 814 insertions(+) create mode 100644 media/libwebp/dsp/alpha_processing_neon.c create mode 100644 media/libwebp/dsp/filters_neon.c create mode 100644 media/libwebp/dsp/yuv_neon.c diff --git a/media/libwebp/dsp/alpha_processing_neon.c b/media/libwebp/dsp/alpha_processing_neon.c new file mode 100644 index 000000000..53dfce2b3 --- /dev/null +++ b/media/libwebp/dsp/alpha_processing_neon.c @@ -0,0 +1,191 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel, NEON version. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "../dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include "../dsp/neon.h" + +//------------------------------------------------------------------------------ + +#define MULTIPLIER(a) ((a) * 0x8081) +#define PREMULTIPLY(x, m) (((x) * (m)) >> 23) + +#define MULTIPLY_BY_ALPHA(V, ALPHA, OTHER) do { \ + const uint8x8_t alpha = (V).val[(ALPHA)]; \ + const uint16x8_t r1 = vmull_u8((V).val[1], alpha); \ + const uint16x8_t g1 = vmull_u8((V).val[2], alpha); \ + const uint16x8_t b1 = vmull_u8((V).val[(OTHER)], alpha); \ + /* we use: v / 255 = (v + 1 + (v >> 8)) >> 8 */ \ + const uint16x8_t r2 = vsraq_n_u16(r1, r1, 8); \ + const uint16x8_t g2 = vsraq_n_u16(g1, g1, 8); \ + const uint16x8_t b2 = vsraq_n_u16(b1, b1, 8); \ + const uint16x8_t r3 = vaddq_u16(r2, kOne); \ + const uint16x8_t g3 = vaddq_u16(g2, kOne); \ + const uint16x8_t b3 = vaddq_u16(b2, kOne); \ + (V).val[1] = vshrn_n_u16(r3, 8); \ + (V).val[2] = vshrn_n_u16(g3, 8); \ + (V).val[(OTHER)] = vshrn_n_u16(b3, 8); \ +} while (0) + +static void ApplyAlphaMultiply_NEON(uint8_t* rgba, int alpha_first, + int w, int h, int stride) { + const uint16x8_t kOne = vdupq_n_u16(1u); + while (h-- > 0) { + uint32_t* const rgbx = (uint32_t*)rgba; + int i = 0; + if (alpha_first) { + for (; i + 8 <= w; i += 8) { + // load aaaa...|rrrr...|gggg...|bbbb... + uint8x8x4_t RGBX = vld4_u8((const uint8_t*)(rgbx + i)); + MULTIPLY_BY_ALPHA(RGBX, 0, 3); + vst4_u8((uint8_t*)(rgbx + i), RGBX); + } + } else { + for (; i + 8 <= w; i += 8) { + uint8x8x4_t RGBX = vld4_u8((const uint8_t*)(rgbx + i)); + MULTIPLY_BY_ALPHA(RGBX, 3, 0); + vst4_u8((uint8_t*)(rgbx + i), RGBX); + } + } + // Finish with left-overs. + for (; i < w; ++i) { + uint8_t* const rgb = rgba + (alpha_first ? 1 : 0); + const uint8_t* const alpha = rgba + (alpha_first ? 0 : 3); + const uint32_t a = alpha[4 * i]; + if (a != 0xff) { + const uint32_t mult = MULTIPLIER(a); + rgb[4 * i + 0] = PREMULTIPLY(rgb[4 * i + 0], mult); + rgb[4 * i + 1] = PREMULTIPLY(rgb[4 * i + 1], mult); + rgb[4 * i + 2] = PREMULTIPLY(rgb[4 * i + 2], mult); + } + } + rgba += stride; + } +} +#undef MULTIPLY_BY_ALPHA +#undef MULTIPLIER +#undef PREMULTIPLY + +//------------------------------------------------------------------------------ + +static int DispatchAlpha_NEON(const uint8_t* alpha, int alpha_stride, + int width, int height, + uint8_t* dst, int dst_stride) { + uint32_t alpha_mask = 0xffffffffu; + uint8x8_t mask8 = vdup_n_u8(0xff); + uint32_t tmp[2]; + int i, j; + for (j = 0; j < height; ++j) { + // We don't know if alpha is first or last in dst[] (depending on rgbA/Argb + // mode). So we must be sure dst[4*i + 8 - 1] is writable for the store. + // Hence the test with 'width - 1' instead of just 'width'. + for (i = 0; i + 8 <= width - 1; i += 8) { + uint8x8x4_t rgbX = vld4_u8((const uint8_t*)(dst + 4 * i)); + const uint8x8_t alphas = vld1_u8(alpha + i); + rgbX.val[0] = alphas; + vst4_u8((uint8_t*)(dst + 4 * i), rgbX); + mask8 = vand_u8(mask8, alphas); + } + for (; i < width; ++i) { + const uint32_t alpha_value = alpha[i]; + dst[4 * i] = alpha_value; + alpha_mask &= alpha_value; + } + alpha += alpha_stride; + dst += dst_stride; + } + vst1_u8((uint8_t*)tmp, mask8); + alpha_mask &= tmp[0]; + alpha_mask &= tmp[1]; + return (alpha_mask != 0xffffffffu); +} + +static void DispatchAlphaToGreen_NEON(const uint8_t* alpha, int alpha_stride, + int width, int height, + uint32_t* dst, int dst_stride) { + int i, j; + uint8x8x4_t greens; // leave A/R/B channels zero'd. + greens.val[0] = vdup_n_u8(0); + greens.val[2] = vdup_n_u8(0); + greens.val[3] = vdup_n_u8(0); + for (j = 0; j < height; ++j) { + for (i = 0; i + 8 <= width; i += 8) { + greens.val[1] = vld1_u8(alpha + i); + vst4_u8((uint8_t*)(dst + i), greens); + } + for (; i < width; ++i) dst[i] = alpha[i] << 8; + alpha += alpha_stride; + dst += dst_stride; + } +} + +static int ExtractAlpha_NEON(const uint8_t* argb, int argb_stride, + int width, int height, + uint8_t* alpha, int alpha_stride) { + uint32_t alpha_mask = 0xffffffffu; + uint8x8_t mask8 = vdup_n_u8(0xff); + uint32_t tmp[2]; + int i, j; + for (j = 0; j < height; ++j) { + // We don't know if alpha is first or last in dst[] (depending on rgbA/Argb + // mode). So we must be sure dst[4*i + 8 - 1] is writable for the store. + // Hence the test with 'width - 1' instead of just 'width'. + for (i = 0; i + 8 <= width - 1; i += 8) { + const uint8x8x4_t rgbX = vld4_u8((const uint8_t*)(argb + 4 * i)); + const uint8x8_t alphas = rgbX.val[0]; + vst1_u8((uint8_t*)(alpha + i), alphas); + mask8 = vand_u8(mask8, alphas); + } + for (; i < width; ++i) { + alpha[i] = argb[4 * i]; + alpha_mask &= alpha[i]; + } + argb += argb_stride; + alpha += alpha_stride; + } + vst1_u8((uint8_t*)tmp, mask8); + alpha_mask &= tmp[0]; + alpha_mask &= tmp[1]; + return (alpha_mask == 0xffffffffu); +} + +static void ExtractGreen_NEON(const uint32_t* argb, + uint8_t* alpha, int size) { + int i; + for (i = 0; i + 16 <= size; i += 16) { + const uint8x16x4_t rgbX = vld4q_u8((const uint8_t*)(argb + i)); + const uint8x16_t greens = rgbX.val[1]; + vst1q_u8(alpha + i, greens); + } + for (; i < size; ++i) alpha[i] = (argb[i] >> 8) & 0xff; +} + +//------------------------------------------------------------------------------ + +extern void WebPInitAlphaProcessingNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingNEON(void) { + WebPApplyAlphaMultiply = ApplyAlphaMultiply_NEON; + WebPDispatchAlpha = DispatchAlpha_NEON; + WebPDispatchAlphaToGreen = DispatchAlphaToGreen_NEON; + WebPExtractAlpha = ExtractAlpha_NEON; + WebPExtractGreen = ExtractGreen_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(WebPInitAlphaProcessingNEON) + +#endif // WEBP_USE_NEON diff --git a/media/libwebp/dsp/filters_neon.c b/media/libwebp/dsp/filters_neon.c new file mode 100644 index 000000000..4788118c9 --- /dev/null +++ b/media/libwebp/dsp/filters_neon.c @@ -0,0 +1,329 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON variant of alpha filters +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "../dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include +#include "../dsp/neon.h" + +//------------------------------------------------------------------------------ +// Helpful macros. + +# define SANITY_CHECK(in, out) \ + assert(in != NULL); \ + assert(out != NULL); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + assert(row >= 0 && num_rows > 0 && row + num_rows <= height); \ + (void)height; // Silence unused warning. + +// load eight u8 and widen to s16 +#define U8_TO_S16(A) vreinterpretq_s16_u16(vmovl_u8(A)) +#define LOAD_U8_TO_S16(A) U8_TO_S16(vld1_u8(A)) + +// shift left or right by N byte, inserting zeros +#define SHIFT_RIGHT_N_Q(A, N) vextq_u8((A), zero, (N)) +#define SHIFT_LEFT_N_Q(A, N) vextq_u8(zero, (A), (16 - (N)) % 16) + +// rotate left by N bytes +#define ROTATE_LEFT_N(A, N) vext_u8((A), (A), (N)) +// rotate right by N bytes +#define ROTATE_RIGHT_N(A, N) vext_u8((A), (A), (8 - (N)) % 8) + +static void PredictLine_NEON(const uint8_t* src, const uint8_t* pred, + uint8_t* dst, int length) { + int i; + assert(length >= 0); + for (i = 0; i + 16 <= length; i += 16) { + const uint8x16_t A = vld1q_u8(&src[i]); + const uint8x16_t B = vld1q_u8(&pred[i]); + const uint8x16_t C = vsubq_u8(A, B); + vst1q_u8(&dst[i], C); + } + for (; i < length; ++i) dst[i] = src[i] - pred[i]; +} + +// Special case for left-based prediction (when preds==dst-1 or preds==src-1). +static void PredictLineLeft_NEON(const uint8_t* src, uint8_t* dst, int length) { + PredictLine_NEON(src, src - 1, dst, length); +} + +//------------------------------------------------------------------------------ +// Horizontal filter. + +static WEBP_INLINE void DoHorizontalFilter_NEON(const uint8_t* in, + int width, int height, + int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + + if (row == 0) { + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + // Leftmost pixel is predicted from above. + out[0] = in[0] - in[-stride]; + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + ++row; + in += stride; + out += stride; + } +} + +static void HorizontalFilter_NEON(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoHorizontalFilter_NEON(data, width, height, stride, 0, height, + filtered_data); +} + +//------------------------------------------------------------------------------ +// Vertical filter. + +static WEBP_INLINE void DoVerticalFilter_NEON(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + + if (row == 0) { + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + PredictLine_NEON(in, in - stride, out, width); + ++row; + in += stride; + out += stride; + } +} + +static void VerticalFilter_NEON(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoVerticalFilter_NEON(data, width, height, stride, 0, height, + filtered_data); +} + +//------------------------------------------------------------------------------ +// Gradient filter. + +static WEBP_INLINE int GradientPredictor_C(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +static void GradientPredictDirect_NEON(const uint8_t* const row, + const uint8_t* const top, + uint8_t* const out, int length) { + int i; + for (i = 0; i + 8 <= length; i += 8) { + const uint8x8_t A = vld1_u8(&row[i - 1]); + const uint8x8_t B = vld1_u8(&top[i + 0]); + const int16x8_t C = vreinterpretq_s16_u16(vaddl_u8(A, B)); + const int16x8_t D = LOAD_U8_TO_S16(&top[i - 1]); + const uint8x8_t E = vqmovun_s16(vsubq_s16(C, D)); + const uint8x8_t F = vld1_u8(&row[i + 0]); + vst1_u8(&out[i], vsub_u8(F, E)); + } + for (; i < length; ++i) { + out[i] = row[i] - GradientPredictor_C(row[i - 1], top[i], top[i - 1]); + } +} + +static WEBP_INLINE void DoGradientFilter_NEON(const uint8_t* in, + int width, int height, + int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + SANITY_CHECK(in, out); + in += start_offset; + out += start_offset; + + // left prediction for top scan-line + if (row == 0) { + out[0] = in[0]; + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + out[0] = in[0] - in[-stride]; + GradientPredictDirect_NEON(in + 1, in + 1 - stride, out + 1, width - 1); + ++row; + in += stride; + out += stride; + } +} + +static void GradientFilter_NEON(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoGradientFilter_NEON(data, width, height, stride, 0, height, + filtered_data); +} + +#undef SANITY_CHECK + +//------------------------------------------------------------------------------ +// Inverse transforms + +static void HorizontalUnfilter_NEON(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + int i; + const uint8x16_t zero = vdupq_n_u8(0); + uint8x16_t last; + out[0] = in[0] + (prev == NULL ? 0 : prev[0]); + if (width <= 1) return; + last = vsetq_lane_u8(out[0], zero, 0); + for (i = 1; i + 16 <= width; i += 16) { + const uint8x16_t A0 = vld1q_u8(&in[i]); + const uint8x16_t A1 = vaddq_u8(A0, last); + const uint8x16_t A2 = SHIFT_LEFT_N_Q(A1, 1); + const uint8x16_t A3 = vaddq_u8(A1, A2); + const uint8x16_t A4 = SHIFT_LEFT_N_Q(A3, 2); + const uint8x16_t A5 = vaddq_u8(A3, A4); + const uint8x16_t A6 = SHIFT_LEFT_N_Q(A5, 4); + const uint8x16_t A7 = vaddq_u8(A5, A6); + const uint8x16_t A8 = SHIFT_LEFT_N_Q(A7, 8); + const uint8x16_t A9 = vaddq_u8(A7, A8); + vst1q_u8(&out[i], A9); + last = SHIFT_RIGHT_N_Q(A9, 15); + } + for (; i < width; ++i) out[i] = in[i] + out[i - 1]; +} + +static void VerticalUnfilter_NEON(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_NEON(NULL, in, out, width); + } else { + int i; + assert(width >= 0); + for (i = 0; i + 16 <= width; i += 16) { + const uint8x16_t A = vld1q_u8(&in[i]); + const uint8x16_t B = vld1q_u8(&prev[i]); + const uint8x16_t C = vaddq_u8(A, B); + vst1q_u8(&out[i], C); + } + for (; i < width; ++i) out[i] = in[i] + prev[i]; + } +} + +// GradientUnfilter_NEON is correct but slower than the C-version, +// at least on ARM64. For armv7, it's a wash. +// So best is to disable it for now, but keep the idea around... +#if !defined(USE_GRADIENT_UNFILTER) +#define USE_GRADIENT_UNFILTER 0 // ALTERNATE_CODE +#endif + +#if (USE_GRADIENT_UNFILTER == 1) +#define GRAD_PROCESS_LANE(L) do { \ + const uint8x8_t tmp1 = ROTATE_RIGHT_N(pred, 1); /* rotate predictor in */ \ + const int16x8_t tmp2 = vaddq_s16(BC, U8_TO_S16(tmp1)); \ + const uint8x8_t delta = vqmovun_s16(tmp2); \ + pred = vadd_u8(D, delta); \ + out = vext_u8(out, ROTATE_LEFT_N(pred, (L)), 1); \ +} while (0) + +static void GradientPredictInverse_NEON(const uint8_t* const in, + const uint8_t* const top, + uint8_t* const row, int length) { + if (length > 0) { + int i; + uint8x8_t pred = vdup_n_u8(row[-1]); // left sample + uint8x8_t out = vdup_n_u8(0); + for (i = 0; i + 8 <= length; i += 8) { + const int16x8_t B = LOAD_U8_TO_S16(&top[i + 0]); + const int16x8_t C = LOAD_U8_TO_S16(&top[i - 1]); + const int16x8_t BC = vsubq_s16(B, C); // unclipped gradient basis B - C + const uint8x8_t D = vld1_u8(&in[i]); // base input + GRAD_PROCESS_LANE(0); + GRAD_PROCESS_LANE(1); + GRAD_PROCESS_LANE(2); + GRAD_PROCESS_LANE(3); + GRAD_PROCESS_LANE(4); + GRAD_PROCESS_LANE(5); + GRAD_PROCESS_LANE(6); + GRAD_PROCESS_LANE(7); + vst1_u8(&row[i], out); + } + for (; i < length; ++i) { + row[i] = in[i] + GradientPredictor_C(row[i - 1], top[i], top[i - 1]); + } + } +} +#undef GRAD_PROCESS_LANE + +static void GradientUnfilter_NEON(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_NEON(NULL, in, out, width); + } else { + out[0] = in[0] + prev[0]; // predict from above + GradientPredictInverse_NEON(in + 1, prev + 1, out + 1, width - 1); + } +} + +#endif // USE_GRADIENT_UNFILTER + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8FiltersInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInitNEON(void) { + WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter_NEON; + WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter_NEON; +#if (USE_GRADIENT_UNFILTER == 1) + WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter_NEON; +#endif + + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter_NEON; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter_NEON; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8FiltersInitNEON) + +#endif // WEBP_USE_NEON diff --git a/media/libwebp/dsp/moz.build b/media/libwebp/dsp/moz.build index fa6df9e9e..f3c2bdd0b 100644 --- a/media/libwebp/dsp/moz.build +++ b/media/libwebp/dsp/moz.build @@ -9,6 +9,7 @@ with Files('**'): SOURCES += [ 'alpha_processing.c', + 'alpha_processing_neon.c', 'alpha_processing_sse2.c', 'alpha_processing_sse41.c', 'dec.c', @@ -17,6 +18,7 @@ SOURCES += [ 'dec_sse2.c', 'dec_sse41.c', 'filters.c', + 'filters_neon.c', 'filters_sse2.c', 'lossless.c', 'lossless_neon.c', @@ -29,15 +31,19 @@ SOURCES += [ 'upsampling_sse2.c', 'upsampling_sse41.c', 'yuv.c', + 'yuv_neon.c', 'yuv_sse2.c', 'yuv_sse41.c', ] if CONFIG['CPU_ARCH'] == 'arm' and CONFIG['BUILD_ARM_NEON']: + SOURCES['alpha_processing_neon.c'].flags += CONFIG['NEON_FLAGS'] SOURCES['dec_neon.c'].flags += CONFIG['NEON_FLAGS'] + SOURCES['filters_neon.c'].flags += CONFIG['NEON_FLAGS'] SOURCES['lossless_neon.c'].flags += CONFIG['NEON_FLAGS'] SOURCES['rescaler_neon.c'].flags += CONFIG['NEON_FLAGS'] SOURCES['upsampling_neon.c'].flags += CONFIG['NEON_FLAGS'] + SOURCES['yuv_neon.c'].flags += CONFIG['NEON_FLAGS'] elif CONFIG['INTEL_ARCHITECTURE']: SOURCES['alpha_processing_sse2.c'].flags += CONFIG['SSE2_FLAGS'] SOURCES['alpha_processing_sse41.c'].flags += CONFIG['SSE2_FLAGS'] diff --git a/media/libwebp/dsp/yuv_neon.c b/media/libwebp/dsp/yuv_neon.c new file mode 100644 index 000000000..81f00fe5a --- /dev/null +++ b/media/libwebp/dsp/yuv_neon.c @@ -0,0 +1,288 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV->RGB conversion functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "../dsp/yuv.h" + +#if defined(WEBP_USE_NEON) + +#include +#include + +#include "../dsp/neon.h" + +//----------------------------------------------------------------------------- + +static uint8x8_t ConvertRGBToY_NEON(const uint8x8_t R, + const uint8x8_t G, + const uint8x8_t B) { + const uint16x8_t r = vmovl_u8(R); + const uint16x8_t g = vmovl_u8(G); + const uint16x8_t b = vmovl_u8(B); + const uint16x4_t r_lo = vget_low_u16(r); + const uint16x4_t r_hi = vget_high_u16(r); + const uint16x4_t g_lo = vget_low_u16(g); + const uint16x4_t g_hi = vget_high_u16(g); + const uint16x4_t b_lo = vget_low_u16(b); + const uint16x4_t b_hi = vget_high_u16(b); + const uint32x4_t tmp0_lo = vmull_n_u16( r_lo, 16839u); + const uint32x4_t tmp0_hi = vmull_n_u16( r_hi, 16839u); + const uint32x4_t tmp1_lo = vmlal_n_u16(tmp0_lo, g_lo, 33059u); + const uint32x4_t tmp1_hi = vmlal_n_u16(tmp0_hi, g_hi, 33059u); + const uint32x4_t tmp2_lo = vmlal_n_u16(tmp1_lo, b_lo, 6420u); + const uint32x4_t tmp2_hi = vmlal_n_u16(tmp1_hi, b_hi, 6420u); + const uint16x8_t Y1 = vcombine_u16(vrshrn_n_u32(tmp2_lo, 16), + vrshrn_n_u32(tmp2_hi, 16)); + const uint16x8_t Y2 = vaddq_u16(Y1, vdupq_n_u16(16)); + return vqmovn_u16(Y2); +} + +static void ConvertRGB24ToY_NEON(const uint8_t* rgb, uint8_t* y, int width) { + int i; + for (i = 0; i + 8 <= width; i += 8, rgb += 3 * 8) { + const uint8x8x3_t RGB = vld3_u8(rgb); + const uint8x8_t Y = ConvertRGBToY_NEON(RGB.val[0], RGB.val[1], RGB.val[2]); + vst1_u8(y + i, Y); + } + for (; i < width; ++i, rgb += 3) { // left-over + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY_NEON(const uint8_t* bgr, uint8_t* y, int width) { + int i; + for (i = 0; i + 8 <= width; i += 8, bgr += 3 * 8) { + const uint8x8x3_t BGR = vld3_u8(bgr); + const uint8x8_t Y = ConvertRGBToY_NEON(BGR.val[2], BGR.val[1], BGR.val[0]); + vst1_u8(y + i, Y); + } + for (; i < width; ++i, bgr += 3) { // left-over + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +static void ConvertARGBToY_NEON(const uint32_t* argb, uint8_t* y, int width) { + int i; + for (i = 0; i + 8 <= width; i += 8) { + const uint8x8x4_t RGB = vld4_u8((const uint8_t*)&argb[i]); + const uint8x8_t Y = ConvertRGBToY_NEON(RGB.val[2], RGB.val[1], RGB.val[0]); + vst1_u8(y + i, Y); + } + for (; i < width; ++i) { // left-over + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +//----------------------------------------------------------------------------- + +// computes: DST_s16 = [(C0 * r + C1 * g + C2 * b) >> 16] + CST +#define MULTIPLY_16b_PREAMBLE(r, g, b) \ + const int16x4_t r_lo = vreinterpret_s16_u16(vget_low_u16(r)); \ + const int16x4_t r_hi = vreinterpret_s16_u16(vget_high_u16(r)); \ + const int16x4_t g_lo = vreinterpret_s16_u16(vget_low_u16(g)); \ + const int16x4_t g_hi = vreinterpret_s16_u16(vget_high_u16(g)); \ + const int16x4_t b_lo = vreinterpret_s16_u16(vget_low_u16(b)); \ + const int16x4_t b_hi = vreinterpret_s16_u16(vget_high_u16(b)) + +#define MULTIPLY_16b(C0, C1, C2, CST, DST_s16) do { \ + const int32x4_t tmp0_lo = vmull_n_s16( r_lo, C0); \ + const int32x4_t tmp0_hi = vmull_n_s16( r_hi, C0); \ + const int32x4_t tmp1_lo = vmlal_n_s16(tmp0_lo, g_lo, C1); \ + const int32x4_t tmp1_hi = vmlal_n_s16(tmp0_hi, g_hi, C1); \ + const int32x4_t tmp2_lo = vmlal_n_s16(tmp1_lo, b_lo, C2); \ + const int32x4_t tmp2_hi = vmlal_n_s16(tmp1_hi, b_hi, C2); \ + const int16x8_t tmp3 = vcombine_s16(vshrn_n_s32(tmp2_lo, 16), \ + vshrn_n_s32(tmp2_hi, 16)); \ + DST_s16 = vaddq_s16(tmp3, vdupq_n_s16(CST)); \ +} while (0) + +// This needs to be a macro, since (128 << SHIFT) needs to be an immediate. +#define CONVERT_RGB_TO_UV(r, g, b, SHIFT, U_DST, V_DST) do { \ + MULTIPLY_16b_PREAMBLE(r, g, b); \ + MULTIPLY_16b(-9719, -19081, 28800, 128 << SHIFT, U_DST); \ + MULTIPLY_16b(28800, -24116, -4684, 128 << SHIFT, V_DST); \ +} while (0) + +static void ConvertRGBA32ToUV_NEON(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width) { + int i; + for (i = 0; i + 8 <= width; i += 8, rgb += 4 * 8) { + const uint16x8x4_t RGB = vld4q_u16((const uint16_t*)rgb); + int16x8_t U, V; + CONVERT_RGB_TO_UV(RGB.val[0], RGB.val[1], RGB.val[2], 2, U, V); + vst1_u8(u + i, vqrshrun_n_s16(U, 2)); + vst1_u8(v + i, vqrshrun_n_s16(V, 2)); + } + for (; i < width; i += 1, rgb += 4) { + const int r = rgb[0], g = rgb[1], b = rgb[2]; + u[i] = VP8RGBToU(r, g, b, YUV_HALF << 2); + v[i] = VP8RGBToV(r, g, b, YUV_HALF << 2); + } +} + +static void ConvertARGBToUV_NEON(const uint32_t* argb, uint8_t* u, uint8_t* v, + int src_width, int do_store) { + int i; + for (i = 0; i + 16 <= src_width; i += 16, u += 8, v += 8) { + const uint8x16x4_t RGB = vld4q_u8((const uint8_t*)&argb[i]); + const uint16x8_t R = vpaddlq_u8(RGB.val[2]); // pair-wise adds + const uint16x8_t G = vpaddlq_u8(RGB.val[1]); + const uint16x8_t B = vpaddlq_u8(RGB.val[0]); + int16x8_t U_tmp, V_tmp; + CONVERT_RGB_TO_UV(R, G, B, 1, U_tmp, V_tmp); + { + const uint8x8_t U = vqrshrun_n_s16(U_tmp, 1); + const uint8x8_t V = vqrshrun_n_s16(V_tmp, 1); + if (do_store) { + vst1_u8(u, U); + vst1_u8(v, V); + } else { + const uint8x8_t prev_u = vld1_u8(u); + const uint8x8_t prev_v = vld1_u8(v); + vst1_u8(u, vrhadd_u8(U, prev_u)); + vst1_u8(v, vrhadd_u8(V, prev_v)); + } + } + } + if (i < src_width) { // left-over + WebPConvertARGBToUV_C(argb + i, u, v, src_width - i, do_store); + } +} + + +//------------------------------------------------------------------------------ + +extern void WebPInitConvertARGBToYUVNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUVNEON(void) { + WebPConvertRGB24ToY = ConvertRGB24ToY_NEON; + WebPConvertBGR24ToY = ConvertBGR24ToY_NEON; + WebPConvertARGBToY = ConvertARGBToY_NEON; + WebPConvertARGBToUV = ConvertARGBToUV_NEON; + WebPConvertRGBA32ToUV = ConvertRGBA32ToUV_NEON; +} + +//------------------------------------------------------------------------------ + +#define MAX_Y ((1 << 10) - 1) // 10b precision over 16b-arithmetic +static uint16_t clip_y_NEON(int v) { + return (v < 0) ? 0 : (v > MAX_Y) ? MAX_Y : (uint16_t)v; +} + +static uint64_t SharpYUVUpdateY_NEON(const uint16_t* ref, const uint16_t* src, + uint16_t* dst, int len) { + int i; + const int16x8_t zero = vdupq_n_s16(0); + const int16x8_t max = vdupq_n_s16(MAX_Y); + uint64x2_t sum = vdupq_n_u64(0); + uint64_t diff; + + for (i = 0; i + 8 <= len; i += 8) { + const int16x8_t A = vreinterpretq_s16_u16(vld1q_u16(ref + i)); + const int16x8_t B = vreinterpretq_s16_u16(vld1q_u16(src + i)); + const int16x8_t C = vreinterpretq_s16_u16(vld1q_u16(dst + i)); + const int16x8_t D = vsubq_s16(A, B); // diff_y + const int16x8_t F = vaddq_s16(C, D); // new_y + const uint16x8_t H = + vreinterpretq_u16_s16(vmaxq_s16(vminq_s16(F, max), zero)); + const int16x8_t I = vabsq_s16(D); // abs(diff_y) + vst1q_u16(dst + i, H); + sum = vpadalq_u32(sum, vpaddlq_u16(vreinterpretq_u16_s16(I))); + } + diff = vgetq_lane_u64(sum, 0) + vgetq_lane_u64(sum, 1); + for (; i < len; ++i) { + const int diff_y = ref[i] - src[i]; + const int new_y = (int)(dst[i]) + diff_y; + dst[i] = clip_y_NEON(new_y); + diff += (uint64_t)(abs(diff_y)); + } + return diff; +} + +static void SharpYUVUpdateRGB_NEON(const int16_t* ref, const int16_t* src, + int16_t* dst, int len) { + int i; + for (i = 0; i + 8 <= len; i += 8) { + const int16x8_t A = vld1q_s16(ref + i); + const int16x8_t B = vld1q_s16(src + i); + const int16x8_t C = vld1q_s16(dst + i); + const int16x8_t D = vsubq_s16(A, B); // diff_uv + const int16x8_t E = vaddq_s16(C, D); // new_uv + vst1q_s16(dst + i, E); + } + for (; i < len; ++i) { + const int diff_uv = ref[i] - src[i]; + dst[i] += diff_uv; + } +} + +static void SharpYUVFilterRow_NEON(const int16_t* A, const int16_t* B, int len, + const uint16_t* best_y, uint16_t* out) { + int i; + const int16x8_t max = vdupq_n_s16(MAX_Y); + const int16x8_t zero = vdupq_n_s16(0); + for (i = 0; i + 8 <= len; i += 8) { + const int16x8_t a0 = vld1q_s16(A + i + 0); + const int16x8_t a1 = vld1q_s16(A + i + 1); + const int16x8_t b0 = vld1q_s16(B + i + 0); + const int16x8_t b1 = vld1q_s16(B + i + 1); + const int16x8_t a0b1 = vaddq_s16(a0, b1); + const int16x8_t a1b0 = vaddq_s16(a1, b0); + const int16x8_t a0a1b0b1 = vaddq_s16(a0b1, a1b0); // A0+A1+B0+B1 + const int16x8_t a0b1_2 = vaddq_s16(a0b1, a0b1); // 2*(A0+B1) + const int16x8_t a1b0_2 = vaddq_s16(a1b0, a1b0); // 2*(A1+B0) + const int16x8_t c0 = vshrq_n_s16(vaddq_s16(a0b1_2, a0a1b0b1), 3); + const int16x8_t c1 = vshrq_n_s16(vaddq_s16(a1b0_2, a0a1b0b1), 3); + const int16x8_t d0 = vaddq_s16(c1, a0); + const int16x8_t d1 = vaddq_s16(c0, a1); + const int16x8_t e0 = vrshrq_n_s16(d0, 1); + const int16x8_t e1 = vrshrq_n_s16(d1, 1); + const int16x8x2_t f = vzipq_s16(e0, e1); + const int16x8_t g0 = vreinterpretq_s16_u16(vld1q_u16(best_y + 2 * i + 0)); + const int16x8_t g1 = vreinterpretq_s16_u16(vld1q_u16(best_y + 2 * i + 8)); + const int16x8_t h0 = vaddq_s16(g0, f.val[0]); + const int16x8_t h1 = vaddq_s16(g1, f.val[1]); + const int16x8_t i0 = vmaxq_s16(vminq_s16(h0, max), zero); + const int16x8_t i1 = vmaxq_s16(vminq_s16(h1, max), zero); + vst1q_u16(out + 2 * i + 0, vreinterpretq_u16_s16(i0)); + vst1q_u16(out + 2 * i + 8, vreinterpretq_u16_s16(i1)); + } + for (; i < len; ++i) { + const int a0b1 = A[i + 0] + B[i + 1]; + const int a1b0 = A[i + 1] + B[i + 0]; + const int a0a1b0b1 = a0b1 + a1b0 + 8; + const int v0 = (8 * A[i + 0] + 2 * a1b0 + a0a1b0b1) >> 4; + const int v1 = (8 * A[i + 1] + 2 * a0b1 + a0a1b0b1) >> 4; + out[2 * i + 0] = clip_y_NEON(best_y[2 * i + 0] + v0); + out[2 * i + 1] = clip_y_NEON(best_y[2 * i + 1] + v1); + } +} +#undef MAX_Y + +//------------------------------------------------------------------------------ + +extern void WebPInitSharpYUVNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSharpYUVNEON(void) { + WebPSharpYUVUpdateY = SharpYUVUpdateY_NEON; + WebPSharpYUVUpdateRGB = SharpYUVUpdateRGB_NEON; + WebPSharpYUVFilterRow = SharpYUVFilterRow_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(WebPInitConvertARGBToYUVNEON) +WEBP_DSP_INIT_STUB(WebPInitSharpYUVNEON) + +#endif // WEBP_USE_NEON -- cgit v1.2.3 From 22851ce36d97fca5a4768f3c49a4fff6cebd320a Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Sat, 28 Sep 2019 23:16:55 -0400 Subject: Issue #1233 - Part 1: Fix grid overflow and rendering issues by improving Layout CSS-Grid API List of relevant patches applied: 1425599 part 1 - [css-grid] Change the track sizing algorithm for spanning items so that it accumulates individual item contributions to the plan by max() rather than incrementing the planned size directly. Also, fix a bug when copying back the planned limits after updating it for the first span group. It should only copy back track sizes that were actaully spanned by those items, other content-sized tracks' limits should remain at "infinity". 1425599 part 2 - [css-grid] Factor out the min-sizing parts of the track sizing for spanned items to a templated method (idempotent change). 1425599 part 3 - [css-grid] Factor out most of the max-sizing parts of the track sizing for spanned items to a templated method (idempotent change). 1425599 part 4 - [css-grid] Factor out the starting base/limit size to a templated method (idempotent change). 1425599 part 5 - [css-grid] Make CollectGrowable a templated method so that it works with either base/limit sizes (idempotent change). 1425599 part 6 - [css-grid] Make the size distribution methods templated with the intent of merging them in a later patch (idempotent change). This patch also introduces an eInfinitelyGrowable bit to help get rid of the 'limits' temporary track sizes in the next patch. 1425599 part 7 - [css-grid] Remove the 'limits' copy of track sizes since they are no longer needed (idempotent change). 1425599 part 8 - [css-grid] Factor out the fit-content clamping function from DistributeToTrackLimits and pass it as a param instead (idempotent change). 1425599 part 9 - [css-grid] Merge DistributeToTrackLimits/Bases (idempotent change). 1425599 part 10 - [css-grid] Make MarkExcludedTracks a static method since it doesn't use 'this' (idempotent change). 1425599 part 11 - [css-grid] Hoist the marking of excluded tracks out from GrowSelectedTracksUnlimited to a separate method (idempotent change). 1425599 part 12 - [css-grid] Merge CopyPlanToBase/Limits into a templated method instead (idempotent change). 1425599 part 13 - [css-grid] Merge Grow[Base|Limits]ForSpanningItems into a templated method instead (idempotent change). 1425599 part 14 - [css-grid] Use iterators instead of an array + start/end index for the item data (idempotent change). 1425599 part 16 - [css-grid] Make SizeContributionForPhase a template. 1425599 - [css-grid] Follow-up bustage fix for stupid compiler warnings. 1378481 - Assign 'roundingError' in the default branch too, to avoid a maybe-uninitialized compiler warning. 1423292 - [css-grid] Add a couple of ItemState bits to Dump(), and make an ItemState assertion stricter (DEBUG-only changes). 1373678 Part 1: Reduce grid line numbers by count of leading implicit lines, minimum 0. 1416350 - Part 1: Correctly account for removed 'auto-fit' tracks also when there are leading implicit tracks. 1416350 - Part 2: Correct logic for Grid API line numbering with leading implicit tracks. 1418727 part 1 - [css-grid] Introduce StateBitsForRange() that collects the union of the state bits for a range of tracks (idempotent change). 1418727 part 2 - [css-grid] Require that an item spans at least one track with an 'auto' min sizing function for Automatic Minimum Size to apply. --- dom/grid/GridLines.cpp | 42 +- layout/generic/nsGridContainerFrame.cpp | 661 ++++++++++++++++++-------------- 2 files changed, 410 insertions(+), 293 deletions(-) diff --git a/dom/grid/GridLines.cpp b/dom/grid/GridLines.cpp index fac645c64..898885346 100644 --- a/dom/grid/GridLines.cpp +++ b/dom/grid/GridLines.cpp @@ -90,7 +90,9 @@ GridLines::SetLineInfo(const ComputedGridTrackInfo* aTrackInfo, for (uint32_t i = aTrackInfo->mStartFragmentTrack; i < aTrackInfo->mEndFragmentTrack + 1; i++) { - uint32_t line1Index = i + 1; + // Since line indexes are 1-based, calculate a 1-based value + // for this track to simplify some calculations. + const uint32_t line1Index = i + 1; startOfNextTrack = (i < aTrackInfo->mEndFragmentTrack) ? aTrackInfo->mPositions[i] : @@ -127,7 +129,8 @@ GridLines::SetLineInfo(const ComputedGridTrackInfo* aTrackInfo, } } - if (i >= aTrackInfo->mRepeatFirstTrack && + if (i >= (aTrackInfo->mRepeatFirstTrack + + aTrackInfo->mNumLeadingImplicitTracks) && repeatIndex < numRepeatTracks) { numAddedLines += AppendRemovedAutoFits(aTrackInfo, aLineInfo, @@ -139,23 +142,30 @@ GridLines::SetLineInfo(const ComputedGridTrackInfo* aTrackInfo, RefPtr line = new GridLine(this); mLines.AppendElement(line); + MOZ_ASSERT(line1Index > 0, "line1Index must be positive."); + bool isBeforeFirstExplicit = + (line1Index <= aTrackInfo->mNumLeadingImplicitTracks); + // Calculate an actionable line number for this line, that could be used + // in a css grid property to align a grid item or area at that line. + // For implicit lines that appear before line 1, report a number of 0. + // We can't report negative indexes, because those have a different + // meaning in the css grid spec (negative indexes are negative-1-based + // from the end of the grid decreasing towards the front). + uint32_t lineNumber = isBeforeFirstExplicit ? 0 : + (line1Index - aTrackInfo->mNumLeadingImplicitTracks + numAddedLines); + GridDeclaration lineType = + (isBeforeFirstExplicit || + line1Index > (aTrackInfo->mNumLeadingImplicitTracks + + aTrackInfo->mNumExplicitTracks + 1)) + ? GridDeclaration::Implicit + : GridDeclaration::Explicit; line->SetLineValues( lineNames, nsPresContext::AppUnitsToDoubleCSSPixels(lastTrackEdge), nsPresContext::AppUnitsToDoubleCSSPixels(startOfNextTrack - lastTrackEdge), - line1Index + numAddedLines, - ( - // Implicit if there are no explicit tracks, or if the index - // is before the first explicit track, or after - // a track beyond the last explicit track. - (aTrackInfo->mNumExplicitTracks == 0) || - (i < aTrackInfo->mNumLeadingImplicitTracks) || - (i > aTrackInfo->mNumLeadingImplicitTracks + - aTrackInfo->mNumExplicitTracks) ? - GridDeclaration::Implicit : - GridDeclaration::Explicit - ) + lineNumber, + lineType ); if (i < aTrackInfo->mEndFragmentTrack) { @@ -215,11 +225,13 @@ GridLines::AppendRemovedAutoFits(const ComputedGridTrackInfo* aTrackInfo, RefPtr line = new GridLine(this); mLines.AppendElement(line); + uint32_t lineNumber = aTrackInfo->mRepeatFirstTrack + + aRepeatIndex + 1; line->SetLineValues( aLineNames, nsPresContext::AppUnitsToDoubleCSSPixels(aLastTrackEdge), nsPresContext::AppUnitsToDoubleCSSPixels(0), - aTrackInfo->mRepeatFirstTrack + aRepeatIndex + 1, + lineNumber, GridDeclaration::Explicit ); diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 3a2d5ad1d..59f58f268 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -198,7 +198,7 @@ struct nsGridContainerFrame::TrackSize eMaxContentMinSizing = 0x4, eMinOrMaxContentMinSizing = eMinContentMinSizing | eMaxContentMinSizing, eIntrinsicMinSizing = eMinOrMaxContentMinSizing | eAutoMinSizing, - // 0x8 is unused, feel free to take it! + eModified = 0x8, eAutoMaxSizing = 0x10, eMinContentMaxSizing = 0x20, eMaxContentMaxSizing = 0x40, @@ -211,6 +211,7 @@ struct nsGridContainerFrame::TrackSize eSkipGrowUnlimited = eSkipGrowUnlimited1 | eSkipGrowUnlimited2, eBreakBefore = 0x800, eFitContent = 0x1000, + eInfinitelyGrowable = 0x2000, }; StateBits Initialize(nscoord aPercentageBasis, @@ -856,6 +857,8 @@ struct nsGridContainerFrame::GridItemInfo // Return true if we should apply Automatic Minimum Size to this item. // https://drafts.csswg.org/css-grid/#min-size-auto + // @note the caller should also check that the item spans at least one track + // that has a min track sizing function that is 'auto' before applying it. bool ShouldApplyAutoMinSize(WritingMode aContainerWM, LogicalAxis aContainerAxis, nscoord aPercentageBasis) const @@ -915,6 +918,12 @@ nsGridContainerFrame::GridItemInfo::Dump() const if (state & ItemState::eIsFlexing) { printf("flexing "); } + if (state & ItemState::eApplyAutoMinSize) { + printf("auto-min-size "); + } + if (state & ItemState::eClampMarginBoxMinSize) { + printf("clamp "); + } if (state & ItemState::eFirstBaseline) { printf("first baseline %s-alignment ", (state & ItemState::eSelfBaseline) ? "self" : "content"); @@ -1091,6 +1100,7 @@ private: const nsTArray& mRepeatAutoLineNameListBefore; const nsTArray& mRepeatAutoLineNameListAfter; // The index of the repeat(auto-fill/fit) track, or zero if there is none. + // Relative to mExplicitGridOffset (repeat tracks are explicit by definition). const uint32_t mRepeatAutoStart; // The (hypothetical) index of the last such repeat() track. const uint32_t mRepeatAutoEnd; @@ -1101,6 +1111,7 @@ private: // generates one track (making mRepeatEndDelta == 0). const uint32_t mTemplateLinesEnd; // True if there is a specified repeat(auto-fill/fit) track. + // Indexed relative to mExplicitGridOffset + mRepeatAutoStart. const bool mHasRepeatAuto; }; @@ -1340,15 +1351,9 @@ struct nsGridContainerFrame::Tracks nscoord aContentBoxSize); /** - * Return true if aRange spans at least one track with an intrinsic sizing - * function and does not span any tracks with a max-sizing function. - * @param aRange the span of tracks to check - * @param aState will be set to the union of the state bits of all the spanned - * tracks, unless a flex track is found - then it only contains - * the union of the tracks up to and including the flex track. + * Return the union of the state bits for the tracks in aRange. */ - bool HasIntrinsicButNoFlexSizingInRange(const LineRange& aRange, - TrackSize::StateBits* aState) const; + TrackSize::StateBits StateBitsForRange(const LineRange& aRange) const; // Some data we collect for aligning baseline-aligned items. struct ItemBaselineData @@ -1383,6 +1388,62 @@ struct nsGridContainerFrame::Tracks */ void AlignBaselineSubtree(const GridItemInfo& aGridItem) const; + enum class TrackSizingPhase + { + eIntrinsicMinimums, + eContentBasedMinimums, + eMaxContentMinimums, + eIntrinsicMaximums, + eMaxContentMaximums, + }; + + // Some data we collect on each item for Step 2 of the Track Sizing Algorithm + // in ResolveIntrinsicSize below. + struct Step2ItemData final + { + uint32_t mSpan; + TrackSize::StateBits mState; + LineRange mLineRange; + nscoord mMinSize; + nscoord mMinContentContribution; + nscoord mMaxContentContribution; + nsIFrame* mFrame; + static bool IsSpanLessThan(const Step2ItemData& a, const Step2ItemData& b) + { + return a.mSpan < b.mSpan; + } + + template + nscoord SizeContributionForPhase() const + { + switch (phase) { + case TrackSizingPhase::eIntrinsicMinimums: + case TrackSizingPhase::eIntrinsicMaximums: + return mMinSize; + case TrackSizingPhase::eContentBasedMinimums: + return mMinContentContribution; + case TrackSizingPhase::eMaxContentMinimums: + case TrackSizingPhase::eMaxContentMaximums: + return mMaxContentContribution; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected phase"); + } + }; + + using FitContentClamper = + function; + + // Helper method for ResolveIntrinsicSize. + template + bool GrowSizeForSpanningItems(nsTArray::iterator aIter, + const nsTArray::iterator aEnd, + nsTArray& aTracks, + nsTArray& aPlan, + nsTArray& aItemPlan, + TrackSize::StateBits aSelector, + const FitContentClamper& aClamper = nullptr, + bool aNeedInfinitelyGrowableFlag = false); + /** * Resolve Intrinsic Track Sizes. * http://dev.w3.org/csswg/css-grid/#algo-content @@ -1405,66 +1466,117 @@ struct nsGridContainerFrame::Tracks SizingConstraint aConstraint, const LineRange& aRange, const GridItemInfo& aGridItem); + + // Helper method that returns the track size to use in §11.5.1.2 + // https://drafts.csswg.org/css-grid/#extra-space + template static + nscoord StartSizeInDistribution(const TrackSize& aSize) + { + switch (phase) { + case TrackSizingPhase::eIntrinsicMinimums: + case TrackSizingPhase::eContentBasedMinimums: + case TrackSizingPhase::eMaxContentMinimums: + return aSize.mBase; + case TrackSizingPhase::eIntrinsicMaximums: + case TrackSizingPhase::eMaxContentMaximums: + if (aSize.mLimit == NS_UNCONSTRAINEDSIZE) { + return aSize.mBase; + } + return aSize.mLimit; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected phase"); + } + /** * Collect the tracks which are growable (matching aSelector) into * aGrowableTracks, and return the amount of space that can be used - * to grow those tracks. Specifically, we return aAvailableSpace minus - * the sum of mBase's (and corresponding grid gaps) in aPlan (clamped to 0) - * for the tracks in aRange, or zero when there are no growable tracks. - * @note aPlan[*].mBase represents a planned new base or limit. + * to grow those tracks. This method implements CSS Grid §11.5.1.2. + * https://drafts.csswg.org/css-grid/#extra-space */ - nscoord CollectGrowable(nscoord aAvailableSpace, - const nsTArray& aPlan, - const LineRange& aRange, - TrackSize::StateBits aSelector, - nsTArray& aGrowableTracks) const + template + nscoord CollectGrowable(nscoord aAvailableSpace, + const LineRange& aRange, + TrackSize::StateBits aSelector, + nsTArray& aGrowableTracks) const { MOZ_ASSERT(aAvailableSpace > 0, "why call me?"); nscoord space = aAvailableSpace - mGridGap * (aRange.Extent() - 1); const uint32_t start = aRange.mStart; const uint32_t end = aRange.mEnd; for (uint32_t i = start; i < end; ++i) { - const TrackSize& sz = aPlan[i]; - space -= sz.mBase; + const TrackSize& sz = mSizes[i]; + space -= StartSizeInDistribution(sz); if (space <= 0) { return 0; } - if ((sz.mState & aSelector) && !sz.IsFrozen()) { + if (sz.mState & aSelector) { aGrowableTracks.AppendElement(i); } } return aGrowableTracks.IsEmpty() ? 0 : space; } - void SetupGrowthPlan(nsTArray& aPlan, - const nsTArray& aTracks) const + template + void InitializeItemPlan(nsTArray& aItemPlan, + const nsTArray& aTracks) const { for (uint32_t track : aTracks) { - aPlan[track] = mSizes[track]; + auto& plan = aItemPlan[track]; + const TrackSize& sz = mSizes[track]; + plan.mBase = StartSizeInDistribution(sz); + bool unlimited = sz.mState & TrackSize::eInfinitelyGrowable; + plan.mLimit = unlimited ? NS_UNCONSTRAINEDSIZE : sz.mLimit; + plan.mState = sz.mState; } } - void CopyPlanToBase(const nsTArray& aPlan, - const nsTArray& aTracks) + template + void InitializePlan(nsTArray& aPlan) const { - for (uint32_t track : aTracks) { - MOZ_ASSERT(mSizes[track].mBase <= aPlan[track].mBase); - mSizes[track].mBase = aPlan[track].mBase; + for (size_t i = 0, len = aPlan.Length(); i < len; ++i) { + auto& plan = aPlan[i]; + const auto& sz = mSizes[i]; + plan.mBase = StartSizeInDistribution(sz); + MOZ_ASSERT(phase == TrackSizingPhase::eMaxContentMaximums || + !(sz.mState & TrackSize::eInfinitelyGrowable), + "forgot to reset the eInfinitelyGrowable bit?"); + plan.mState = sz.mState; } } - void CopyPlanToLimit(const nsTArray& aPlan, - const nsTArray& aTracks) + template + void CopyPlanToSize(const nsTArray& aPlan, + bool aNeedInfinitelyGrowableFlag = false) { - for (uint32_t track : aTracks) { - MOZ_ASSERT(mSizes[track].mLimit == NS_UNCONSTRAINEDSIZE || - mSizes[track].mLimit <= aPlan[track].mBase); - mSizes[track].mLimit = aPlan[track].mBase; + for (size_t i = 0, len = mSizes.Length(); i < len; ++i) { + const auto& plan = aPlan[i]; + MOZ_ASSERT(plan.mBase >= 0); + auto& sz = mSizes[i]; + switch (phase) { + case TrackSizingPhase::eIntrinsicMinimums: + case TrackSizingPhase::eContentBasedMinimums: + case TrackSizingPhase::eMaxContentMinimums: + sz.mBase = plan.mBase; + break; + case TrackSizingPhase::eIntrinsicMaximums: + if (plan.mState & TrackSize::eModified) { + if (sz.mLimit == NS_UNCONSTRAINEDSIZE && + aNeedInfinitelyGrowableFlag) { + sz.mState |= TrackSize::eInfinitelyGrowable; + } + sz.mLimit = plan.mBase; + } + break; + case TrackSizingPhase::eMaxContentMaximums: + if (plan.mState & TrackSize::eModified) { + sz.mLimit = plan.mBase; + } + sz.mState &= ~TrackSize::eInfinitelyGrowable; + break; + } } } - using FitContentClamper = - function; /** * Grow the planned size for tracks in aGrowableTracks up to their limit * and then freeze them (all aGrowableTracks must be unfrozen on entry). @@ -1524,12 +1636,13 @@ struct nsGridContainerFrame::Tracks * assumed that aPlan have no aSkipFlag set for tracks in aGrowableTracks * on entry to this method. */ - uint32_t MarkExcludedTracks(nsTArray& aPlan, - uint32_t aNumGrowable, - const nsTArray& aGrowableTracks, - TrackSize::StateBits aMinSizingSelector, - TrackSize::StateBits aMaxSizingSelector, - TrackSize::StateBits aSkipFlag) const + static uint32_t + MarkExcludedTracks(nsTArray& aPlan, + uint32_t aNumGrowable, + const nsTArray& aGrowableTracks, + TrackSize::StateBits aMinSizingSelector, + TrackSize::StateBits aMaxSizingSelector, + TrackSize::StateBits aSkipFlag) { bool foundOneSelected = false; bool foundOneGrowable = false; @@ -1559,41 +1672,60 @@ struct nsGridContainerFrame::Tracks } /** - * Increase the planned size for tracks in aGrowableTracks that match - * aSelector (or all tracks if aSelector is zero) beyond their limit. + * Mark all tracks in aGrowableTracks with an eSkipGrowUnlimited bit if + * they *shouldn't* grow unlimited in §11.5.1.2.3 "Distribute space beyond + * growth limits" https://drafts.csswg.org/css-grid/#extra-space + * Return the number of tracks that are still growable. + */ + template + static uint32_t + MarkExcludedTracks(nsTArray& aPlan, + const nsTArray& aGrowableTracks, + TrackSize::StateBits aSelector) + { + uint32_t numGrowable = aGrowableTracks.Length(); + if (phase == TrackSizingPhase::eIntrinsicMaximums || + phase == TrackSizingPhase::eMaxContentMaximums) { + // "when handling any intrinsic growth limit: all affected tracks" + return numGrowable; + } + MOZ_ASSERT(aSelector == (aSelector & TrackSize::eIntrinsicMinSizing) && + (aSelector & TrackSize::eMaxContentMinSizing), + "Should only get here for track sizing steps 2.1 to 2.3"); + // Note that eMaxContentMinSizing is always included. We do those first: + numGrowable = MarkExcludedTracks(aPlan, numGrowable, aGrowableTracks, + TrackSize::eMaxContentMinSizing, + TrackSize::eMaxContentMaxSizing, + TrackSize::eSkipGrowUnlimited1); + // Now mark min-content/auto min-sizing tracks if requested. + auto minOrAutoSelector = aSelector & ~TrackSize::eMaxContentMinSizing; + if (minOrAutoSelector) { + numGrowable = MarkExcludedTracks(aPlan, numGrowable, aGrowableTracks, + minOrAutoSelector, + TrackSize::eIntrinsicMaxSizing, + TrackSize::eSkipGrowUnlimited2); + } + return numGrowable; + } + + /** + * Increase the planned size for tracks in aGrowableTracks that aren't + * marked with a eSkipGrowUnlimited flag beyond their limit. * This implements the "Distribute space beyond growth limits" step in * https://drafts.csswg.org/css-grid/#distribute-extra-space */ void GrowSelectedTracksUnlimited(nscoord aAvailableSpace, nsTArray& aPlan, const nsTArray& aGrowableTracks, - TrackSize::StateBits aSelector, + uint32_t aNumGrowable, FitContentClamper aFitContentClamper) const { - MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0); - uint32_t numGrowable = aGrowableTracks.Length(); - if (aSelector) { - MOZ_ASSERT(aSelector == (aSelector & TrackSize::eIntrinsicMinSizing) && - (aSelector & TrackSize::eMaxContentMinSizing), - "Should only get here for track sizing steps 2.1 to 2.3"); - // Note that eMaxContentMinSizing is always included. We do those first: - numGrowable = MarkExcludedTracks(aPlan, numGrowable, aGrowableTracks, - TrackSize::eMaxContentMinSizing, - TrackSize::eMaxContentMaxSizing, - TrackSize::eSkipGrowUnlimited1); - // Now mark min-content/auto min-sizing tracks if requested. - auto minOrAutoSelector = aSelector & ~TrackSize::eMaxContentMinSizing; - if (minOrAutoSelector) { - numGrowable = MarkExcludedTracks(aPlan, numGrowable, aGrowableTracks, - minOrAutoSelector, - TrackSize::eIntrinsicMaxSizing, - TrackSize::eSkipGrowUnlimited2); - } - } + MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0 && + aNumGrowable <= aGrowableTracks.Length()); nscoord space = aAvailableSpace; DebugOnly didClamp = false; - while (numGrowable) { - nscoord spacePerTrack = std::max(space / numGrowable, 1); + while (aNumGrowable) { + nscoord spacePerTrack = std::max(space / aNumGrowable, 1); for (uint32_t track : aGrowableTracks) { TrackSize& sz = aPlan[track]; if (sz.mState & TrackSize::eSkipGrowUnlimited) { @@ -1609,7 +1741,7 @@ struct nsGridContainerFrame::Tracks delta = newBase - sz.mBase; MOZ_ASSERT(delta >= 0, "track size shouldn't shrink"); sz.mState |= TrackSize::eSkipGrowUnlimited1; - --numGrowable; + --aNumGrowable; } } sz.mBase = newBase; @@ -1628,46 +1760,30 @@ struct nsGridContainerFrame::Tracks * Distribute aAvailableSpace to the planned base size for aGrowableTracks * up to their limits, then distribute the remaining space beyond the limits. */ - void DistributeToTrackBases(nscoord aAvailableSpace, + template + void DistributeToTrackSizes(nscoord aAvailableSpace, nsTArray& aPlan, + nsTArray& aItemPlan, nsTArray& aGrowableTracks, - TrackSize::StateBits aSelector) + TrackSize::StateBits aSelector, + const FitContentClamper& aFitContentClamper) { - SetupGrowthPlan(aPlan, aGrowableTracks); - nscoord space = GrowTracksToLimit(aAvailableSpace, aPlan, aGrowableTracks, nullptr); + InitializeItemPlan(aItemPlan, aGrowableTracks); + nscoord space = GrowTracksToLimit(aAvailableSpace, aItemPlan, aGrowableTracks, + aFitContentClamper); if (space > 0) { - GrowSelectedTracksUnlimited(space, aPlan, aGrowableTracks, aSelector, nullptr); + uint32_t numGrowable = + MarkExcludedTracks(aItemPlan, aGrowableTracks, aSelector); + GrowSelectedTracksUnlimited(space, aItemPlan, aGrowableTracks, + numGrowable, aFitContentClamper); } - CopyPlanToBase(aPlan, aGrowableTracks); - } - - /** - * Distribute aAvailableSpace to the planned limits for aGrowableTracks. - */ - void DistributeToTrackLimits(nscoord aAvailableSpace, - nsTArray& aPlan, - nsTArray& aGrowableTracks, - const TrackSizingFunctions& aFunctions, - nscoord aPercentageBasis) - { - auto fitContentClamper = [&aFunctions, aPercentageBasis] (uint32_t aTrack, - nscoord aMinSize, - nscoord* aSize) { - nscoord fitContentLimit = - ::ResolveToDefiniteSize(aFunctions.MaxSizingFor(aTrack), aPercentageBasis); - if (*aSize > fitContentLimit) { - *aSize = std::max(aMinSize, fitContentLimit); - return true; + for (uint32_t track : aGrowableTracks) { + nscoord& plannedSize = aPlan[track].mBase; + nscoord itemIncurredSize = aItemPlan[track].mBase; + if (plannedSize < itemIncurredSize) { + plannedSize = itemIncurredSize; } - return false; - }; - nscoord space = GrowTracksToLimit(aAvailableSpace, aPlan, aGrowableTracks, - fitContentClamper); - if (space > 0) { - GrowSelectedTracksUnlimited(aAvailableSpace, aPlan, aGrowableTracks, - TrackSize::StateBits(0), fitContentClamper); } - CopyPlanToLimit(aPlan, aGrowableTracks); } /** @@ -3545,19 +3661,27 @@ nsGridContainerFrame::Grid::PlaceGridItems(GridReflowInput& aState, // Count empty 'auto-fit' tracks in the repeat() range. // |colAdjust| will have a count for each line in the grid of how many // tracks were empty between the start of the grid and that line. + + // Since this loop is concerned with just the repeat tracks, we + // iterate from 0..NumRepeatTracks() which is the natural range of + // mRemoveRepeatTracks. This means we have to add + // (mExplicitGridOffset + mRepeatAutoStart) to get a zero-based + // index for arrays like mCellMap and colAdjust. We'll then fill out + // the colAdjust array for all the remaining lines. Maybe> colAdjust; uint32_t numEmptyCols = 0; if (aState.mColFunctions.mHasRepeatAuto && !gridStyle->mGridTemplateColumns.mIsAutoFill && aState.mColFunctions.NumRepeatTracks() > 0) { - for (uint32_t col = aState.mColFunctions.mRepeatAutoStart, - endRepeat = aState.mColFunctions.mRepeatAutoEnd, - numColLines = mGridColEnd + 1; - col < numColLines; ++col) { + const uint32_t repeatStart = (aState.mColFunctions.mExplicitGridOffset + + aState.mColFunctions.mRepeatAutoStart); + const uint32_t numRepeats = aState.mColFunctions.NumRepeatTracks(); + const uint32_t numColLines = mGridColEnd + 1; + for (uint32_t i = 0; i < numRepeats; ++i) { if (numEmptyCols) { - (*colAdjust)[col] = numEmptyCols; + (*colAdjust)[repeatStart + i] = numEmptyCols; } - if (col < endRepeat && mCellMap.IsEmptyCol(col)) { + if (mCellMap.IsEmptyCol(repeatStart + i)) { ++numEmptyCols; if (colAdjust.isNothing()) { colAdjust.emplace(numColLines); @@ -3565,26 +3689,34 @@ nsGridContainerFrame::Grid::PlaceGridItems(GridReflowInput& aState, PodZero(colAdjust->Elements(), colAdjust->Length()); } - uint32_t repeatIndex = col - aState.mColFunctions.mRepeatAutoStart; - MOZ_ASSERT(aState.mColFunctions.mRemovedRepeatTracks.Length() > - repeatIndex); - aState.mColFunctions.mRemovedRepeatTracks[repeatIndex] = true; + aState.mColFunctions.mRemovedRepeatTracks[i] = true; + } + } + // Fill out the colAdjust array for all the columns after the + // repeats. + if (numEmptyCols) { + for (uint32_t col = repeatStart + numRepeats; + col < numColLines; ++col) { + (*colAdjust)[col] = numEmptyCols; } } } + + // Do similar work for the row tracks, with the same logic. Maybe> rowAdjust; uint32_t numEmptyRows = 0; if (aState.mRowFunctions.mHasRepeatAuto && !gridStyle->mGridTemplateRows.mIsAutoFill && aState.mRowFunctions.NumRepeatTracks() > 0) { - for (uint32_t row = aState.mRowFunctions.mRepeatAutoStart, - endRepeat = aState.mRowFunctions.mRepeatAutoEnd, - numRowLines = mGridRowEnd + 1; - row < numRowLines; ++row) { + const uint32_t repeatStart = (aState.mRowFunctions.mExplicitGridOffset + + aState.mRowFunctions.mRepeatAutoStart); + const uint32_t numRepeats = aState.mRowFunctions.NumRepeatTracks(); + const uint32_t numRowLines = mGridRowEnd + 1; + for (uint32_t i = 0; i < numRepeats; ++i) { if (numEmptyRows) { - (*rowAdjust)[row] = numEmptyRows; + (*rowAdjust)[repeatStart + i] = numEmptyRows; } - if (row < endRepeat && mCellMap.IsEmptyRow(row)) { + if (mCellMap.IsEmptyRow(repeatStart + i)) { ++numEmptyRows; if (rowAdjust.isNothing()) { rowAdjust.emplace(numRowLines); @@ -3592,10 +3724,13 @@ nsGridContainerFrame::Grid::PlaceGridItems(GridReflowInput& aState, PodZero(rowAdjust->Elements(), rowAdjust->Length()); } - uint32_t repeatIndex = row - aState.mRowFunctions.mRepeatAutoStart; - MOZ_ASSERT(aState.mRowFunctions.mRemovedRepeatTracks.Length() > - repeatIndex); - aState.mRowFunctions.mRemovedRepeatTracks[repeatIndex] = true; + aState.mRowFunctions.mRemovedRepeatTracks[i] = true; + } + } + if (numEmptyRows) { + for (uint32_t row = repeatStart + numRepeats; + row < numRowLines; ++row) { + (*rowAdjust)[row] = numEmptyRows; } } } @@ -3974,28 +4109,16 @@ nsGridContainerFrame::Tracks::CalculateSizes( } } -bool -nsGridContainerFrame::Tracks::HasIntrinsicButNoFlexSizingInRange( - const LineRange& aRange, - TrackSize::StateBits* aState) const +TrackSize::StateBits +nsGridContainerFrame::Tracks::StateBitsForRange(const LineRange& aRange) const { - MOZ_ASSERT(!aRange.IsAuto(), "must have a definite range"); + TrackSize::StateBits state = TrackSize::StateBits(0); const uint32_t start = aRange.mStart; const uint32_t end = aRange.mEnd; - const TrackSize::StateBits selector = - TrackSize::eIntrinsicMinSizing | TrackSize::eIntrinsicMaxSizing; - bool foundIntrinsic = false; for (uint32_t i = start; i < end; ++i) { - TrackSize::StateBits state = mSizes[i].mState; - *aState |= state; - if (state & TrackSize::eFlexMaxSizing) { - return false; - } - if (state & selector) { - foundIntrinsic = true; - } + state |= mSizes[i].mState; } - return foundIntrinsic; + return state; } bool @@ -4010,6 +4133,13 @@ nsGridContainerFrame::Tracks::ResolveIntrinsicSizeStep1( CachedIntrinsicSizes cache; TrackSize& sz = mSizes[aRange.mStart]; WritingMode wm = aState.mWM; + + // Check if we need to apply "Automatic Minimum Size" and cache it. + if ((sz.mState & TrackSize::eAutoMinSizing) && + aGridItem.ShouldApplyAutoMinSize(wm, mAxis, aPercentageBasis)) { + aGridItem.mState[mAxis] |= ItemState::eApplyAutoMinSize; + } + // Calculate data for "Automatic Minimum Size" clamping, if needed. bool needed = ((sz.mState & TrackSize::eIntrinsicMinSizing) || aConstraint == SizingConstraint::eNoConstraint) && @@ -4370,6 +4500,55 @@ nsGridContainerFrame::Tracks::AlignBaselineSubtree( } } +template +bool +nsGridContainerFrame::Tracks::GrowSizeForSpanningItems( + nsTArray::iterator aIter, + const nsTArray::iterator aIterEnd, + nsTArray& aTracks, + nsTArray& aPlan, + nsTArray& aItemPlan, + TrackSize::StateBits aSelector, + const FitContentClamper& aFitContentClamper, + bool aNeedInfinitelyGrowableFlag) +{ + constexpr bool isMaxSizingPhase = + phase == TrackSizingPhase::eIntrinsicMaximums || + phase == TrackSizingPhase::eMaxContentMaximums; + bool needToUpdateSizes = false; + InitializePlan(aPlan); + for (; aIter != aIterEnd; ++aIter) { + const Step2ItemData& item = *aIter; + if (!(item.mState & aSelector)) { + continue; + } + if (isMaxSizingPhase) { + for (auto j = item.mLineRange.mStart, end = item.mLineRange.mEnd; j < end; ++j) { + aPlan[j].mState |= TrackSize::eModified; + } + } + nscoord space = item.SizeContributionForPhase(); + if (space <= 0) { + continue; + } + aTracks.ClearAndRetainStorage(); + space = CollectGrowable(space, item.mLineRange, aSelector, + aTracks); + if (space > 0) { + DistributeToTrackSizes(space, aPlan, aItemPlan, aTracks, aSelector, + aFitContentClamper); + needToUpdateSizes = true; + } + } + if (isMaxSizingPhase) { + needToUpdateSizes = true; + } + if (needToUpdateSizes) { + CopyPlanToSize(aPlan, aNeedInfinitelyGrowableFlag); + } + return needToUpdateSizes; +} + void nsGridContainerFrame::Tracks::ResolveIntrinsicSize( GridReflowInput& aState, @@ -4379,21 +4558,6 @@ nsGridContainerFrame::Tracks::ResolveIntrinsicSize( nscoord aPercentageBasis, SizingConstraint aConstraint) { - // Some data we collect on each item for Step 2 of the algorithm below. - struct Step2ItemData - { - uint32_t mSpan; - TrackSize::StateBits mState; - LineRange mLineRange; - nscoord mMinSize; - nscoord mMinContentContribution; - nscoord mMaxContentContribution; - nsIFrame* mFrame; - static bool IsSpanLessThan(const Step2ItemData& a, const Step2ItemData& b) - { - return a.mSpan < b.mSpan; - } - }; // Resolve Intrinsic Track Sizes // http://dev.w3.org/csswg/css-grid/#algo-content @@ -4418,12 +4582,10 @@ nsGridContainerFrame::Tracks::ResolveIntrinsicSize( for (; !iter.AtEnd(); iter.Next()) { auto& gridItem = aGridItems[iter.GridItemIndex()]; - // Check if we need to apply "Automatic Minimum Size" and cache it. - MOZ_ASSERT(!(gridItem.mState[mAxis] & ItemState::eApplyAutoMinSize), - "Why is eApplyAutoMinSize set already?"); - if (gridItem.ShouldApplyAutoMinSize(wm, mAxis, aPercentageBasis)) { - gridItem.mState[mAxis] |= ItemState::eApplyAutoMinSize; - } + MOZ_ASSERT(!(gridItem.mState[mAxis] & + (ItemState::eApplyAutoMinSize | ItemState::eIsFlexing | + ItemState::eClampMarginBoxMinSize)), + "Why are any of these bits set already?"); const GridArea& area = gridItem.mArea; const LineRange& lineRange = area.*aRange; @@ -4435,8 +4597,17 @@ nsGridContainerFrame::Tracks::ResolveIntrinsicSize( gridItem.mState[mAxis] |= ItemState::eIsFlexing; } } else { - TrackSize::StateBits state = TrackSize::StateBits(0); - if (HasIntrinsicButNoFlexSizingInRange(lineRange, &state)) { + TrackSize::StateBits state = StateBitsForRange(lineRange); + + // Check if we need to apply "Automatic Minimum Size" and cache it. + if ((state & TrackSize::eAutoMinSizing) && + gridItem.ShouldApplyAutoMinSize(wm, mAxis, aPercentageBasis)) { + gridItem.mState[mAxis] |= ItemState::eApplyAutoMinSize; + } + + if ((state & (TrackSize::eIntrinsicMinSizing | + TrackSize::eIntrinsicMaxSizing)) && + !(state & TrackSize::eFlexMaxSizing)) { // Collect data for Step 2. maxSpan = std::max(maxSpan, span); if (span >= stateBitsPerSpan.Length()) { @@ -4500,6 +4671,18 @@ nsGridContainerFrame::Tracks::ResolveIntrinsicSize( // Step 2. if (maxSpan) { + auto fitContentClamper = [&aFunctions, aPercentageBasis] (uint32_t aTrack, + nscoord aMinSize, + nscoord* aSize) + { + nscoord fitContentLimit = + ::ResolveToDefiniteSize(aFunctions.MaxSizingFor(aTrack), aPercentageBasis); + if (*aSize > fitContentLimit) { + *aSize = std::max(aMinSize, fitContentLimit); + return true; + } + return false; + }; // Sort the collected items on span length, shortest first. std::stable_sort(step2Items.begin(), step2Items.end(), Step2ItemData::IsSpanLessThan); @@ -4507,85 +4690,44 @@ nsGridContainerFrame::Tracks::ResolveIntrinsicSize( nsTArray tracks(maxSpan); nsTArray plan(mSizes.Length()); plan.SetLength(mSizes.Length()); - for (uint32_t i = 0, len = step2Items.Length(); i < len; ) { - // Start / end index for items of the same span length: - const uint32_t spanGroupStartIndex = i; - uint32_t spanGroupEndIndex = len; - const uint32_t span = step2Items[i].mSpan; - for (++i; i < len; ++i) { - if (step2Items[i].mSpan != span) { - spanGroupEndIndex = i; - break; - } - } - + nsTArray itemPlan(mSizes.Length()); + itemPlan.SetLength(mSizes.Length()); + // Start / end iterator for items of the same span length: + auto spanGroupStart = step2Items.begin(); + auto spanGroupEnd = spanGroupStart; + const auto end = step2Items.end(); + for (; spanGroupStart != end; spanGroupStart = spanGroupEnd) { + while (spanGroupEnd != end && + !Step2ItemData::IsSpanLessThan(*spanGroupStart, *spanGroupEnd)) { + ++spanGroupEnd; + } + + const uint32_t span = spanGroupStart->mSpan; bool updatedBase = false; // Did we update any mBase in step 2.1 - 2.3? TrackSize::StateBits selector(TrackSize::eIntrinsicMinSizing); if (stateBitsPerSpan[span] & selector) { // Step 2.1 MinSize to intrinsic min-sizing. - for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) { - Step2ItemData& item = step2Items[i]; - if (!(item.mState & selector)) { - continue; - } - nscoord space = item.mMinSize; - if (space <= 0) { - continue; - } - tracks.ClearAndRetainStorage(); - space = CollectGrowable(space, mSizes, item.mLineRange, selector, - tracks); - if (space > 0) { - DistributeToTrackBases(space, plan, tracks, selector); - updatedBase = true; - } - } + updatedBase = + GrowSizeForSpanningItems( + spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector); } selector = contentBasedMinSelector; if (stateBitsPerSpan[span] & selector) { // Step 2.2 MinContentContribution to min-/max-content (and 'auto' when // sizing under a min-content constraint) min-sizing. - for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) { - Step2ItemData& item = step2Items[i]; - if (!(item.mState & selector)) { - continue; - } - nscoord space = item.mMinContentContribution; - if (space <= 0) { - continue; - } - tracks.ClearAndRetainStorage(); - space = CollectGrowable(space, mSizes, item.mLineRange, selector, - tracks); - if (space > 0) { - DistributeToTrackBases(space, plan, tracks, selector); - updatedBase = true; - } - } + updatedBase |= + GrowSizeForSpanningItems( + spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector); } selector = maxContentMinSelector; if (stateBitsPerSpan[span] & selector) { // Step 2.3 MaxContentContribution to max-content (and 'auto' when // sizing under a max-content constraint) min-sizing. - for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) { - Step2ItemData& item = step2Items[i]; - if (!(item.mState & selector)) { - continue; - } - nscoord space = item.mMaxContentContribution; - if (space <= 0) { - continue; - } - tracks.ClearAndRetainStorage(); - space = CollectGrowable(space, mSizes, item.mLineRange, selector, - tracks); - if (space > 0) { - DistributeToTrackBases(space, plan, tracks, selector); - updatedBase = true; - } - } + updatedBase |= + GrowSizeForSpanningItems( + spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector); } if (updatedBase) { @@ -4596,63 +4738,22 @@ nsGridContainerFrame::Tracks::ResolveIntrinsicSize( } } } - if (stateBitsPerSpan[span] & TrackSize::eIntrinsicMaxSizing) { - plan = mSizes; - for (TrackSize& sz : plan) { - if (sz.mLimit == NS_UNCONSTRAINEDSIZE) { - // use mBase as the planned limit - } else { - sz.mBase = sz.mLimit; - } - } + selector = TrackSize::eIntrinsicMaxSizing; + if (stateBitsPerSpan[span] & selector) { + const bool willRunStep2_6 = + stateBitsPerSpan[span] & TrackSize::eAutoOrMaxContentMaxSizing; // Step 2.5 MinSize to intrinsic max-sizing. - for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) { - Step2ItemData& item = step2Items[i]; - if (!(item.mState & TrackSize::eIntrinsicMaxSizing)) { - continue; - } - nscoord space = item.mMinSize; - if (space <= 0) { - continue; - } - tracks.ClearAndRetainStorage(); - space = CollectGrowable(space, plan, item.mLineRange, - TrackSize::eIntrinsicMaxSizing, - tracks); - if (space > 0) { - DistributeToTrackLimits(space, plan, tracks, aFunctions, - aPercentageBasis); - } - } - for (size_t j = 0, len = mSizes.Length(); j < len; ++j) { - TrackSize& sz = plan[j]; - sz.mState &= ~(TrackSize::eFrozen | TrackSize::eSkipGrowUnlimited); - if (sz.mLimit != NS_UNCONSTRAINEDSIZE) { - sz.mLimit = sz.mBase; // collect the results from 2.5 - } - } + GrowSizeForSpanningItems( + spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector, + fitContentClamper, willRunStep2_6); - if (stateBitsPerSpan[span] & TrackSize::eAutoOrMaxContentMaxSizing) { + if (willRunStep2_6) { // Step 2.6 MaxContentContribution to max-content max-sizing. - for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) { - Step2ItemData& item = step2Items[i]; - if (!(item.mState & TrackSize::eAutoOrMaxContentMaxSizing)) { - continue; - } - nscoord space = item.mMaxContentContribution; - if (space <= 0) { - continue; - } - tracks.ClearAndRetainStorage(); - space = CollectGrowable(space, plan, item.mLineRange, - TrackSize::eAutoOrMaxContentMaxSizing, - tracks); - if (space > 0) { - DistributeToTrackLimits(space, plan, tracks, aFunctions, - aPercentageBasis); - } - } + selector = TrackSize::eAutoOrMaxContentMaxSizing; + GrowSizeForSpanningItems( + spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector, + fitContentClamper); } } } @@ -4984,6 +5085,7 @@ nsGridContainerFrame::Tracks::AlignJustifyContent( default: MOZ_ASSERT_UNREACHABLE("unknown align-/justify-content value"); between = 0; // just to avoid a compiler warning + roundingError = 0; // just to avoid a compiler warning } between += mGridGap; for (TrackSize& sz : mSizes) { @@ -7113,6 +7215,9 @@ nsGridContainerFrame::TrackSize::Dump() const if (mState & eFrozen) { printf("frozen "); } + if (mState & eModified) { + printf("modified "); + } if (mState & eBreakBefore) { printf("break-before "); } -- cgit v1.2.3 From f1adcd1eeed03591f10ecc72c5e5b71856a18ca9 Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Sat, 28 Sep 2019 23:25:06 -0400 Subject: Issue #1233 - Part 2: Update Reftests List of relevant patches applied: 1425599 part 15 - [css-grid] Test reference fixes + more tests. 1373678 Part 3: Add line number checks to test_grid_implicit.html. 1416350 - Part 3: Add test to verify line numbers of grids with leading implicit tracks. 1416350 - Part 4: Add a reftest of repeat:auto-fit grids with leading implicit tracks. 1417711 - [css-grid] An abs.pos. grid container child that only covers removed 'auto-fit' tracks should not span to the end padding edge. 1416350 - Part 5: Correct the expected results for grids that have leading implicit tracks. 1418727 part 3 - [css-grid] Reftest updates. --- dom/grid/test/chrome.ini | 1 + dom/grid/test/chrome/test_grid_implicit.html | 63 +- dom/grid/test/chrome/test_grid_line_numbers.html | 101 ++ layout/generic/nsGridContainerFrame.cpp | 10 +- layout/generic/test/mochitest.ini | 4 + .../test/test_grid_track_sizing_algo_001.html | 1641 ++++++++++++++++++++ .../test/test_grid_track_sizing_algo_002.html | 1641 ++++++++++++++++++++ .../grid-col-max-sizing-max-content-001-ref.html | 15 +- .../grid-col-max-sizing-max-content-002-ref.html | 15 +- .../css-grid/grid-flex-min-sizing-002-ref.html | 8 +- .../css-grid/grid-max-sizing-flex-004-ref.html | 4 +- .../grid-repeat-auto-fill-fit-006-ref.html | 64 +- .../grid-repeat-auto-fill-fit-007-ref.html | 22 +- .../grid-repeat-auto-fill-fit-008-ref.html | 33 +- .../css-grid/grid-repeat-auto-fill-fit-008.html | 34 +- .../grid-repeat-auto-fill-fit-012-ref.html | 144 ++ .../css-grid/grid-repeat-auto-fill-fit-012.html | 160 ++ .../grid-repeat-auto-fill-fit-013-ref.html | 116 ++ .../css-grid/grid-repeat-auto-fill-fit-013.html | 135 ++ .../grid-track-intrinsic-sizing-002-ref.html | 30 +- .../grid-track-intrinsic-sizing-003-ref.html | 48 +- layout/reftests/css-grid/reftest.list | 6 +- 22 files changed, 4150 insertions(+), 145 deletions(-) create mode 100644 dom/grid/test/chrome/test_grid_line_numbers.html create mode 100644 layout/generic/test/test_grid_track_sizing_algo_001.html create mode 100644 layout/generic/test/test_grid_track_sizing_algo_002.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-012-ref.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-012.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-013-ref.html create mode 100644 layout/reftests/css-grid/grid-repeat-auto-fill-fit-013.html diff --git a/dom/grid/test/chrome.ini b/dom/grid/test/chrome.ini index 2241cf9eb..169fa9b89 100644 --- a/dom/grid/test/chrome.ini +++ b/dom/grid/test/chrome.ini @@ -2,6 +2,7 @@ [chrome/test_grid_fragmentation.html] [chrome/test_grid_implicit.html] [chrome/test_grid_lines.html] +[chrome/test_grid_line_numbers.html] [chrome/test_grid_object.html] [chrome/test_grid_repeats.html] [chrome/test_grid_tracks.html] diff --git a/dom/grid/test/chrome/test_grid_implicit.html b/dom/grid/test/chrome/test_grid_implicit.html index c7782e0e5..1f7142658 100644 --- a/dom/grid/test/chrome/test_grid_implicit.html +++ b/dom/grid/test/chrome/test_grid_implicit.html @@ -33,6 +33,11 @@ body { grid-template-rows: [areaA-end areaB-start areaC-end] 50px [areaA-start areaB-end areaC-start]; } +.template4 { + grid-template-columns: 100px 50px 100px; + grid-template-rows: 50px; +} + .box { background-color: #444; color: #fff; @@ -50,6 +55,9 @@ body { .d { grid-area: areaD; } +.e { + grid-column: -7 / 5; +} @@ -246,5 +299,9 @@ function runTests() {

C
+
+
E
+
+ diff --git a/dom/grid/test/chrome/test_grid_line_numbers.html b/dom/grid/test/chrome/test_grid_line_numbers.html new file mode 100644 index 000000000..c8e5226b6 --- /dev/null +++ b/dom/grid/test/chrome/test_grid_line_numbers.html @@ -0,0 +1,101 @@ + + + + + + + + + + + + +
+
A
+
B
+
C
+
+ +
+
A
+
B
+
C
+
D
+
+ + + diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 59f58f268..054f01297 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -725,9 +725,6 @@ struct nsGridContainerFrame::LineRange mEnd > mStart, "invalid line range"); mEnd -= aNumRemovedTracks[mEnd]; } - if (mStart == mEnd) { - mEnd = nsGridContainerFrame::kAutoLine; - } } /** * Return the contribution of this line range for step 2 in @@ -5179,10 +5176,15 @@ nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos( : GridLineSide::eBeforeGridGap; nscoord endPos = aTracks.GridLineEdge(mEnd, side); *aLength = std::max(aGridOrigin + endPos, 0); - } else { + } else if (MOZ_LIKELY(mStart != mEnd)) { nscoord pos; ToPositionAndLength(aTracks.mSizes, &pos, aLength); *aPos = aGridOrigin + pos; + } else { + // The grid area only covers removed 'auto-fit' tracks. + nscoord pos = aTracks.GridLineEdge(mStart, GridLineSide::eBeforeGridGap); + *aPos = aGridOrigin + pos; + *aLength = nscoord(0); } } } diff --git a/layout/generic/test/mochitest.ini b/layout/generic/test/mochitest.ini index 934ffc8a4..33dacddab 100644 --- a/layout/generic/test/mochitest.ini +++ b/layout/generic/test/mochitest.ini @@ -138,3 +138,7 @@ support-files = file_taintedfilters_feDisplacementMap-tainted-1.svg file_tainted support-files = file_scroll_position_restore.html [test_scroll_animation_restore.html] [test_scroll_position_iframe.html] +[test_grid_track_sizing_algo_001.html] +skip-if = debug == true # the test is slow +[test_grid_track_sizing_algo_002.html] +skip-if = debug == true # the test is slow diff --git a/layout/generic/test/test_grid_track_sizing_algo_001.html b/layout/generic/test/test_grid_track_sizing_algo_001.html new file mode 100644 index 000000000..68956c2df --- /dev/null +++ b/layout/generic/test/test_grid_track_sizing_algo_001.html @@ -0,0 +1,1641 @@ + + + + + CSS Grid Test: intrinsic track sizing with spanning items + + + + + + + + + + + + + + + diff --git a/layout/generic/test/test_grid_track_sizing_algo_002.html b/layout/generic/test/test_grid_track_sizing_algo_002.html new file mode 100644 index 000000000..40f50e20f --- /dev/null +++ b/layout/generic/test/test_grid_track_sizing_algo_002.html @@ -0,0 +1,1641 @@ + + + + + CSS Grid Test: intrinsic track sizing with spanning items + + + + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-col-max-sizing-max-content-001-ref.html b/layout/reftests/css-grid/grid-col-max-sizing-max-content-001-ref.html index eda06249b..da30a8b89 100644 --- a/layout/reftests/css-grid/grid-col-max-sizing-max-content-001-ref.html +++ b/layout/reftests/css-grid/grid-col-max-sizing-max-content-001-ref.html @@ -17,9 +17,9 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; clear:left; } -.c1 { min-width:40px; margin-bottom: 2px; margin-right: 47px; } +.c1 { width:40px; margin-bottom: 2px; margin-right: 47px; } .r1 { min-width:70px; margin-left: 38px; margin-top: 2px; } -.c3 { min-width:0; margin: 2px 18px 1px 85px; } +.c3 { width:10px; margin: 2px 18px 1px 71px; } span { display: block; @@ -52,21 +52,22 @@ x { display:inline-block; width:10px; height:18px; }     +
-  +     
-  +       
-  +     
@@ -74,13 +75,13 @@ x { display:inline-block; width:10px; height:18px; }
    -  + 
    -  + 
diff --git a/layout/reftests/css-grid/grid-col-max-sizing-max-content-002-ref.html b/layout/reftests/css-grid/grid-col-max-sizing-max-content-002-ref.html index 23ca12e7b..eeb4e407f 100644 --- a/layout/reftests/css-grid/grid-col-max-sizing-max-content-002-ref.html +++ b/layout/reftests/css-grid/grid-col-max-sizing-max-content-002-ref.html @@ -21,9 +21,9 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; clear:left; } -.c1 { min-width:40px; margin-bottom: 2px; margin-right: 47px; } +.c1 { width:40px; margin-bottom: 2px; margin-right: 47px; } .r1 { min-width:70px; margin-left: 38px; margin-top: 2px; } -.c3 { min-width:0; margin: 2px 18px 1px 85px; } +.c3 { width:10px; margin: 2px 18px 1px 71px; } span { display: block; @@ -56,21 +56,22 @@ x { display:inline-block; width:10px; height:18px; }     +
-  +     
-  +       
-  +     
@@ -78,13 +79,13 @@ x { display:inline-block; width:10px; height:18px; }
    -  + 
    -  + 
diff --git a/layout/reftests/css-grid/grid-flex-min-sizing-002-ref.html b/layout/reftests/css-grid/grid-flex-min-sizing-002-ref.html index d9af7e43c..d811447ff 100644 --- a/layout/reftests/css-grid/grid-flex-min-sizing-002-ref.html +++ b/layout/reftests/css-grid/grid-flex-min-sizing-002-ref.html @@ -127,10 +127,10 @@ body,html { color:black; background:white; font-family:monospace; font-size:16px } .gF { - grid-template-columns: 22px - 1px - 1px - auto; + grid-template-columns: 2px + 20px + 2px + 0; } diff --git a/layout/reftests/css-grid/grid-max-sizing-flex-004-ref.html b/layout/reftests/css-grid/grid-max-sizing-flex-004-ref.html index 6446c0ee6..b0ac02bf5 100644 --- a/layout/reftests/css-grid/grid-max-sizing-flex-004-ref.html +++ b/layout/reftests/css-grid/grid-max-sizing-flex-004-ref.html @@ -51,8 +51,8 @@ x { display:inline-block; height:10px; width:18px; }
--> -
-
+
+
+ + + Reference: repeat(auto-fit) with removed tracks + + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-012.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-012.html new file mode 100644 index 000000000..7ed0843af --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-012.html @@ -0,0 +1,160 @@ + + + + + CSS Grid Test: repeat(auto-fit) with removed tracks + + + + + + + +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-013-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-013-ref.html new file mode 100644 index 000000000..9b8267f88 --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-013-ref.html @@ -0,0 +1,116 @@ + + + + +CSS Grid Test Reference: test auto placement in repeat auto-fit grids with leading implicit tracks + + + + +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-013.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-013.html new file mode 100644 index 000000000..5a9c05d73 --- /dev/null +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-013.html @@ -0,0 +1,135 @@ + + + + +CSS Grid Test: test placement in repeat auto-fit grids with leading implicit tracks + + + + + + + +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-track-intrinsic-sizing-002-ref.html b/layout/reftests/css-grid/grid-track-intrinsic-sizing-002-ref.html index 7fb00f1da..23dc42b69 100644 --- a/layout/reftests/css-grid/grid-track-intrinsic-sizing-002-ref.html +++ b/layout/reftests/css-grid/grid-track-intrinsic-sizing-002-ref.html @@ -27,34 +27,34 @@ body,html { color:black; background:white; font-family:monospace; font-size:16px background: grey; } .g1 .d1 { - width: 52px; + width: 0px; } .g2 .d1 { - width: 56px; + width: 0px; } .g2f .d1 { - width: 69px; + width: 0px; } .g3 .d1 { - width: 56px; + width: 0px; } .g4 .d1 { - width: 96px; + width: 80px; } .g4f .d1 { - width: 69px; + width: 0px; } .g5 .d1 { - width: 96px; + width: 80px; } .g6 .d1 { - width: 69px; + width: 0px; } .g6f .d1 { - width: 69px; + width: 0px; } .g7 .d1 { - width: 69px; + width: 0px; } .g8 .t { width: 196px; @@ -63,19 +63,19 @@ body,html { color:black; background:white; font-family:monospace; font-size:16px width: 200px; } .g9 .d1 { - width: 69px; + width: 0px; } .gA .d1 { - width: 93px; + width: 80px; } .gB .d1 { - width: 93px; + width: 80px; } .gC .d1 { - width: 93px; + width: 80px; } .gD .d1 { - width: 93px; + width: 80px; } .t { grid-column: span 1; border:2px solid; } diff --git a/layout/reftests/css-grid/grid-track-intrinsic-sizing-003-ref.html b/layout/reftests/css-grid/grid-track-intrinsic-sizing-003-ref.html index bc52f4ca0..01739578c 100644 --- a/layout/reftests/css-grid/grid-track-intrinsic-sizing-003-ref.html +++ b/layout/reftests/css-grid/grid-track-intrinsic-sizing-003-ref.html @@ -27,34 +27,34 @@ body,html { color:black; background:white; font-family:monospace; font-size:16px background: grey; } .g1 .d1 { - width: 52px; + width: 0px; } .g2 .d1 { - width: 56px; + width: 0px; } .g2f .d1 { width: 69px; } .g3 .d1 { - width: 56px; + width: 0px; } .g4 .d1 { - width: 96px; + width: 80px; } .g4f .d1 { width: 104px; } .g5 .d1 { - width: 96px; + width: 80px; } .g6 .d1 { - width: 69px; + width: 0px; } .g6f .d1 { width: 89px; } .g7 .d1 { - width: 69px; + width: 0px; } .g8 .t { width: 196px; @@ -63,19 +63,19 @@ body,html { color:black; background:white; font-family:monospace; font-size:16px width: 200px; } .g9 .d1 { - width: 69px; + width: 0px; } .gA .d1 { - width: 93px; + width: 80px; } .gB .d1 { - width: 93px; + width: 80px; } .gC .d1 { - width: 93px; + width: 80px; } .gD .d1 { - width: 93px; + width: 80px; } .d2 { position: absolute; @@ -84,10 +84,10 @@ body,html { color:black; background:white; font-family:monospace; font-size:16px background: blue; } .g1 .d2 { - width: 448px; + width: 500px; } .g2 .d2 { - width: 444px; + width: 500px; } .g2f .d2 { right: auto; @@ -95,10 +95,10 @@ body,html { color:black; background:white; font-family:monospace; font-size:16px width: 35px; } .g3 .d2 { - width: 444px; + width: 500px; } .g4 .d2 { - width: 404px; + width: 420px; } .g4f .d2 { right: auto; @@ -106,10 +106,10 @@ body,html { color:black; background:white; font-family:monospace; font-size:16px width: 35px; } .g5 .d2 { - width: 404px; + width: 420px; } .g6 .d2 { - width: 431px; + width: 500px; } .g6f .d2 { right: auto; @@ -117,25 +117,25 @@ body,html { color:black; background:white; font-family:monospace; font-size:16px width: 35px; } .g7 .d2 { - width: 431px; + width: 500px; } .g8 .d2 { width: 300px; } .g9 .d2 { - width: 431px; + width: 500px; } .gA .d2 { - width: 407px; + width: 420px; } .gB .d2 { - width: 407px; + width: 420px; } .gC .d2 { - width: 407px; + width: 420px; } .gD .d2 { - width: 407px; + width: 420px; } .t { grid-column: span 1; border:2px solid; } diff --git a/layout/reftests/css-grid/reftest.list b/layout/reftests/css-grid/reftest.list index 3087ca49b..7c5e6be51 100644 --- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -112,9 +112,9 @@ skip-if(Android) == grid-auto-min-sizing-percent-001.html grid-auto-min-sizing-p == grid-item-auto-min-size-clamp-001.html grid-item-auto-min-size-clamp-001-ref.html == grid-item-auto-min-size-clamp-002.html grid-item-auto-min-size-clamp-002-ref.html == grid-item-auto-min-size-clamp-003.html grid-item-auto-min-size-clamp-003-ref.html -== grid-item-auto-min-size-clamp-004.html grid-item-auto-min-size-clamp-004-ref.html +# == grid-item-auto-min-size-clamp-004.html grid-item-auto-min-size-clamp-004-ref.html # bug 1421976 == grid-item-auto-min-size-clamp-005.html grid-item-auto-min-size-clamp-005-ref.html -== grid-item-auto-min-size-clamp-006.html grid-item-auto-min-size-clamp-006-ref.html +# == grid-item-auto-min-size-clamp-006.html grid-item-auto-min-size-clamp-006-ref.html # bug 1421976 == grid-item-auto-min-size-clamp-007.html grid-item-auto-min-size-clamp-007-ref.html == grid-item-overflow-stretch-001.html grid-item-overflow-stretch-001-ref.html == grid-item-overflow-stretch-002.html grid-item-overflow-stretch-002-ref.html @@ -184,6 +184,8 @@ skip-if(Android&&isDebugBuild) == grid-row-gap-004.html grid-row-gap-004-ref.htm == grid-repeat-auto-fill-fit-009.html grid-repeat-auto-fill-fit-009-ref.html == grid-repeat-auto-fill-fit-010.html grid-repeat-auto-fill-fit-010-ref.html == grid-repeat-auto-fill-fit-011.html grid-repeat-auto-fill-fit-010-ref.html +== grid-repeat-auto-fill-fit-012.html grid-repeat-auto-fill-fit-012-ref.html +== grid-repeat-auto-fill-fit-013.html grid-repeat-auto-fill-fit-013-ref.html == grid-item-blockifying-001.html grid-item-blockifying-001-ref.html == grid-fragmentation-001.html grid-fragmentation-001-ref.html == grid-fragmentation-002.html grid-fragmentation-002-ref.html -- cgit v1.2.3 From 8ff295747e7f5e205313e4405d5a63ce23fca993 Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Sat, 28 Sep 2019 23:36:05 -0400 Subject: Issue #1230 - Part 1: Fix Back-computing percentages for intrinsic sizing in Layout CSS-Grid List of relevant patches applied: 1398537 part 2 - [css-multicol] Implement percentages for 'column-gap' (Gecko part). 1434478 part 1 - [css-grid] Stop back-computing percentage grid gaps when the percentage basis is indefinite. Treat them as zero sized instead. 1434478 part 2 - Stop back-computing percentage padding/margin when the percentage basis is indefinite. Treat them as zero sized instead. 1434478 part 3 - Remove IntrinsicISizeOffsetData::hPctPadding/hPctMargin members since they are now unused. 1434478 part 4 - Factor out constants like NS_UNCONSTRAINEDSIZE so they can be used in headers without needing nsIFrame.h (idempotent patch). 1434478 part 5 - Create nsLayoutUtils::ResolveToLength for resolving CSS (idempotent patch). 1434478 part 6 - Propagate a percentage basis to nsIFrame::IntrinsicISizeOffsets for resolving padding/margin. This is needed only for CSS Grid since in other cases we're only using IntrinsicISizeOffsets in the inline-axis and the percentage basis is always indefinite for *intrinsic sizing*. When calculating the intrinsic size of grid items in the grid container's block axis however, we do have a definite size for the grid area in the inline-axis and it should be used per: https://drafts.csswg.org/css-grid/#algo-overview "2. Next, the track sizing algorithm resolves the sizes of the grid rows, using the grid column sizes calculated in the previous step." (Percentage padding/margin for grid items is always resolved against the grid area's inline-size nowadays.) --- layout/base/LayoutConstants.h | 31 ++++++ layout/base/moz.build | 4 + layout/base/nsLayoutUtils.cpp | 76 +++++--------- layout/base/nsLayoutUtils.h | 95 ++++++++++++----- layout/generic/nsColumnSetFrame.cpp | 19 ++-- layout/generic/nsFrame.cpp | 93 ++++++----------- layout/generic/nsFrame.h | 3 +- layout/generic/nsGridContainerFrame.cpp | 180 +++++++------------------------- layout/generic/nsIFrame.h | 31 ++---- layout/style/nsCSSPropList.h | 2 +- layout/style/nsStyleStruct.h | 2 +- layout/style/test/property_database.js | 4 +- layout/tables/nsTableCellFrame.cpp | 6 +- layout/tables/nsTableCellFrame.h | 3 +- layout/tables/nsTableFrame.cpp | 7 +- layout/tables/nsTableFrame.h | 3 +- 16 files changed, 240 insertions(+), 319 deletions(-) create mode 100644 layout/base/LayoutConstants.h diff --git a/layout/base/LayoutConstants.h b/layout/base/LayoutConstants.h new file mode 100644 index 000000000..cd6e1c3f5 --- /dev/null +++ b/layout/base/LayoutConstants.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +/* constants used throughout the Layout module */ + +#ifndef LayoutConstants_h___ +#define LayoutConstants_h___ + +#include "nsSize.h" // for NS_MAXSIZE + +/** + * Constant used to indicate an unconstrained size. + */ +#define NS_UNCONSTRAINEDSIZE NS_MAXSIZE + +// NOTE: There are assumptions all over that these have the same value, +// namely NS_UNCONSTRAINEDSIZE. +#define NS_INTRINSICSIZE NS_UNCONSTRAINEDSIZE +#define NS_AUTOHEIGHT NS_UNCONSTRAINEDSIZE +#define NS_AUTOOFFSET NS_UNCONSTRAINEDSIZE + +// +1 is to avoid clamped huge margin values being processed as auto margins +#define NS_AUTOMARGIN (NS_UNCONSTRAINEDSIZE + 1) + +#define NS_INTRINSIC_WIDTH_UNKNOWN nscoord_MIN + + +#endif // LayoutConstants_h___ diff --git a/layout/base/moz.build b/layout/base/moz.build index 4308a6e4d..afc683665 100644 --- a/layout/base/moz.build +++ b/layout/base/moz.build @@ -13,6 +13,9 @@ with Files('Display*'): with Files('FrameLayerBuilder.*'): BUG_COMPONENT = ('Core', 'Layout: View Rendering') +with Files('LayoutConstants.*'): + BUG_COMPONENT = ('Core', 'Layout: View Rendering') + with Files('LayerState.*'): BUG_COMPONENT = ('Core', 'Layout: View Rendering') @@ -63,6 +66,7 @@ EXPORTS += [ 'FrameLayerBuilder.h', 'FrameProperties.h', 'LayerState.h', + 'LayoutConstants.h', 'LayoutLogging.h', 'nsArenaMemoryStats.h', 'nsBidi.h', diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 06690b208..21d20c69f 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -4671,8 +4671,6 @@ GetDefiniteSize(const nsStyleCoord& aStyle, nscoord pb = aIsInlineAxis ? aPercentageBasis.value().ISize(wm) : aPercentageBasis.value().BSize(wm); if (pb == NS_UNCONSTRAINEDSIZE) { - // XXXmats given that we're calculating an intrinsic size here, - // maybe we should back-compute the calc-size using AddPercents? return false; } *aResult = std::max(0, calc->mLength + @@ -4916,12 +4914,9 @@ AddIntrinsicSizeOffset(nsRenderingContext* aRenderingContext, nscoord result = aContentSize; nscoord min = aContentMinSize; nscoord coordOutsideSize = 0; - float pctOutsideSize = 0; - float pctTotal = 0.0f; if (!(aFlags & nsLayoutUtils::IGNORE_PADDING)) { coordOutsideSize += aOffsets.hPadding; - pctOutsideSize += aOffsets.hPctPadding; } coordOutsideSize += aOffsets.hBorder; @@ -4929,21 +4924,15 @@ AddIntrinsicSizeOffset(nsRenderingContext* aRenderingContext, if (aBoxSizing == StyleBoxSizing::Border) { min += coordOutsideSize; result = NSCoordSaturatingAdd(result, coordOutsideSize); - pctTotal += pctOutsideSize; coordOutsideSize = 0; - pctOutsideSize = 0.0f; } coordOutsideSize += aOffsets.hMargin; - pctOutsideSize += aOffsets.hPctMargin; min += coordOutsideSize; result = NSCoordSaturatingAdd(result, coordOutsideSize); - pctTotal += pctOutsideSize; - const bool shouldAddPercent = aType == nsLayoutUtils::PREF_ISIZE || - (aFlags & nsLayoutUtils::ADD_PERCENTS); nscoord size; if (aType == nsLayoutUtils::MIN_ISIZE && (((aStyleSize.HasPercent() || aStyleMaxSize.HasPercent()) && @@ -4961,18 +4950,6 @@ AddIntrinsicSizeOffset(nsRenderingContext* aRenderingContext, GetIntrinsicCoord(aStyleSize, aRenderingContext, aFrame, PROP_WIDTH, size)) { result = size + coordOutsideSize; - if (shouldAddPercent) { - result = nsLayoutUtils::AddPercents(result, pctOutsideSize); - } - } else { - // NOTE: We could really do a lot better for percents and for some - // cases of calc() containing percent (certainly including any where - // the coefficient on the percent is positive and there are no max() - // expressions). However, doing better for percents wouldn't be - // backwards compatible. - if (shouldAddPercent) { - result = nsLayoutUtils::AddPercents(result, pctTotal); - } } nscoord maxSize = aFixedMaxSize ? *aFixedMaxSize : 0; @@ -4980,9 +4957,6 @@ AddIntrinsicSizeOffset(nsRenderingContext* aRenderingContext, GetIntrinsicCoord(aStyleMaxSize, aRenderingContext, aFrame, PROP_MAX_WIDTH, maxSize)) { maxSize += coordOutsideSize; - if (shouldAddPercent) { - maxSize = nsLayoutUtils::AddPercents(maxSize, pctOutsideSize); - } if (result > maxSize) { result = maxSize; } @@ -4993,17 +4967,11 @@ AddIntrinsicSizeOffset(nsRenderingContext* aRenderingContext, GetIntrinsicCoord(aStyleMinSize, aRenderingContext, aFrame, PROP_MIN_WIDTH, minSize)) { minSize += coordOutsideSize; - if (shouldAddPercent) { - minSize = nsLayoutUtils::AddPercents(minSize, pctOutsideSize); - } if (result < minSize) { result = minSize; } } - if (shouldAddPercent) { - min = nsLayoutUtils::AddPercents(min, pctTotal); - } if (result < min) { result = min; } @@ -5020,9 +4988,6 @@ AddIntrinsicSizeOffset(nsRenderingContext* aRenderingContext, : devSize.width); // GetMinimumWidgetSize() returns a border-box width. themeSize += aOffsets.hMargin; - if (shouldAddPercent) { - themeSize = nsLayoutUtils::AddPercents(themeSize, aOffsets.hPctMargin); - } if (themeSize > result || !canOverride) { result = themeSize; } @@ -5267,9 +5232,19 @@ nsLayoutUtils::IntrinsicForAxis(PhysicalAxis aAxis, min = aFrame->GetMinISize(aRenderingContext); } + nscoord pmPercentageBasis = NS_UNCONSTRAINEDSIZE; + if (aPercentageBasis.isSome()) { + // The padding/margin percentage basis is the inline-size in the parent's + // writing-mode. + auto childWM = aFrame->GetWritingMode(); + pmPercentageBasis = + aFrame->GetParent()->GetWritingMode().IsOrthogonalTo(childWM) ? + aPercentageBasis->BSize(childWM) : + aPercentageBasis->ISize(childWM); + } nsIFrame::IntrinsicISizeOffsetData offsets = - MOZ_LIKELY(isInlineAxis) ? aFrame->IntrinsicISizeOffsets() - : aFrame->IntrinsicBSizeOffsets(); + MOZ_LIKELY(isInlineAxis) ? aFrame->IntrinsicISizeOffsets(pmPercentageBasis) + : aFrame->IntrinsicBSizeOffsets(pmPercentageBasis); nscoord contentBoxSize = result; result = AddIntrinsicSizeOffset(aRenderingContext, aFrame, offsets, aType, boxSizing, result, min, styleISize, @@ -5310,11 +5285,12 @@ nsLayoutUtils::IntrinsicForContainer(nsRenderingContext* aRenderingContext, } /* static */ nscoord -nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, +nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, nsRenderingContext* aRC, - nsIFrame* aFrame, - IntrinsicISizeType aType, - uint32_t aFlags) + nsIFrame* aFrame, + IntrinsicISizeType aType, + const LogicalSize& aPercentageBasis, + uint32_t aFlags) { MOZ_ASSERT(aFrame); MOZ_ASSERT(aFrame->IsFlexOrGridItem(), @@ -5328,9 +5304,7 @@ nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, aWM.IsVertical() ? "vertical" : "horizontal"); #endif - // Note: this method is only meant for grid/flex items which always - // include percentages in their intrinsic size. - aFlags |= nsLayoutUtils::ADD_PERCENTS; + // Note: this method is only meant for grid/flex items. const nsStylePosition* const stylePos = aFrame->StylePosition(); const nsStyleCoord* style = aAxis == eAxisHorizontal ? &stylePos->mMinWidth : &stylePos->mMinHeight; @@ -5375,11 +5349,17 @@ nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, // wrapping inside of it should not apply font size inflation. AutoMaybeDisableFontInflation an(aFrame); - PhysicalAxis ourInlineAxis = - aFrame->GetWritingMode().PhysicalAxis(eLogicalAxisInline); + // The padding/margin percentage basis is the inline-size in the parent's + // writing-mode. + auto childWM = aFrame->GetWritingMode(); + nscoord pmPercentageBasis = + aFrame->GetParent()->GetWritingMode().IsOrthogonalTo(childWM) ? + aPercentageBasis.BSize(childWM) : + aPercentageBasis.ISize(childWM); + PhysicalAxis ourInlineAxis = childWM.PhysicalAxis(eLogicalAxisInline); nsIFrame::IntrinsicISizeOffsetData offsets = - ourInlineAxis == aAxis ? aFrame->IntrinsicISizeOffsets() - : aFrame->IntrinsicBSizeOffsets(); + ourInlineAxis == aAxis ? aFrame->IntrinsicISizeOffsets(pmPercentageBasis) + : aFrame->IntrinsicBSizeOffsets(pmPercentageBasis); nscoord result = 0; nscoord min = 0; diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 0a82dbf6a..bba1f3265 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -6,6 +6,7 @@ #ifndef nsLayoutUtils_h__ #define nsLayoutUtils_h__ +#include "LayoutConstants.h" #include "mozilla/MemoryReporting.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Maybe.h" @@ -154,6 +155,7 @@ public: typedef mozilla::ScreenMargin ScreenMargin; typedef mozilla::LayoutDeviceIntSize LayoutDeviceIntSize; typedef mozilla::StyleGeometryBox StyleGeometryBox; + typedef mozilla::LogicalSize LogicalSize; /** * Finds previously assigned ViewID for the given content element, if any. @@ -1381,7 +1383,8 @@ public: * variations if that's what matches aAxis) and its padding, border and margin * in the corresponding dimension. * @param aPercentageBasis an optional percentage basis (in aFrame's WM). - * Pass NS_UNCONSTRAINEDSIZE if the basis is indefinite in either/both axes. + * If the basis is indefinite in a given axis, pass a size with + * NS_UNCONSTRAINEDSIZE in that component. * If you pass Nothing() a percentage basis will be calculated from aFrame's * ancestors' computed size in the relevant axis, if needed. * @param aMarginBoxMinSizeClamp make the result fit within this margin-box @@ -1395,14 +1398,13 @@ public: IGNORE_PADDING = 0x01, BAIL_IF_REFLOW_NEEDED = 0x02, // returns NS_INTRINSIC_WIDTH_UNKNOWN if so MIN_INTRINSIC_ISIZE = 0x04, // use min-width/height instead of width/height - ADD_PERCENTS = 0x08, // apply AddPercents also for MIN_ISIZE }; static nscoord IntrinsicForAxis(mozilla::PhysicalAxis aAxis, nsRenderingContext* aRenderingContext, nsIFrame* aFrame, IntrinsicISizeType aType, - const mozilla::Maybe& aPercentageBasis = mozilla::Nothing(), + const mozilla::Maybe& aPercentageBasis = mozilla::Nothing(), uint32_t aFlags = 0, nscoord aMarginBoxMinSizeClamp = NS_MAXSIZE); /** @@ -1427,31 +1429,18 @@ public: * calculates the result as if the 'min-' computed value is zero. * Otherwise, return NS_UNCONSTRAINEDSIZE. * + * @param aPercentageBasis the percentage basis (in aFrame's WM). + * Pass NS_UNCONSTRAINEDSIZE if the basis is indefinite in either/both axes. * @note this behavior is specific to Grid/Flexbox (currently) so aFrame * should be a grid/flex item. */ - static nscoord MinSizeContributionForAxis(mozilla::PhysicalAxis aAxis, - nsRenderingContext* aRC, - nsIFrame* aFrame, - IntrinsicISizeType aType, - uint32_t aFlags = 0); - - /** - * This function increases an initial intrinsic size, 'aCurrent', according - * to the given 'aPercent', such that the size-increase makes up exactly - * 'aPercent' percent of the returned value. If 'aPercent' or 'aCurrent' are - * less than or equal to zero the original 'aCurrent' value is returned. - * If 'aPercent' is greater than or equal to 1.0 the value nscoord_MAX is - * returned. - */ - static nscoord AddPercents(nscoord aCurrent, float aPercent) - { - if (aPercent > 0.0f && aCurrent > 0) { - return MOZ_UNLIKELY(aPercent >= 1.0f) ? nscoord_MAX - : NSToCoordRound(float(aCurrent) / (1.0f - aPercent)); - } - return aCurrent; - } + static nscoord + MinSizeContributionForAxis(mozilla::PhysicalAxis aAxis, + nsRenderingContext* aRC, + nsIFrame* aFrame, + IntrinsicISizeType aType, + const LogicalSize& aPercentageBasis, + uint32_t aFlags = 0); /* * Convert nsStyleCoord to nscoord when percentages depend on the @@ -2876,6 +2865,62 @@ public: static nsRect ComputeGeometryBox(nsIFrame* aFrame, StyleGeometryBox aGeometryBox); + /** + * Resolve a CSS value to a definite size. + */ + template + static nscoord ResolveToLength(const nsStyleCoord& aCoord, + nscoord aPercentageBasis) + { + NS_WARNING_ASSERTION(aPercentageBasis >= nscoord(0), "nscoord overflow?"); + + switch (aCoord.GetUnit()) { + case eStyleUnit_Coord: + MOZ_ASSERT(!clampNegativeResultToZero || aCoord.GetCoordValue() >= 0, + "This value should have been rejected by the style system"); + return aCoord.GetCoordValue(); + case eStyleUnit_Percent: + if (aPercentageBasis == NS_UNCONSTRAINEDSIZE) { + return nscoord(0); + } + MOZ_ASSERT(!clampNegativeResultToZero || aCoord.GetPercentValue() >= 0, + "This value should have been rejected by the style system"); + return NSToCoordFloorClamped(aPercentageBasis * + aCoord.GetPercentValue()); + case eStyleUnit_Calc: { + nsStyleCoord::Calc* calc = aCoord.GetCalcValue(); + nscoord result; + if (aPercentageBasis == NS_UNCONSTRAINEDSIZE) { + result = calc->mLength; + } else { + result = calc->mLength + + NSToCoordFloorClamped(aPercentageBasis * calc->mPercent); + } + if (clampNegativeResultToZero && result < 0) { + return nscoord(0); + } + return result; + } + default: + MOZ_ASSERT_UNREACHABLE("Unexpected unit!"); + return nscoord(0); + } + } + + /** + * Resolve a column-gap/row-gap to a definite size. + * @note This method resolves 'normal' to zero. + * Callers who want different behavior should handle 'normal' on their own. + */ + static nscoord ResolveGapToLength(const nsStyleCoord& aGap, + nscoord aPercentageBasis) + { + if (aGap.GetUnit() == eStyleUnit_Normal) { + return nscoord(0); + } + return ResolveToLength(aGap, aPercentageBasis); + } + private: static uint32_t sFontSizeInflationEmPerLine; static uint32_t sFontSizeInflationMinTwips; diff --git a/layout/generic/nsColumnSetFrame.cpp b/layout/generic/nsColumnSetFrame.cpp index ad36ba1a8..6ea15d4d2 100644 --- a/layout/generic/nsColumnSetFrame.cpp +++ b/layout/generic/nsColumnSetFrame.cpp @@ -183,18 +183,15 @@ nsColumnSetFrame::GetAvailableContentBSize(const ReflowInput& aReflowInput) static nscoord GetColumnGap(nsColumnSetFrame* aFrame, - const nsStyleColumn* aColStyle) + const nsStyleColumn* aColStyle, + nscoord aPercentageBasis) { - if (eStyleUnit_Normal == aColStyle->mColumnGap.GetUnit()) + const auto& columnGap = aColStyle->mColumnGap; + if (columnGap.GetUnit() == eStyleUnit_Normal) { return aFrame->StyleFont()->mFont.size; - if (eStyleUnit_Coord == aColStyle->mColumnGap.GetUnit()) { - nscoord colGap = aColStyle->mColumnGap.GetCoordValue(); - NS_ASSERTION(colGap >= 0, "negative column gap"); - return colGap; } - NS_NOTREACHED("Unknown gap type"); - return 0; + return nsLayoutUtils::ResolveGapToLength(columnGap, aPercentageBasis); } nsColumnSetFrame::ReflowConfig @@ -227,7 +224,7 @@ nsColumnSetFrame::ChooseColumnStrategy(const ReflowInput& aReflowInput, colBSize = std::min(colBSize, aReflowInput.ComputedMaxBSize()); } - nscoord colGap = GetColumnGap(this, colStyle); + nscoord colGap = GetColumnGap(this, colStyle, aReflowInput.ComputedISize()); int32_t numColumns = colStyle->mColumnCount; // If column-fill is set to 'balance', then we want to balance the columns. @@ -403,7 +400,7 @@ nsColumnSetFrame::GetMinISize(nsRenderingContext *aRenderingContext) // include n-1 column gaps. colISize = iSize; iSize *= colStyle->mColumnCount; - nscoord colGap = GetColumnGap(this, colStyle); + nscoord colGap = GetColumnGap(this, colStyle, NS_UNCONSTRAINEDSIZE); iSize += colGap * (colStyle->mColumnCount - 1); // The multiplication above can make 'width' negative (integer overflow), // so use std::max to protect against that. @@ -424,7 +421,7 @@ nsColumnSetFrame::GetPrefISize(nsRenderingContext *aRenderingContext) nscoord result = 0; DISPLAY_PREF_WIDTH(this, result); const nsStyleColumn* colStyle = StyleColumn(); - nscoord colGap = GetColumnGap(this, colStyle); + nscoord colGap = GetColumnGap(this, colStyle, NS_UNCONSTRAINEDSIZE); nscoord colISize; if (colStyle->mColumnWidth.GetUnit() == eStyleUnit_Coord) { diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index bd96f213b..a531dea07 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -4515,68 +4515,44 @@ nsIFrame::InlinePrefISizeData::ForceBreak() mSkipWhitespace = true; } -static void -AddCoord(const nsStyleCoord& aStyle, - nsIFrame* aFrame, - nscoord* aCoord, float* aPercent, - bool aClampNegativeToZero) -{ - switch (aStyle.GetUnit()) { - case eStyleUnit_Coord: { - NS_ASSERTION(!aClampNegativeToZero || aStyle.GetCoordValue() >= 0, - "unexpected negative value"); - *aCoord += aStyle.GetCoordValue(); - return; - } - case eStyleUnit_Percent: { - NS_ASSERTION(!aClampNegativeToZero || aStyle.GetPercentValue() >= 0.0f, - "unexpected negative value"); - *aPercent += aStyle.GetPercentValue(); - return; - } - case eStyleUnit_Calc: { - const nsStyleCoord::Calc *calc = aStyle.GetCalcValue(); - if (aClampNegativeToZero) { - // This is far from ideal when one is negative and one is positive. - *aCoord += std::max(calc->mLength, 0); - *aPercent += std::max(calc->mPercent, 0.0f); - } else { - *aCoord += calc->mLength; - *aPercent += calc->mPercent; - } - return; - } - default: { - return; - } +static nscoord +ResolveMargin(const nsStyleCoord& aStyle, nscoord aPercentageBasis) +{ + if (aStyle.GetUnit() == eStyleUnit_Auto) { + return nscoord(0); } + return nsLayoutUtils::ResolveToLength(aStyle, aPercentageBasis); +} + +static nscoord +ResolvePadding(const nsStyleCoord& aStyle, nscoord aPercentageBasis) +{ + return nsLayoutUtils::ResolveToLength(aStyle, aPercentageBasis); } static nsIFrame::IntrinsicISizeOffsetData -IntrinsicSizeOffsets(nsIFrame* aFrame, bool aForISize) +IntrinsicSizeOffsets(nsIFrame* aFrame, nscoord aPercentageBasis, bool aForISize) { nsIFrame::IntrinsicISizeOffsetData result; WritingMode wm = aFrame->GetWritingMode(); - const nsStyleMargin* styleMargin = aFrame->StyleMargin(); + const auto& margin = aFrame->StyleMargin()->mMargin; bool verticalAxis = aForISize == wm.IsVertical(); - AddCoord(verticalAxis ? styleMargin->mMargin.GetTop() - : styleMargin->mMargin.GetLeft(), - aFrame, &result.hMargin, &result.hPctMargin, - false); - AddCoord(verticalAxis ? styleMargin->mMargin.GetBottom() - : styleMargin->mMargin.GetRight(), - aFrame, &result.hMargin, &result.hPctMargin, - false); - - const nsStylePadding* stylePadding = aFrame->StylePadding(); - AddCoord(verticalAxis ? stylePadding->mPadding.GetTop() - : stylePadding->mPadding.GetLeft(), - aFrame, &result.hPadding, &result.hPctPadding, - true); - AddCoord(verticalAxis ? stylePadding->mPadding.GetBottom() - : stylePadding->mPadding.GetRight(), - aFrame, &result.hPadding, &result.hPctPadding, - true); + if (verticalAxis) { + result.hMargin += ResolveMargin(margin.GetTop(), aPercentageBasis); + result.hMargin += ResolveMargin(margin.GetBottom(), aPercentageBasis); + } else { + result.hMargin += ResolveMargin(margin.GetLeft(), aPercentageBasis); + result.hMargin += ResolveMargin(margin.GetRight(), aPercentageBasis); + } + + const auto& padding = aFrame->StylePadding()->mPadding; + if (verticalAxis) { + result.hPadding += ResolvePadding(padding.GetTop(), aPercentageBasis); + result.hPadding += ResolvePadding(padding.GetBottom(), aPercentageBasis); + } else { + result.hPadding += ResolvePadding(padding.GetLeft(), aPercentageBasis); + result.hPadding += ResolvePadding(padding.GetRight(), aPercentageBasis); + } const nsStyleBorder* styleBorder = aFrame->StyleBorder(); if (verticalAxis) { @@ -4606,22 +4582,21 @@ IntrinsicSizeOffsets(nsIFrame* aFrame, bool aForISize) result.hPadding = presContext->DevPixelsToAppUnits(verticalAxis ? padding.TopBottom() : padding.LeftRight()); - result.hPctPadding = 0; } } return result; } /* virtual */ nsIFrame::IntrinsicISizeOffsetData -nsFrame::IntrinsicISizeOffsets() +nsFrame::IntrinsicISizeOffsets(nscoord aPercentageBasis) { - return IntrinsicSizeOffsets(this, true); + return IntrinsicSizeOffsets(this, aPercentageBasis, true); } nsIFrame::IntrinsicISizeOffsetData -nsIFrame::IntrinsicBSizeOffsets() +nsIFrame::IntrinsicBSizeOffsets(nscoord aPercentageBasis) { - return IntrinsicSizeOffsets(this, false); + return IntrinsicSizeOffsets(this, aPercentageBasis, false); } /* virtual */ IntrinsicSize diff --git a/layout/generic/nsFrame.h b/layout/generic/nsFrame.h index af1c95ef2..439e39856 100644 --- a/layout/generic/nsFrame.h +++ b/layout/generic/nsFrame.h @@ -261,7 +261,8 @@ public: InlineMinISizeData *aData) override; virtual void AddInlinePrefISize(nsRenderingContext *aRenderingContext, InlinePrefISizeData *aData) override; - virtual IntrinsicISizeOffsetData IntrinsicISizeOffsets() override; + IntrinsicISizeOffsetData + IntrinsicISizeOffsets(nscoord aPercentageBasis = NS_UNCONSTRAINEDSIZE) override; virtual mozilla::IntrinsicSize GetIntrinsicSize() override; virtual nsSize GetIntrinsicRatio() override; diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 054f01297..959061e33 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -8,8 +8,8 @@ #include "nsGridContainerFrame.h" -#include // for std::stable_sort #include +#include // for div() #include "mozilla/CSSAlignUtils.h" #include "mozilla/dom/GridBinding.h" #include "mozilla/Function.h" @@ -127,43 +127,6 @@ ResolveToDefiniteSize(const nsStyleCoord& aCoord, nscoord aPercentBasis) nsRuleNode::ComputeCoordPercentCalc(aCoord, aPercentBasis)); } -static bool -GetPercentSizeParts(const nsStyleCoord& aCoord, nscoord* aLength, float* aPercent) -{ - switch (aCoord.GetUnit()) { - case eStyleUnit_Percent: - *aLength = 0; - *aPercent = aCoord.GetPercentValue(); - return true; - case eStyleUnit_Calc: { - nsStyleCoord::Calc* calc = aCoord.GetCalcValue(); - *aLength = calc->mLength; - *aPercent = calc->mPercent; - return true; - } - default: - return false; - } -} - -static void -ResolvePercentSizeParts(const nsStyleCoord& aCoord, nscoord aPercentBasis, - nscoord* aLength, float* aPercent) -{ - MOZ_ASSERT(aCoord.IsCoordPercentCalcUnit()); - if (aPercentBasis != NS_UNCONSTRAINEDSIZE) { - *aLength = std::max(nscoord(0), - nsRuleNode::ComputeCoordPercentCalc(aCoord, - aPercentBasis)); - *aPercent = 0.0f; - return; - } - if (!GetPercentSizeParts(aCoord, aLength, aPercent)) { - *aLength = aCoord.ToLength(); - *aPercent = 0.0f; - } -} - // Synthesize a baseline from a border box. For an alphabetical baseline // this is the end edge of the border box. For a central baseline it's // the center of the border box. @@ -1172,7 +1135,7 @@ struct nsGridContainerFrame::TrackSizingFunctions return 1; } nscoord repeatTrackSize = 0; - // Note that the repeat() track size is included in |sum| in this loop. + // Note that one repeat() track size is included in |sum| in this loop. nscoord sum = 0; const nscoord percentBasis = aSize; for (uint32_t i = 0; i < numTracks; ++i) { @@ -1189,54 +1152,31 @@ struct nsGridContainerFrame::TrackSizingFunctions } nscoord trackSize = ::ResolveToDefiniteSize(*coord, percentBasis); if (i == mRepeatAutoStart) { - if (percentBasis != NS_UNCONSTRAINEDSIZE) { - // Use a minimum 1px for the repeat() track-size. - if (trackSize < AppUnitsPerCSSPixel()) { - trackSize = AppUnitsPerCSSPixel(); - } + // Use a minimum 1px for the repeat() track-size. + if (trackSize < AppUnitsPerCSSPixel()) { + trackSize = AppUnitsPerCSSPixel(); } repeatTrackSize = trackSize; } sum += trackSize; } - nscoord gridGap; - float percentSum = 0.0f; - float gridGapPercent; - ResolvePercentSizeParts(aGridGap, percentBasis, &gridGap, &gridGapPercent); + nscoord gridGap = nsLayoutUtils::ResolveGapToLength(aGridGap, aSize); if (numTracks > 1) { // Add grid-gaps for all the tracks including the repeat() track. sum += gridGap * (numTracks - 1); - percentSum = gridGapPercent * (numTracks - 1); } // Calculate the max number of tracks that fits without overflow. nscoord available = maxFill != NS_UNCONSTRAINEDSIZE ? maxFill : aMinSize; - nscoord size = nsLayoutUtils::AddPercents(sum, percentSum); - if (available - size < 0) { + nscoord spaceToFill = available - sum; + if (spaceToFill <= 0) { // "if any number of repetitions would overflow, then 1 repetition" return 1; } - uint32_t numRepeatTracks = 1; - bool exactFit = false; - while (true) { - sum += gridGap + repeatTrackSize; - percentSum += gridGapPercent; - nscoord newSize = nsLayoutUtils::AddPercents(sum, percentSum); - if (newSize <= size) { - // Adding more repeat-tracks won't make forward progress. - return numRepeatTracks; - } - size = newSize; - nscoord remaining = available - size; - exactFit = remaining == 0; - if (remaining >= 0) { - ++numRepeatTracks; - } - if (remaining <= 0) { - break; - } - } - - if (!exactFit && maxFill == NS_UNCONSTRAINEDSIZE) { + // Calculate the max number of tracks that fits without overflow. + div_t q = div(spaceToFill, repeatTrackSize + gridGap); + // The +1 here is for the one repeat track we already accounted for above. + uint32_t numRepeatTracks = q.quot + 1; + if (q.rem != 0 && maxFill == NS_UNCONSTRAINEDSIZE) { // "Otherwise, if the grid container has a definite min size in // the relevant axis, the number of repetitions is the largest possible // positive integer that fulfills that minimum requirement." @@ -1882,13 +1822,6 @@ struct nsGridContainerFrame::Tracks WritingMode aWM, const LogicalSize& aContainerSize); - /** - * Return the intrinsic size by back-computing percentages as: - * IntrinsicSize = SumOfCoordSizes / (1 - SumOfPercentages). - */ - nscoord BackComputedIntrinsicSize(const TrackSizingFunctions& aFunctions, - const nsStyleCoord& aGridGap) const; - nscoord GridLineEdge(uint32_t aLine, GridLineSide aSide) const { if (MOZ_UNLIKELY(mSizes.IsEmpty())) { @@ -2198,11 +2131,10 @@ struct MOZ_STACK_CLASS nsGridContainerFrame::GridReflowInput } /** - * Calculate our track sizes. If the given aContentBox block-axis size is - * unconstrained, it is assigned to the resulting intrinsic block-axis size. + * Calculate our track sizes. */ void CalculateTrackSizes(const Grid& aGrid, - LogicalSize& aContentBox, + const LogicalSize& aContentBox, SizingConstraint aConstraint); /** @@ -2688,7 +2620,7 @@ struct MOZ_STACK_CLASS nsGridContainerFrame::Grid void nsGridContainerFrame::GridReflowInput::CalculateTrackSizes( const Grid& aGrid, - LogicalSize& aContentBox, + const LogicalSize& aContentBox, SizingConstraint aConstraint) { mCols.Initialize(mColFunctions, mGridStyle->mGridColumnGap, @@ -2706,12 +2638,6 @@ nsGridContainerFrame::GridReflowInput::CalculateTrackSizes( mRows.CalculateSizes(*this, mGridItems, mRowFunctions, aContentBox.BSize(mWM), &GridArea::mRows, aConstraint); - if (aContentBox.BSize(mWM) == NS_AUTOHEIGHT) { - aContentBox.BSize(mWM) = - mRows.BackComputedIntrinsicSize(mRowFunctions, mGridStyle->mGridRowGap); - mRows.mGridGap = - ::ResolveToDefiniteSize(mGridStyle->mGridRowGap, aContentBox.BSize(mWM)); - } } /** @@ -3813,7 +3739,7 @@ nsGridContainerFrame::Tracks::Initialize( aFunctions.MinSizingFor(i), aFunctions.MaxSizingFor(i)); } - mGridGap = ::ResolveToDefiniteSize(aGridGap, aContentBoxSize); + mGridGap = nsLayoutUtils::ResolveGapToLength(aGridGap, aContentBoxSize); mContentBoxSize = aContentBoxSize; } @@ -3894,8 +3820,7 @@ ContentContribution(const GridItemInfo& aGridItem, PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis)); nscoord size = nsLayoutUtils::IntrinsicForAxis(axis, aRC, child, aConstraint, aPercentageBasis, - aFlags | nsLayoutUtils::BAIL_IF_REFLOW_NEEDED | - nsLayoutUtils::ADD_PERCENTS, + aFlags | nsLayoutUtils::BAIL_IF_REFLOW_NEEDED, aMinSizeClamp); if (size == NS_INTRINSIC_WIDTH_UNKNOWN) { // We need to reflow the child to find its BSize contribution. @@ -3932,15 +3857,7 @@ ContentContribution(const GridItemInfo& aGridItem, LogicalSize availableSize(childWM, availISize, availBSize); size = ::MeasuringReflow(child, aState.mReflowInput, aRC, availableSize, cbSize, iMinSizeClamp, bMinSizeClamp); - nsIFrame::IntrinsicISizeOffsetData offsets = child->IntrinsicBSizeOffsets(); - size += offsets.hMargin; - auto percent = offsets.hPctMargin; - if (availBSize == NS_UNCONSTRAINEDSIZE) { - // We always want to add in percent padding too, unless we already did so - // using a resolved column size above. - percent += offsets.hPctPadding; - } - size = nsLayoutUtils::AddPercents(size, percent); + size += child->GetLogicalUsedMargin(childWM).BStartEnd(childWM); nscoord overflow = size - aMinSizeClamp; if (MOZ_UNLIKELY(overflow > 0)) { nscoord contentSize = child->ContentBSize(childWM); @@ -4045,6 +3962,10 @@ MinSize(const GridItemInfo& aGridItem, return s; } + if (aCache->mPercentageBasis.isNothing()) { + aCache->mPercentageBasis.emplace(aState.PercentageBasisFor(aAxis, aGridItem)); + } + // https://drafts.csswg.org/css-grid/#min-size-auto // This calculates the min-content contribution from either a definite // min-width (or min-height depending on aAxis), or the "specified / @@ -4058,7 +3979,8 @@ MinSize(const GridItemInfo& aGridItem, "baseline offset should be zero when not baseline-aligned"); nscoord sz = aGridItem.mBaselineOffset[aAxis] + nsLayoutUtils::MinSizeContributionForAxis(axis, aRC, child, - nsLayoutUtils::MIN_ISIZE); + nsLayoutUtils::MIN_ISIZE, + *aCache->mPercentageBasis); const nsStyleCoord& style = axis == eAxisHorizontal ? stylePos->mMinWidth : stylePos->mMinHeight; auto unit = style.GetUnit(); @@ -4067,9 +3989,6 @@ MinSize(const GridItemInfo& aGridItem, child->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE)) { // Now calculate the "content size" part and return whichever is smaller. MOZ_ASSERT(unit != eStyleUnit_Enumerated || sz == NS_UNCONSTRAINEDSIZE); - if (aCache->mPercentageBasis.isNothing()) { - aCache->mPercentageBasis.emplace(aState.PercentageBasisFor(aAxis, aGridItem)); - } sz = std::min(sz, ContentContribution(aGridItem, aState, aRC, aCBWM, aAxis, aCache->mPercentageBasis, nsLayoutUtils::MIN_ISIZE, @@ -5097,36 +5016,6 @@ nsGridContainerFrame::Tracks::AlignJustifyContent( MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?"); } -nscoord -nsGridContainerFrame::Tracks::BackComputedIntrinsicSize( - const TrackSizingFunctions& aFunctions, - const nsStyleCoord& aGridGap) const -{ - // Sum up the current sizes (where percentage tracks were treated as 'auto') - // in 'size'. - nscoord size = 0; - for (size_t i = 0, len = mSizes.Length(); i < len; ++i) { - size += mSizes[i].mBase; - } - - // Add grid-gap contributions to 'size' and calculate a 'percent' sum. - float percent = 0.0f; - size_t numTracks = mSizes.Length(); - if (numTracks > 1) { - const size_t gridGapCount = numTracks - 1; - nscoord gridGapLength; - float gridGapPercent; - if (::GetPercentSizeParts(aGridGap, &gridGapLength, &gridGapPercent)) { - percent = gridGapCount * gridGapPercent; - } else { - gridGapLength = aGridGap.ToLength(); - } - size += gridGapCount * gridGapLength; - } - - return std::max(0, nsLayoutUtils::AddPercents(size, percent)); -} - void nsGridContainerFrame::LineRange::ToPositionAndLength( const nsTArray& aTrackSizes, nscoord* aPos, nscoord* aLength) const @@ -6292,7 +6181,7 @@ nsGridContainerFrame::Reflow(nsPresContext* aPresContext, LogicalSize computedSize(wm, computedISize, computedBSize); nscoord consumedBSize = 0; - nscoord bSize; + nscoord bSize = 0; if (!prevInFlow) { Grid grid; grid.PlaceGridItems(gridReflowInput, aReflowInput.ComputedMinSize(), @@ -6300,7 +6189,12 @@ nsGridContainerFrame::Reflow(nsPresContext* aPresContext, gridReflowInput.CalculateTrackSizes(grid, computedSize, SizingConstraint::eNoConstraint); - bSize = computedSize.BSize(wm); + // Note: we can't use GridLineEdge here since we haven't calculated + // the rows' mPosition yet (happens in AlignJustifyContent below). + for (const auto& sz : gridReflowInput.mRows.mSizes) { + bSize += sz.mBase; + } + bSize += gridReflowInput.mRows.SumOfGridGaps(); } else { consumedBSize = GetConsumedBSize(); gridReflowInput.InitializeForContinuation(this, consumedBSize); @@ -6699,8 +6593,14 @@ nsGridContainerFrame::IntrinsicISize(nsRenderingContext* aRenderingContext, state.mCols.CalculateSizes(state, state.mGridItems, state.mColFunctions, NS_UNCONSTRAINEDSIZE, &GridArea::mCols, constraint); - return state.mCols.BackComputedIntrinsicSize(state.mColFunctions, - state.mGridStyle->mGridColumnGap); + state.mCols.mGridGap = + nsLayoutUtils::ResolveGapToLength(state.mGridStyle->mGridColumnGap, + NS_UNCONSTRAINEDSIZE); + nscoord length = 0; + for (const TrackSize& sz : state.mCols.mSizes) { + length += sz.mBase; + } + return length + state.mCols.SumOfGridGaps(); } nscoord diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index ec3568483..57f5c460c 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -25,6 +25,7 @@ #include "CaretAssociationHint.h" #include "FrameProperties.h" +#include "LayoutConstants.h" #include "mozilla/layout/FrameChildList.h" #include "mozilla/Maybe.h" #include "mozilla/WritingModes.h" @@ -130,30 +131,12 @@ typedef uint32_t nsSplittableType; #define NS_FRAME_IS_NOT_SPLITTABLE(type)\ (0 == ((type) & NS_FRAME_SPLITTABLE)) -#define NS_INTRINSIC_WIDTH_UNKNOWN nscoord_MIN - //---------------------------------------------------------------------- #define NS_SUBTREE_DIRTY(_frame) \ (((_frame)->GetStateBits() & \ (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) != 0) -/** - * Constant used to indicate an unconstrained size. - * - * @see #Reflow() - */ -#define NS_UNCONSTRAINEDSIZE NS_MAXSIZE - -#define NS_INTRINSICSIZE NS_UNCONSTRAINEDSIZE -#define NS_AUTOHEIGHT NS_UNCONSTRAINEDSIZE -// +1 is to avoid clamped huge margin values being processed as auto margins -#define NS_AUTOMARGIN (NS_UNCONSTRAINEDSIZE + 1) -#define NS_AUTOOFFSET NS_UNCONSTRAINEDSIZE -// NOTE: there are assumptions all over that these have the same value, namely NS_UNCONSTRAINEDSIZE -// if any are changed to be a value other than NS_UNCONSTRAINEDSIZE -// at least update AdjustComputedHeight/Width and test ad nauseum - // 1 million CSS pixels less than our max app unit measure. // For reflowing with an "infinite" available inline space per [css-sizing]. // (reflowing with an NS_UNCONSTRAINEDSIZE available inline size isn't allowed @@ -2050,23 +2033,27 @@ public: /** * Return the horizontal components of padding, border, and margin * that contribute to the intrinsic width that applies to the parent. + * @param aPercentageBasis the percentage basis to use for padding/margin - + * i.e. the Containing Block's inline-size */ struct IntrinsicISizeOffsetData { nscoord hPadding, hBorder, hMargin; - float hPctPadding, hPctMargin; IntrinsicISizeOffsetData() : hPadding(0), hBorder(0), hMargin(0) - , hPctPadding(0.0f), hPctMargin(0.0f) {} }; - virtual IntrinsicISizeOffsetData IntrinsicISizeOffsets() = 0; + virtual IntrinsicISizeOffsetData + IntrinsicISizeOffsets(nscoord aPercentageBasis = NS_UNCONSTRAINEDSIZE) = 0; /** * Return the bsize components of padding, border, and margin * that contribute to the intrinsic width that applies to the parent. + * @param aPercentageBasis the percentage basis to use for padding/margin - + * i.e. the Containing Block's inline-size */ - IntrinsicISizeOffsetData IntrinsicBSizeOffsets(); + IntrinsicISizeOffsetData + IntrinsicBSizeOffsets(nscoord aPercentageBasis = NS_UNCONSTRAINEDSIZE); virtual mozilla::IntrinsicSize GetIntrinsicSize() = 0; diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index 2049f70e8..07db6d3dd 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -1499,7 +1499,7 @@ CSS_PROP_COLUMN( CSS_PROPERTY_PARSE_VALUE | CSS_PROPERTY_VALUE_NONNEGATIVE, "", - VARIANT_HL | VARIANT_NORMAL | VARIANT_CALC, + VARIANT_HLP | VARIANT_NORMAL | VARIANT_CALC, nullptr, offsetof(nsStyleColumn, mColumnGap), eStyleAnimType_Coord) diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index c8182b8f1..b257c6bb5 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -3495,7 +3495,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleColumn uint32_t mColumnCount; // [reset] see nsStyleConsts.h nsStyleCoord mColumnWidth; // [reset] coord, auto - nsStyleCoord mColumnGap; // [reset] coord, normal + nsStyleCoord mColumnGap; // [reset] | normal mozilla::StyleComplexColor mColumnRuleColor; // [reset] uint8_t mColumnRuleStyle; // [reset] diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 272931c15..c75f7b498 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -1438,7 +1438,7 @@ var gCSSProperties = { inherited: false, type: CSS_TYPE_LONGHAND, initial_values: [ "normal", "1em", "calc(-2em + 3em)" ], - other_values: [ "2px", "4em", + other_values: [ "2px", "1em", "4em", "3%", "calc(3%)", "calc(1em - 3%)", "calc(2px)", "calc(-2px)", "calc(0px)", @@ -1448,7 +1448,7 @@ var gCSSProperties = { "calc(25px*3)", "calc(3*25px + 5em)", ], - invalid_values: [ "3%", "-1px", "4" ] + invalid_values: [ "-3%", "-1px", "4" ] }, "-moz-column-gap": { domProp: "MozColumnGap", diff --git a/layout/tables/nsTableCellFrame.cpp b/layout/tables/nsTableCellFrame.cpp index 316a96613..dea82ea59 100644 --- a/layout/tables/nsTableCellFrame.cpp +++ b/layout/tables/nsTableCellFrame.cpp @@ -796,12 +796,12 @@ nsTableCellFrame::GetPrefISize(nsRenderingContext *aRenderingContext) } /* virtual */ nsIFrame::IntrinsicISizeOffsetData -nsTableCellFrame::IntrinsicISizeOffsets() +nsTableCellFrame::IntrinsicISizeOffsets(nscoord aPercentageBasis) { - IntrinsicISizeOffsetData result = nsContainerFrame::IntrinsicISizeOffsets(); + IntrinsicISizeOffsetData result = + nsContainerFrame::IntrinsicISizeOffsets(aPercentageBasis); result.hMargin = 0; - result.hPctMargin = 0; WritingMode wm = GetWritingMode(); result.hBorder = GetBorderWidth(wm).IStartEnd(wm); diff --git a/layout/tables/nsTableCellFrame.h b/layout/tables/nsTableCellFrame.h index 6717e1b70..240809850 100644 --- a/layout/tables/nsTableCellFrame.h +++ b/layout/tables/nsTableCellFrame.h @@ -118,7 +118,8 @@ public: virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override; virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override; - virtual IntrinsicISizeOffsetData IntrinsicISizeOffsets() override; + IntrinsicISizeOffsetData IntrinsicISizeOffsets(nscoord aPercentageBasis = + NS_UNCONSTRAINEDSIZE) override; virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, diff --git a/layout/tables/nsTableFrame.cpp b/layout/tables/nsTableFrame.cpp index b9b6ca5fe..272a77406 100644 --- a/layout/tables/nsTableFrame.cpp +++ b/layout/tables/nsTableFrame.cpp @@ -1564,16 +1564,15 @@ nsTableFrame::GetPrefISize(nsRenderingContext *aRenderingContext) } /* virtual */ nsIFrame::IntrinsicISizeOffsetData -nsTableFrame::IntrinsicISizeOffsets() +nsTableFrame::IntrinsicISizeOffsets(nscoord aPercentageBasis) { - IntrinsicISizeOffsetData result = nsContainerFrame::IntrinsicISizeOffsets(); + IntrinsicISizeOffsetData result = + nsContainerFrame::IntrinsicISizeOffsets(aPercentageBasis); result.hMargin = 0; - result.hPctMargin = 0; if (IsBorderCollapse()) { result.hPadding = 0; - result.hPctPadding = 0; WritingMode wm = GetWritingMode(); LogicalMargin outerBC = GetIncludedOuterBCBorder(wm); diff --git a/layout/tables/nsTableFrame.h b/layout/tables/nsTableFrame.h index 7e56c28c1..c7b92d387 100644 --- a/layout/tables/nsTableFrame.h +++ b/layout/tables/nsTableFrame.h @@ -324,7 +324,8 @@ public: // border to the results of these functions. virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override; virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override; - virtual IntrinsicISizeOffsetData IntrinsicISizeOffsets() override; + IntrinsicISizeOffsetData IntrinsicISizeOffsets(nscoord aPercentageBasis = + NS_UNCONSTRAINEDSIZE) override; virtual mozilla::LogicalSize ComputeSize(nsRenderingContext* aRenderingContext, -- cgit v1.2.3 From b0ad7679b2fc578b195f8628f78a9799bbf70ab1 Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Sat, 28 Sep 2019 23:38:15 -0400 Subject: Issue #1230 - Part 2: Align devtools to the changes in css-grid Ref: 1398537 part 4 - [css-multicol] Implement percentages for 'column-gap' (automated update of devtools). --- devtools/shared/css/generated/properties-db.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js index 070167496..25d9e2d33 100644 --- a/devtools/shared/css/generated/properties-db.js +++ b/devtools/shared/css/generated/properties-db.js @@ -779,7 +779,8 @@ exports.CSS_PROPERTIES = { "column-gap" ], "supports": [ - 6 + 6, + 8 ], "values": [ "-moz-calc", @@ -5434,7 +5435,8 @@ exports.CSS_PROPERTIES = { "column-gap" ], "supports": [ - 6 + 6, + 8 ], "values": [ "-moz-calc", -- cgit v1.2.3 From 30d65c382f9404a31c46c9976a3b170273fd9509 Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Sat, 28 Sep 2019 23:43:36 -0400 Subject: Issue #1230 - Part 3: Update Reftests Ref: 1434478 part 7 - Update tests and enable some previously temporarily disabled Grid reftests from bug 1427608. --- layout/reftests/bugs/403519-2-ref.html | 2 +- .../grid-auto-min-sizing-definite-001-ref.html | 51 +++++++++----- ...to-min-sizing-min-content-min-size-002-ref.html | 2 +- ...to-min-sizing-min-content-min-size-004-ref.html | 2 +- .../grid-auto-min-sizing-percent-001-ref.html | 10 +-- ...d-auto-min-sizing-transferred-size-002-ref.html | 2 +- ...d-auto-min-sizing-transferred-size-004-ref.html | 2 +- .../css-grid/grid-item-sizing-percent-003-ref.html | 78 +++++++++++----------- .../css-grid/grid-item-sizing-percent-004-ref.html | 78 +++++++++++----------- .../css-grid/grid-percent-grid-gap-001-ref.html | 16 ++--- .../grid-repeat-auto-fill-fit-002-ref.html | 6 +- .../writing-mode/1174450-intrinsic-sizing-ref.html | 16 ++--- 12 files changed, 143 insertions(+), 122 deletions(-) diff --git a/layout/reftests/bugs/403519-2-ref.html b/layout/reftests/bugs/403519-2-ref.html index e00fb5e24..72176a89a 100644 --- a/layout/reftests/bugs/403519-2-ref.html +++ b/layout/reftests/bugs/403519-2-ref.html @@ -17,7 +17,7 @@ table { - + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html index 8858b4ea8..f2c76f78b 100644 --- a/layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html +++ b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html @@ -65,32 +65,49 @@ b40 { .h.r { height: 42px; width: 42px; - /* This margin-left is 20% of 98px-wide grid area */ - margin-left: 19.6px; - /* This padding-bottom is 10% of 98px wide grid area */ - /* This padding-left is 30% of 98px wide grid area */ - padding: 1px 3px 9.8px 29.4px; + /* 49px is the percentage basis, i.e. the column size */ + margin-left: calc(0.2 * 49px); + padding: 1px 3px calc(0.1 * 49px) calc(0.3 * 49px); } .v.r { height: 42px; width: 42px; - /* This margin-left is 20% of 54px-wide grid area */ - margin-left: 10.8px; - /* This padding-bottom is 10% of 54px wide grid area */ - /* This padding-left is 30% of 54px wide grid area */ - padding: 1px 3px 5.4px 16.2px; + /* 27px is the percentage basis, i.e. the column size */ + margin-left: calc(0.2 * 27px); + padding: 1px 3px calc(0.1 * 27px) calc(0.3 * 27px); } .r { position:relative; } +.t4 { width: 49px; + height: calc(10px /* item min-height */ + + 7px /* item margin-top */ + + 1px /* item padding-top */ + + 1px /* item border-top */ + + calc(0.5 * 49px) /* item margin-bottom */ + + calc(0.1 * 49px) /* item padding-bottom */); + } + .t6 { width:46px; } -.t8 { width:118px; height: 102.5px; } +.t8 { width: 27px; + height: calc(30px /* item min-height */ + + 7px /* item margin-top */ + + 3px /* item padding-top */ + + 1px /* item border-top */ + + calc(0.5 * 27px) /* item margin-bottom */ + + calc(0.1 * 27px) /* item padding-bottom */); + } xx { display: block; background: lime; - padding:32px 32px 16px 32px; - margin: 0 0 32px 16px; + padding: 32px 32px calc(0.2 * 32px) calc(0.4 * 32px); + margin: 0 0 calc(0.4 * 32px) calc(0.2 * 32px); +} +.t9, .t10 { + position: relative; + z-index: 1; + grid: calc(32px + calc(0.4 * 32px) + calc(0.2 * 32px)) 0 / 32px; } @@ -100,15 +117,15 @@ xx {
-
+

-
+
-
-
+
+
diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-min-content-min-size-002-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-min-content-min-size-002-ref.html index 35f596928..183f00e24 100644 --- a/layout/reftests/css-grid/grid-auto-min-sizing-min-content-min-size-002-ref.html +++ b/layout/reftests/css-grid/grid-auto-min-sizing-min-content-min-size-002-ref.html @@ -36,7 +36,7 @@ var coltest = [ "min-height:40%", "min-height:40%; max-width:30px" ]; var results = [ -"360px", "360px", "360px", "24px", "24px", "360px", "80px", "24px", "24px", "24px", +"24px", "24px", "24px", "24px", "24px", "24px", "80px", "24px", "24px", "24px", "24px", "24px", "24px" ]; var item_width = [ diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-min-content-min-size-004-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-min-content-min-size-004-ref.html index caef8b031..6533c97b6 100644 --- a/layout/reftests/css-grid/grid-auto-min-sizing-min-content-min-size-004-ref.html +++ b/layout/reftests/css-grid/grid-auto-min-sizing-min-content-min-size-004-ref.html @@ -36,7 +36,7 @@ var rowtest = [ "min-width:80%; max-height:20px", "min-width:50%", "margin-left: 50px; width:50%" ]; var results = [ -"0/2px", "0/2px", "0/4px", "0/2px", "0/2px", "0/2px", "12px/2px", "20px/2px", "20px/2px", "24px/2px", "24px/52px" +"0/2px", "0/2px", "0/4px", "0/2px", "0/2px", "0/2px", "12px/2px", "20px/2px", "20px/2px", "24px/2px", "312px/52px" ]; var item_height = [ "0", "0", "0", "0", "0", "0", "12px", "20px", "20px", "24px", "312px" diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-percent-001-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-percent-001-ref.html index fd2302add..d435f8f3e 100644 --- a/layout/reftests/css-grid/grid-auto-min-sizing-percent-001-ref.html +++ b/layout/reftests/css-grid/grid-auto-min-sizing-percent-001-ref.html @@ -54,9 +54,9 @@ br { clear:both; } .c10 { grid-template-columns: minmax(10px,0) 1fr; } #px-border .c10 { grid-template-columns: minmax(30px,0) 1fr; } -#percent-border .c100 { grid-template-columns: 111px 0; } -#percent-border .c100calc100 { grid-template-columns: 100px 11px; } -#percent-border .c10 { grid-template-columns: minmax(11px,0) 0; } +#percent-border .c100 { grid-template-columns: 100px 0; } +#percent-border .c100calc100 { grid-template-columns: 100px 10px; } +#percent-border .c10 { grid-template-columns: minmax(10px,0) 0; } #percent-border .c100100 { grid-template-columns: minmax(100px,0) 150px; } #percent-border .c200 { grid-template-columns: 250px; } @@ -99,7 +99,7 @@ var grids = [ "grid c100", "grid c100", "grid", -"grid c200", +"grid c100", "grid c10", "grid c100", "grid c100", @@ -110,7 +110,7 @@ var grids = [ "grid c100", "grid c100", "grid", -"grid c200", +"grid c100", "grid c10", "grid c100", "grid c100", diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-transferred-size-002-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-transferred-size-002-ref.html index 8dcdd563b..528d63bc7 100644 --- a/layout/reftests/css-grid/grid-auto-min-sizing-transferred-size-002-ref.html +++ b/layout/reftests/css-grid/grid-auto-min-sizing-transferred-size-002-ref.html @@ -36,7 +36,7 @@ var coltest = [ "min-height:40%", "min-height:40%; max-width:30px" ]; var results = [ -"360px", "360px", "360px", "24px", "24px", "360px", "80px", "24px", "20px", "24px", +"360px", "0px", "0px", "0px", "24px", "360px", "80px", "24px", "20px", "24px", "6px", "24px", "24px" ]; var item_width = [ diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-transferred-size-004-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-transferred-size-004-ref.html index 36a2d4920..4eb623b7d 100644 --- a/layout/reftests/css-grid/grid-auto-min-sizing-transferred-size-004-ref.html +++ b/layout/reftests/css-grid/grid-auto-min-sizing-transferred-size-004-ref.html @@ -36,7 +36,7 @@ var rowtest = [ "min-width:80%; max-height:20px", "min-width:50%", "margin-left: 50px; width:50%" ]; var results = [ -"0/2px", "0/2px", "0/4px", "0/2px", "0/2px", "0/2px", "12px/2px", "20px/2px", "20px/2px", "24px/2px", "24px/52px" +"0/2px", "0/2px", "0/4px", "0/2px", "0/2px", "0/2px", "12px/2px", "20px/2px", "20px/2px", "24px/2px", "312px/52px" ]; var item_height = [ "0", "0", "0", "0", "0", "0", "12px", "20px", "20px", "24px", "312px" diff --git a/layout/reftests/css-grid/grid-item-sizing-percent-003-ref.html b/layout/reftests/css-grid/grid-item-sizing-percent-003-ref.html index fb4d15ff8..a55dcc989 100644 --- a/layout/reftests/css-grid/grid-item-sizing-percent-003-ref.html +++ b/layout/reftests/css-grid/grid-item-sizing-percent-003-ref.html @@ -69,67 +69,67 @@ a {
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+

-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/layout/reftests/css-grid/grid-item-sizing-percent-004-ref.html b/layout/reftests/css-grid/grid-item-sizing-percent-004-ref.html index 51f71e662..96365b468 100644 --- a/layout/reftests/css-grid/grid-item-sizing-percent-004-ref.html +++ b/layout/reftests/css-grid/grid-item-sizing-percent-004-ref.html @@ -62,71 +62,71 @@ a {
-
-
-
-
+
+
+
+
-
-
-
+
+
+

-
-
-
-
+
+
+
+
-
-
-
+
+
+

-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+

-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/layout/reftests/css-grid/grid-percent-grid-gap-001-ref.html b/layout/reftests/css-grid/grid-percent-grid-gap-001-ref.html index 945b43b52..7ad85e1e5 100644 --- a/layout/reftests/css-grid/grid-percent-grid-gap-001-ref.html +++ b/layout/reftests/css-grid/grid-percent-grid-gap-001-ref.html @@ -65,7 +65,7 @@ br { clear: both; }
-
+
@@ -74,7 +74,7 @@ br { clear: both; }
-
+
@@ -141,12 +141,12 @@ br { clear: both; }
-
-
- - - - +
+
+ + + +
diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-002-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-002-ref.html index a404a0905..682e0ca38 100644 --- a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-002-ref.html +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-002-ref.html @@ -83,12 +83,16 @@ fill,fit { .zero-progress { grid-row-gap: calc(10px - 1%); - grid-template-rows: [a] 10px repeat(4, [b] minmax(0,auto) [c]) [d]; + grid-template-rows: [a] 10px repeat(3, [b] 0 [c]) [d]; } .w50.zero-progress { grid-row-gap: calc(10px - 1%); grid-template-rows: [a] 10px repeat(3, [b] 0 [c]) [d]; } +.mw50.zero-progress { + grid-row-gap: calc(10px - 1%); + grid-template-rows: [a] 10px repeat(4, [b] 0 [c]) [d]; +} diff --git a/layout/reftests/writing-mode/1174450-intrinsic-sizing-ref.html b/layout/reftests/writing-mode/1174450-intrinsic-sizing-ref.html index 629c0a917..3645fa006 100644 --- a/layout/reftests/writing-mode/1174450-intrinsic-sizing-ref.html +++ b/layout/reftests/writing-mode/1174450-intrinsic-sizing-ref.html @@ -13,20 +13,20 @@ div.v, div.h { display: block; position: relative; border: 1px dashed silver; - width:92px; - height:60px; + width:74px; + height:24px; } div.h { - width:124px; - height:98px; + width:62px; + height:61.2px; } .h span { - margin: 7px 13px 62px 25px; - padding: 1px 3px 12px 37px; + margin: 7px 13px 32px 12px; + padding: 1px 3px 6px 19px; } .v span { - margin: 7px 13px 30px 12px; - padding: 1px 3px 6px 18px; + margin: 7px 13px 30px 5px; + padding: 1px 3px 2px 7px; } span { -- cgit v1.2.3 From 924693bdb01ec7e8053317cd45e8d4215ddbc320 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 2 Oct 2019 14:00:30 +0200 Subject: No issue - Update TZ data to 2019c --- config/external/icu/data/icudt58l.dat | Bin 11695584 -> 11696784 bytes intl/tzdata/GIT-INFO | 8 +- intl/tzdata/VERSION | 2 +- intl/tzdata/source/be/metaZones.res | Bin 41488 -> 41488 bytes intl/tzdata/source/be/windowsZones.res | Bin 22496 -> 22528 bytes intl/tzdata/source/be/zoneinfo64.res | Bin 154224 -> 155392 bytes intl/tzdata/source/ee/metaZones.res | Bin 41488 -> 41488 bytes intl/tzdata/source/ee/windowsZones.res | Bin 22496 -> 22528 bytes intl/tzdata/source/ee/zoneinfo64.res | Bin 154224 -> 155392 bytes intl/tzdata/source/le/metaZones.res | Bin 41488 -> 41488 bytes intl/tzdata/source/le/windowsZones.res | Bin 22496 -> 22528 bytes intl/tzdata/source/le/zoneinfo64.res | Bin 154224 -> 155392 bytes intl/tzdata/source/metaZones.txt | 4 +- intl/tzdata/source/windowsZones.txt | 25 ++-- intl/tzdata/source/zoneinfo64.txt | 160 ++++++++++----------- js/src/builtin/IntlTimeZoneData.h | 2 +- .../Intl/DateTimeFormat/timeZone_backward_links.js | 2 +- .../tests/Intl/DateTimeFormat/timeZone_backzone.js | 2 +- .../Intl/DateTimeFormat/timeZone_backzone_links.js | 2 +- .../DateTimeFormat/timeZone_notbackward_links.js | 2 +- 20 files changed, 103 insertions(+), 106 deletions(-) diff --git a/config/external/icu/data/icudt58l.dat b/config/external/icu/data/icudt58l.dat index 528ff5ebe..0bded3ec6 100644 Binary files a/config/external/icu/data/icudt58l.dat and b/config/external/icu/data/icudt58l.dat differ diff --git a/intl/tzdata/GIT-INFO b/intl/tzdata/GIT-INFO index 9c6206b1b..80b232e89 100644 --- a/intl/tzdata/GIT-INFO +++ b/intl/tzdata/GIT-INFO @@ -1,5 +1,5 @@ -commit a1dd3c5661404aa93924e737aeb86acf130b8889 -Author: yumaoka -Date: Tue Mar 26 19:02:10 2019 -0400 +commit 4d027901ef4d3288b2ca264ba1f532cf826190ac +Author: Yoshito Umaoka +Date: Fri Sep 13 18:23:18 2019 -0400 - ICU-20522 tzdata2019a updates + ICU-20823 ICU time zone data updates for 2019c diff --git a/intl/tzdata/VERSION b/intl/tzdata/VERSION index 149d790c3..db18f8311 100644 --- a/intl/tzdata/VERSION +++ b/intl/tzdata/VERSION @@ -1 +1 @@ -2019a +2019c diff --git a/intl/tzdata/source/be/metaZones.res b/intl/tzdata/source/be/metaZones.res index 3df8225d6..bc6b490a3 100644 Binary files a/intl/tzdata/source/be/metaZones.res and b/intl/tzdata/source/be/metaZones.res differ diff --git a/intl/tzdata/source/be/windowsZones.res b/intl/tzdata/source/be/windowsZones.res index e6bb5af0f..9e84b8f22 100644 Binary files a/intl/tzdata/source/be/windowsZones.res and b/intl/tzdata/source/be/windowsZones.res differ diff --git a/intl/tzdata/source/be/zoneinfo64.res b/intl/tzdata/source/be/zoneinfo64.res index fc5c0f58e..949fd8986 100644 Binary files a/intl/tzdata/source/be/zoneinfo64.res and b/intl/tzdata/source/be/zoneinfo64.res differ diff --git a/intl/tzdata/source/ee/metaZones.res b/intl/tzdata/source/ee/metaZones.res index e495c2aa9..bb2226702 100644 Binary files a/intl/tzdata/source/ee/metaZones.res and b/intl/tzdata/source/ee/metaZones.res differ diff --git a/intl/tzdata/source/ee/windowsZones.res b/intl/tzdata/source/ee/windowsZones.res index 2a6500de3..c2ec2db8a 100644 Binary files a/intl/tzdata/source/ee/windowsZones.res and b/intl/tzdata/source/ee/windowsZones.res differ diff --git a/intl/tzdata/source/ee/zoneinfo64.res b/intl/tzdata/source/ee/zoneinfo64.res index faef29683..33bc1df91 100644 Binary files a/intl/tzdata/source/ee/zoneinfo64.res and b/intl/tzdata/source/ee/zoneinfo64.res differ diff --git a/intl/tzdata/source/le/metaZones.res b/intl/tzdata/source/le/metaZones.res index 3b3ee0134..3d894c944 100644 Binary files a/intl/tzdata/source/le/metaZones.res and b/intl/tzdata/source/le/metaZones.res differ diff --git a/intl/tzdata/source/le/windowsZones.res b/intl/tzdata/source/le/windowsZones.res index 77b503f23..89eb10001 100644 Binary files a/intl/tzdata/source/le/windowsZones.res and b/intl/tzdata/source/le/windowsZones.res differ diff --git a/intl/tzdata/source/le/zoneinfo64.res b/intl/tzdata/source/le/zoneinfo64.res index 7e0afd405..2dd90caaa 100644 Binary files a/intl/tzdata/source/le/zoneinfo64.res and b/intl/tzdata/source/le/zoneinfo64.res differ diff --git a/intl/tzdata/source/metaZones.txt b/intl/tzdata/source/metaZones.txt index d6b002005..62439a51a 100644 --- a/intl/tzdata/source/metaZones.txt +++ b/intl/tzdata/source/metaZones.txt @@ -3647,11 +3647,11 @@ metaZones:table(nofallback){ { "Europe_Eastern", "1970-01-01 00:00", - "1978-10-14 21:00", + "1978-06-28 21:00", } { "Turkey", - "1978-10-14 21:00", + "1978-06-28 21:00", "1985-04-19 21:00", } { diff --git a/intl/tzdata/source/windowsZones.txt b/intl/tzdata/source/windowsZones.txt index e29b769c9..cb139642f 100644 --- a/intl/tzdata/source/windowsZones.txt +++ b/intl/tzdata/source/windowsZones.txt @@ -17,8 +17,8 @@ windowsZones:table(nofallback){ "Alaskan Standard Time"{ 001{"America/Anchorage"} US{ - "America/Anchorage America/Juneau America/Nome America/Sitka America/" - "Yakutat" + "America/Anchorage America/Juneau America/Metlakatla America/Nome Ame" + "rica/Sitka America/Yakutat" } } "Aleutian Standard Time"{ @@ -287,7 +287,7 @@ windowsZones:table(nofallback){ } "GTB Standard Time"{ 001{"Europe/Bucharest"} - CY{"Asia/Famagusta Asia/Nicosia"} + CY{"Asia/Nicosia Asia/Famagusta"} GR{"Europe/Athens"} RO{"Europe/Bucharest"} } @@ -371,7 +371,6 @@ windowsZones:table(nofallback){ } "Magallanes Standard Time"{ 001{"America/Punta_Arenas"} - AQ{"Antarctica/Palmer"} CL{"America/Punta_Arenas"} } "Marquesas Standard Time"{ @@ -468,7 +467,7 @@ windowsZones:table(nofallback){ "Pacific Standard Time"{ 001{"America/Los_Angeles"} CA{"America/Vancouver America/Dawson America/Whitehorse"} - US{"America/Los_Angeles America/Metlakatla"} + US{"America/Los_Angeles"} ZZ{"PST8PDT"} } "Pakistan Standard Time"{ @@ -479,6 +478,10 @@ windowsZones:table(nofallback){ 001{"America/Asuncion"} PY{"America/Asuncion"} } + "Qyzylorda Standard Time"{ + 001{"Asia/Qyzylorda"} + KZ{"Asia/Qyzylorda"} + } "Romance Standard Time"{ 001{"Europe/Paris"} BE{"Europe/Brussels"} @@ -500,12 +503,12 @@ windowsZones:table(nofallback){ } "Russian Standard Time"{ 001{"Europe/Moscow"} - RU{"Europe/Moscow Europe/Kirov Europe/Volgograd"} + RU{"Europe/Moscow Europe/Kirov"} UA{"Europe/Simferopol"} } "SA Eastern Standard Time"{ 001{"America/Cayenne"} - AQ{"Antarctica/Rothera"} + AQ{"Antarctica/Rothera Antarctica/Palmer"} BR{ "America/Fortaleza America/Belem America/Maceio America/Recife Americ" "a/Santarem" @@ -590,6 +593,7 @@ windowsZones:table(nofallback){ } "Singapore Standard Time"{ 001{"Asia/Singapore"} + AQ{"Antarctica/Casey"} BN{"Asia/Brunei"} ID{"Asia/Makassar"} MY{"Asia/Kuala_Lumpur Asia/Kuching"} @@ -731,9 +735,12 @@ windowsZones:table(nofallback){ 001{"Asia/Vladivostok"} RU{"Asia/Vladivostok Asia/Ust-Nera"} } + "Volgograd Standard Time"{ + 001{"Europe/Volgograd"} + RU{"Europe/Volgograd"} + } "W. Australia Standard Time"{ 001{"Australia/Perth"} - AQ{"Antarctica/Casey"} AU{"Australia/Perth"} } "W. Central Africa Standard Time"{ @@ -779,7 +786,7 @@ windowsZones:table(nofallback){ "West Asia Standard Time"{ 001{"Asia/Tashkent"} AQ{"Antarctica/Mawson"} - KZ{"Asia/Oral Asia/Aqtau Asia/Aqtobe Asia/Atyrau Asia/Qyzylorda"} + KZ{"Asia/Oral Asia/Aqtau Asia/Aqtobe Asia/Atyrau"} MV{"Indian/Maldives"} TF{"Indian/Kerguelen"} TJ{"Asia/Dushanbe"} diff --git a/intl/tzdata/source/zoneinfo64.txt b/intl/tzdata/source/zoneinfo64.txt index e64943419..89b89737e 100644 --- a/intl/tzdata/source/zoneinfo64.txt +++ b/intl/tzdata/source/zoneinfo64.txt @@ -3,17 +3,17 @@ // License & terms of use: http://www.unicode.org/copyright.html#License //--------------------------------------------------------- // Build tool: tz2icu -// Build date: Tue Mar 26 16:57:59 2019 +// Build date: Fri Sep 13 17:31:25 2019 // tz database: ftp://ftp.iana.org/tz/ -// tz version: 2019a -// ICU version: 64.1 +// tz version: 2019c +// ICU version: 65.1 //--------------------------------------------------------- // >> !!! >> THIS IS A MACHINE-GENERATED FILE << !!! << // >> !!! >>> DO NOT EDIT <<< !!! << //--------------------------------------------------------- zoneinfo64:table(nofallback) { - TZVersion { "2019a" } + TZVersion { "2019c" } Zones:array { /* ACT */ :int { 355 } //Z#0 /* AET */ :int { 367 } //Z#1 @@ -60,8 +60,9 @@ zoneinfo64:table(nofallback) { } //Z#18 /* Africa/Casablanca */ :table { trans:intvector { -1773012580, -956361600, -950490000, -942019200, -761187600, -617241600, -605149200, -81432000, -71110800, 141264000, 147222000, 199756800, 207702000, 231292800, 244249200, 265507200, 271033200, 448243200, 504918000, 1212278400, 1220223600, 1243814400, 1250809200, 1272758400, 1281222000, 1301788800, 1312066800, 1335664800, 1342749600, 1345428000, 1348970400, 1367114400, 1373162400, 1376100000, 1382839200, 1396144800, 1403920800, 1406944800, 1414288800, 1427594400, 1434247200, 1437271200, 1445738400, 1459044000, 1465092000, 1468116000, 1477792800, 1490493600, 1495332000, 1498960800, 1509242400, 1521943200, 1526176800, 1529200800, 1557021600, 1560045600, 1587261600, 1590285600, 1618106400, 1621130400, 1648346400, 1651975200, 1679191200, 1682215200, 1710036000, 1713060000, 1740276000, 1743904800, 1771120800, 1774144800, 1801965600, 1804989600, 1832205600, 1835229600, 1863050400, 1866074400, 1893290400, 1896919200, 1924135200, 1927159200, 1954980000, 1958004000, 1985220000, 1988848800, 2016064800, 2019088800, 2046304800, 2049933600, 2077149600, 2080173600, 2107994400, 2111018400, 2138234400, 2141863200 } + transPost32:intvector { 0, -2125888096, 0, -2122864096, 0, -2095043296, 0, -2092019296, 0, -2064803296, 0, -2061174496, 0, -2033958496, 0, -2030934496, 0, -2003718496, 0, -2000089696, 0, -1972873696, 0, -1969849696, 0, -1942028896, 0, -1939004896, 0, -1911788896, 0, -1908160096, 0, -1880944096, 0, -1877920096, 0, -1850099296, 0, -1847075296, 0, -1819859296, 0, -1816230496, 0, -1789014496, 0, -1785990496, 0, -1758774496, 0, -1755145696, 0, -1727929696, 0, -1724905696, 0, -1697084896, 0, -1694060896, 0, -1666844896, 0, -1663216096, 0, -1636000096, 0, -1632976096, 0, -1605155296, 0, -1602131296, 0, -1574915296, 0, -1571286496, 0, -1544070496, 0, -1541046496, 0, -1513830496, 0, -1510201696, 0, -1482985696, 0, -1479961696, 0, -1452140896, 0, -1449116896, 0, -1421900896, 0, -1418272096, 0, -1391056096, 0, -1388032096, 0, -1360211296, 0, -1357187296, 0, -1329971296, 0, -1326947296, 0, -1299126496, 0, -1296102496, 0, -1268886496, 0, -1265257696, 0, -1238041696, 0, -1235017696, 0, -1207196896, 0, -1204172896, 0, -1176956896, 0, -1173328096, 0, -1146112096, 0, -1143088096, 0, -1115267296, 0, -1112243296, 0, -1085027296, 0, -1082003296, 0, -1054182496, 0, -1051158496, 0, -1023942496, 0, -1020313696, 0, -993097696, 0, -990073696, 0, -962252896, 0, -959228896, 0, -932012896, 0, -928384096, 0, -901168096, 0, -898144096, 0, -870323296, 0, -867299296, 0, -840083296, 0, -837059296, 0, -809238496, 0, -806214496, 0, -778998496, 0, -775369696, 0, -748153696, 0, -745129696, 0, -717308896, 0, -714284896, 0, -687068896, 0, -683440096, 0, -656224096, 0, -653200096, 0, -625379296, 0, -622355296, 0, -595139296, 0, -592115296 } typeOffsets:intvector { -1820, 0, 0, 0, 0, 3600, 3600, 0 } - typeMap:bin { "01020102010201020102010201020102010301020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102" } + typeMap:bin { "01020102010201020102010201020102010301020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102" } } //Z#19 /* Africa/Ceuta */ :table { transPre32:intvector { -1, 2117514496 } @@ -79,8 +80,9 @@ zoneinfo64:table(nofallback) { /* Africa/Douala */ :int { 36 } //Z#25 /* Africa/El_Aaiun */ :table { trans:intvector { -1136070432, 198291600, 199756800, 207702000, 231292800, 244249200, 265507200, 271033200, 1212278400, 1220223600, 1243814400, 1250809200, 1272758400, 1281222000, 1301788800, 1312066800, 1335664800, 1342749600, 1345428000, 1348970400, 1367114400, 1373162400, 1376100000, 1382839200, 1396144800, 1403920800, 1406944800, 1414288800, 1427594400, 1434247200, 1437271200, 1445738400, 1459044000, 1465092000, 1468116000, 1477792800, 1490493600, 1495332000, 1498960800, 1509242400, 1521943200, 1526176800, 1529200800, 1557021600, 1560045600, 1587261600, 1590285600, 1618106400, 1621130400, 1648346400, 1651975200, 1679191200, 1682215200, 1710036000, 1713060000, 1740276000, 1743904800, 1771120800, 1774144800, 1801965600, 1804989600, 1832205600, 1835229600, 1863050400, 1866074400, 1893290400, 1896919200, 1924135200, 1927159200, 1954980000, 1958004000, 1985220000, 1988848800, 2016064800, 2019088800, 2046304800, 2049933600, 2077149600, 2080173600, 2107994400, 2111018400, 2138234400, 2141863200 } + transPost32:intvector { 0, -2125888096, 0, -2122864096, 0, -2095043296, 0, -2092019296, 0, -2064803296, 0, -2061174496, 0, -2033958496, 0, -2030934496, 0, -2003718496, 0, -2000089696, 0, -1972873696, 0, -1969849696, 0, -1942028896, 0, -1939004896, 0, -1911788896, 0, -1908160096, 0, -1880944096, 0, -1877920096, 0, -1850099296, 0, -1847075296, 0, -1819859296, 0, -1816230496, 0, -1789014496, 0, -1785990496, 0, -1758774496, 0, -1755145696, 0, -1727929696, 0, -1724905696, 0, -1697084896, 0, -1694060896, 0, -1666844896, 0, -1663216096, 0, -1636000096, 0, -1632976096, 0, -1605155296, 0, -1602131296, 0, -1574915296, 0, -1571286496, 0, -1544070496, 0, -1541046496, 0, -1513830496, 0, -1510201696, 0, -1482985696, 0, -1479961696, 0, -1452140896, 0, -1449116896, 0, -1421900896, 0, -1418272096, 0, -1391056096, 0, -1388032096, 0, -1360211296, 0, -1357187296, 0, -1329971296, 0, -1326947296, 0, -1299126496, 0, -1296102496, 0, -1268886496, 0, -1265257696, 0, -1238041696, 0, -1235017696, 0, -1207196896, 0, -1204172896, 0, -1176956896, 0, -1173328096, 0, -1146112096, 0, -1143088096, 0, -1115267296, 0, -1112243296, 0, -1085027296, 0, -1082003296, 0, -1054182496, 0, -1051158496, 0, -1023942496, 0, -1020313696, 0, -993097696, 0, -990073696, 0, -962252896, 0, -959228896, 0, -932012896, 0, -928384096, 0, -901168096, 0, -898144096, 0, -870323296, 0, -867299296, 0, -840083296, 0, -837059296, 0, -809238496, 0, -806214496, 0, -778998496, 0, -775369696, 0, -748153696, 0, -745129696, 0, -717308896, 0, -714284896, 0, -687068896, 0, -683440096, 0, -656224096, 0, -653200096, 0, -625379296, 0, -622355296, 0, -595139296, 0, -592115296 } typeOffsets:intvector { -3168, 0, -3600, 0, 0, 0, 0, 3600 } - typeMap:bin { "0102030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203" } + typeMap:bin { "0102030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203" } } //Z#26 /* Africa/Freetown */ :int { 5 } //Z#27 /* Africa/Gaborone */ :int { 43 } //Z#28 @@ -355,13 +357,9 @@ zoneinfo64:table(nofallback) { finalYear:int { 2008 } } //Z#91 /* America/Campo_Grande */ :table { - trans:intvector { -1767212492, -1206954000, -1191358800, -1175371200, -1159822800, -633816000, -622065600, -602280000, -591829200, -570744000, -560206800, -539121600, -531349200, -191361600, -184194000, -155160000, -150066000, -128894400, -121122000, -99950400, -89586000, -68414400, -57963600, 499752000, 511239600, 530596800, 540270000, 562132800, 571201200, 592977600, 602046000, 624427200, 634705200, 656481600, 666759600, 687931200, 697604400, 719985600, 728449200, 750830400, 761713200, 782280000, 793162800, 813729600, 824007600, 844574400, 856062000, 876110400, 888721200, 908078400, 919566000, 938923200, 951620400, 970977600, 982465200, 1003032000, 1013914800, 1036296000, 1045364400, 1066536000, 1076814000, 1099368000, 1108868400, 1129435200, 1140318000, 1162699200, 1172372400, 1192334400, 1203217200, 1224388800, 1234666800, 1255838400, 1266721200, 1287288000, 1298170800, 1318737600, 1330225200, 1350792000, 1361070000, 1382241600, 1392519600, 1413691200, 1424574000, 1445140800, 1456023600, 1476590400, 1487473200, 1508040000, 1518922800, 1541304000, 1550372400, 1572753600, 1581822000, 1604203200, 1613876400, 1636257600, 1645326000, 1667707200, 1677380400, 1699156800, 1708225200, 1730606400, 1739674800, 1762056000, 1771729200, 1793505600, 1803178800, 1825560000, 1834628400, 1857009600, 1866078000, 1888459200, 1897527600, 1919908800, 1928977200, 1951358400, 1960426800, 1983412800, 1992481200, 2014862400, 2024535600, 2046312000, 2055380400, 2077761600, 2086830000, 2109211200, 2118884400, 2140660800 } - transPost32:intvector { 0, -2144633296, 0, -2122252096 } + trans:intvector { -1767212492, -1206954000, -1191358800, -1175371200, -1159822800, -633816000, -622065600, -602280000, -591829200, -570744000, -560206800, -539121600, -531349200, -191361600, -184194000, -155160000, -150066000, -128894400, -121122000, -99950400, -89586000, -68414400, -57963600, 499752000, 511239600, 530596800, 540270000, 562132800, 571201200, 592977600, 602046000, 624427200, 634705200, 656481600, 666759600, 687931200, 697604400, 719985600, 728449200, 750830400, 761713200, 782280000, 793162800, 813729600, 824007600, 844574400, 856062000, 876110400, 888721200, 908078400, 919566000, 938923200, 951620400, 970977600, 982465200, 1003032000, 1013914800, 1036296000, 1045364400, 1066536000, 1076814000, 1099368000, 1108868400, 1129435200, 1140318000, 1162699200, 1172372400, 1192334400, 1203217200, 1224388800, 1234666800, 1255838400, 1266721200, 1287288000, 1298170800, 1318737600, 1330225200, 1350792000, 1361070000, 1382241600, 1392519600, 1413691200, 1424574000, 1445140800, 1456023600, 1476590400, 1487473200, 1508040000, 1518922800, 1541304000, 1550372400 } typeOffsets:intvector { -13108, 0, -14400, 0, -14400, 3600 } - typeMap:bin { "01020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102" } - finalRule { "Brazil" } - finalRaw:int { -14400 } - finalYear:int { 2039 } + typeMap:bin { "01020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201" } } //Z#92 /* America/Cancun */ :table { trans:intvector { -1514743200, 377935200, 828860400, 846396000, 860310000, 877845600, 891759600, 902037600, 909298800, 923212800, 941353200, 954662400, 972802800, 989136000, 1001833200, 1018166400, 1035702000, 1049616000, 1067151600, 1081065600, 1099206000, 1112515200, 1130655600, 1143964800, 1162105200, 1175414400, 1193554800, 1207468800, 1225004400, 1238918400, 1256454000, 1270368000, 1288508400, 1301817600, 1319958000, 1333267200, 1351407600, 1365321600, 1382857200, 1396771200, 1414306800, 1422777600 } @@ -413,13 +411,9 @@ zoneinfo64:table(nofallback) { typeMap:bin { "020102" } } //Z#103 /* America/Cuiaba */ :table { - trans:intvector { -1767212140, -1206954000, -1191358800, -1175371200, -1159822800, -633816000, -622065600, -602280000, -591829200, -570744000, -560206800, -539121600, -531349200, -191361600, -184194000, -155160000, -150066000, -128894400, -121122000, -99950400, -89586000, -68414400, -57963600, 499752000, 511239600, 530596800, 540270000, 562132800, 571201200, 592977600, 602046000, 624427200, 634705200, 656481600, 666759600, 687931200, 697604400, 719985600, 728449200, 750830400, 761713200, 782280000, 793162800, 813729600, 824007600, 844574400, 856062000, 876110400, 888721200, 908078400, 919566000, 938923200, 951620400, 970977600, 982465200, 1003032000, 1013914800, 1036296000, 1045364400, 1099368000, 1108868400, 1129435200, 1140318000, 1162699200, 1172372400, 1192334400, 1203217200, 1224388800, 1234666800, 1255838400, 1266721200, 1287288000, 1298170800, 1318737600, 1330225200, 1350792000, 1361070000, 1382241600, 1392519600, 1413691200, 1424574000, 1445140800, 1456023600, 1476590400, 1487473200, 1508040000, 1518922800, 1541304000, 1550372400, 1572753600, 1581822000, 1604203200, 1613876400, 1636257600, 1645326000, 1667707200, 1677380400, 1699156800, 1708225200, 1730606400, 1739674800, 1762056000, 1771729200, 1793505600, 1803178800, 1825560000, 1834628400, 1857009600, 1866078000, 1888459200, 1897527600, 1919908800, 1928977200, 1951358400, 1960426800, 1983412800, 1992481200, 2014862400, 2024535600, 2046312000, 2055380400, 2077761600, 2086830000, 2109211200, 2118884400, 2140660800 } - transPost32:intvector { 0, -2144633296, 0, -2122252096 } + trans:intvector { -1767212140, -1206954000, -1191358800, -1175371200, -1159822800, -633816000, -622065600, -602280000, -591829200, -570744000, -560206800, -539121600, -531349200, -191361600, -184194000, -155160000, -150066000, -128894400, -121122000, -99950400, -89586000, -68414400, -57963600, 499752000, 511239600, 530596800, 540270000, 562132800, 571201200, 592977600, 602046000, 624427200, 634705200, 656481600, 666759600, 687931200, 697604400, 719985600, 728449200, 750830400, 761713200, 782280000, 793162800, 813729600, 824007600, 844574400, 856062000, 876110400, 888721200, 908078400, 919566000, 938923200, 951620400, 970977600, 982465200, 1003032000, 1013914800, 1036296000, 1045364400, 1099368000, 1108868400, 1129435200, 1140318000, 1162699200, 1172372400, 1192334400, 1203217200, 1224388800, 1234666800, 1255838400, 1266721200, 1287288000, 1298170800, 1318737600, 1330225200, 1350792000, 1361070000, 1382241600, 1392519600, 1413691200, 1424574000, 1445140800, 1456023600, 1476590400, 1487473200, 1508040000, 1518922800, 1541304000, 1550372400 } typeOffsets:intvector { -13460, 0, -14400, 0, -14400, 3600 } - typeMap:bin { "0102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102" } - finalRule { "Brazil" } - finalRaw:int { -14400 } - finalYear:int { 2039 } + typeMap:bin { "0102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201" } } //Z#104 /* America/Curacao */ :table { trans:intvector { -1826738653, -157750200 } @@ -458,9 +452,9 @@ zoneinfo64:table(nofallback) { links:intvector { 109, 204, 544, 624 } } //Z#109 /* America/Detroit */ :table { - trans:intvector { -2051202469, -1724083200, -880218000, -765396000, -684349200, -671047200, 104914800, 120636000, 126687600, 152085600, 167814000, 183535200, 199263600, 215589600, 230713200, 247039200, 262767600, 278488800, 294217200, 309938400, 325666800, 341388000, 357116400, 372837600, 388566000, 404892000, 420015600, 436341600, 452070000, 467791200, 483519600, 499240800, 514969200, 530690400, 544604400, 562140000, 576054000, 594194400, 607503600, 625644000, 638953200, 657093600, 671007600, 688543200, 702457200, 719992800, 733906800, 752047200, 765356400, 783496800, 796806000, 814946400, 828860400, 846396000, 860310000, 877845600, 891759600, 909295200, 923209200, 941349600, 954658800, 972799200, 986108400, 1004248800, 1018162800, 1035698400, 1049612400, 1067148000, 1081062000, 1099202400, 1112511600, 1130652000, 1143961200, 1162101600, 1173596400, 1194156000 } + trans:intvector { -2051202469, -1724083200, -880218000, -765396000, -684349200, -671047200, -80506740, -68666400, -52938000, -37216800, 104914800, 120636000, 126687600, 152085600, 167814000, 183535200, 199263600, 215589600, 230713200, 247039200, 262767600, 278488800, 294217200, 309938400, 325666800, 341388000, 357116400, 372837600, 388566000, 404892000, 420015600, 436341600, 452070000, 467791200, 483519600, 499240800, 514969200, 530690400, 544604400, 562140000, 576054000, 594194400, 607503600, 625644000, 638953200, 657093600, 671007600, 688543200, 702457200, 719992800, 733906800, 752047200, 765356400, 783496800, 796806000, 814946400, 828860400, 846396000, 860310000, 877845600, 891759600, 909295200, 923209200, 941349600, 954658800, 972799200, 986108400, 1004248800, 1018162800, 1035698400, 1049612400, 1067148000, 1081062000, 1099202400, 1112511600, 1130652000, 1143961200, 1162101600, 1173596400, 1194156000 } typeOffsets:intvector { -19931, 0, -21600, 0, -18000, 0, -18000, 3600 } - typeMap:bin { "01020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302" } + typeMap:bin { "0102030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302" } finalRule { "US" } finalRaw:int { -18000 } finalYear:int { 2008 } @@ -468,9 +462,9 @@ zoneinfo64:table(nofallback) { } //Z#110 /* America/Dominica */ :int { 186 } //Z#111 /* America/Edmonton */ :table { - trans:intvector { -1998663968, -1632063600, -1615132800, -1600614000, -1596816000, -1567954800, -1551628800, -1536505200, -1523203200, -1504450800, -1491753600, -1473001200, -1459699200, -880210800, -765388800, -715791600, -702489600, -84380400, -68659200, -21481200, -5760000, 73472400, 89193600, 104922000, 120643200, 136371600, 152092800, 167821200, 183542400, 199270800, 215596800, 230720400, 247046400, 262774800, 278496000, 294224400, 309945600, 325674000, 341395200, 357123600, 372844800, 388573200, 404899200, 420022800, 436348800, 452077200, 467798400, 483526800, 499248000, 514976400, 530697600, 544611600, 562147200, 576061200, 594201600, 607510800, 625651200, 638960400, 657100800, 671014800, 688550400, 702464400, 720000000, 733914000, 752054400, 765363600, 783504000, 796813200, 814953600, 828867600, 846403200, 860317200, 877852800, 891766800, 909302400, 923216400, 941356800, 954666000, 972806400, 986115600, 1004256000, 1018170000, 1035705600, 1049619600, 1067155200, 1081069200, 1099209600, 1112518800, 1130659200, 1143968400, 1162108800, 1173603600, 1194163200 } + trans:intvector { -1998663968, -1632063600, -1615132800, -1600614000, -1596816000, -1567954800, -1551628800, -1536505200, -1523203200, -1504450800, -1491753600, -1473001200, -1459699200, -880210800, -765388800, -715791600, -702489600, 73472400, 89193600, 104922000, 120643200, 136371600, 152092800, 167821200, 183542400, 199270800, 215596800, 230720400, 247046400, 262774800, 278496000, 294224400, 309945600, 325674000, 341395200, 357123600, 372844800, 388573200, 404899200, 420022800, 436348800, 452077200, 467798400, 483526800, 499248000, 514976400, 530697600, 544611600, 562147200, 576061200, 594201600, 607510800, 625651200, 638960400, 657100800, 671014800, 688550400, 702464400, 720000000, 733914000, 752054400, 765363600, 783504000, 796813200, 814953600, 828867600, 846403200, 860317200, 877852800, 891766800, 909302400, 923216400, 941356800, 954666000, 972806400, 986115600, 1004256000, 1018170000, 1035705600, 1049619600, 1067155200, 1081069200, 1099209600, 1112518800, 1130659200, 1143968400, 1162108800, 1173603600, 1194163200 } typeOffsets:intvector { -27232, 0, -25200, 0, -25200, 3600 } - typeMap:bin { "010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201" } + typeMap:bin { "0102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201" } finalRule { "Canada" } finalRaw:int { -25200 } finalYear:int { 2008 } @@ -615,9 +609,9 @@ zoneinfo64:table(nofallback) { } //Z#134 /* America/Indiana/Tell_City */ :table { transPre32:intvector { -1, 1577320096 } - trans:intvector { -1633276800, -1615136400, -1601827200, -1583686800, -880214400, -765392400, -747244800, -733942800, -526492800, -513190800, -495043200, -481741200, -462996000, -450291600, -431539200, -418237200, -400089600, -386787600, -368640000, -355338000, -337190400, -323888400, -305740800, -289414800, -273686400, -260989200, -242236800, -226515600, -210787200, -195066000, -179337600, -21488400, -5767200, 9961200, 25682400, 1143961200, 1162105200, 1173600000, 1194159600 } + trans:intvector { -1633276800, -1615136400, -1601827200, -1583686800, -880214400, -765392400, -462996000, -450291600, -431539200, -418237200, -400089600, -386787600, -368640000, -355338000, -337190400, -323888400, -305740800, -292438800, -273686400, -257965200, -242236800, -226515600, -210787200, -195066000, -179337600, -68662800, -52934400, -37213200, -21484800, -5767200, 9961200, 25682400, 1143961200, 1162105200, 1173600000, 1194159600 } typeOffsets:intvector { -20823, 0, -21600, 0, -21600, 3600, -18000, 0, -18000, 3600 } - typeMap:bin { "01020102010201020102010201020102010201020102010201020102010201030403040302010201" } + typeMap:bin { "01020102010201020102010201020102010201020102010201030102010403040302010201" } finalRule { "US" } finalRaw:int { -21600 } finalYear:int { 2008 } @@ -684,7 +678,7 @@ zoneinfo64:table(nofallback) { } //Z#144 /* America/Kentucky/Louisville */ :table { transPre32:intvector { -1, 1577320096 } - trans:intvector { -1633276800, -1615136400, -1601827200, -1583686800, -1535904000, -1525280400, -905097600, -891795600, -880214400, -765392400, -757360800, -744224400, -715795200, -608144400, -589392000, -576090000, -557942400, -544640400, -526492800, -513190800, -495043200, -481741200, -463593600, -450291600, -431539200, -415818000, -400089600, -384368400, -368640000, -352918800, -337190400, -321469200, -305740800, -289414800, -273686400, -266432400, -52938000, -37216800, -21488400, -5767200, 9961200, 25682400, 41410800, 57736800, 73465200, 89186400, 104914800, 120636000, 126687600, 152089200, 162370800, 183535200, 199263600, 215589600, 230713200, 247039200, 262767600, 278488800, 294217200, 309938400, 325666800, 341388000, 357116400, 372837600, 388566000, 404892000, 420015600, 436341600, 452070000, 467791200, 483519600, 499240800, 514969200, 530690400, 544604400, 562140000, 576054000, 594194400, 607503600, 625644000, 638953200, 657093600, 671007600, 688543200, 702457200, 719992800, 733906800, 752047200, 765356400, 783496800, 796806000, 814946400, 828860400, 846396000, 860310000, 877845600, 891759600, 909295200, 923209200, 941349600, 954658800, 972799200, 986108400, 1004248800, 1018162800, 1035698400, 1049612400, 1067148000, 1081062000, 1099202400, 1112511600, 1130652000, 1143961200, 1162101600, 1173596400, 1194156000 } + trans:intvector { -1633276800, -1615136400, -1601827200, -1583686800, -1535904000, -1525280400, -905097600, -891795600, -880214400, -765392400, -747251940, -744224400, -620841600, -608144400, -589392000, -576090000, -557942400, -544640400, -526492800, -513190800, -495043200, -481741200, -463593600, -450291600, -431539200, -415818000, -400089600, -384368400, -368640000, -352918800, -337190400, -321469200, -305740800, -289414800, -273686400, -266432400, -52938000, -37216800, -21488400, -5767200, 9961200, 25682400, 41410800, 57736800, 73465200, 89186400, 104914800, 120636000, 126687600, 152089200, 162370800, 183535200, 199263600, 215589600, 230713200, 247039200, 262767600, 278488800, 294217200, 309938400, 325666800, 341388000, 357116400, 372837600, 388566000, 404892000, 420015600, 436341600, 452070000, 467791200, 483519600, 499240800, 514969200, 530690400, 544604400, 562140000, 576054000, 594194400, 607503600, 625644000, 638953200, 657093600, 671007600, 688543200, 702457200, 719992800, 733906800, 752047200, 765356400, 783496800, 796806000, 814946400, 828860400, 846396000, 860310000, 877845600, 891759600, 909295200, 923209200, 941349600, 954658800, 972799200, 986108400, 1004248800, 1018162800, 1035698400, 1049612400, 1067148000, 1081062000, 1099202400, 1112511600, 1130652000, 1143961200, 1162101600, 1173596400, 1194156000 } typeOffsets:intvector { -20582, 0, -21600, 0, -21600, 3600, -18000, 0, -18000, 3600 } typeMap:bin { "010201020102010201020102010201020102010201020102010201020102010201020102030403040304030403040304030203040304030403040304030403040304030403040304030403040304030403040304030403040304030403040304030403040304030403040304030403040304030403" } finalRule { "US" } @@ -1037,13 +1031,9 @@ zoneinfo64:table(nofallback) { typeMap:bin { "0401030102010201020102010201050105" } } //Z#201 /* America/Sao_Paulo */ :table { - trans:intvector { -1767214412, -1206957600, -1191362400, -1175374800, -1159826400, -633819600, -622069200, -602283600, -591832800, -570747600, -560210400, -539125200, -531352800, -195426000, -184197600, -155163600, -150069600, -128898000, -121125600, -99954000, -89589600, -68418000, -57967200, 499748400, 511236000, 530593200, 540266400, 562129200, 571197600, 592974000, 602042400, 624423600, 634701600, 656478000, 666756000, 687927600, 697600800, 719982000, 728445600, 750826800, 761709600, 782276400, 793159200, 813726000, 824004000, 844570800, 856058400, 876106800, 888717600, 908074800, 919562400, 938919600, 951616800, 970974000, 982461600, 1003028400, 1013911200, 1036292400, 1045360800, 1066532400, 1076810400, 1099364400, 1108864800, 1129431600, 1140314400, 1162695600, 1172368800, 1192330800, 1203213600, 1224385200, 1234663200, 1255834800, 1266717600, 1287284400, 1298167200, 1318734000, 1330221600, 1350788400, 1361066400, 1382238000, 1392516000, 1413687600, 1424570400, 1445137200, 1456020000, 1476586800, 1487469600, 1508036400, 1518919200, 1541300400, 1550368800, 1572750000, 1581818400, 1604199600, 1613872800, 1636254000, 1645322400, 1667703600, 1677376800, 1699153200, 1708221600, 1730602800, 1739671200, 1762052400, 1771725600, 1793502000, 1803175200, 1825556400, 1834624800, 1857006000, 1866074400, 1888455600, 1897524000, 1919905200, 1928973600, 1951354800, 1960423200, 1983409200, 1992477600, 2014858800, 2024532000, 2046308400, 2055376800, 2077758000, 2086826400, 2109207600, 2118880800, 2140657200 } - transPost32:intvector { 0, -2144636896, 0, -2122255696 } + trans:intvector { -1767214412, -1206957600, -1191362400, -1175374800, -1159826400, -633819600, -622069200, -602283600, -591832800, -570747600, -560210400, -539125200, -531352800, -195426000, -184197600, -155163600, -150069600, -128898000, -121125600, -99954000, -89589600, -68418000, -57967200, 499748400, 511236000, 530593200, 540266400, 562129200, 571197600, 592974000, 602042400, 624423600, 634701600, 656478000, 666756000, 687927600, 697600800, 719982000, 728445600, 750826800, 761709600, 782276400, 793159200, 813726000, 824004000, 844570800, 856058400, 876106800, 888717600, 908074800, 919562400, 938919600, 951616800, 970974000, 982461600, 1003028400, 1013911200, 1036292400, 1045360800, 1066532400, 1076810400, 1099364400, 1108864800, 1129431600, 1140314400, 1162695600, 1172368800, 1192330800, 1203213600, 1224385200, 1234663200, 1255834800, 1266717600, 1287284400, 1298167200, 1318734000, 1330221600, 1350788400, 1361066400, 1382238000, 1392516000, 1413687600, 1424570400, 1445137200, 1456020000, 1476586800, 1487469600, 1508036400, 1518919200, 1541300400, 1550368800 } typeOffsets:intvector { -11188, 0, -10800, 0, -10800, 3600 } - typeMap:bin { "01020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102" } - finalRule { "Brazil" } - finalRaw:int { -10800 } - finalYear:int { 2039 } + typeMap:bin { "01020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201" } links:intvector { 202, 372, 376 } } //Z#202 /* America/Scoresbysund */ :table { @@ -1127,7 +1117,7 @@ zoneinfo64:table(nofallback) { /* America/Tortola */ :int { 186 } //Z#218 /* America/Vancouver */ :table { transPre32:intvector { -1, 1581086444 } - trans:intvector { -1632060000, -1615129200, -880207200, -765385200, -747237600, -732726000, -715788000, -702486000, -684338400, -671036400, -652888800, -639586800, -620834400, -608137200, -589384800, -576082800, -557935200, -544633200, -526485600, -513183600, -495036000, -481734000, -463586400, -450284400, -431532000, -418230000, -400082400, -386780400, -368632800, -355330800, -337183200, -323881200, -305733600, -292431600, -273679200, -260982000, -242229600, -226508400, -210780000, -195058800, -179330400, -163609200, -147880800, -131554800, -116431200, -100105200, -84376800, -68655600, -52927200, -37206000, -21477600, -5756400, 9972000, 25693200, 41421600, 57747600, 73476000, 89197200, 104925600, 120646800, 136375200, 152096400, 167824800, 183546000, 199274400, 215600400, 230724000, 247050000, 262778400, 278499600, 294228000, 309949200, 325677600, 341398800, 357127200, 372848400, 388576800, 404902800, 420026400, 436352400, 452080800, 467802000, 483530400, 499251600, 514980000, 530701200, 544615200, 562150800, 576064800, 594205200, 607514400, 625654800, 638964000, 657104400, 671018400, 688554000, 702468000, 720003600, 733917600, 752058000, 765367200, 783507600, 796816800, 814957200, 828871200, 846406800, 860320800, 877856400, 891770400, 909306000, 923220000, 941360400, 954669600, 972810000, 986119200, 1004259600, 1018173600, 1035709200, 1049623200, 1067158800, 1081072800, 1099213200, 1112522400, 1130662800, 1143972000, 1162112400, 1173607200, 1194166800 } + trans:intvector { -1632060000, -1615129200, -880207200, -765385200, -747237600, -733935600, -715788000, -702486000, -684338400, -671036400, -652888800, -639586800, -620834400, -608137200, -589384800, -576082800, -557935200, -544633200, -526485600, -513183600, -495036000, -481734000, -463586400, -450284400, -431532000, -418230000, -400082400, -386780400, -368632800, -355330800, -337183200, -323881200, -305733600, -292431600, -273679200, -260982000, -242229600, -226508400, -210780000, -195058800, -179330400, -163609200, -147880800, -131554800, -116431200, -100105200, -84376800, -68655600, -52927200, -37206000, -21477600, -5756400, 9972000, 25693200, 41421600, 57747600, 73476000, 89197200, 104925600, 120646800, 136375200, 152096400, 167824800, 183546000, 199274400, 215600400, 230724000, 247050000, 262778400, 278499600, 294228000, 309949200, 325677600, 341398800, 357127200, 372848400, 388576800, 404902800, 420026400, 436352400, 452080800, 467802000, 483530400, 499251600, 514980000, 530701200, 544615200, 562150800, 576064800, 594205200, 607514400, 625654800, 638964000, 657104400, 671018400, 688554000, 702468000, 720003600, 733917600, 752058000, 765367200, 783507600, 796816800, 814957200, 828871200, 846406800, 860320800, 877856400, 891770400, 909306000, 923220000, 941360400, 954669600, 972810000, 986119200, 1004259600, 1018173600, 1035709200, 1049623200, 1067158800, 1081072800, 1099213200, 1112522400, 1130662800, 1143972000, 1162112400, 1173607200, 1194166800 } typeOffsets:intvector { -29548, 0, -28800, 0, -28800, 3600 } typeMap:bin { "010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201" } finalRule { "Canada" } @@ -1374,22 +1364,22 @@ zoneinfo64:table(nofallback) { } //Z#267 /* Asia/Gaza */ :table { transPre32:intvector { -1, 2109557424 } - trans:intvector { -933645600, -857358000, -844300800, -825822000, -812685600, -794199600, -779853600, -762656400, -748310400, -731127600, -399088800, -386650800, -368330400, -355114800, -336790800, -323654400, -305168400, -292032000, -273632400, -260496000, -242096400, -228960000, -210560400, -197424000, -178938000, -165801600, -147402000, -134265600, -115866000, -102643200, -84330000, -81313200, 142380000, 150843600, 167176800, 178664400, 334015200, 337644000, 452556000, 462232800, 482277600, 495579600, 516751200, 526424400, 545436000, 558478800, 576626400, 589323600, 609890400, 620773200, 638316000, 651618000, 669765600, 683672400, 701820000, 715726800, 733701600, 747176400, 765151200, 778021200, 796600800, 810075600, 828655200, 843170400, 860104800, 874620000, 891554400, 906069600, 924213600, 939934800, 956268000, 971989200, 987717600, 1003438800, 1019167200, 1034888400, 1050616800, 1066338000, 1082066400, 1096581600, 1113516000, 1128380400, 1143842400, 1158872400, 1175378400, 1189638000, 1206655200, 1219957200, 1238104800, 1252015200, 1269640860, 1281474000, 1301608860, 1312146000, 1333058400, 1348178400, 1364508000, 1380229200, 1395957600, 1414098000, 1427493600, 1445547600, 1458946800, 1477692000 } + trans:intvector { -933645600, -857358000, -844300800, -825822000, -812685600, -794199600, -779853600, -762656400, -748310400, -731127600, -399088800, -386650800, -368330400, -355114800, -336790800, -323654400, -305168400, -292032000, -273632400, -260496000, -242096400, -228960000, -210560400, -197424000, -178938000, -165801600, -147402000, -134265600, -115866000, -102643200, -84330000, -81313200, 142380000, 150843600, 167176800, 178664400, 334015200, 337644000, 452556000, 462232800, 482277600, 495579600, 516751200, 526424400, 545436000, 558478800, 576626400, 589323600, 609890400, 620773200, 638316000, 651618000, 669765600, 683672400, 701820000, 715726800, 733701600, 747176400, 765151200, 778021200, 796600800, 810075600, 828655200, 843170400, 860104800, 874620000, 891554400, 906069600, 924213600, 939934800, 956268000, 971989200, 987717600, 1003438800, 1019167200, 1034888400, 1050616800, 1066338000, 1082066400, 1096581600, 1113516000, 1128380400, 1143842400, 1158872400, 1175378400, 1189638000, 1206655200, 1219957200, 1238104800, 1252015200, 1269640860, 1281474000, 1301608860, 1312146000, 1333058400, 1348178400, 1364508000, 1380229200, 1395957600, 1414098000, 1427493600, 1445547600, 1458946800, 1477692000, 1490396400, 1509141600, 1521846000, 1540591200, 1553810400, 1572040800 } typeOffsets:intvector { 8272, 0, 7200, 0, 7200, 3600 } - typeMap:bin { "010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201" } + typeMap:bin { "010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201" } finalRule { "Palestine" } finalRaw:int { 7200 } - finalYear:int { 2017 } + finalYear:int { 2020 } } //Z#268 /* Asia/Harbin */ :int { 314 } //Z#269 /* Asia/Hebron */ :table { transPre32:intvector { -1, 2109557273 } - trans:intvector { -933645600, -857358000, -844300800, -825822000, -812685600, -794199600, -779853600, -762656400, -748310400, -731127600, -399088800, -386650800, -368330400, -355114800, -336790800, -323654400, -305168400, -292032000, -273632400, -260496000, -242096400, -228960000, -210560400, -197424000, -178938000, -165801600, -147402000, -134265600, -115866000, -102643200, -84330000, -81313200, 142380000, 150843600, 167176800, 178664400, 334015200, 337644000, 452556000, 462232800, 482277600, 495579600, 516751200, 526424400, 545436000, 558478800, 576626400, 589323600, 609890400, 620773200, 638316000, 651618000, 669765600, 683672400, 701820000, 715726800, 733701600, 747176400, 765151200, 778021200, 796600800, 810075600, 828655200, 843170400, 860104800, 874620000, 891554400, 906069600, 924213600, 939934800, 956268000, 971989200, 987717600, 1003438800, 1019167200, 1034888400, 1050616800, 1066338000, 1082066400, 1096581600, 1113516000, 1128380400, 1143842400, 1158872400, 1175378400, 1189638000, 1206655200, 1220216400, 1238104800, 1252015200, 1269554400, 1281474000, 1301608860, 1312146000, 1314655200, 1317330000, 1333058400, 1348178400, 1364508000, 1380229200, 1395957600, 1414098000, 1427493600, 1445547600, 1458946800, 1477692000 } + trans:intvector { -933645600, -857358000, -844300800, -825822000, -812685600, -794199600, -779853600, -762656400, -748310400, -731127600, -399088800, -386650800, -368330400, -355114800, -336790800, -323654400, -305168400, -292032000, -273632400, -260496000, -242096400, -228960000, -210560400, -197424000, -178938000, -165801600, -147402000, -134265600, -115866000, -102643200, -84330000, -81313200, 142380000, 150843600, 167176800, 178664400, 334015200, 337644000, 452556000, 462232800, 482277600, 495579600, 516751200, 526424400, 545436000, 558478800, 576626400, 589323600, 609890400, 620773200, 638316000, 651618000, 669765600, 683672400, 701820000, 715726800, 733701600, 747176400, 765151200, 778021200, 796600800, 810075600, 828655200, 843170400, 860104800, 874620000, 891554400, 906069600, 924213600, 939934800, 956268000, 971989200, 987717600, 1003438800, 1019167200, 1034888400, 1050616800, 1066338000, 1082066400, 1096581600, 1113516000, 1128380400, 1143842400, 1158872400, 1175378400, 1189638000, 1206655200, 1220216400, 1238104800, 1252015200, 1269554400, 1281474000, 1301608860, 1312146000, 1314655200, 1317330000, 1333058400, 1348178400, 1364508000, 1380229200, 1395957600, 1414098000, 1427493600, 1445547600, 1458946800, 1477692000, 1490396400, 1509141600, 1521846000, 1540591200, 1553810400, 1572040800 } typeOffsets:intvector { 8423, 0, 7200, 0, 7200, 3600 } - typeMap:bin { "0102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201" } + typeMap:bin { "0102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201" } finalRule { "Palestine" } finalRaw:int { 7200 } - finalYear:int { 2017 } + finalYear:int { 2020 } } //Z#270 /* Asia/Ho_Chi_Minh */ :table { trans:intvector { -2004073600, -1851577590, -852105600, -782643600, -767869200, -718095600, -457776000, -315648000, 171820800 } @@ -1398,9 +1388,9 @@ zoneinfo64:table(nofallback) { links:intvector { 271, 310, 630 } } //Z#271 /* Asia/Hong_Kong */ :table { - trans:intvector { -2056690800, -900909000, -891579600, -884248200, -766659600, -747981000, -728544600, -717049800, -694503000, -683785800, -668064600, -654755400, -636615000, -623305800, -605165400, -591856200, -573715800, -559801800, -541661400, -528352200, -510211800, -498112200, -478762200, -466662600, -446707800, -435213000, -415258200, -403158600, -383808600, -371709000, -352359000, -340259400, -320909400, -308809800, -288855000, -277360200, -257405400, -245910600, -225955800, -213856200, -194506200, -182406600, -163056600, -148537800, -132816600, -117088200, -101367000, -85638600, -69312600, -53584200, -37863000, -22134600, -6413400, 9315000, 25036200, 40764600, 56485800, 72214200, 88540200, 104268600, 119989800, 126041400, 151439400, 167167800, 182889000, 198617400, 214338600, 295385400, 309292200 } - typeOffsets:intvector { 27402, 0, 28800, 0, 28800, 3600, 30600, 0, 32400, 0 } - typeMap:bin { "010203040102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201" } + trans:intvector { -2056690800, -900910800, -891579600, -884248200, -761209200, -747907200, -728541000, -717049800, -697091400, -683785800, -668061000, -654755400, -636611400, -623305800, -605161800, -591856200, -573712200, -559801800, -541657800, -528352200, -510211800, -498112200, -478762200, -466662600, -446707800, -435213000, -415258200, -403158600, -383808600, -371709000, -352359000, -340259400, -320909400, -308809800, -288855000, -277360200, -257405400, -245910600, -225955800, -213856200, -194506200, -182406600, -163056600, -148537800, -132816600, -117088200, -101367000, -85638600, -69312600, -53584200, -37863000, -22134600, -6413400, 9315000, 25036200, 40764600, 56485800, 72214200, 88540200, 104268600, 119989800, 126041400, 151439400, 167167800, 182889000, 198617400, 214338600, 295385400, 309292200 } + typeOffsets:intvector { 27402, 0, 28800, 0, 28800, 1800, 28800, 3600, 32400, 0 } + typeMap:bin { "010302040103010301030103010301030103010301030103010301030103010301030103010301030103010301030103010301030103010301030103010301030103010301" } links:intvector { 272, 511 } } //Z#272 /* Asia/Hovd */ :table { @@ -1588,9 +1578,9 @@ zoneinfo64:table(nofallback) { typeMap:bin { "010203040302030203020302030203020302030203020302" } } //Z#312 /* Asia/Seoul */ :table { - trans:intvector { -1948782472, -1830414600, -498128400, -462702600, -451733400, -429784200, -418296600, -399544200, -387451800, -368094600, -356002200, -336645000, -324552600, -305195400, -293103000, -264933000, 547578000, 560883600, 579027600, 592333200 } + trans:intvector { -1948782472, -1830414600, -681210000, -672228000, -654771600, -640864800, -623408400, -609415200, -588848400, -577965600, -498128400, -462702600, -451733400, -429784200, -418296600, -399544200, -387451800, -368094600, -356002200, -336645000, -324552600, -305195400, -293103000, -264933000, 547578000, 560883600, 579027600, 592333200 } typeOffsets:intvector { 30472, 0, 30600, 0, 30600, 3600, 32400, 0, 32400, 3600 } - typeMap:bin { "0103010201020102010201020102010304030403" } + typeMap:bin { "01030403040304030403010201020102010201020102010304030403" } links:intvector { 313, 597 } } //Z#313 /* Asia/Shanghai */ :table { @@ -2093,7 +2083,7 @@ zoneinfo64:table(nofallback) { } //Z#445 /* Europe/Bratislava */ :int { 477 } //Z#446 /* Europe/Brussels */ :table { - transPre32:intvector { -1, 1844014246 } + transPre32:intvector { -1, 1843972096 } trans:intvector { -1740355200, -1693702800, -1680483600, -1663455600, -1650150000, -1632006000, -1618700400, -1613826000, -1604278800, -1585530000, -1574038800, -1552266000, -1539997200, -1520557200, -1507510800, -1490576400, -1473642000, -1459126800, -1444006800, -1427677200, -1411952400, -1396227600, -1379293200, -1364778000, -1348448400, -1333328400, -1316394000, -1301263200, -1284328800, -1269813600, -1253484000, -1238364000, -1221429600, -1206914400, -1191189600, -1175464800, -1160344800, -1143410400, -1127685600, -1111960800, -1096840800, -1080511200, -1063576800, -1049061600, -1033336800, -1017612000, -1002492000, -986162400, -969228000, -950479200, -942012000, -934668000, -857257200, -844556400, -828226800, -812502000, -798073200, -781052400, -766623600, -745455600, -733273200, 228877200, 243997200, 260326800, 276051600, 291776400, 307501200, 323830800, 338950800, 354675600, 370400400, 386125200, 401850000, 417574800, 433299600, 449024400, 465354000, 481078800, 496803600, 512528400, 528253200, 543978000, 559702800, 575427600, 591152400, 606877200, 622602000, 638326800, 654656400, 670381200, 686106000, 701830800, 717555600, 733280400, 749005200, 764730000, 780454800, 796179600, 811904400, 828234000, 846378000 } typeOffsets:intvector { 1050, 0, 0, 0, 0, 3600, 3600, 0, 3600, 3600 } typeMap:bin { "010304030403040301020102010201020102010201020102010201020102010201020102010201020102010201020102010201020403040304030403040304030403040304030403040304030403040304030403040304030403040304030403040304030403" } @@ -2170,17 +2160,17 @@ zoneinfo64:table(nofallback) { /* Europe/Isle_of_Man */ :int { 465 } //Z#457 /* Europe/Istanbul */ :table { transPre32:intvector { -1, 1454819544 } - trans:intvector { -1869875816, -1693706400, -1680490800, -1570413600, -1552186800, -1538359200, -1522551600, -1507514400, -1490583600, -1440208800, -1428030000, -1409709600, -1396494000, -931140000, -922762800, -917834400, -892436400, -875844000, -857358000, -781063200, -764737200, -744343200, -733806000, -716436000, -701924400, -684986400, -670474800, -654141600, -639025200, -621828000, -606970800, -590032800, -575434800, -235620000, -228279600, -177732000, -165726000, 10533600, 23835600, 41983200, 55285200, 74037600, 87339600, 107910000, 121219200, 133920000, 152676000, 165362400, 183502800, 202428000, 215557200, 228866400, 245797200, 260316000, 277246800, 308779200, 323827200, 340228800, 354672000, 371678400, 386121600, 403128000, 428446800, 433886400, 482792400, 496702800, 512521200, 528246000, 543970800, 559695600, 575420400, 591145200, 606870000, 622594800, 638319600, 654649200, 670374000, 686098800, 701823600, 717548400, 733273200, 748998000, 764118000, 780447600, 796172400, 811897200, 828226800, 846370800, 859676400, 877820400, 891126000, 909270000, 922575600, 941324400, 954025200, 972774000, 985474800, 1004223600, 1017529200, 1035673200, 1048978800, 1067122800, 1080428400, 1099177200, 1111878000, 1130626800, 1143327600, 1162076400, 1174784400, 1193533200, 1206838800, 1224982800, 1238288400, 1256432400, 1269738000, 1288486800, 1301274000, 1319936400, 1332637200, 1351386000, 1364691600, 1382835600, 1396227600, 1414285200, 1427590800, 1446944400, 1459040400, 1473195600 } + trans:intvector { -1869875816, -1693706400, -1680490800, -1570413600, -1552186800, -1538359200, -1522551600, -1507514400, -1490583600, -1440208800, -1428030000, -1409709600, -1396494000, -931053600, -922676400, -917834400, -892436400, -875844000, -764737200, -744343200, -733806000, -716436000, -701924400, -684986400, -670474800, -654141600, -639025200, -622087200, -606970800, -590032800, -575521200, -235620000, -194842800, -177732000, -165726000, 107910000, 121215600, 133920000, 152665200, 164678400, 184114800, 196214400, 215564400, 228873600, 245804400, 260323200, 267915600, 428454000, 433893600, 468111600, 482799600, 496710000, 512521200, 528246000, 543970800, 559695600, 575420400, 591145200, 606870000, 622594800, 638319600, 654649200, 670374000, 686098800, 701823600, 717548400, 733273200, 748998000, 764118000, 780447600, 796172400, 811897200, 828226800, 846370800, 859676400, 877820400, 891126000, 909270000, 922575600, 941324400, 954025200, 972774000, 985474800, 1004223600, 1017529200, 1035673200, 1048978800, 1067122800, 1080428400, 1099177200, 1111878000, 1130626800, 1143327600, 1162076400, 1174784400, 1193533200, 1206838800, 1224982800, 1238288400, 1256432400, 1269738000, 1288486800, 1301274000, 1319936400, 1332637200, 1351386000, 1364691600, 1382835600, 1396227600, 1414285200, 1427590800, 1446944400, 1459040400, 1473195600 } typeOffsets:intvector { 6952, 0, 7016, 0, 7200, 0, 7200, 3600, 10800, 0, 10800, 3600 } - typeMap:bin { "010203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030504050405040504050403020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020304" } + typeMap:bin { "01020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030405040203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020302030203020304" } links:intvector { 275, 458, 613 } } //Z#458 /* Europe/Jersey */ :int { 465 } //Z#459 /* Europe/Kaliningrad */ :table { transPre32:intvector { -1, 1872911176 } - trans:intvector { -1693706400, -1680483600, -1663455600, -1650150000, -1632006000, -1618700400, -938905200, -857257200, -844556400, -828226800, -812502000, -796777200, -788922000, -778730400, -762663600, -757389600, 354920400, 370728000, 386456400, 402264000, 417992400, 433800000, 449614800, 465346800, 481071600, 496796400, 512521200, 528246000, 543970800, 559695600, 575420400, 591145200, 606870000, 622598400, 638323200, 654652800, 670377600, 686102400, 701827200, 717552000, 733276800, 749001600, 764726400, 780451200, 796176000, 811900800, 828230400, 846374400, 859680000, 877824000, 891129600, 909273600, 922579200, 941328000, 954028800, 972777600, 985478400, 1004227200, 1017532800, 1035676800, 1048982400, 1067126400, 1080432000, 1099180800, 1111881600, 1130630400, 1143331200, 1162080000, 1174780800, 1193529600, 1206835200, 1224979200, 1238284800, 1256428800, 1269734400, 1288483200, 1301184000, 1414278000 } + trans:intvector { -1693706400, -1680483600, -1663455600, -1650150000, -1632006000, -1618700400, -938905200, -857257200, -844556400, -828226800, -812502000, -796777200, -781052400, -780372000, -778730400, -762663600, -749095200, 354920400, 370728000, 386456400, 402264000, 417992400, 433800000, 449614800, 465346800, 481071600, 496796400, 512521200, 528246000, 543970800, 559695600, 575420400, 591145200, 606870000, 622598400, 638323200, 654652800, 670377600, 686102400, 701827200, 717552000, 733276800, 749001600, 764726400, 780451200, 796176000, 811900800, 828230400, 846374400, 859680000, 877824000, 891129600, 909273600, 922579200, 941328000, 954028800, 972777600, 985478400, 1004227200, 1017532800, 1035676800, 1048982400, 1067126400, 1080432000, 1099180800, 1111881600, 1130630400, 1143331200, 1162080000, 1174780800, 1193529600, 1206835200, 1224979200, 1238284800, 1256428800, 1269734400, 1288483200, 1301184000, 1414278000 } typeOffsets:intvector { 4920, 0, 3600, 0, 3600, 3600, 7200, 0, 7200, 3600, 10800, 0, 10800, 3600 } - typeMap:bin { "01020102010201020102010201030403050605060506050605060506050605060504030403040304030403040304030403040304030403040304030403040304030403040304030403040304030503" } + typeMap:bin { "0102010201020102010201020102030403050605060506050605060506050605060504030403040304030403040304030403040304030403040304030403040304030403040304030403040304030503" } } //Z#460 /* Europe/Kiev */ :table { trans:intvector { -1441159324, -1247536800, -892522800, -857257200, -844556400, -828226800, -825382800, 354920400, 370728000, 386456400, 402264000, 417992400, 433800000, 449614800, 465346800, 481071600, 496796400, 512521200, 528246000, 543970800, 559695600, 575420400, 591145200, 606870000, 622594800, 638319600, 646783200, 686102400, 701820000, 717541200, 733269600, 748990800, 764719200, 780440400, 796179600, 811904400, 828234000, 846378000 } @@ -2384,7 +2374,7 @@ zoneinfo64:table(nofallback) { /* Europe/Vatican */ :int { 479 } //Z#494 /* Europe/Vienna */ :table { transPre32:intvector { -1, 1872912175 } - trans:intvector { -1693706400, -1680483600, -1663455600, -1650150000, -1632006000, -1618700400, -1569711600, -1555801200, -938905200, -857257200, -844556400, -828226800, -812502000, -796777200, -781052400, -780188400, -748479600, -733359600, -717634800, -701910000, -684975600, -670460400, 323823600, 338940000, 354675600, 370400400, 386125200, 401850000, 417574800, 433299600, 449024400, 465354000, 481078800, 496803600, 512528400, 528253200, 543978000, 559702800, 575427600, 591152400, 606877200, 622602000, 638326800, 654656400, 670381200, 686106000, 701830800, 717555600, 733280400, 749005200, 764730000, 780454800, 796179600, 811904400, 828234000, 846378000 } + trans:intvector { -1693706400, -1680483600, -1663455600, -1650150000, -1632006000, -1618700400, -1569711600, -1555801200, -938905200, -857257200, -844556400, -828226800, -812502000, -796777200, -781052400, -780188400, -748479600, -733273200, -717634800, -701910000, -684975600, -670460400, 323823600, 338940000, 354675600, 370400400, 386125200, 401850000, 417574800, 433299600, 449024400, 465354000, 481078800, 496803600, 512528400, 528253200, 543978000, 559702800, 575427600, 591152400, 606877200, 622602000, 638326800, 654656400, 670381200, 686106000, 701830800, 717555600, 733280400, 749005200, 764730000, 780454800, 796179600, 811904400, 828234000, 846378000 } typeOffsets:intvector { 3921, 0, 3600, 0, 3600, 3600 } typeMap:bin { "010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201020102010201" } finalRule { "EU" } @@ -2612,12 +2602,12 @@ zoneinfo64:table(nofallback) { typeMap:bin { "0102" } } //Z#559 /* Pacific/Fiji */ :table { - trans:intvector { -1709985344, 909842400, 920124000, 941896800, 951573600, 1259416800, 1269698400, 1287842400, 1299333600, 1319292000, 1327154400, 1350741600, 1358604000, 1382796000, 1390050000, 1414850400, 1421503200, 1446300000 } + trans:intvector { -1709985344, 909842400, 920124000, 941896800, 951573600, 1259416800, 1269698400, 1287842400, 1299333600, 1319292000, 1327154400, 1350741600, 1358604000, 1382796000, 1390050000, 1414850400, 1421503200, 1446300000, 1452952800, 1478354400, 1484402400, 1509804000, 1515852000, 1541253600, 1547301600, 1573308000 } typeOffsets:intvector { 42944, 0, 43200, 0, 43200, 3600 } - typeMap:bin { "010201020102010201020102010201020102" } + typeMap:bin { "0102010201020102010201020102010201020102010201020102" } finalRule { "Fiji" } finalRaw:int { 43200 } - finalYear:int { 2016 } + finalYear:int { 2020 } } //Z#560 /* Pacific/Funafuti */ :table { transPre32:intvector { -1, 2117471484 } @@ -2699,9 +2689,12 @@ zoneinfo64:table(nofallback) { } //Z#575 /* Pacific/Norfolk */ :table { transPre32:intvector { -1, 2117474184 } - trans:intvector { -599656320, 152029800, 162912600, 1443882600 } - typeOffsets:intvector { 40312, 0, 39600, 0, 40320, 0, 41400, 0, 41400, 3600 } - typeMap:bin { "0203040301" } + trans:intvector { -599656320, 152029800, 162916200, 1443882600, 1570287600, 1586012400, 1601737200 } + typeOffsets:intvector { 40312, 0, 39600, 0, 39600, 3600, 40320, 0, 41400, 0, 41400, 3600 } + typeMap:bin { "0304050401020102" } + finalRule { "AN" } + finalRaw:int { 39600 } + finalYear:int { 2021 } } //Z#576 /* Pacific/Noumea */ :table { trans:intvector { -1829387148, 250002000, 257342400, 281451600, 288878400, 849366000, 857228400 } @@ -3086,84 +3079,81 @@ zoneinfo64:table(nofallback) { AV:intvector { 9, 1, -1, 7200, 1, 3, 1, -1, 7200, 1, 3600 } //_#3 - Brazil:intvector { - 10, 1, -1, 0, 0, 1, 15, -1, 0, 0, 3600 - } //_#4 C-Eur:intvector { 2, -31, -1, 7200, 1, 9, -31, -1, 7200, 1, 3600 - } //_#5 + } //_#4 Canada:intvector { 2, 8, -1, 7200, 0, 10, 1, -1, 7200, 0, 3600 - } //_#6 + } //_#5 Chatham:intvector { 8, -30, -1, 9900, 1, 3, 1, -1, 9900, 1, 3600 - } //_#7 + } //_#6 Chile:intvector { 8, 2, -1, 14400, 2, 3, 2, -1, 10800, 2, 3600 - } //_#8 + } //_#7 Cuba:intvector { 2, 8, -1, 0, 1, 10, 1, -1, 0, 1, 3600 - } //_#9 + } //_#8 EU:intvector { 2, -31, -1, 3600, 2, 9, -31, -1, 3600, 2, 3600 - } //_#10 + } //_#9 EUAsia:intvector { 2, -31, -1, 3600, 2, 9, -31, -1, 3600, 2, 3600 - } //_#11 + } //_#10 Fiji:intvector { - 10, 1, -1, 7200, 0, 0, 13, -1, 10800, 0, 3600 - } //_#12 + 10, 8, -1, 7200, 0, 0, 12, -1, 10800, 0, 3600 + } //_#11 Haiti:intvector { 2, 8, -1, 7200, 0, 10, 1, -1, 7200, 0, 3600 - } //_#13 + } //_#12 Iran:intvector { 2, 20, 0, 86400, 0, 8, 20, 0, 86400, 0, 3600 - } //_#14 + } //_#13 Jordan:intvector { 2, -31, -5, 86400, 0, 9, -31, -6, 0, 1, 3600 - } //_#15 + } //_#14 LH:intvector { 9, 1, -1, 7200, 0, 3, 1, -1, 7200, 0, 1800 - } //_#16 + } //_#15 Lebanon:intvector { 2, -31, -1, 0, 0, 9, -31, -1, 0, 0, 3600 - } //_#17 + } //_#16 Mexico:intvector { 3, 1, -1, 7200, 0, 9, -31, -1, 7200, 0, 3600 - } //_#18 + } //_#17 Moldova:intvector { 2, -31, -1, 7200, 0, 9, -31, -1, 10800, 0, 3600 - } //_#19 + } //_#18 NZ:intvector { 8, -30, -1, 7200, 1, 3, 1, -1, 7200, 1, 3600 - } //_#20 + } //_#19 Palestine:intvector { - 2, 24, -7, 3600, 0, 9, -31, -7, 3600, 0, 3600 - } //_#21 + 2, -31, -6, 0, 0, 9, -31, -7, 3600, 0, 3600 + } //_#20 Para:intvector { 9, 1, -1, 0, 0, 2, 22, -1, 0, 0, 3600 - } //_#22 + } //_#21 Syria:intvector { 2, -31, -6, 0, 0, 9, -31, -6, 0, 0, 3600 - } //_#23 + } //_#22 SystemV:intvector { 3, -30, -1, 7200, 0, 9, -31, -1, 7200, 0, 3600 - } //_#24 + } //_#23 Thule:intvector { 2, 8, -1, 7200, 0, 10, 1, -1, 7200, 0, 3600 - } //_#25 + } //_#24 Troll:intvector { 2, -31, -1, 3600, 2, 9, -31, -1, 3600, 2, 7200 - } //_#26 + } //_#25 US:intvector { 2, 8, -1, 7200, 0, 10, 1, -1, 7200, 0, 3600 - } //_#27 + } //_#26 WS:intvector { 8, -30, -1, 10800, 0, 3, 1, -1, 14400, 0, 3600 - } //_#28 + } //_#27 Zion:intvector { 2, 23, -6, 7200, 0, 9, -31, -1, 7200, 0, 3600 - } //_#29 + } //_#28 } Regions:array { "AU", //Z#0 ACT diff --git a/js/src/builtin/IntlTimeZoneData.h b/js/src/builtin/IntlTimeZoneData.h index 8f963ffbc..1612f0f6b 100644 --- a/js/src/builtin/IntlTimeZoneData.h +++ b/js/src/builtin/IntlTimeZoneData.h @@ -1,5 +1,5 @@ // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2019a +// tzdata version = 2019c #ifndef builtin_IntlTimeZoneData_h #define builtin_IntlTimeZoneData_h diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js b/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js index 8cda44d87..8bd0512c5 100644 --- a/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js +++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js @@ -1,7 +1,7 @@ // |reftest| skip-if(!this.hasOwnProperty("Intl")) // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2019a +// tzdata version = 2019c const tzMapper = [ x => x, diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js index a3efe0310..c760fd85e 100644 --- a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js +++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js @@ -1,7 +1,7 @@ // |reftest| skip-if(!this.hasOwnProperty("Intl")) // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2019a +// tzdata version = 2019c const tzMapper = [ x => x, diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js index b61593f78..38db5e77d 100644 --- a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js +++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js @@ -1,7 +1,7 @@ // |reftest| skip-if(!this.hasOwnProperty("Intl")) // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2019a +// tzdata version = 2019c const tzMapper = [ x => x, diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js b/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js index 12b55c214..64a25c241 100644 --- a/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js +++ b/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js @@ -1,7 +1,7 @@ // |reftest| skip-if(!this.hasOwnProperty("Intl")) // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2019a +// tzdata version = 2019c const tzMapper = [ x => x, -- cgit v1.2.3 From d43b7d5c8fd472dd2cb935c8335f328797df2412 Mon Sep 17 00:00:00 2001 From: Dmitry Grigoryev Date: Fri, 4 Oct 2019 21:10:42 +0200 Subject: Added missing libwebp files to update.sh --- media/libwebp/update.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/media/libwebp/update.sh b/media/libwebp/update.sh index 652993004..4fff43d69 100644 --- a/media/libwebp/update.sh +++ b/media/libwebp/update.sh @@ -38,6 +38,7 @@ cp $1/src/demux/demux.c demux mkdir -p dsp cp $1/src/dsp/*.h dsp cp $1/src/dsp/alpha_processing.c dsp +cp $1/src/dsp/alpha_processing_neon.c dsp cp $1/src/dsp/alpha_processing_sse2.c dsp cp $1/src/dsp/alpha_processing_sse41.c dsp cp $1/src/dsp/dec.c dsp @@ -46,6 +47,7 @@ cp $1/src/dsp/dec_neon.c dsp cp $1/src/dsp/dec_sse2.c dsp cp $1/src/dsp/dec_sse41.c dsp cp $1/src/dsp/filters.c dsp +cp $1/src/dsp/filters_neon.c dsp cp $1/src/dsp/filters_sse2.c dsp cp $1/src/dsp/lossless.c dsp cp $1/src/dsp/lossless_neon.c dsp @@ -58,6 +60,7 @@ cp $1/src/dsp/upsampling_neon.c dsp cp $1/src/dsp/upsampling_sse2.c dsp cp $1/src/dsp/upsampling_sse41.c dsp cp $1/src/dsp/yuv.c dsp +cp $1/src/dsp/yuv_neon.c dsp cp $1/src/dsp/yuv_sse2.c dsp cp $1/src/dsp/yuv_sse41.c dsp -- cgit v1.2.3 From 1a86abc159e788c40ebdfc6a99c9143d168737af Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Tue, 8 Oct 2019 21:02:28 +0200 Subject: No Issue - Expand HWA over RDP to Windows 8.1 and 10. When Mozilla implemented this initially, only Windows 8 existed. Because of the strict equal check, 8.1 and 10 didn't get HWA over RDP while they are perfectly capable of doing so with RemoteFX. This change allows any version of Windows from 8.0 onwards to use HWA over RDP. --- widget/windows/GfxInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp index c62f5873e..6f3c5313f 100644 --- a/widget/windows/GfxInfo.cpp +++ b/widget/windows/GfxInfo.cpp @@ -329,7 +329,7 @@ GfxInfo::Init() // Unfortunately, the Device ID is nullptr, and we can't enumerate // it using the setup infrastructure (SetupDiGetClassDevsW below // will return INVALID_HANDLE_VALUE). - if (mWindowsVersion == kWindows8 && + if (mWindowsVersion >= kWindows8 && mDeviceID.Length() == 0 && mDeviceString.EqualsLiteral("RDPUDD Chained DD")) { -- cgit v1.2.3 From a68a09618787980ff58b248c484b4017e3d2e663 Mon Sep 17 00:00:00 2001 From: Dmitry Grigoryev Date: Fri, 4 Oct 2019 22:58:16 +0200 Subject: Replace calls to undefined functions isMarkable() and toMarkablePointer() --- js/src/jit/arm/MacroAssembler-arm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index a4161ab00..3421001f7 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -3462,8 +3462,8 @@ MacroAssemblerARMCompat::storePayload(const Value& val, const Address& dest) ScratchRegisterScope scratch(asMasm()); SecondScratchRegisterScope scratch2(asMasm()); - if (val.isMarkable()) - ma_mov(ImmGCPtr(val.toMarkablePointer()), scratch); + if (val.isGCThing()) + ma_mov(ImmGCPtr(val.toGCThing()), scratch); else ma_mov(Imm32(val.toNunboxPayload()), scratch); ma_str(scratch, ToPayload(dest), scratch2); -- cgit v1.2.3 From e31d79e8da99456247df84c2f99ba9083d46efe1 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Wed, 16 Oct 2019 12:37:07 -0400 Subject: Fix build errors with newer glibc versions --- js/src/jsnativestack.cpp | 6 +----- tools/profiler/tasktracer/GeckoTaskTracer.cpp | 12 +++--------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/js/src/jsnativestack.cpp b/js/src/jsnativestack.cpp index 98f8fc741..94a296bd0 100644 --- a/js/src/jsnativestack.cpp +++ b/js/src/jsnativestack.cpp @@ -26,11 +26,7 @@ # include # include # include -static pid_t -gettid() -{ - return syscall(__NR_gettid); -} +# define gettid() static_cast(syscall(SYS_gettid)) # endif #else diff --git a/tools/profiler/tasktracer/GeckoTaskTracer.cpp b/tools/profiler/tasktracer/GeckoTaskTracer.cpp index ada695614..36d1bffc3 100644 --- a/tools/profiler/tasktracer/GeckoTaskTracer.cpp +++ b/tools/profiler/tasktracer/GeckoTaskTracer.cpp @@ -20,22 +20,16 @@ #include -// We need a definition of gettid(), but glibc doesn't provide a +// We need a definition of gettid(), but older glibc versions don't provide a // wrapper for it. #if defined(__GLIBC__) #include #include -static inline pid_t gettid() -{ - return (pid_t) syscall(SYS_gettid); -} +#define gettid() static_cast(syscall(SYS_gettid)) #elif defined(XP_MACOSX) #include #include -static inline pid_t gettid() -{ - return (pid_t) syscall(SYS_thread_selfid); -} +#define gettid() static_cast(syscall(SYS_thread_selfid)) #elif defined(LINUX) #include pid_t gettid(); -- cgit v1.2.3 From b51ff1f69273f116eb9a1a36043e23d77d2235cb Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Wed, 16 Oct 2019 12:43:20 -0400 Subject: Don't treat format warnings as errors in xpconnect GCC 9 compiler does not like the way we have it in XPCWrappedNative.cpp --- js/xpconnect/src/moz.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/xpconnect/src/moz.build b/js/xpconnect/src/moz.build index 7d9cd5b37..29cfc4776 100644 --- a/js/xpconnect/src/moz.build +++ b/js/xpconnect/src/moz.build @@ -66,4 +66,4 @@ LOCAL_INCLUDES += [ ] if CONFIG['GNU_CXX']: - CXXFLAGS += ['-Wno-shadow', '-Werror=format'] + CXXFLAGS += ['-Wno-shadow'] -- cgit v1.2.3 From fb74b5be30ff15e915e099923c3479f9743a9faf Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Thu, 17 Oct 2019 19:20:58 -0400 Subject: Issue #1243 - Update List of NSS Exported Symbols Add NSS_CMSSignedData_GetDigestAlgs and NSS_CMSSignedData_hasDigests which are required for security patches in mailnews applications. Ref: m-c bug 1526473 --- config/external/nss/nss.symbols | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/external/nss/nss.symbols b/config/external/nss/nss.symbols index 7a968b6c8..9172407b2 100644 --- a/config/external/nss/nss.symbols +++ b/config/external/nss/nss.symbols @@ -216,7 +216,9 @@ NSS_CMSSignedData_Create NSS_CMSSignedData_CreateCertsOnly NSS_CMSSignedData_Destroy NSS_CMSSignedData_GetContentInfo +NSS_CMSSignedData_GetDigestAlgs NSS_CMSSignedData_GetSignerInfo +NSS_CMSSignedData_HasDigests NSS_CMSSignedData_ImportCerts NSS_CMSSignedData_SetDigestValue NSS_CMSSignedData_SignerInfoCount -- cgit v1.2.3 From 38feb30d4e1dd0126fd4944fa47d4f445213aed4 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Mon, 21 Oct 2019 08:42:45 +0200 Subject: Issue #1231 - Correct defines for Mac and keep universal prefs generic. --- modules/libpref/init/all.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index b64c157c5..ea76c30e5 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -732,19 +732,19 @@ pref("gfx.layerscope.port", 23456); // This should be use to quickly find which slow paths are used by test cases. pref("gfx.perf-warnings.enabled", false); +// Color Management System // 0 = Off, 1 = All Images, 2 = Tagged Images Only. // See eCMSMode in gfx/thebes/gfxPlatform.h -#ifdef XP_WIN +// Enabled by default on Windows and Mac, disabled elsewhere +#if defined(XP_WIN) || defined(XP_MACOSX) pref("gfx.color_management.mode", 2); -pref("gfx.color_management.display_profile", ""); -pref("gfx.color_management.rendering_intent", 0); -pref("gfx.color_management.enablev4", true); #else pref("gfx.color_management.mode", 0); +#endif pref("gfx.color_management.display_profile", ""); pref("gfx.color_management.rendering_intent", 0); -pref("gfx.color_management.enablev4", false); -#endif +pref("gfx.color_management.enablev4", true); + pref("gfx.downloadable_fonts.enabled", true); pref("gfx.downloadable_fonts.fallback_delay", 3000); -- cgit v1.2.3 From c4c4189933f8d610e501cfc8c86dfbe52c4c160d Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Mon, 21 Oct 2019 11:16:19 +0200 Subject: Issue #1229 - Remove fallback for $INSTDIR This resolves #1229 --- toolkit/mozapps/installer/windows/nsis/common.nsh | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/toolkit/mozapps/installer/windows/nsis/common.nsh b/toolkit/mozapps/installer/windows/nsis/common.nsh index 36e228797..57a25df9d 100755 --- a/toolkit/mozapps/installer/windows/nsis/common.nsh +++ b/toolkit/mozapps/installer/windows/nsis/common.nsh @@ -5577,21 +5577,6 @@ StrCpy $INSTDIR "$R9" !endif - ; If the user doesn't have write access to the installation directory set - ; the installation directory to a subdirectory of the All Users application - ; directory and if the user can't write to that location set the installation - ; directory to a subdirectory of the users local application directory - ; (e.g. non-roaming). - ${CanWriteToInstallDir} $R9 - StrCmp "$R9" "false" +1 finish_check_install_dir - - SetShellVarContext all ; Set SHCTX to All Users - StrCpy $INSTDIR "$APPDATA\${BrandFullName}\" - ${CanWriteToInstallDir} $R9 - StrCmp "$R9" "false" +2 +1 - StrCpy $INSTDIR "$LOCALAPPDATA\${BrandFullName}\" - - finish_check_install_dir: IfFileExists "$INSTDIR" +3 +1 Pop $R9 Return -- cgit v1.2.3 From afc187cc3f907947453b428f857acf16b2b0774e Mon Sep 17 00:00:00 2001 From: athenian200 Date: Tue, 1 Oct 2019 06:07:31 -0500 Subject: MoonchildProductions#1251 - Part 1: Restore initial Solaris support, fixed up. Compared with what Pale Moon had for Solaris originally, this is mostly the same zero point I started patching from, but I've made the following changes here after reviewing all this initial code I never looked at closely before. 1. In package-manifest.in for both Basilisk and Pale Moon, I've made the SPARC code for libfreebl not interefere with the x86 code, use the proper build flags, and also updated it to allow a SPARC64 build which is more likely to be used than the 32-bit SPARC code we had there. 2. See Mozilla bug #832272 and the old rules.mk patch from around Firefox 30 in oracle/solaris-userland. I believe they screwed up NSINSTALL on Solaris when they were trying to streamline the NSS buildsystem, because they started having unexplained issues with it around that time after Firefox 22 that they never properly resolved until Mozilla began building NSS with gyp files. I'm actually not even sure how relevant the thing they broke actually is to Solaris at this point, bug 665509 is so old it predates Firefox itself and goes back to the Mozilla suite days. I believe $(INSTALL) -t was wrong, and they meant $(NSINSTALL) -t because that makes more sense and is closer to what was there originally. It's what they have for WINNT, and it's possible a fix more like that could serve for Solaris as well. Alternatively, we could get rid of all these half-broken Makefiles and start building NSS with gyp files like Mozilla did. 3. I've completely cut out support for the Sun compiler and taken into account the reality that everyone builds Firefox (and therefore its forks) with GCC now on Solaris. This alone helped clean up a lot of the uglier parts of the code. 4. I've updated all remaining SOLARIS build flags to the newer XP_SOLARIS, because the SOLARIS flag is no longer set when building Solaris. 5. I've confirmed the workaround in gtxFontconfigFonts.cpp is no longer necessary. The Solaris people got impatient about implementing a half-baked patch for a fontconfig feature that wasn't ready yet back in 2009, and somehow convinced Mozilla to patch their software to work around it when really they should have just fixed or removed their broken fontconfig patch. The feature they wanted has since been implemented properly, and no version of Solaris still uses the broken patch that required this fix. If anyone had ever properly audited this code, it would have been removed a long time ago. --- Makefile.in | 2 +- accessible/tests/mochitest/common.js | 1 + .../tests/mochitest/elm/test_nsApplicationAcc.html | 2 +- .../mochitest/treeupdate/test_contextmenu.xul | 6 +- .../tests/mochitest/treeupdate/test_menu.xul | 4 +- application/basilisk/installer/package-manifest.in | 7 ++ application/palemoon/installer/package-manifest.in | 7 ++ build/gyp.mozbuild | 1 + config/external/nss/Makefile.in | 20 ++++ dom/base/nsContentUtils.h | 4 + dom/plugins/base/nptypes.h | 13 ++ dom/plugins/base/nsPluginsDirUnix.cpp | 16 ++- .../tests/test_htmleditor_keyevent_handling.html | 2 +- editor/reftests/xul/platform.js | 2 + js/src/Makefile.in | 11 ++ js/src/builtin/TestingFunctions.cpp | 5 +- js/src/ctypes/CTypes.cpp | 4 + js/src/jsnativestack.cpp | 13 ++ js/src/moz.build | 8 ++ js/src/vm/Time.cpp | 12 ++ js/xpconnect/src/XPCShellImpl.cpp | 2 + layout/style/nsRuleNode.cpp | 3 + memory/mozjemalloc/jemalloc.c | 21 +++- .../components/osfile/modules/osfile_unix_back.jsm | 16 ++- toolkit/library/moz.build | 6 + toolkit/mozapps/update/common/updatedefines.h | 4 + toolkit/mozapps/update/updater/updater.cpp | 82 +++++++++++++ toolkit/xre/nsSigHandlers.cpp | 57 +++++++++ xpcom/base/nsDebugImpl.cpp | 8 ++ xpcom/base/nsMemoryReporterManager.cpp | 83 +++++++++++++ xpcom/ds/nsMathUtils.h | 4 + xpcom/io/nsLocalFileUnix.cpp | 17 +++ xpcom/reflect/xptcall/md/unix/moz.build | 32 ++++- .../md/unix/xptcinvoke_asm_sparc_solaris_GCC3.s | 52 ++++++++ .../xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S | 122 +++++++++++++++++++ .../xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp | 4 +- .../xptcall/md/unix/xptcinvoke_sparc_solaris.cpp | 131 +++++++++++++++++++++ .../xptcall/md/unix/xptcstubs_asm_sparc_solaris.s | 49 ++++++++ .../xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp | 7 +- .../xptcall/md/unix/xptcstubs_sparc_solaris.cpp | 112 ++++++++++++++++++ xpcom/reflect/xptcall/status.html | 14 +++ 41 files changed, 945 insertions(+), 21 deletions(-) create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_solaris_GCC3.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_solaris.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_solaris.cpp diff --git a/Makefile.in b/Makefile.in index 4adf3ac9a..26cd688d4 100644 --- a/Makefile.in +++ b/Makefile.in @@ -290,7 +290,7 @@ MAKE_SYM_STORE_PATH := $(DIST)/bin endif DUMP_SYMS_BIN ?= $(DIST)/host/bin/dump_syms endif -ifeq (,$(filter-out Linux,$(OS_ARCH))) +ifeq (,$(filter-out Linux SunOS,$(OS_ARCH))) MAKE_SYM_STORE_ARGS := -c --vcs-info DUMP_SYMS_BIN ?= $(DIST)/host/bin/dump_syms MAKE_SYM_STORE_PATH := $(DIST)/bin diff --git a/accessible/tests/mochitest/common.js b/accessible/tests/mochitest/common.js index b3f83ea84..1e48fa067 100644 --- a/accessible/tests/mochitest/common.js +++ b/accessible/tests/mochitest/common.js @@ -59,6 +59,7 @@ const nsIPropertyElement = Components.interfaces.nsIPropertyElement; const MAC = (navigator.platform.indexOf("Mac") != -1); const LINUX = (navigator.platform.indexOf("Linux") != -1); +const SOLARIS = (navigator.platform.indexOf("SunOS") != -1); const WIN = (navigator.platform.indexOf("Win") != -1); //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/tests/mochitest/elm/test_nsApplicationAcc.html b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html index e23eb37bc..58763e437 100644 --- a/accessible/tests/mochitest/elm/test_nsApplicationAcc.html +++ b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html @@ -32,7 +32,7 @@ // nsIAccessible::name var applicationName = ""; - if (LINUX) { + if (LINUX || SOLARIS) { applicationName = appInfo.name; } else { try { diff --git a/accessible/tests/mochitest/treeupdate/test_contextmenu.xul b/accessible/tests/mochitest/treeupdate/test_contextmenu.xul index 916b815eb..5b31e0136 100644 --- a/accessible/tests/mochitest/treeupdate/test_contextmenu.xul +++ b/accessible/tests/mochitest/treeupdate/test_contextmenu.xul @@ -130,7 +130,7 @@ */ function getMenuTree1() { - if (LINUX) { + if (LINUX || SOLARIS) { var tree = { role: ROLE_MENUPOPUP, children: [ @@ -190,7 +190,7 @@ function getMenuTree2() { var tree = getMenuTree1(); - if (LINUX) { + if (LINUX || SOLARIS) { var submenuTree = { name: "item2.0", @@ -232,7 +232,7 @@ children: [] }; - if (LINUX) + if (LINUX || SOLARIS) tree.children[2].children[0].children.push(subsubmenuTree); else tree.children[2].children[0].children[0].children[0].children.push(subsubmenuTree); diff --git a/accessible/tests/mochitest/treeupdate/test_menu.xul b/accessible/tests/mochitest/treeupdate/test_menu.xul index d62ac8c5d..abdea217e 100644 --- a/accessible/tests/mochitest/treeupdate/test_menu.xul +++ b/accessible/tests/mochitest/treeupdate/test_menu.xul @@ -32,7 +32,7 @@ this.invoke = function openMenu_invoke() { var tree; - if (LINUX) { + if (LINUX || SOLARIS) { tree = { PARENT_MENUITEM: [ ] }; @@ -51,7 +51,7 @@ this.finalCheck = function openMenu_finalCheck() { var tree; - if (LINUX) { + if (LINUX || SOLARIS) { tree = { PARENT_MENUITEM: [ { MENUITEM: [ ] }, diff --git a/application/basilisk/installer/package-manifest.in b/application/basilisk/installer/package-manifest.in index bffab0f6e..d8f65bcfb 100644 --- a/application/basilisk/installer/package-manifest.in +++ b/application/basilisk/installer/package-manifest.in @@ -685,6 +685,13 @@ #ifndef MOZ_SYSTEM_NSS #if defined(XP_LINUX) && !defined(ANDROID) @BINPATH@/@DLL_PREFIX@freeblpriv3@DLL_SUFFIX@ +#elif defined(XP_SOLARIS) && defined(SPARC64) +@BINPATH@/@DLL_PREFIX@freebl_64fpu_3@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@freebl_64int_3@DLL_SUFFIX@ +#elif defined(XP_SOLARIS) && defined(SPARC) +@BINPATH@/@DLL_PREFIX@freebl_32fpu_3@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@freebl_32int64_3@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@freebl3@DLL_SUFFIX@ #else @BINPATH@/@DLL_PREFIX@freebl3@DLL_SUFFIX@ #endif diff --git a/application/palemoon/installer/package-manifest.in b/application/palemoon/installer/package-manifest.in index f95f18f77..e24b605e8 100644 --- a/application/palemoon/installer/package-manifest.in +++ b/application/palemoon/installer/package-manifest.in @@ -296,6 +296,13 @@ #ifndef MOZ_SYSTEM_NSS #if defined(XP_LINUX) && !defined(ANDROID) @BINPATH@/@DLL_PREFIX@freeblpriv3@DLL_SUFFIX@ +#elif defined(XP_SOLARIS) && defined(SPARC64) +@BINPATH@/@DLL_PREFIX@freebl_64fpu_3@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@freebl_64int_3@DLL_SUFFIX@ +#elif defined(XP_SOLARIS) && defined(SPARC) +@BINPATH@/@DLL_PREFIX@freebl_32fpu_3@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@freebl_32int64_3@DLL_SUFFIX@ +@BINPATH@/@DLL_PREFIX@freebl3@DLL_SUFFIX@ #else @BINPATH@/@DLL_PREFIX@freebl3@DLL_SUFFIX@ #endif diff --git a/build/gyp.mozbuild b/build/gyp.mozbuild index 545b888bd..fd8714ecf 100644 --- a/build/gyp.mozbuild +++ b/build/gyp.mozbuild @@ -89,6 +89,7 @@ flavors = { 'Android': 'android', 'Linux': 'linux', 'Darwin': 'mac' if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa' else 'ios', + 'SunOS': 'solaris', 'GNU/kFreeBSD': 'freebsd', 'DragonFly': 'dragonfly', 'FreeBSD': 'freebsd', diff --git a/config/external/nss/Makefile.in b/config/external/nss/Makefile.in index 2c266eb20..71954b403 100644 --- a/config/external/nss/Makefile.in +++ b/config/external/nss/Makefile.in @@ -43,6 +43,21 @@ endif # Default HAVE_FREEBL_LIBS = 1 +# SunOS SPARC + +ifeq ($(OS_ARCH), SunOS) +ifneq (86,$(findstring 86,$(OS_TEST))) +ifdef HAVE_64BIT_BUILD +HAVE_FREEBL_LIBS = +HAVE_FREEBL_LIBS_64 = 1 +else +HAVE_FREEBL_LIBS = +HAVE_FREEBL_LIBS_32FPU = 1 +HAVE_FREEBL_LIBS_32INT64 = 1 +endif +endif +endif + ifeq ($(OS_TARGET),Linux) HAVE_FREEBL_LIBS = HAVE_FREEBL_LIBS_PRIV = 1 @@ -317,6 +332,11 @@ NSS_DIST_DLL_DEST := $(DIST)/bin NSS_DIST_DLL_TARGET := target INSTALL_TARGETS += NSS_DIST_DLL +ifeq ($(OS_ARCH)_$(1), SunOS_softokn3) +# has to use copy mode on Solaris, see #665509 +$(DIST)/bin/$(DLL_PREFIX)softokn3$(DLL_SUFFIX): INSTALL := $(NSINSTALL) -t +endif + NSS_SDK_LIB_FILES := \ $(addprefix $(DIST)/lib/$(LIB_PREFIX),$(addsuffix .$(LIB_SUFFIX),$(SDK_LIBS))) \ $(addprefix $(DIST)/bin/$(DLL_PREFIX),$(addsuffix $(DLL_SUFFIX),$(NSS_DLLS))) \ diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 299a8e859..26635acd6 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -13,6 +13,10 @@ #include #endif +#if defined(XP_SOLARIS) +#include +#endif + #include "js/TypeDecls.h" #include "js/Value.h" #include "js/RootingAPI.h" diff --git a/dom/plugins/base/nptypes.h b/dom/plugins/base/nptypes.h index c36532472..d0cef6540 100644 --- a/dom/plugins/base/nptypes.h +++ b/dom/plugins/base/nptypes.h @@ -22,6 +22,19 @@ typedef unsigned int uint32_t; typedef long long int64_t; typedef unsigned long long uint64_t; +#elif defined(__sun) + /* + * SunOS ships an inttypes.h header that defines [u]int32_t, + * but not bool for C. + */ + #include + + + #ifndef __cplusplus + typedef int bool; + #define true 1 + #define false 0 + #endif #elif defined(bsdi) || defined(FREEBSD) || defined(OPENBSD) /* * BSD/OS, FreeBSD, and OpenBSD ship sys/types.h that define int32_t and diff --git a/dom/plugins/base/nsPluginsDirUnix.cpp b/dom/plugins/base/nsPluginsDirUnix.cpp index e6956c34c..22d6bd44b 100644 --- a/dom/plugins/base/nsPluginsDirUnix.cpp +++ b/dom/plugins/base/nsPluginsDirUnix.cpp @@ -19,7 +19,9 @@ #include "nsIPrefService.h" #define LOCAL_PLUGIN_DLL_SUFFIX ".so" -#if defined(LINUX) +#if defined(XP_SOLARIS) +#define DEFAULT_X11_PATH "/usr/openwin/lib" +#elif defined(LINUX) #define DEFAULT_X11_PATH "/usr/X11R6/lib/" #elif defined(__APPLE__) #define DEFAULT_X11_PATH "/usr/X11R6/lib" @@ -92,7 +94,11 @@ static bool LoadExtraSharedLib(const char *name, char **soname, bool tryToGetSon #define PLUGIN_MAX_NUMBER_OF_EXTRA_LIBS 32 #define PREF_PLUGINS_SONAME "plugin.soname.list" +#if defined (XP_SOLARIS) +#define DEFAULT_EXTRA_LIBS_LIST "libXt" LOCAL_PLUGIN_DLL_SUFFIX ":libXext" LOCAL_PLUGIN_DLL_SUFFIX ":libXm" LOCAL_PLUGIN_DLL_SUFFIX +#else #define DEFAULT_EXTRA_LIBS_LIST "libXt" LOCAL_PLUGIN_DLL_SUFFIX ":libXext" LOCAL_PLUGIN_DLL_SUFFIX +#endif /* this function looks for user_pref("plugin.soname.list", "/usr/X11R6/lib/libXt.so.6:libXext.so"); @@ -264,11 +270,15 @@ nsresult nsPluginFile::LoadPlugin(PRLibrary **outLibrary) // at runtime. Explicitly opening Xt/Xext into the global // namespace before attempting to load the plug-in seems to // work fine. - - +#if defined(XP_SOLARIS) + // Acrobat/libXm: Lazy resolving might cause crash later (bug 211587) + *outLibrary = PR_LoadLibraryWithFlags(libSpec, PR_LD_NOW); + pLibrary = *outLibrary; +#else // Some dlopen() doesn't recover from a failed PR_LD_NOW (bug 223744) *outLibrary = PR_LoadLibraryWithFlags(libSpec, 0); pLibrary = *outLibrary; +#endif if (!pLibrary) { LoadExtraSharedLibs(); // try reload plugin once more diff --git a/editor/libeditor/tests/test_htmleditor_keyevent_handling.html b/editor/libeditor/tests/test_htmleditor_keyevent_handling.html index 414045ac0..bfec290a5 100644 --- a/editor/libeditor/tests/test_htmleditor_keyevent_handling.html +++ b/editor/libeditor/tests/test_htmleditor_keyevent_handling.html @@ -27,7 +27,7 @@ var htmlEditor = document.getElementById("htmlEditor"); const kIsMac = navigator.platform.indexOf("Mac") == 0; const kIsWin = navigator.platform.indexOf("Win") == 0; -const kIsLinux = navigator.platform.indexOf("Linux") == 0; +const kIsLinux = navigator.platform.indexOf("Linux") == 0 || navigator.platform.indexOf("SunOS") == 0 ; function runTests() { diff --git a/editor/reftests/xul/platform.js b/editor/reftests/xul/platform.js index a8633fb09..d3f5d33bf 100644 --- a/editor/reftests/xul/platform.js +++ b/editor/reftests/xul/platform.js @@ -13,6 +13,8 @@ if (/Windows/.test(ua)) { } else if (/Linux/.test(ua)) id = "linux"; +else if (/SunOS/.test(ua)) + id = "linux"; else if (/Mac OS X/.test(ua)) id = "mac"; diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 20678c68c..bc99e62b5 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -138,6 +138,17 @@ distclean:: CFLAGS += $(MOZ_ZLIB_CFLAGS) +ifeq ($(OS_ARCH),SunOS) +ifeq ($(TARGET_CPU),sparc) + +ifdef GNU_CC +CFLAGS += -mcpu=v9 +CXXFLAGS += -mcpu=v9 +endif #GNU_CC + +endif +endif + $(LIBRARY_NAME).pc: js.pc cp $^ $@ diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 992fe2c97..6beb82932 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -239,8 +239,11 @@ GetBuildConfiguration(JSContext* cx, unsigned argc, Value* vp) value = BooleanValue(true); if (!JS_SetProperty(cx, info, "intl-api", value)) return false; - +#if defined(XP_SOLARIS) + value = BooleanValue(false); +#else value = BooleanValue(true); +#endif if (!JS_SetProperty(cx, info, "mapped-array-buffer", value)) return false; diff --git a/js/src/ctypes/CTypes.cpp b/js/src/ctypes/CTypes.cpp index d6adfac2c..aed1114bd 100644 --- a/js/src/ctypes/CTypes.cpp +++ b/js/src/ctypes/CTypes.cpp @@ -20,6 +20,10 @@ #include #endif +#if defined(XP_SOLARIS) +#include +#endif + #ifdef HAVE_SSIZE_T #include #endif diff --git a/js/src/jsnativestack.cpp b/js/src/jsnativestack.cpp index 94a296bd0..389d7e657 100644 --- a/js/src/jsnativestack.cpp +++ b/js/src/jsnativestack.cpp @@ -67,6 +67,19 @@ js::GetNativeStackBaseImpl() # endif } +#elif defined(XP_SOLARIS) + +#include + +JS_STATIC_ASSERT(JS_STACK_GROWTH_DIRECTION < 0); + +void js::GetNativeStackBaseImpl() +{ + stack_t st; + stack_getbounds(&st); + return static_cast(st.ss_sp) + st.ss_size; +} + #elif defined(XP_LINUX) && !defined(ANDROID) && defined(__GLIBC__) void* js::GetNativeStackBaseImpl() diff --git a/js/src/moz.build b/js/src/moz.build index a0f074d1c..47ffe0159 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -721,6 +721,14 @@ if CONFIG['OS_ARCH'] == 'Linux': 'dl', ] +if CONFIG['OS_ARCH'] == 'SunOS': + OS_LIBS += [ + 'posix4', + 'dl', + 'nsl', + 'socket', + ] + OS_LIBS += CONFIG['REALTIME_LIBS'] CFLAGS += CONFIG['MOZ_ICU_CFLAGS'] diff --git a/js/src/vm/Time.cpp b/js/src/vm/Time.cpp index 87531c148..a9a5b7f0f 100644 --- a/js/src/vm/Time.cpp +++ b/js/src/vm/Time.cpp @@ -11,6 +11,10 @@ #include "mozilla/DebugOnly.h" #include "mozilla/MathAlgorithms.h" +#ifdef XP_SOLARIS +#define _REENTRANT 1 +#endif + #include #include @@ -30,6 +34,10 @@ #ifdef XP_UNIX +#ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris */ +extern int gettimeofday(struct timeval* tv); +#endif + #include #endif /* XP_UNIX */ @@ -42,7 +50,11 @@ PRMJ_Now() { struct timeval tv; +#ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris */ + gettimeofday(&tv); +#else gettimeofday(&tv, 0); +#endif /* _SVID_GETTOD */ return int64_t(tv.tv_sec) * PRMJ_USEC_PER_SEC + int64_t(tv.tv_usec); } diff --git a/js/xpconnect/src/XPCShellImpl.cpp b/js/xpconnect/src/XPCShellImpl.cpp index 4ddc8deb3..abe50f449 100644 --- a/js/xpconnect/src/XPCShellImpl.cpp +++ b/js/xpconnect/src/XPCShellImpl.cpp @@ -644,6 +644,7 @@ env_setProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue ObjectOpResult& result) { /* XXX porting may be easy, but these don't seem to supply setenv by default */ +#if !defined XP_SOLARIS RootedString valstr(cx); RootedString idstr(cx); int rv; @@ -686,6 +687,7 @@ env_setProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue return false; } vp.setString(valstr); +#endif /* !defined XP_SOLARIS */ return result.succeed(); } diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 08400635b..a0f65c069 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -67,6 +67,9 @@ #define alloca _alloca #endif #endif +#ifdef XP_SOLARIS +#include +#endif using std::max; using std::min; diff --git a/memory/mozjemalloc/jemalloc.c b/memory/mozjemalloc/jemalloc.c index 9a97bbb09..c54a85959 100644 --- a/memory/mozjemalloc/jemalloc.c +++ b/memory/mozjemalloc/jemalloc.c @@ -280,7 +280,9 @@ typedef long ssize_t; #define JEMALLOC_RECYCLE #ifndef MOZ_MEMORY_WINDOWS +#ifndef MOZ_MEMORY_SOLARIS #include +#endif #ifndef __DECONST # define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) #endif @@ -306,7 +308,7 @@ __FBSDID("$FreeBSD: head/lib/libc/stdlib/malloc.c 180599 2008-07-18 19:35:44Z ja #endif #include #include -#if !defined(MOZ_MEMORY_ANDROID) +#if !defined(MOZ_MEMORY_SOLARIS) && !defined(MOZ_MEMORY_ANDROID) #include #endif #include @@ -408,6 +410,10 @@ void *_mmap(void *addr, size_t length, int prot, int flags, #endif #endif +#if defined(MOZ_MEMORY_SOLARIS && defined(MAP_ALIGN) && !defined(JEMALLOC_NEVER_USES_MAP_ALIGN) +#define JEMALLOC_USES_MAP_ALIGN /* Required on Solaris 10. Might improve performance elsewhere. */ +#endif + #ifndef __DECONST #define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) #endif @@ -5120,6 +5126,13 @@ malloc_ncpus(void) else return (n); } +#elif (defined(MOZ_MEMORY_SOLARIS)) + +static inline unsigned +malloc_ncpus(void) +{ + return sysconf(_SC_NPROCESSORS_ONLN); +} #elif (defined(MOZ_MEMORY_WINDOWS)) static inline unsigned malloc_ncpus(void) @@ -5916,9 +5929,15 @@ RETURN: #define MOZ_MEMORY_ELF #endif +#ifdef MOZ_MEMORY_SOLARIS +# if (defined(__GNUC__)) +__attribute__((noinline)) +# endif +#else #if (defined(MOZ_MEMORY_ELF)) __attribute__((visibility ("hidden"))) #endif +#endif #endif /* MOZ_REPLACE_MALLOC */ #ifdef MOZ_MEMORY_ELF diff --git a/toolkit/components/osfile/modules/osfile_unix_back.jsm b/toolkit/components/osfile/modules/osfile_unix_back.jsm index bf5c66b8d..7c2c6f28d 100644 --- a/toolkit/components/osfile/modules/osfile_unix_back.jsm +++ b/toolkit/components/osfile/modules/osfile_unix_back.jsm @@ -585,10 +585,18 @@ } else if (Const._STAT_VER != undefined) { const ver = Const._STAT_VER; let xstat_name, lxstat_name, fxstat_name; - // Linux, all widths - xstat_name = "__xstat"; - lxstat_name = "__lxstat"; - fxstat_name = "__fxstat"; + if (OS.Constants.Sys.Name == "SunOS") { + // Solaris + xstat_name = "_xstat"; + lxstat_name = "_lxstat"; + fxstat_name = "_fxstat"; + } else { + + // Linux, all widths + xstat_name = "__xstat"; + lxstat_name = "__lxstat"; + fxstat_name = "__fxstat"; + } let Stat = {}; libc.declareLazyFFI(Stat, "xstat", diff --git a/toolkit/library/moz.build b/toolkit/library/moz.build index ba7fb5032..cd900a615 100644 --- a/toolkit/library/moz.build +++ b/toolkit/library/moz.build @@ -270,6 +270,12 @@ if CONFIG['MOZ_ENABLE_STARTUP_NOTIFICATION']: if CONFIG['MOZ_ENABLE_LIBPROXY']: OS_LIBS += CONFIG['MOZ_LIBPROXY_LIBS'] +if CONFIG['OS_ARCH'] == 'SunOS': + OS_LIBS += [ + 'elf', + 'demangle', + ] + if CONFIG['OS_ARCH'] == 'FreeBSD': OS_LIBS += [ 'util', diff --git a/toolkit/mozapps/update/common/updatedefines.h b/toolkit/mozapps/update/common/updatedefines.h index 5790cf996..871755246 100644 --- a/toolkit/mozapps/update/common/updatedefines.h +++ b/toolkit/mozapps/update/common/updatedefines.h @@ -96,7 +96,11 @@ static inline int mywcsprintf(WCHAR* dest, size_t count, const WCHAR* fmt, ...) # include # include +#ifdef XP_SOLARIS +# include +#else # include +#endif # include #ifdef XP_MACOSX diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp index 8025deaaf..f5f71935d 100644 --- a/toolkit/mozapps/update/updater/updater.cpp +++ b/toolkit/mozapps/update/updater/updater.cpp @@ -3648,6 +3648,88 @@ int add_dir_entries(const NS_tchar *dirpath, ActionList *list) return rv; } +#elif defined(XP_SOLARIS) +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + NS_tchar foundpath[MAXPATHLEN]; + struct { + dirent dent_buffer; + char chars[MAXNAMLEN]; + } ent_buf; + struct dirent* ent; + mozilla::UniquePtr searchpath(get_full_path(dirpath)); + + DIR* dir = opendir(searchpath.get()); + if (!dir) { + LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath.get(), + errno)); + return UNEXPECTED_FILE_OPERATION_ERROR; + } + + while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent) { + if ((strcmp(ent->d_name, ".") == 0) || + (strcmp(ent->d_name, "..") == 0)) + continue; + + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s%s"), searchpath.get(), ent->d_name); + struct stat64 st_buf; + int test = stat64(foundpath, &st_buf); + if (test) { + closedir(dir); + return UNEXPECTED_FILE_OPERATION_ERROR; + } + if (S_ISDIR(st_buf.st_mode)) { + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), foundpath); + // Recurse into the directory. + rv = add_dir_entries(foundpath, list); + if (rv) { + LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv)); + closedir(dir); + return rv; + } + } else { + // Add the file to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(get_relative_path(foundpath)); + if (!quotedpath) { + closedir(dir); + return PARSE_ERROR; + } + + Action *action = new RemoveFile(); + rv = action->Parse(quotedpath); + if (rv) { + LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", + quotedpath, rv)); + closedir(dir); + return rv; + } + + list->Append(action); + } + } + closedir(dir); + + // Add the directory to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(get_relative_path(dirpath)); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveDir(); + rv = action->Parse(quotedpath); + if (rv) { + LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", + quotedpath, rv)); + } + else { + list->Append(action); + } + + return rv; +} + #else int add_dir_entries(const NS_tchar *dirpath, ActionList *list) diff --git a/toolkit/xre/nsSigHandlers.cpp b/toolkit/xre/nsSigHandlers.cpp index 454882c1b..bcbcb6e3d 100644 --- a/toolkit/xre/nsSigHandlers.cpp +++ b/toolkit/xre/nsSigHandlers.cpp @@ -32,6 +32,11 @@ #endif #endif +#if defined(XP_SOLARIS) +#include +#include +#endif + static const char* gProgname = "huh?"; // Note: some tests manipulate this value. @@ -193,6 +198,33 @@ static void fpehandler(int signum, siginfo_t *si, void *context) *mxcsr &= ~SSE_STATUS_FLAGS; /* clear all pending SSE exceptions */ #endif #endif +#ifdef XP_SOLARIS + ucontext_t *uc = (ucontext_t *)context; + +#if defined(__i386) + uint32_t *cw = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.state[0]; + *cw |= FPU_EXCEPTION_MASK; + + uint32_t *sw = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.state[1]; + *sw &= ~FPU_STATUS_FLAGS; + + /* address of the instruction that caused the exception */ + uint32_t *ip = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.state[3]; + uc->uc_mcontext.gregs[REG_PC] = *ip; +#endif +#if defined(__amd64__) + uint16_t *cw = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.cw; + *cw |= FPU_EXCEPTION_MASK; + + uint16_t *sw = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.sw; + *sw &= ~FPU_STATUS_FLAGS; + + uint32_t *mxcsr = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.mxcsr; + *mxcsr |= SSE_EXCEPTION_MASK; /* disable all SSE exceptions */ + *mxcsr &= ~SSE_STATUS_FLAGS; /* clear all pending SSE exceptions */ +#endif +#endif +} } #endif @@ -255,6 +287,31 @@ void InstallSignalHandlers(const char *aProgname) } #endif +#if defined(XP_SOLARIS) +#define NOFILES 512 + + // Boost Solaris file descriptors + { + struct rlimit rl; + + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) + + if (rl.rlim_cur < NOFILES) { + rl.rlim_cur = NOFILES; + + if (setrlimit(RLIMIT_NOFILE, &rl) < 0) { + perror("setrlimit(RLIMIT_NOFILE)"); + fprintf(stderr, "Cannot exceed hard limit for open files"); + } +#if defined(DEBUG) + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) + printf("File descriptors set to %d\n", rl.rlim_cur); +#endif //DEBUG + } + } +#endif //XP_SOLARIS + + #if defined(MOZ_WIDGET_GTK) && (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 6)) const char *assertString = PR_GetEnv("XPCOM_DEBUG_BREAK"); if (assertString && diff --git a/xpcom/base/nsDebugImpl.cpp b/xpcom/base/nsDebugImpl.cpp index a81eb3d71..84ba150db 100644 --- a/xpcom/base/nsDebugImpl.cpp +++ b/xpcom/base/nsDebugImpl.cpp @@ -450,6 +450,12 @@ RealBreak() ".object_arch armv4t\n" #endif "BKPT #0"); +#elif defined(__sun) +#if defined (__i386__) || defined(__i386) || defined(__x86_64__) + asm("int $3"); +#else + raise(SIGTRAP); +#endif #else #warning do not know how to break on this platform #endif @@ -524,6 +530,8 @@ Break(const char* aMsg) RealBreak(); #elif defined(__arm__) RealBreak(); +#elif defined(__sun) + RealBreak(); #else #warning do not know how to break on this platform #endif diff --git a/xpcom/base/nsMemoryReporterManager.cpp b/xpcom/base/nsMemoryReporterManager.cpp index 88964f9b5..9c2d620cc 100644 --- a/xpcom/base/nsMemoryReporterManager.cpp +++ b/xpcom/base/nsMemoryReporterManager.cpp @@ -317,6 +317,85 @@ VsizeMaxContiguousDistinguishedAmount(int64_t* aN) } #endif // FreeBSD +#elif defined(XP_SOLARIS) + +#include +#include +#include + +static void +XMappingIter(int64_t& aVsize, int64_t& aResident) +{ + aVsize = -1; + aResident = -1; + int mapfd = open("/proc/self/xmap", O_RDONLY); + struct stat st; + prxmap_t* prmapp = nullptr; + if (mapfd >= 0) { + if (!fstat(mapfd, &st)) { + int nmap = st.st_size / sizeof(prxmap_t); + while (1) { + // stat(2) on /proc//xmap returns an incorrect value, + // prior to the release of Solaris 11. + // Here is a workaround for it. + nmap *= 2; + prmapp = (prxmap_t*)malloc((nmap + 1) * sizeof(prxmap_t)); + if (!prmapp) { + // out of memory + break; + } + int n = pread(mapfd, prmapp, (nmap + 1) * sizeof(prxmap_t), 0); + if (n < 0) { + break; + } + if (nmap >= n / sizeof(prxmap_t)) { + aVsize = 0; + aResident = 0; + for (int i = 0; i < n / sizeof(prxmap_t); i++) { + aVsize += prmapp[i].pr_size; + aResident += prmapp[i].pr_rss * prmapp[i].pr_pagesize; + } + break; + } + free(prmapp); + } + free(prmapp); + } + close(mapfd); + } +} + +#define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +static MOZ_MUST_USE nsresult +VsizeDistinguishedAmount(int64_t* aN) +{ + int64_t vsize, resident; + XMappingIter(vsize, resident); + if (vsize == -1) { + return NS_ERROR_FAILURE; + } + *aN = vsize; + return NS_OK; +} + +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmount(int64_t* aN) +{ + int64_t vsize, resident; + XMappingIter(vsize, resident); + if (resident == -1) { + return NS_ERROR_FAILURE; + } + *aN = resident; + return NS_OK; +} + +static MOZ_MUST_USE nsresult +ResidentFastDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmount(aN); +} + #elif defined(XP_MACOSX) #include @@ -1066,9 +1145,13 @@ ResidentPeakDistinguishedAmount(int64_t* aN) if (0 == getrusage(RUSAGE_SELF, &usage)) { // The units for ru_maxrrs: // - Mac: bytes + // - Solaris: pages? But some sources it actually always returns 0, so + // check for that // - Linux, {Net/Open/Free}BSD, DragonFly: KiB #ifdef XP_MACOSX *aN = usage.ru_maxrss; +#elif defined(XP_SOLARIS) + *aN = usage.ru.maxrss * getpagesize(); #else *aN = usage.ru_maxrss * 1024; #endif diff --git a/xpcom/ds/nsMathUtils.h b/xpcom/ds/nsMathUtils.h index cbbd38611..08bf908da 100644 --- a/xpcom/ds/nsMathUtils.h +++ b/xpcom/ds/nsMathUtils.h @@ -11,6 +11,10 @@ #include #include +#ifdef XP_SOLARIS +#include +#endif + /* * round */ diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp index 272153bba..c0b5c0f48 100644 --- a/xpcom/io/nsLocalFileUnix.cpp +++ b/xpcom/io/nsLocalFileUnix.cpp @@ -1591,6 +1591,23 @@ nsLocalFile::IsExecutable(bool* aResult) // Then check the execute bit. *aResult = (access(mPath.get(), X_OK) == 0); +#ifdef XP_SOLARIS + // On Solaris, access will always return 0 for root user, however + // the file is only executable if S_IXUSR | S_IXGRP | S_IXOTH is set. + // See bug 351950, https://bugzilla.mozilla.org/show_bug.cgi?id=351950 + if (*aResult) { + struct STAT buf; + + *aResult = (STAT(mPath.get(), &buf) == 0); + if (*aResult || errno == EACCES) { + *aResult = *aResult && (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)); + return NS_OK; + } + + return NSRESULT_FOR_ERRNO(); + } +#endif + if (*aResult || errno == EACCES) { if (*aResult || errno == EACCES) { return NS_OK; } diff --git a/xpcom/reflect/xptcall/md/unix/moz.build b/xpcom/reflect/xptcall/md/unix/moz.build index d455ed854..c5cba0c8d 100644 --- a/xpcom/reflect/xptcall/md/unix/moz.build +++ b/xpcom/reflect/xptcall/md/unix/moz.build @@ -46,6 +46,21 @@ if CONFIG['OS_ARCH'] in ('Linux', 'FreeBSD'): 'xptcstubs_ipf64.cpp' ] +# Without the need to accomodate the Sun compiler, Solaris/GCC support is pretty +# standard, actually. Not all that different from Linux. + +if CONFIG['OS_ARCH'] == 'SunOS': + if CONFIG['OS_TEST'] == 'x86_64': + SOURCES += [ + 'xptcinvoke_asm_x86_64_unix.S', + 'xptcinvoke_x86_64_unix.cpp', + 'xptcstubs_x86_64_linux.cpp' + ] + elif CONFIG['OS_TEST'] == 'x86': + SOURCES += [ + 'xptcinvoke_gcc_x86_unix.cpp', + 'xptcstubs_gcc_x86_unix.cpp' + ] if CONFIG['OS_TEST'] == 'alpha': if CONFIG['OS_ARCH'] in ('Linux', 'FreeBSD', 'NetBSD'): @@ -186,7 +201,11 @@ if CONFIG['OS_ARCH'] == 'OpenBSD' and CONFIG['OS_TEST'] == 'sparc': 'xptcstubs_sparc_openbsd.cpp', ] -if CONFIG['OS_ARCH'] in ('OpenBSD', 'FreeBSD') and CONFIG['OS_TEST'] == 'sparc64': +# Mozilla discovered at some point that Solaris and Linux can also use these +# files for 64-bit SPARC with no ill effects, so basically the entire mess that +# was there before is no longer needed. + +if CONFIG['OS_ARCH'] in ('OpenBSD', 'FreeBSD', 'SunOS') and CONFIG['OS_TEST'] == 'sparc64': SOURCES += [ 'xptcinvoke_asm_sparc64_openbsd.s', 'xptcinvoke_sparc64_openbsd.cpp', @@ -194,6 +213,17 @@ if CONFIG['OS_ARCH'] in ('OpenBSD', 'FreeBSD') and CONFIG['OS_TEST'] == 'sparc64 'xptcstubs_sparc64_openbsd.cpp', ] +# As for the 32-bit SPARC build? That would look like the Linux version with +# one file changed. + +if CONFIG['OS_ARCH'] == 'SunOS' and CONFIG['OS_TEST'] == 'sparc': + SOURCES += [ + 'xptcinvoke_asm_sparc_solaris_GCC3.s', + 'xptcinvoke_sparc_solaris.cpp', + 'xptcstubs_asm_sparc_solaris.s', + 'xptcstubs_sparc_solaris.cpp', + ] + if CONFIG['OS_ARCH'] == 'Linux': if CONFIG['OS_TEST'] == 's390': SOURCES += [ diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_solaris_GCC3.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_solaris_GCC3.s new file mode 100644 index 000000000..54adcd147 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_solaris_GCC3.s @@ -0,0 +1,52 @@ +/* -*- 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/. */ + +/* + * Platform specific code to invoke XPCOM methods on native objects for + * solaris/sparc with gcc 3 ABI. + */ + .global NS_InvokeByIndex +/* + * NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + * uint32_t paramCount, nsXPTCVariant* params); + */ +NS_InvokeByIndex: + save %sp,-(64 + 32),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 32 + mov %i2,%o0 ! paramCount + call invoke_count_words ! returns the required stack size in %o0 + mov %i3,%o1 ! params + + sll %o0,2,%l0 ! number of bytes + sub %sp,%l0,%sp ! create the additional stack space + + mov %sp,%o0 ! pointer for copied args + add %o0,72,%o0 ! step past the register window, the + ! struct result pointer and the 'this' slot + mov %i2,%o1 ! paramCount + call invoke_copy_to_stack + mov %i3,%o2 ! params +! +! calculate the target address from the vtable +! + ld [%i0],%l1 ! *that --> vTable + sll %i1,2,%i1 ! multiply index by 4 + add %i1,%l1,%l1 ! l1 now points to vTable entry + ld [%l1],%l0 ! target address + +.L5: ld [%sp + 88],%o5 +.L4: ld [%sp + 84],%o4 +.L3: ld [%sp + 80],%o3 +.L2: ld [%sp + 76],%o2 +.L1: ld [%sp + 72],%o1 +.L0: + jmpl %l0,%o7 ! call the routine +! always have a 'this', from the incoming 'that' + mov %i0,%o0 + + mov %o0,%i0 ! propagate return value + ret + restore diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S new file mode 100644 index 000000000..131cfc334 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S @@ -0,0 +1,122 @@ +# 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/. + +# Darwin gives a leading '_' to symbols defined in C code. +#ifdef XP_DARWIN +#define SYM(x) _ ## x +#define CFI_STARTPROC +#define CFI_ENDPROC +#define CFI_DEF_CFA_OFFSET(offset) +#define CFI_OFFSET(reg, offset) +#define CFI_DEF_CFA_REGISTER(reg) +#define CFI_DEF_CFA(reg, offset) +#else +#define SYM(x) x +#define CFI_STARTPROC .cfi_startproc +#define CFI_ENDPROC .cfi_endproc +#define CFI_DEF_CFA_OFFSET(offset) .cfi_def_cfa_offset offset +#define CFI_OFFSET(reg, offset) .cfi_offset reg, offset +#define CFI_DEF_CFA_REGISTER(reg) .cfi_def_cfa_register reg +#define CFI_DEF_CFA(reg, offset) .cfi_def_cfa reg, offset +#endif + +.intel_syntax noprefix + +# nsresult NS_InvokeByIndex(nsISupports* this, uint32_t aVtableIndex, +# uint32_t argc, nsXPTCVariant* argv); +.text +.global SYM(NS_InvokeByIndex) +#ifndef XP_DARWIN +.type NS_InvokeByIndex, @function +#endif +.align 4 +SYM(NS_InvokeByIndex): + CFI_STARTPROC + push rbp + CFI_DEF_CFA_OFFSET(16) + CFI_OFFSET(6, -16) + mov rbp, rsp + CFI_DEF_CFA_REGISTER(6) + +# save r12 and r13 because we use them and they are callee saved. + push r12 + push r13 + CFI_OFFSET(12, -24) + CFI_OFFSET(13, -32) + +# save this and the vtable index because we need them after setting up the +# stack. + mov r12, rdi + mov r13, rsi + +# allocate space for stack arguments, in theory we only need 8 * (argc - 5) +# bytes because at least 5 arguments will go in registers, but for now it is +# just simpler to allocate 8 * argc bytes. Note that we treat the this +# pointer specially. + lea eax, [edx * 8] + sub rsp, rax + +# If there is an odd number of args the stack can be misaligned so realign it. + and rsp, 0xfffffffffffffff0 + +# pass the stack slot area to InvokeCopyToStack. + mov r8, rsp + +# setup space for the register slots: there are 5 integer ones and 8 floating +# point ones. So we need 104 bytes of space, but we allocate 112 to keep rsp +# aligned to 16 bytes. + sub rsp, 112 + +# the first argument to InvokeCopyToStack is the integer register area, and the +# second is the floating point area. + mov rdi, rsp + lea rsi, [rsp + 40] + +# The 3rd and 4th arguments to InvokeCopyToStack are already in the right +# registers. So now we can just call InvokeCopyToStack. + call SYM(InvokeCopyToStack) + +# setup this + mov rdi, r12 + +# copy the integer arguments into place. + mov rsi, [rsp] + mov rdx, [rsp + 8] + mov rcx, [rsp + 16] + mov r8, [rsp + 24] + mov r9, [rsp + 32] + +# copy the float arguments into place + movsd xmm0, [rsp + 40] + movsd xmm1, [rsp + 48] + movsd xmm2, [rsp + 56] + movsd xmm3, [rsp + 64] + movsd xmm4, [rsp + 72] + movsd xmm5, [rsp + 80] + movsd xmm6, [rsp + 88] + movsd xmm7, [rsp + 96] + +# get rid of the scratch space for registers + add rsp, 112 + +# load the function pointer and call + lea eax, [r13d * 8] + add rax, [rdi] + call [rax] + +# r12 and r13 were pushed relative to the old stack pointer which is now the +# frame pointer. + mov r12, [rbp - 0x8] + mov r13, [rbp - 0x10] + + mov rsp, rbp + pop rbp + CFI_DEF_CFA(7, 8) + ret + CFI_ENDPROC + +#ifndef XP_DARWIN +// Magic indicating no need for an executable stack +.section .note.GNU-stack, "", @progbits ; .previous +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp index 050f9414d..1b5039ed9 100644 --- a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp @@ -30,9 +30,9 @@ invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) { if (regCount < 5) regCount++; - if (l_s->IsPtrData()) + if (l_s->IsIndirect()) { - *l_d = (uint64_t)l_s->ptr; + *l_d = (uint64_t) &l_s->val; continue; } switch (l_s->type) diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp new file mode 100644 index 000000000..126ef1dad --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp @@ -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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +/* solaris defines __sparc for workshop compilers and + linux defines __sparc__ */ + +#if !defined(__sparc) && !defined(__sparc__) +#error "This code is for Sparc only" +#endif + +typedef unsigned nsXPCVariant; + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + // nuts, I know there's a cooler way of doing this, but it's late + // now and it'll probably come to me in the morning. + if (result & 0x3) result += 4 - (result & 0x3); // ensure q-word alignment + return result; +} + +extern "C" uint32_t +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ +/* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. +*/ + uint32_t *l_d = d; + nsXPTCVariant *l_s = s; + uint32_t l_paramCount = paramCount; + uint32_t regCount = 0; // return the number of registers to load from the stack + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + + for(uint32_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if (regCount < 5) regCount++; + if(l_s->IsPtrData()) + { + if(l_s->type == nsXPTType::T_JSVAL) + { + // On SPARC, we need to pass a pointer to HandleValue + *((void**)l_d) = &l_s->ptr; + } else + { + *((void**)l_d) = l_s->ptr; + } + continue; + } + switch(l_s->type) + { + case nsXPTType::T_I8 : *((int32_t*) l_d) = l_s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) l_d) = l_s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) l_d) = l_s->val.i32; break; + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + case nsXPTType::T_DOUBLE : *((uint32_t*) l_d++) = ((DU *)l_s)->hi; + if (regCount < 5) regCount++; + *((uint32_t*) l_d) = ((DU *)l_s)->lo; + break; + case nsXPTType::T_U8 : *((uint32_t*) l_d) = l_s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*) l_d) = l_s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*) l_d) = l_s->val.u32; break; + case nsXPTType::T_FLOAT : *((float*) l_d) = l_s->val.f; break; + case nsXPTType::T_BOOL : *((uint32_t*) l_d) = l_s->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*) l_d) = l_s->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) l_d) = l_s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)l_d) = l_s->val.p; + break; + } + } + return regCount; +} + diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_solaris.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_solaris.s new file mode 100644 index 000000000..9b448d7c7 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_solaris.s @@ -0,0 +1,49 @@ +/* -*- 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/. */ + + .global SharedStub + +/* + in the frame for the function that called SharedStub are the + rest of the parameters we need + +*/ + +SharedStub: +! we don't create a new frame yet, but work within the frame of the calling +! function to give ourselves the other parameters we want + + mov %o0, %o1 ! shuffle the index up to 2nd place + mov %i0, %o0 ! the original 'this' + add %fp, 72, %o2 ! previous stack top adjusted to the first argument slot (beyond 'this') +! save off the original incoming parameters that arrived in +! registers, the ABI guarantees the space for us to do this + st %i1, [%fp + 72] + st %i2, [%fp + 76] + st %i3, [%fp + 80] + st %i4, [%fp + 84] + st %i5, [%fp + 88] +! now we can build our own stack frame + save %sp,-(64 + 32),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 32 +! our function now appears to have been called +! as SharedStub(nsISupports* that, uint32_t index, uint32_t* args) +! so we can just copy these through + + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + call PrepareAndDispatch + nop + mov %o0,%i0 ! propagate return value + b .LL1 + nop +.LL1: + ret + restore + + .size SharedStub, .-SharedStub + .type SharedStub, #function diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp index b8a09c97e..e28a03794 100644 --- a/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp @@ -85,8 +85,13 @@ PrepareAndDispatch(nsXPTCStubBase* self, uint64_t methodIndex, uint64_t* args) extern "C" nsresult SharedStub(int, int*); +/* + * Avoid GCC stack protector to wipe out imput registers since the compiler + * thinks the function takes no arguments. + */ + #define STUB_ENTRY(n) \ -nsresult nsXPTCStubBase::Stub##n() \ +nsresult __attribute__((__optimize__("no-stack-protector"))) nsXPTCStubBase::Stub##n() \ { \ int dummy; /* defeat tail-call optimization */ \ return SharedStub(n, &dummy); \ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_solaris.cpp new file mode 100644 index 000000000..61f3df4ff --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_solaris.cpp @@ -0,0 +1,112 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +#if defined(sparc) || defined(__sparc__) + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (type == nsXPTType::T_JSVAL) + dp->val.p = *((void**) *ap); + else + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int32_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int32_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_DOUBLE : + case nsXPTType::T_U64 : + case nsXPTType::T_I64 : ((DU *)dp)->hi = ((DU *)ap)->hi; + ((DU *)dp)->lo = ((DU *)ap)->lo; + ap++; + break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint32_t*)ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint32_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_BOOL : dp->val.b = *((uint32_t*)ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((uint32_t*)ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((int32_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +extern "C" nsresult SharedStub(int, int*); + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + int dummy; /* defeat tail-call optimization */ \ + return SharedStub(n, &dummy); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* sparc || __sparc__ */ diff --git a/xpcom/reflect/xptcall/status.html b/xpcom/reflect/xptcall/status.html index deb4da92e..996159c82 100644 --- a/xpcom/reflect/xptcall/status.html +++ b/xpcom/reflect/xptcall/status.html @@ -239,6 +239,20 @@ a port.
+ + + + + + + -- cgit v1.2.3 From 9d449ce614ef70479228dd119f6361624d4a6b88 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Tue, 1 Oct 2019 06:38:45 -0500 Subject: MoonchildProductions#1251 - Part 2: Make the mozconfig loader POSIX-compliant. https://bugzilla.mozilla.org/show_bug.cgi?id=1360571 Solaris uses ksh as the default shell, and furthermore bash doesn't seem to support the 'local' keyword feature when invoked as sh on OpenIndiana. We could just change the script to use bash (it is an option even on Solaris), but this fix is available and Mozilla has been using it since Firefox 55 with no issues on any other platforms. It was specfically done this way to avoid any need to change existing mozconfig files, so I feel confident saying the change is totally benign and if anything the way it is now is technically a POSIX compliance issue inherited from Mozilla that we'll hit if we ever try to compile this on any Unix platform where bash isn't sh. --- python/mozbuild/mozbuild/mozconfig_loader | 38 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/python/mozbuild/mozbuild/mozconfig_loader b/python/mozbuild/mozbuild/mozconfig_loader index 6b1e05dce..6c3df47ac 100755 --- a/python/mozbuild/mozbuild/mozconfig_loader +++ b/python/mozbuild/mozbuild/mozconfig_loader @@ -10,49 +10,46 @@ set -e ac_add_options() { - local opt - for opt; do - case "$opt" in + for _mozconfig_opt; do + case "$_mozconfig_opt" in --target=*) echo "------BEGIN_MK_OPTION" - echo $opt | sed s/--target/CONFIG_GUESS/ + echo $_mozconfig_opt | sed s/--target/CONFIG_GUESS/ echo "------END_MK_OPTION" ;; esac echo "------BEGIN_AC_OPTION" - echo $opt + echo $_mozconfig_opt echo "------END_AC_OPTION" done } ac_add_app_options() { - local app - app=$1 + _mozconfig_app=$1 shift echo "------BEGIN_AC_APP_OPTION" - echo $app + echo $_mozconfig_app echo "$*" echo "------END_AC_APP_OPTION" } mk_add_options() { - local opt name op value - for opt; do + for _mozconfig_opt; do echo "------BEGIN_MK_OPTION" - echo $opt + echo $_mozconfig_opt # Remove any leading "export" - opt=${opt#export} - case "$opt" in - *\?=*) op="?=" ;; - *:=*) op=":=" ;; - *+=*) op="+=" ;; - *=*) op="=" ;; + opt=${_mozconfig_opt#export} + case "$_mozconfig_opt" in + *\?=*) _mozconfig_op="?=" ;; + *:=*) _mozconfig_op=":=" ;; + *+=*) _mozconfig_op="+=" ;; + *=*) _mozconfig_op="=" ;; esac # Remove the operator and the value that follows - name=${opt%%${op}*} - # Note: $(echo ${name}) strips the variable from any leading and trailing + _mozconfig_name=${_mozconfig_opt%%${_mozconfig_op}*} + # Note: $(echo ${_mozconfig_name}) strips the variable from any leading and trailing # whitespaces. - eval "$(echo ${name})_IS_SET=1" + eval "$(echo ${_mozconfig_name})_IS_SET=1" echo "------END_MK_OPTION" done } @@ -77,4 +74,5 @@ echo "------END_AFTER_SOURCE" echo "------BEGIN_ENV_AFTER_SOURCE" $3 $4 + echo "------END_ENV_AFTER_SOURCE" -- cgit v1.2.3 From 64e03d0149bed895d00e20e71da775e2aabf2f81 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Tue, 1 Oct 2019 07:26:40 -0500 Subject: MoonchildProductions#1251 - Part 3: Finally end the long tradition of casting getpid() to int. https://bugzilla.mozilla.org/show_bug.cgi?id=535106 https://bugzilla.mozilla.org/show_bug.cgi?id=1359841 Like many parts of the busted Solaris support, this one has its origins in the pre-Firefox days. Bug 535106, another Mozilla suite bug. It keeps coming up because the core issue is never addressed, the fact that nsTSubstring doesn't know how to handle pid_t. I think the explicit cast to int is a band-aid they use because they know if they touch that substring header file to make it handle pid_t, they'll probably be asked to fix all the other problems with it. I honestly think it just works by accident on other platforms because it's implicitly cast to signed or unsigned int, even though the POSIX standard says pid_t can be either long or int, and work as either a signed or unsigned integer. Whatever the case may be, it's handled better on Solaris now than it was. Ironically enough, the main point of having pid_t rather than just having pids be int or something is to hide this little implementation detail so you can just use pid_t for the return type in portable code without having to worry about what it is on a specific platform. The unfortunate way Mozilla implemented string functions turns that on its head and makes the good things about pid_t into liabilities rather than assets. --- xpcom/base/nsTraceRefcnt.cpp | 2 +- xpcom/string/nsTSubstring.h | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/xpcom/base/nsTraceRefcnt.cpp b/xpcom/base/nsTraceRefcnt.cpp index da51305a9..48448dbd9 100644 --- a/xpcom/base/nsTraceRefcnt.cpp +++ b/xpcom/base/nsTraceRefcnt.cpp @@ -657,7 +657,7 @@ InitLog(const char* aEnvVar, const char* aMsg, FILE** aResult) fname.Append('_'); fname.Append((char*)XRE_ChildProcessTypeToString(XRE_GetProcessType())); fname.AppendLiteral("_pid"); - fname.AppendInt((uint32_t)getpid()); + fname.AppendInt(getpid()); if (hasLogExtension) { fname.AppendLiteral(".log"); } diff --git a/xpcom/string/nsTSubstring.h b/xpcom/string/nsTSubstring.h index 53b4fb9a8..2b0723c04 100644 --- a/xpcom/string/nsTSubstring.h +++ b/xpcom/string/nsTSubstring.h @@ -9,6 +9,9 @@ #include "mozilla/MemoryReporting.h" #include "mozilla/IntegerTypeTraits.h" #include "mozilla/Span.h" +#ifdef XP_SOLARIS +#include +#endif #ifndef MOZILLA_INTERNAL_API #error Cannot use internal string classes without MOZILLA_INTERNAL_API defined. Use the frozen header nsStringAPI.h instead. @@ -587,6 +590,17 @@ public: const char* fmt = aRadix == 10 ? "%d" : aRadix == 8 ? "%o" : "%x"; AppendPrintf(fmt, aInteger); } +#ifdef XP_SOLARIS + void AppendInt(pid_t aInteger) + { + AppendPrintf("%lu", aInteger); + } + void AppendInt(pid_t aInteger, int aRadix) + { + const char* fmt = aRadix == 10 ? "%lu" : aRadix == 8 ? "%lo" : "%lx"; + AppendPrintf(fmt, aInteger); + } +#endif void AppendInt(uint32_t aInteger) { AppendPrintf("%u", aInteger); -- cgit v1.2.3 From 4105ebb6ed85aaffec5e4469a939945fb9eea066 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Tue, 1 Oct 2019 18:28:10 -0500 Subject: MoonchildProductions#1251 - Part 4: Core build system changes, lots of libevent/IPC junk. This is mostly ifdefs, but as you can see, Solaris is actually a lot like Linux. They're both more SysV than BSD at core, and most of the differences have more to do with Solaris not using glibc than anything else. I still need to audit a lot of these changes and understand why they're needed and what the alternative approaches are. After this patch, most of the core functionality needed to build Solaris is here. --- build/moz.configure/init.configure | 24 ++ config/external/nspr/pr/moz.build | 14 + config/external/nspr/prcpucfg.h | 2 + dom/media/AudioStream.h | 4 + dom/plugins/ipc/PluginMessageUtils.cpp | 6 +- dom/plugins/ipc/PluginModuleChild.cpp | 4 +- dom/plugins/ipc/PluginModuleChild.h | 2 +- intl/icu/source/common/uposixdefs.h | 2 +- ipc/chromium/chromium-config.mozbuild | 3 + ipc/chromium/moz.build | 11 +- ipc/chromium/src/base/message_loop.cc | 4 +- ipc/chromium/src/base/message_pump_glib.cc | 19 +- ipc/chromium/src/base/message_pump_libevent.cc | 3 + ipc/chromium/src/base/platform_thread.h | 2 +- ipc/chromium/src/base/platform_thread_posix.cc | 4 +- ipc/chromium/src/base/process_util.h | 2 +- ipc/chromium/src/base/process_util_posix.cc | 4 +- ipc/chromium/src/base/sys_info_posix.cc | 8 + ipc/chromium/src/base/time.h | 7 + ipc/chromium/src/base/time_posix.cc | 29 +- ipc/chromium/src/build/build_config.h | 4 +- ipc/chromium/src/chrome/common/transport_dib.h | 6 +- ipc/chromium/src/third_party/libevent/arc4random.c | 6 + ipc/chromium/src/third_party/libevent/event.c | 2 + .../src/third_party/libevent/evutil_rand.c | 2 +- .../src/third_party/libevent/include/event2/util.h | 2 +- .../libevent/solaris/event2/event-config.h | 480 +++++++++++++++++++++ .../src/third_party/libeventcommon.mozbuild | 4 + ipc/chromium/src/third_party/moz.build | 5 + ipc/glue/BrowserProcessSubThread.cpp | 4 +- ipc/glue/BrowserProcessSubThread.h | 2 +- ipc/glue/GeckoChildProcessHost.cpp | 4 +- ipc/glue/MessageLink.cpp | 18 +- python/mozbuild/mozbuild/configure/constants.py | 3 + toolkit/components/terminator/nsTerminator.cpp | 4 + widget/GfxInfoX11.cpp | 9 + xpcom/ds/nsMathUtils.h | 3 +- xpcom/glue/FileUtils.cpp | 2 +- 38 files changed, 682 insertions(+), 32 deletions(-) create mode 100644 ipc/chromium/src/third_party/libevent/solaris/event2/event-config.h diff --git a/build/moz.configure/init.configure b/build/moz.configure/init.configure index 7664591ad..a275957b7 100644 --- a/build/moz.configure/init.configure +++ b/build/moz.configure/init.configure @@ -336,6 +336,8 @@ def split_triplet(triplet): canonical_os = canonical_kernel = 'NetBSD' elif os.startswith('openbsd'): canonical_os = canonical_kernel = 'OpenBSD' + elif os.startswith('solaris'): + canonical_os = canonical_kernel = 'SunOS' else: die('Unknown OS: %s' % os) @@ -569,6 +571,28 @@ def target_is_linux(target): set_define('XP_LINUX', target_is_linux) +@depends(target) +def target_is_solaris(target): + if target.kernel == 'SunOS': + return True + +set_define('XP_SOLARIS', target_is_solaris) + +@depends(target) +def target_is_sparc(target): + if target.cpu == 'sparc': + return True + +set_define('SPARC', target_is_sparc) + +@depends(target) +def target_is_sparc64(target): + if target.cpu == 'sparc64': + return True + +set_define('SPARC64', target_is_sparc64) + + # The application/project to build # ============================================================== option('--enable-application', nargs=1, env='MOZ_BUILD_APP', diff --git a/config/external/nspr/pr/moz.build b/config/external/nspr/pr/moz.build index af710f850..02811aae8 100644 --- a/config/external/nspr/pr/moz.build +++ b/config/external/nspr/pr/moz.build @@ -51,6 +51,19 @@ elif CONFIG['OS_TARGET'] == 'Darwin': ] if not CONFIG['MOZ_IOS']: DEFINES['HAVE_CRT_EXTERNS_H'] = True +elif CONFIG['OS_TARGET'] == 'SunOS': + DEFINES.update( + HAVE_FCNTL_FILE_LOCKING=True, + HAVE_SOCKLEN_T=True, + _PR_HAVE_OFF64_T=True, + _PR_INET6=True, + ) + DEFINES['SOLARIS'] = True + SOURCES += ['/nsprpub/pr/src/md/unix/solaris.c'] + if CONFIG['CPU_ARCH'] == 'x86_64': + SOURCES += ['/nsprpub/pr/src/md/unix/os_SunOS_x86_64.s'] + elif CONFIG['CPU_ARCH'] == 'x86': + SOURCES += ['/nsprpub/pr/src/md/unix/os_SunOS/x86.s'] elif CONFIG['OS_TARGET'] == 'WINNT': OS_LIBS += [ 'advapi32', @@ -224,6 +237,7 @@ EXPORTS.nspr.md += [ '/nsprpub/pr/include/md/_linux.cfg', '/nsprpub/pr/include/md/_netbsd.cfg', '/nsprpub/pr/include/md/_openbsd.cfg', + '/nsprpub/pr/include/md/_solaris.cfg', '/nsprpub/pr/include/md/_win95.cfg', ] diff --git a/config/external/nspr/prcpucfg.h b/config/external/nspr/prcpucfg.h index 5f7962733..8769abeeb 100644 --- a/config/external/nspr/prcpucfg.h +++ b/config/external/nspr/prcpucfg.h @@ -22,6 +22,8 @@ #include "md/_openbsd.cfg" #elif defined(__linux__) #include "md/_linux.cfg" +#elif defined(__sun__) +#include "md/_solaris.cfg" #else #error "Unsupported platform!" #endif diff --git a/dom/media/AudioStream.h b/dom/media/AudioStream.h index acc38b93d..13cbb0c75 100644 --- a/dom/media/AudioStream.h +++ b/dom/media/AudioStream.h @@ -17,6 +17,10 @@ #include "mozilla/UniquePtr.h" #include "CubebUtils.h" #include "soundtouch/SoundTouchFactory.h" +#if defined(XP_SOLARIS) +#include "soundtouch/SoundTouch.h" +#endif + namespace mozilla { diff --git a/dom/plugins/ipc/PluginMessageUtils.cpp b/dom/plugins/ipc/PluginMessageUtils.cpp index 47653fe6e..5b1d1667f 100644 --- a/dom/plugins/ipc/PluginMessageUtils.cpp +++ b/dom/plugins/ipc/PluginMessageUtils.cpp @@ -82,7 +82,7 @@ MediateRace(const MessageChannel::MessageInfo& parent, } } -#if defined(OS_LINUX) +#if defined(OS_LINUX) || defined(OS_SOLARIS) static string ReplaceAll(const string& haystack, const string& needle, const string& with) { @@ -101,7 +101,7 @@ ReplaceAll(const string& haystack, const string& needle, const string& with) string MungePluginDsoPath(const string& path) { -#if defined(OS_LINUX) +#if defined(OS_LINUX) || defined(OS_SOLARIS) // https://bugzilla.mozilla.org/show_bug.cgi?id=519601 return ReplaceAll(path, "netscape", "netsc@pe"); #else @@ -112,7 +112,7 @@ MungePluginDsoPath(const string& path) string UnmungePluginDsoPath(const string& munged) { -#if defined(OS_LINUX) +#if defined(OS_LINUX) || defined(OS_SOLARIS) return ReplaceAll(munged, "netsc@pe", "netscape"); #else return munged; diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp index cbf6e509f..102c44a5e 100644 --- a/dom/plugins/ipc/PluginModuleChild.cpp +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -1821,7 +1821,7 @@ PluginModuleChild::AnswerNP_GetEntryPoints(NPError* _retval) AssertPluginThread(); MOZ_ASSERT(mIsChrome); -#if defined(OS_LINUX) || defined(OS_BSD) +#if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) return true; #elif defined(OS_WIN) || defined(OS_MACOSX) *_retval = mGetEntryPointsFunc(&mFunctions); @@ -1866,7 +1866,7 @@ PluginModuleChild::DoNP_Initialize(const PluginSettings& aSettings) #endif NPError result; -#if defined(OS_LINUX) || defined(OS_BSD) +#if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) result = mInitializeFunc(&sBrowserFuncs, &mFunctions); #elif defined(OS_WIN) || defined(OS_MACOSX) result = mInitializeFunc(&sBrowserFuncs); diff --git a/dom/plugins/ipc/PluginModuleChild.h b/dom/plugins/ipc/PluginModuleChild.h index 681743582..5e4fa7d20 100644 --- a/dom/plugins/ipc/PluginModuleChild.h +++ b/dom/plugins/ipc/PluginModuleChild.h @@ -258,7 +258,7 @@ private: // we get this from the plugin NP_PLUGINSHUTDOWN mShutdownFunc; -#if defined(OS_LINUX) || defined(OS_BSD) +#if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) NP_PLUGINUNIXINIT mInitializeFunc; #elif defined(OS_WIN) || defined(OS_MACOSX) NP_PLUGININIT mInitializeFunc; diff --git a/intl/icu/source/common/uposixdefs.h b/intl/icu/source/common/uposixdefs.h index 495deea49..a5b7fd09f 100644 --- a/intl/icu/source/common/uposixdefs.h +++ b/intl/icu/source/common/uposixdefs.h @@ -54,7 +54,7 @@ * * z/OS needs this definition for timeval and to get usleep. */ -#if !defined(_XOPEN_SOURCE_EXTENDED) +#if !defined(_XOPEN_SOURCE_EXTENDED) && (U_PLATFORM != U_PF_SOLARIS) # define _XOPEN_SOURCE_EXTENDED 1 #endif diff --git a/ipc/chromium/chromium-config.mozbuild b/ipc/chromium/chromium-config.mozbuild index 9df182cb8..f045bca54 100644 --- a/ipc/chromium/chromium-config.mozbuild +++ b/ipc/chromium/chromium-config.mozbuild @@ -41,6 +41,9 @@ else: if CONFIG['OS_ARCH'] == 'Darwin': DEFINES['OS_MACOSX'] = 1 + elif CONFIG['OS_ARCH'] == 'SunOS': + DEFINES['OS_SOLARIS'] = 1 + elif CONFIG['OS_ARCH'] == 'DragonFly': DEFINES.update({ 'OS_DRAGONFLY': 1, diff --git a/ipc/chromium/moz.build b/ipc/chromium/moz.build index f047e7011..ba5f4e512 100644 --- a/ipc/chromium/moz.build +++ b/ipc/chromium/moz.build @@ -132,12 +132,21 @@ if os_linux: DEFINES['ANDROID'] = True DEFINES['_POSIX_MONOTONIC_CLOCK'] = 0 -if os_bsd or os_linux: +if os_bsd or os_linux or os_solaris: if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: SOURCES += [ 'src/base/message_pump_glib.cc', ] +if os_solaris: + SOURCES += [ + 'src/base/atomicops_internals_x86_gcc.cc', + 'src/base/process_util_linux.cc', + 'src/base/time_posix.cc', +] + +LOCAL_INCLUDES += ['src/third_party/libevent/linux'] + ost = CONFIG['OS_TEST'] if '86' not in ost and 'arm' not in ost and 'aarch64' != ost and 'mips' not in ost: SOURCES += [ diff --git a/ipc/chromium/src/base/message_loop.cc b/ipc/chromium/src/base/message_loop.cc index 3c794b276..638018feb 100644 --- a/ipc/chromium/src/base/message_loop.cc +++ b/ipc/chromium/src/base/message_loop.cc @@ -21,7 +21,7 @@ #if defined(OS_POSIX) #include "base/message_pump_libevent.h" #endif -#if defined(OS_LINUX) || defined(OS_BSD) +#if defined(OS_LINUX) || defined(OS_BSD) || defined (OS_SOLARIS) #if defined(MOZ_WIDGET_GTK) #include "base/message_pump_glib.h" #endif @@ -149,7 +149,7 @@ MessageLoop::MessageLoop(Type type, nsIThread* aThread) if (type_ == TYPE_UI) { #if defined(OS_MACOSX) pump_ = base::MessagePumpMac::Create(); -#elif defined(OS_LINUX) || defined(OS_BSD) +#elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) pump_ = new base::MessagePumpForUI(); #endif // OS_LINUX } else if (type_ == TYPE_IO) { diff --git a/ipc/chromium/src/base/message_pump_glib.cc b/ipc/chromium/src/base/message_pump_glib.cc index 36ebfdd69..a5e719c72 100644 --- a/ipc/chromium/src/base/message_pump_glib.cc +++ b/ipc/chromium/src/base/message_pump_glib.cc @@ -9,6 +9,9 @@ #include #include +#if defined(OS_SOLARIS) +#include +#endif #include #include @@ -131,6 +134,12 @@ MessagePumpForUI::MessagePumpForUI() // Create our wakeup pipe, which is used to flag when work was scheduled. int fds[2]; CHECK(pipe(fds) == 0); +#if defined(OS_SOLARIS) + int flags = fcntl(fds[0], F_GETFL,0); + if (flags == -1) + flags = 0; + fntl(fds[0], F_SETFL, flags | O_NDELAY); +#endif wakeup_pipe_read_ = fds[0]; wakeup_pipe_write_ = fds[1]; wakeup_gpollfd_->fd = wakeup_pipe_read_; @@ -238,11 +247,15 @@ bool MessagePumpForUI::HandleCheck() { // whether there was data, so this read shouldn't block. if (wakeup_gpollfd_->revents & G_IO_IN) { pipe_full_ = false; - +#ifndef OS_SOLARIS char msg; if (HANDLE_EINTR(read(wakeup_pipe_read_, &msg, 1)) != 1 || msg != '!') { NOTREACHED() << "Error reading from the wakeup pipe."; } +#else + char buf[32]; + while (HANDLE_EINTR(read(wakeup_pipe_read_, &buf, 32))); +#endif // Since we ate the message, we need to record that we have more work, // because HandleCheck() may be called without HandleDispatch being called // afterwards. @@ -311,6 +324,10 @@ void MessagePumpForUI::ScheduleWork() { // variables as we would then need locks all over. This ensures that if // we are sleeping in a poll that we will wake up. char msg = '!'; +#if defined(OS_SOLARIS) + char buf[32]; + while (HANDLE_EINTR(read(wakeup_pipe_read_, &buf, 32))); +#endif if (HANDLE_EINTR(write(wakeup_pipe_write_, &msg, 1)) != 1) { NOTREACHED() << "Could not write to the UI message loop wakeup pipe!"; } diff --git a/ipc/chromium/src/base/message_pump_libevent.cc b/ipc/chromium/src/base/message_pump_libevent.cc index 3cca238c1..daba7c2fe 100644 --- a/ipc/chromium/src/base/message_pump_libevent.cc +++ b/ipc/chromium/src/base/message_pump_libevent.cc @@ -8,6 +8,9 @@ #include #include +#if defined(OS_SOLARIS) +#include +#endif #if defined(ANDROID) || defined(OS_POSIX) #include #endif diff --git a/ipc/chromium/src/base/platform_thread.h b/ipc/chromium/src/base/platform_thread.h index 727a13a84..3432128a6 100644 --- a/ipc/chromium/src/base/platform_thread.h +++ b/ipc/chromium/src/base/platform_thread.h @@ -24,7 +24,7 @@ typedef void* PlatformThreadHandle; // HANDLE #elif defined(OS_POSIX) #include typedef pthread_t PlatformThreadHandle; -#if defined(OS_LINUX) || defined(OS_OPENBSD) || defined(__GLIBC__) +#if defined(OS_LINUX) || defined(OS_OPENBSD) || defined(OS_SOLARIS) || defined(__GLIBC__) #include typedef pid_t PlatformThreadId; #elif defined(OS_BSD) diff --git a/ipc/chromium/src/base/platform_thread_posix.cc b/ipc/chromium/src/base/platform_thread_posix.cc index 4acd95f23..fdb2d9200 100644 --- a/ipc/chromium/src/base/platform_thread_posix.cc +++ b/ipc/chromium/src/base/platform_thread_posix.cc @@ -49,7 +49,7 @@ PlatformThreadId PlatformThread::CurrentId() { return port; #elif defined(OS_LINUX) return syscall(__NR_gettid); -#elif defined(OS_OPENBSD) || defined(__GLIBC__) +#elif defined(OS_OPENBSD) || defined(OS_SOLARIS) || defined(__GLIBC__) return (intptr_t) (pthread_self()); #elif defined(OS_NETBSD) return _lwp_self(); @@ -103,6 +103,8 @@ void PlatformThread::SetName(const char* name) { pthread_setname_np(pthread_self(), "%s", (void *)name); #elif defined(OS_BSD) && !defined(__GLIBC__) pthread_set_name_np(pthread_self(), name); +#elif defined(OS_SOLARIS) + pthread_setname_np(pthread_self(), name); #else #endif } diff --git a/ipc/chromium/src/base/process_util.h b/ipc/chromium/src/base/process_util.h index 4df1f29fb..2b257b587 100644 --- a/ipc/chromium/src/base/process_util.h +++ b/ipc/chromium/src/base/process_util.h @@ -19,7 +19,7 @@ #ifndef STDOUT_FILENO #define STDOUT_FILENO 1 #endif -#elif defined(OS_LINUX) || defined(__GLIBC__) +#elif defined(OS_LINUX) || defined(OS_SOLARIS) || defined(__GLIBC__) #include #include #include diff --git a/ipc/chromium/src/base/process_util_posix.cc b/ipc/chromium/src/base/process_util_posix.cc index ade3ebe5e..3eb952a32 100644 --- a/ipc/chromium/src/base/process_util_posix.cc +++ b/ipc/chromium/src/base/process_util_posix.cc @@ -117,7 +117,7 @@ void CloseSuperfluousFds(const base::InjectiveMultimap& saved_mapping) { #if defined(ANDROID) static const rlim_t kSystemDefaultMaxFds = 1024; static const char kFDDir[] = "/proc/self/fd"; -#elif defined(OS_LINUX) +#elif defined(OS_LINUX) || defined(OS_SOLARIS) static const rlim_t kSystemDefaultMaxFds = 8192; static const char kFDDir[] = "/proc/self/fd"; #elif defined(OS_MACOSX) @@ -209,7 +209,7 @@ void CloseSuperfluousFds(const base::InjectiveMultimap& saved_mapping) { // TODO(agl): Remove this function. It's fundamentally broken for multithreaded // apps. void SetAllFDsToCloseOnExec() { -#if defined(OS_LINUX) +#if defined(OS_LINUX) || defined(OS_SOLARIS) const char fd_dir[] = "/proc/self/fd"; #elif defined(OS_MACOSX) || defined(OS_BSD) const char fd_dir[] = "/dev/fd"; diff --git a/ipc/chromium/src/base/sys_info_posix.cc b/ipc/chromium/src/base/sys_info_posix.cc index 81ec78053..1b88883eb 100644 --- a/ipc/chromium/src/base/sys_info_posix.cc +++ b/ipc/chromium/src/base/sys_info_posix.cc @@ -121,7 +121,11 @@ std::wstring SysInfo::GetEnvVar(const wchar_t* var) { // static std::string SysInfo::OperatingSystemName() { +#if !defined(XP_SOLARIS) utsname info; +#else + struct utsname info; +#endif if (uname(&info) < 0) { NOTREACHED(); return ""; @@ -131,7 +135,11 @@ std::string SysInfo::OperatingSystemName() { // static std::string SysInfo::CPUArchitecture() { +#if !defined(XP_SOLARIS) utsname info; +#else + struct utsname info; +#endif if (uname(&info) < 0) { NOTREACHED(); return ""; diff --git a/ipc/chromium/src/base/time.h b/ipc/chromium/src/base/time.h index 55bc7b451..6a5c92d14 100644 --- a/ipc/chromium/src/base/time.h +++ b/ipc/chromium/src/base/time.h @@ -64,6 +64,10 @@ class TimeDelta { return delta_; } +#if defined(OS_SOLARIS) + struct timespec ToTimeSpec() const; +#endif + // Returns the time delta in some unit. The F versions return a floating // point value, the "regular" versions return a rounded-down value. int InDays() const; @@ -226,6 +230,9 @@ class Time { static Time FromDoubleT(double dt); double ToDoubleT() const; +#if defined(OS_SOLARIS) + struct timeval ToTimeVal() const; +#endif #if defined(OS_WIN) static Time FromFileTime(FILETIME ft); diff --git a/ipc/chromium/src/base/time_posix.cc b/ipc/chromium/src/base/time_posix.cc index 2eb76a989..419cc8afb 100644 --- a/ipc/chromium/src/base/time_posix.cc +++ b/ipc/chromium/src/base/time_posix.cc @@ -67,11 +67,13 @@ Time Time::FromExploded(bool is_local, const Exploded& exploded) { timestruct.tm_wday = exploded.day_of_week; // mktime/timegm ignore this timestruct.tm_yday = 0; // mktime/timegm ignore this timestruct.tm_isdst = -1; // attempt to figure it out +#ifndef OS_SOLARIS timestruct.tm_gmtoff = 0; // not a POSIX field, so mktime/timegm ignore timestruct.tm_zone = NULL; // not a POSIX field, so mktime/timegm ignore +#endif time_t seconds; -#ifdef ANDROID +#if defined(ANDROID) || defined(OS_SOLARIS) seconds = mktime(×truct); #else if (is_local) @@ -175,7 +177,7 @@ TimeTicks TimeTicks::Now() { // With numer and denom = 1 (the expected case), the 64-bit absolute time // reported in nanoseconds is enough to last nearly 585 years. -#elif defined(OS_OPENBSD) || defined(OS_POSIX) && \ +#elif defined(OS_OPENBSD) || defined(OS_SOLARIS) || defined(OS_POSIX) && \ defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0 struct timespec ts; @@ -200,4 +202,27 @@ TimeTicks TimeTicks::HighResNow() { return Now(); } +#if defined(OS_SOLARIS) +struct timespec TimeDelta::ToTimeSpec() const { + int64_t microseconds = InMicroseconds(); + time_t seconds = 0; + if (microseconds >= Time::kMicrosecondsPerSecond) { + seconds = InSeconds(); + microseconds -= seconds * Time::kMicrosecondsPerSecond; + } + struct timespec result = + {seconds, + microseconds * Time::kNanosecondsPerMicrosecond}; + return result; +} + +struct timeval Time::ToTimeVal() const { + struct timeval result; + int64_t us = us_ - kTimeTToMicrosecondsOffset; + result.tv_sec = us / Time::kMicrosecondsPerSecond; + result.tv_usec = us % Time::kMicrosecondsPerSecond; + return result; +} +#endif + } // namespace base diff --git a/ipc/chromium/src/build/build_config.h b/ipc/chromium/src/build/build_config.h index 390fa77a8..7e6beb37a 100644 --- a/ipc/chromium/src/build/build_config.h +++ b/ipc/chromium/src/build/build_config.h @@ -32,6 +32,8 @@ #define OS_NETBSD 1 #elif defined(__OpenBSD__) #define OS_OPENBSD 1 +#elif defined(__sun__) +#define OS_SOLARIS 1 #elif defined(_WIN32) #define OS_WIN 1 #else @@ -47,7 +49,7 @@ // For access to standard POSIX features, use OS_POSIX instead of a more // specific macro. -#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_BSD) +#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) #define OS_POSIX 1 #endif diff --git a/ipc/chromium/src/chrome/common/transport_dib.h b/ipc/chromium/src/chrome/common/transport_dib.h index b1e5c0fab..e3c40768c 100644 --- a/ipc/chromium/src/chrome/common/transport_dib.h +++ b/ipc/chromium/src/chrome/common/transport_dib.h @@ -68,7 +68,7 @@ class TransportDIB { typedef base::SharedMemoryHandle Handle; // On Mac, the inode number of the backing file is used as an id. typedef base::SharedMemoryId Id; -#elif defined(OS_LINUX) +#elif defined(OS_LINUX) || defined(OS_SOLARIS) typedef int Handle; // These two ints are SysV IPC shared memory keys typedef int Id; #endif @@ -98,7 +98,7 @@ class TransportDIB { // wire to give this transport DIB to another process. Handle handle() const; -#if defined(OS_LINUX) +#if defined(OS_LINUX) || defined(OS_SOLARIS) // Map the shared memory into the X server and return an id for the shared // segment. XID MapToX(Display* connection); @@ -109,7 +109,7 @@ class TransportDIB { #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_BSD) explicit TransportDIB(base::SharedMemoryHandle dib); base::SharedMemory shared_memory_; -#elif defined(OS_LINUX) +#elif defined(OS_LINUX) || defined(OS_SOLARIS) int key_; // SysV shared memory id void* address_; // mapped address XID x_shm_; // X id for the shared segment diff --git a/ipc/chromium/src/third_party/libevent/arc4random.c b/ipc/chromium/src/third_party/libevent/arc4random.c index cabc46f4b..0dd6934ef 100644 --- a/ipc/chromium/src/third_party/libevent/arc4random.c +++ b/ipc/chromium/src/third_party/libevent/arc4random.c @@ -41,6 +41,10 @@ * RC4 is a registered trademark of RSA Laboratories. */ +#ifdef XP_SOLARIS +#include "build/build_config.h" +#endif + #ifndef ARC4RANDOM_EXPORT #define ARC4RANDOM_EXPORT #endif @@ -477,6 +481,7 @@ arc4random(void) } #endif +#if defined(_we_have_arc4random_buf) || !defined(XP_SOLARIS) ARC4RANDOM_EXPORT void arc4random_buf(void *_buf, size_t n) { @@ -490,6 +495,7 @@ arc4random_buf(void *_buf, size_t n) } _ARC4_UNLOCK(); } +#endif #ifndef ARC4RANDOM_NOUNIFORM /* diff --git a/ipc/chromium/src/third_party/libevent/event.c b/ipc/chromium/src/third_party/libevent/event.c index a979f1f26..f6b44bcaf 100644 --- a/ipc/chromium/src/third_party/libevent/event.c +++ b/ipc/chromium/src/third_party/libevent/event.c @@ -2883,8 +2883,10 @@ event_global_setup_locks_(const int enable_locks) #endif if (evsig_global_setup_locks_(enable_locks) < 0) return -1; +#ifndef OS_SOLARIS if (evutil_secure_rng_global_setup_locks_(enable_locks) < 0) return -1; +#endif return 0; } #endif diff --git a/ipc/chromium/src/third_party/libevent/evutil_rand.c b/ipc/chromium/src/third_party/libevent/evutil_rand.c index 3bab23121..7c92bae23 100644 --- a/ipc/chromium/src/third_party/libevent/evutil_rand.c +++ b/ipc/chromium/src/third_party/libevent/evutil_rand.c @@ -139,7 +139,7 @@ evutil_secure_rng_get_bytes(void *buf, size_t n) ev_arc4random_buf(buf, n); } -#if !defined(__OpenBSD__) && !defined(ANDROID) +#if !defined(__OpenBSD__) && !defined(ANDROID) && !defined(__sun__) void evutil_secure_rng_add_bytes(const char *buf, size_t n) { diff --git a/ipc/chromium/src/third_party/libevent/include/event2/util.h b/ipc/chromium/src/third_party/libevent/include/event2/util.h index 42a28adcb..78516c156 100644 --- a/ipc/chromium/src/third_party/libevent/include/event2/util.h +++ b/ipc/chromium/src/third_party/libevent/include/event2/util.h @@ -672,7 +672,7 @@ void evutil_secure_rng_get_bytes(void *buf, size_t n); */ int evutil_secure_rng_init(void); -#if !defined(__OpenBSD__) && !defined(ANDROID) +#if !defined(__OpenBSD__) && !defined(ANDROID) && !defined(__sun__) /** Seed the random number generator with extra random bytes. You should almost never need to call this function; it should be diff --git a/ipc/chromium/src/third_party/libevent/solaris/event2/event-config.h b/ipc/chromium/src/third_party/libevent/solaris/event2/event-config.h new file mode 100644 index 000000000..73000106b --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/solaris/event2/event-config.h @@ -0,0 +1,480 @@ +/* event2/event-config.h + * + * This file was generated by autoconf when libevent was built, and post- + * processed by Libevent so that its macros would have a uniform prefix. + * + * DO NOT EDIT THIS FILE. + * + * Do not rely on macros in this file existing in later versions. + */ + +#ifndef _EVENT2_EVENT_CONFIG_H_ +#define _EVENT2_EVENT_CONFIG_H_ + +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define if libevent should build without support for a debug mode */ +/* #undef _EVENT_DISABLE_DEBUG_MODE */ + +/* Define if libevent should not allow replacing the mm functions */ +/* #undef _EVENT_DISABLE_MM_REPLACEMENT */ + +/* Define if libevent should not be compiled with thread support */ +/* #undef _EVENT_DISABLE_THREAD_SUPPORT */ + +/* Define to 1 if you have the `arc4random' function. */ +#define _EVENT_HAVE_ARC4RANDOM 1 + +/* Define to 1 if you have the `arc4random_buf' function. */ +#define _EVENT_HAVE_ARC4RANDOM_BUF 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the `clock_gettime' function. */ +#define _EVENT_HAVE_CLOCK_GETTIME 1 + +/* Define to 1 if you have the declaration of `CTL_KERN', and to 0 if you + don't. */ +/* #undef _EVENT_HAVE_DECL_CTL_KERN */ + +/* Define to 1 if you have the declaration of `KERN_ARND', and to 0 if you + don't. */ +/* #undef _EVENT_HAVE_DECL_KERN_ARND */ + +/* Define to 1 if you have the declaration of `KERN_RANDOM', and to 0 if you + don't. */ +/* #undef _EVENT_HAVE_DECL_KERN_RANDOM */ + +/* Define to 1 if you have the declaration of `RANDOM_UUID', and to 0 if you + don't. */ +/* #undef _EVENT_HAVE_DECL_RANDOM_UUID */ + +/* Define if /dev/poll is available */ +#define _EVENT_HAVE_DEVPOLL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_DLFCN_H 1 + +/* Define if your system supports the epoll system calls */ +#undef _EVENT_HAVE_EPOLL + +/* Define to 1 if you have the `epoll_ctl' function. */ +/* #undef _EVENT_HAVE_EPOLL_CTL */ + +/* Define to 1 if you have the `eventfd' function. */ +/* #undef _EVENT_HAVE_EVENTFD */ + +/* Define if your system supports event ports */ +/*#define _EVENT_HAVE_EVENT_PORTS 1*/ + +/* Define to 1 if you have the `fcntl' function. */ +#define _EVENT_HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_FCNTL_H 1 + +/* Define to 1 if the system has the type `fd_mask'. */ +#define _EVENT_HAVE_FD_MASK 1 + +/* Do we have getaddrinfo()? */ +#define _EVENT_HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `getegid' function. */ +#define _EVENT_HAVE_GETEGID 1 + +/* Define to 1 if you have the `geteuid' function. */ +#define _EVENT_HAVE_GETEUID 1 + +/* Define this if you have any gethostbyname_r() */ +/* #undef _EVENT_HAVE_GETHOSTBYNAME_R */ + +/* Define this if gethostbyname_r takes 3 arguments */ +/* #undef _EVENT_HAVE_GETHOSTBYNAME_R_3_ARG */ + +/* Define this if gethostbyname_r takes 5 arguments */ +/* #undef _EVENT_HAVE_GETHOSTBYNAME_R_5_ARG */ + +/* Define this if gethostbyname_r takes 6 arguments */ +/* #undef _EVENT_HAVE_GETHOSTBYNAME_R_6_ARG */ + +/* Define to 1 if you have the `getnameinfo' function. */ +#define _EVENT_HAVE_GETNAMEINFO 1 + +/* Define to 1 if you have the `getprotobynumber' function. */ +#define _EVENT_HAVE_GETPROTOBYNUMBER 1 + +/* Define to 1 if you have the `getservbyname' function. */ +/* #undef _EVENT_HAVE_GETSERVBYNAME */ + +/* Define to 1 if you have the `gettimeofday' function. */ +#define _EVENT_HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_aton' function. */ +#define _EVENT_HAVE_INET_ATON 1 + +/* Define to 1 if you have the `inet_ntop' function. */ +#define _EVENT_HAVE_INET_NTOP 1 + +/* Define to 1 if you have the `inet_pton' function. */ +#define _EVENT_HAVE_INET_PTON 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `issetugid' function. */ +#define _EVENT_HAVE_ISSETUGID 1 + +/* Define to 1 if you have the `kqueue' function. */ +/* #undef _EVENT_HAVE_KQUEUE */ + +/* Define if the system has zlib */ +#define _EVENT_HAVE_LIBZ 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mmap' function. */ +#define _EVENT_HAVE_MMAP 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_NETDB_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_NETINET_IN6_H */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_NETINET_IN_H 1 + +/* Define if the system has openssl */ +#define _EVENT_HAVE_OPENSSL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_OPENSSL_BIO_H 1 + +/* Define to 1 if you have the `pipe' function. */ +#define _EVENT_HAVE_PIPE 1 + +/* Define to 1 if you have the `poll' function. */ +#define _EVENT_HAVE_POLL 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_POLL_H 1 + +/* Define to 1 if you have the `port_create' function. */ +#define _EVENT_HAVE_PORT_CREATE 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_PORT_H 1 + +/* Define if you have POSIX threads libraries and header files. */ +/* #undef _EVENT_HAVE_PTHREAD */ + +/* Define if we have pthreads on this system */ +#define _EVENT_HAVE_PTHREADS 1 + +/* Define to 1 if you have the `putenv' function. */ +#define _EVENT_HAVE_PUTENV 1 + +/* Define to 1 if the system has the type `sa_family_t'. */ +#define _EVENT_HAVE_SA_FAMILY_T 1 + +/* Define to 1 if you have the `select' function. */ +#define _EVENT_HAVE_SELECT 1 + +/* Define to 1 if you have the `sendfile' function. */ +#define _EVENT_HAVE_SENDFILE 1 + +/* Define to 1 if you have the `setenv' function. */ +#define _EVENT_HAVE_SETENV 1 + +/* Define if F_SETFD is defined in */ +#define _EVENT_HAVE_SETFD 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define _EVENT_HAVE_SIGACTION 1 + +/* Define to 1 if you have the `signal' function. */ +#define _EVENT_HAVE_SIGNAL 1 + +/* Define to 1 if you have the `splice' function. */ +/* #undef _EVENT_HAVE_SPLICE */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDDEF_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +#define _EVENT_HAVE_STRLCPY 1 + +/* Define to 1 if you have the `strsep' function. */ +#define _EVENT_HAVE_STRSEP 1 + +/* Define to 1 if you have the `strtok_r' function. */ +#define _EVENT_HAVE_STRTOK_R 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define _EVENT_HAVE_STRTOLL 1 + +/* Define to 1 if the system has the type `struct addrinfo'. */ +#define _EVENT_HAVE_STRUCT_ADDRINFO 1 + +/* Define to 1 if the system has the type `struct in6_addr'. */ +#define _EVENT_HAVE_STRUCT_IN6_ADDR 1 + +/* Define to 1 if `s6_addr16' is a member of `struct in6_addr'. */ +/* #undef _EVENT_HAVE_STRUCT_IN6_ADDR_S6_ADDR16 */ + +/* Define to 1 if `s6_addr32' is a member of `struct in6_addr'. */ +/* #undef _EVENT_HAVE_STRUCT_IN6_ADDR_S6_ADDR32 */ + +/* Define to 1 if the system has the type `struct sockaddr_in6'. */ +#define _EVENT_HAVE_STRUCT_SOCKADDR_IN6 1 + +/* Define to 1 if `sin6_len' is a member of `struct sockaddr_in6'. */ +/* #undef _EVENT_HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN */ + +/* Define to 1 if `sin_len' is a member of `struct sockaddr_in'. */ +/* #undef _EVENT_HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + +/* Define to 1 if the system has the type `struct sockaddr_storage'. */ +#define _EVENT_HAVE_STRUCT_SOCKADDR_STORAGE 1 + +/* Define to 1 if `ss_family' is a member of `struct sockaddr_storage'. */ +#define _EVENT_HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY 1 + +/* Define to 1 if `__ss_family' is a member of `struct sockaddr_storage'. */ +/* #undef _EVENT_HAVE_STRUCT_SOCKADDR_STORAGE___SS_FAMILY */ + +/* Define to 1 if you have the `sysctl' function. */ +/* #undef _EVENT_HAVE_SYSCTL */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_DEVPOLL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_EPOLL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_EVENTFD_H */ + +/* Define to 1 if you have the header file. */ +/* #undef _EVENT_HAVE_SYS_EVENT_H */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_MMAN_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_QUEUE_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SENDFILE_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#undef _EVENT_HAVE_SYS_SYSCTL_H + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_UIO_H 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_SYS_WAIT_H 1 + +/* Define if TAILQ_FOREACH is defined in */ +#define _EVENT_HAVE_TAILQFOREACH 1 + +/* Define if timeradd is defined in */ +#define _EVENT_HAVE_TIMERADD 1 + +/* Define if timerclear is defined in */ +#define _EVENT_HAVE_TIMERCLEAR 1 + +/* Define if timercmp is defined in */ +#define _EVENT_HAVE_TIMERCMP 1 + +/* Define if timerisset is defined in */ +#define _EVENT_HAVE_TIMERISSET 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define _EVENT_HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define _EVENT_HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define _EVENT_HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define _EVENT_HAVE_UINT8_T 1 + +/* Define to 1 if the system has the type `uintptr_t'. */ +#define _EVENT_HAVE_UINTPTR_T 1 + +/* Define to 1 if you have the `umask' function. */ +#define _EVENT_HAVE_UMASK 1 + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `unsetenv' function. */ +#define _EVENT_HAVE_UNSETENV 1 + +/* Define to 1 if you have the `vasprintf' function. */ +#define _EVENT_HAVE_VASPRINTF 1 + +/* Define if kqueue works correctly with pipes */ +/* #undef _EVENT_HAVE_WORKING_KQUEUE */ + +/* Define to 1 if you have the header file. */ +#define _EVENT_HAVE_ZLIB_H 1 + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define _EVENT_LT_OBJDIR ".libs/" + +/* Define to 1 if your C compiler doesn't accept -c and -o together. */ +/* #undef _EVENT_NO_MINUS_C_MINUS_O */ + +/* Numeric representation of the version */ +#define _EVENT_NUMERIC_VERSION 0x02001600 + +/* Name of package */ +#define _EVENT_PACKAGE "libevent" + +/* Define to the address where bug reports for this package should be sent. */ +#define _EVENT_PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define _EVENT_PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define _EVENT_PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define _EVENT_PACKAGE_TARNAME "" + +/* Define to the home page for this package. */ +#define _EVENT_PACKAGE_URL "" + +/* Define to the version of this package. */ +#define _EVENT_PACKAGE_VERSION "" + +/* Define to necessary symbol if this constant uses a non-standard name on + your system. */ +/* #undef _EVENT_PTHREAD_CREATE_JOINABLE */ + +/* ------------------------------------------------------------------------ */ +/* MOZILLA NOTE: the following constants are hand-modified to be suitable */ +/* for both 32-bit and 64-bit platforms. See README.mozilla for details. */ +/* ------------------------------------------------------------------------ */ + +/* The size of `int', as computed by sizeof. */ +#define _EVENT_SIZEOF_INT 4 + +/* The size of `long', as computed by sizeof. */ +#ifdef __LP64__ +#define _EVENT_SIZEOF_LONG 8 +#else +#define _EVENT_SIZEOF_LONG 4 +#endif + +/* The size of `long long', as computed by sizeof. */ +#define _EVENT_SIZEOF_LONG_LONG 8 + +/* The size of `pthread_t', as computed by sizeof. */ +#ifdef __LP64__ +#define _EVENT_SIZEOF_PTHREAD_T 8 +#else +#define _EVENT_SIZEOF_PTHREAD_T 4 +#endif + +/* The size of `short', as computed by sizeof. */ +#define _EVENT_SIZEOF_SHORT 2 + +/* The size of `size_t', as computed by sizeof. */ +#ifdef __LP64__ +#define _EVENT_SIZEOF_SIZE_T 8 +#else +#define _EVENT_SIZEOF_SIZE_T 4 +#endif + +/* The size of `void *', as computed by sizeof. */ +#ifdef __LP64__ +#define _EVENT_SIZEOF_VOID_P 8 +#else +#define _EVENT_SIZEOF_VOID_P 4 +#endif + +/* ------------------------------------------------------------------------ */ +/* END MOZILLA NOTE */ +/* ------------------------------------------------------------------------ */ + +/* Define to 1 if you have the ANSI C header files. */ +#define _EVENT_STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define _EVENT_TIME_WITH_SYS_TIME 1 + +/* Version number of package */ +#define _EVENT_VERSION "2.0.22-stable" + +/* Define to appropriate substitue if compiler doesnt have __func__ */ +/* #undef _EVENT___func__ */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef _EVENT_const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef _EVENT___cplusplus +/* #undef _EVENT_inline */ +#endif + +/* Define to `int' if does not define. */ +/* #undef _EVENT_pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef _EVENT_size_t */ + +/* Define to unsigned int if you dont have it */ +/* #undef _EVENT_socklen_t */ + +/* Define to `int' if does not define. */ +/* #undef _EVENT_ssize_t */ + +#endif /* event2/event-config.h */ diff --git a/ipc/chromium/src/third_party/libeventcommon.mozbuild b/ipc/chromium/src/third_party/libeventcommon.mozbuild index 7b0627e17..2b45ecb19 100644 --- a/ipc/chromium/src/third_party/libeventcommon.mozbuild +++ b/ipc/chromium/src/third_party/libeventcommon.mozbuild @@ -9,6 +9,7 @@ os_posix = 0 os_macosx = 0 os_bsd = 0 os_linux = 0 +os_solaris = 0 if CONFIG['OS_ARCH'] == 'WINNT': os_win = 1 @@ -21,6 +22,9 @@ else: 'NetBSD', 'OpenBSD']: os_bsd = 1 libevent_include_suffix = 'bsd' + elif CONFIG['OS_ARCH'] in ['SunOS']: + os_solaris = 1 + libevent_include_suffix = 'solaris' else: os_linux = 1 if CONFIG['OS_TARGET'] == 'Android': diff --git a/ipc/chromium/src/third_party/moz.build b/ipc/chromium/src/third_party/moz.build index 989e2aafc..2b99e53b3 100644 --- a/ipc/chromium/src/third_party/moz.build +++ b/ipc/chromium/src/third_party/moz.build @@ -54,6 +54,11 @@ if os_linux: 'libevent/epoll_sub.c', ] +if os_solaris: + SOURCES += [ + 'libevent/devpoll.c', + ] + # We allow warnings for third-party code that can be updated from upstream. ALLOW_COMPILER_WARNINGS = True diff --git a/ipc/glue/BrowserProcessSubThread.cpp b/ipc/glue/BrowserProcessSubThread.cpp index 7618dc934..589f20003 100644 --- a/ipc/glue/BrowserProcessSubThread.cpp +++ b/ipc/glue/BrowserProcessSubThread.cpp @@ -23,7 +23,7 @@ static const char* kBrowserThreadNames[BrowserProcessSubThread::ID_COUNT] = { // "Chrome_FileThread", // FILE // "Chrome_DBThread", // DB // "Chrome_HistoryThread", // HISTORY -#if defined(OS_LINUX) +#if defined(OS_LINUX) || defined(OS_SOLARIS) "Gecko_Background_X11Thread", // BACKGROUND_X11 #endif }; @@ -34,7 +34,7 @@ BrowserProcessSubThread* BrowserProcessSubThread::sBrowserThreads[ID_COUNT] = { // nullptr, // FILE // nullptr, // DB // nullptr, // HISTORY -#if defined(OS_LINUX) +#if defined(OS_LINUX) || defined(OS_SOLARIS) nullptr, // BACKGROUND_X11 #endif }; diff --git a/ipc/glue/BrowserProcessSubThread.h b/ipc/glue/BrowserProcessSubThread.h index e1b4aef46..cc9051412 100644 --- a/ipc/glue/BrowserProcessSubThread.h +++ b/ipc/glue/BrowserProcessSubThread.h @@ -25,7 +25,7 @@ public: //FILE, //DB, //HISTORY, -#if defined(OS_LINUX) +#if defined(OS_LINUX) || defined(OS_SOLARIS) // This thread has a second connection to the X server and is used // to process UI requests when routing the request to the UI // thread would risk deadlock. diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp index fee429238..ea76f85f0 100644 --- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -599,7 +599,7 @@ GeckoChildProcessHost::PerformAsyncLaunchInternal(std::vector& aExt // and passing wstrings from one config to the other is unsafe. So // we split the logic here. -#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) +#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS) base::environment_map newEnvVars; ChildPrivileges privs = mPrivileges; if (privs == base::PRIVILEGES_DEFAULT) { @@ -744,7 +744,7 @@ GeckoChildProcessHost::PerformAsyncLaunchInternal(std::vector& aExt childArgv.push_back(childProcessType); base::LaunchApp(childArgv, mFileMap, -#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) +#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS) newEnvVars, privs, #endif false, &process, arch); diff --git a/ipc/glue/MessageLink.cpp b/ipc/glue/MessageLink.cpp index a66fbbb32..8c6d9b890 100644 --- a/ipc/glue/MessageLink.cpp +++ b/ipc/glue/MessageLink.cpp @@ -16,6 +16,9 @@ #include "nsDebug.h" #include "nsISupportsImpl.h" #include "nsXULAppAPI.h" +#if defined(__sun__) || defined(__sun) +#include +#endif using namespace mozilla; using namespace std; @@ -267,9 +270,19 @@ ProcessLink::OnChannelOpened() MonitorAutoLock lock(*mChan->mMonitor); mExistingListener = mTransport->set_listener(this); + +// The queue we want here is defined in the namespace 'std' on Solaris, which +// also has another function called queue in a different namespace. Need to +// determine whether queue is defined in 'std' on other supported platforms +// before possibly removing ifdefs. + #ifdef DEBUG if (mExistingListener) { +#if defined(XP_SOLARIS) + std::queue pending; +#else queue pending; +#endif mExistingListener->GetQueuedMessages(pending); MOZ_ASSERT(pending.empty()); } @@ -285,8 +298,11 @@ void ProcessLink::OnTakeConnectedChannel() { AssertIOThread(); - +#if defined(XP_SOLARIS) + std::queue pending; +#else queue pending; +#endif { MonitorAutoLock lock(*mChan->mMonitor); diff --git a/python/mozbuild/mozbuild/configure/constants.py b/python/mozbuild/mozbuild/configure/constants.py index dfc7cf8ad..00d9ff9bb 100644 --- a/python/mozbuild/mozbuild/configure/constants.py +++ b/python/mozbuild/mozbuild/configure/constants.py @@ -23,6 +23,7 @@ OS = EnumString.subclass( 'iOS', 'NetBSD', 'OpenBSD', + 'SunOS', 'OSX', 'WINNT', ) @@ -35,6 +36,7 @@ Kernel = EnumString.subclass( 'Linux', 'NetBSD', 'OpenBSD', + 'SunOS', 'WINNT', ) @@ -97,6 +99,7 @@ kernel_preprocessor_checks = { 'Linux': '__linux__', 'NetBSD': '__NetBSD__', 'OpenBSD': '__OpenBSD__', + 'SunOS': '__sun__', 'WINNT': '_WIN32 || __CYGWIN__', } diff --git a/toolkit/components/terminator/nsTerminator.cpp b/toolkit/components/terminator/nsTerminator.cpp index 91e872821..007885b8d 100644 --- a/toolkit/components/terminator/nsTerminator.cpp +++ b/toolkit/components/terminator/nsTerminator.cpp @@ -385,7 +385,11 @@ nsTerminator::StartWatchdog() } UniquePtr options(new Options()); +#if !defined(XP_SOLARIS) const PRIntervalTime ticksDuration = PR_MillisecondsToInterval(1000); +#else + const PRIntervalTime ticksDuration = 1000; +#endif options->crashAfterTicks = crashAfterMS / ticksDuration; DebugOnly watchdogThread = CreateSystemThread(RunWatchdog, diff --git a/widget/GfxInfoX11.cpp b/widget/GfxInfoX11.cpp index 338dcac67..98a4bb965 100644 --- a/widget/GfxInfoX11.cpp +++ b/widget/GfxInfoX11.cpp @@ -187,6 +187,15 @@ GfxInfo::GetData() note.AppendLiteral(" -- texture_from_pixmap"); note.Append('\n'); + // illumos/Solaris 10 libc lacks a strcasestr function, but NSPR has + // one. A lot of programs just implement one on the spot or use strstr + // and a buffer as some kind of workaround. They've been implementing + // missing functions lately, though, so this may not be needed much longer. + +#if defined(XP_SOLARIS) +#define strcasestr PL_strcasestr +#endif + // determine the major OpenGL version. That's the first integer in the version string. mGLMajorVersion = strtol(mVersion.get(), 0, 10); diff --git a/xpcom/ds/nsMathUtils.h b/xpcom/ds/nsMathUtils.h index 08bf908da..61eb47501 100644 --- a/xpcom/ds/nsMathUtils.h +++ b/xpcom/ds/nsMathUtils.h @@ -11,7 +11,8 @@ #include #include -#ifdef XP_SOLARIS +#if defined(XP_SOLARIS) +#include #include #endif diff --git a/xpcom/glue/FileUtils.cpp b/xpcom/glue/FileUtils.cpp index 699812461..53fe68708 100644 --- a/xpcom/glue/FileUtils.cpp +++ b/xpcom/glue/FileUtils.cpp @@ -532,7 +532,7 @@ mozilla::ReadAheadFile(mozilla::pathstr_t aFilePath, const size_t aOffset, if (!aOutFd) { CloseHandle(fd); } -#elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) +#elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) || defined(XP_SOLARIS) if (!aFilePath) { if (aOutFd) { *aOutFd = -1; -- cgit v1.2.3 From f105b741e549e2c2b985e1458ff6153c3d13929a Mon Sep 17 00:00:00 2001 From: athenian200 Date: Tue, 1 Oct 2019 19:15:26 -0500 Subject: MoonchildProductions#1251 - Part 5: Fix POSIX compliance issue in process_util.h. https://bugzilla.mozilla.org/show_bug.cgi?id=1364865 Solaris doesn't define NAME_MAX because if you read the current POSIX standard literally, no system that supports multiple file systems or networking should be defining it. It's a pedantic choice given that they USED to define NAME_MAX, but Solaris always did take POSIX compliance seriously, for better or worse. --- ipc/chromium/src/base/process_util.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ipc/chromium/src/base/process_util.h b/ipc/chromium/src/base/process_util.h index 2b257b587..9b1e4fed5 100644 --- a/ipc/chromium/src/base/process_util.h +++ b/ipc/chromium/src/base/process_util.h @@ -44,10 +44,12 @@ typedef PROCESSENTRY32 ProcessEntry; typedef IO_COUNTERS IoCounters; #elif defined(OS_POSIX) // TODO(port): we should not rely on a Win32 structure. +// Using NAME_MAX here would raise POSIX compliance issues +// (see Mozilla bug 1364865). struct ProcessEntry { int pid; int ppid; - char szExeFile[NAME_MAX + 1]; + char szExeFile[_POSIX_PATH_MAX + 1]; }; struct IoCounters { -- cgit v1.2.3 From 57bfda37aebdef6a0f7bbb320d508dfaf1a89718 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Tue, 1 Oct 2019 20:29:46 -0500 Subject: MoonchildProductions#1251 - Part 6: Solaris needs an audio implementation. Current versions of libcubeb already have a Sun audio implementation, but in Firefox 52 and earlier, this was all they had. I'm not completely happy with this implementation because it has issues like video freezing if the soundcard isn't working, but I think fixing this or pulling in a newer libcubeb would be going too far for too little gain. --- ipc/chromium/src/base/sys_info_posix.cc | 4 + media/libcubeb/src/cubeb.c | 6 + media/libcubeb/src/cubeb_sun.c | 504 ++++++++++++++++++++++++++++++++ media/libcubeb/src/moz.build | 6 + 4 files changed, 520 insertions(+) create mode 100644 media/libcubeb/src/cubeb_sun.c diff --git a/ipc/chromium/src/base/sys_info_posix.cc b/ipc/chromium/src/base/sys_info_posix.cc index 1b88883eb..6adbb1c06 100644 --- a/ipc/chromium/src/base/sys_info_posix.cc +++ b/ipc/chromium/src/base/sys_info_posix.cc @@ -133,6 +133,10 @@ std::string SysInfo::OperatingSystemName() { return std::string(info.sysname); } +// Solaris contains "extern struct utsname utsname;" +// As a consequence, any use of utsname has to be proceeded with struct on +// Solaris. See Mozilla bugs 758483 and 1353332. + // static std::string SysInfo::CPUArchitecture() { #if !defined(XP_SOLARIS) diff --git a/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c index eb22a9b94..a239319a4 100644 --- a/media/libcubeb/src/cubeb.c +++ b/media/libcubeb/src/cubeb.c @@ -54,6 +54,9 @@ int audiotrack_init(cubeb ** context, char const * context_name); #if defined(USE_KAI) int kai_init(cubeb ** context, char const * context_name); #endif +#if defined(USE_SUN) +int sunaudio_init(cubeb ** context, char const * context_name); +#endif static int @@ -140,6 +143,9 @@ cubeb_init(cubeb ** context, char const * context_name) #endif #if defined(USE_KAI) kai_init, +#endif +#if defined(USE_SUN) + sunaudio_init, #endif }; int i; diff --git a/media/libcubeb/src/cubeb_sun.c b/media/libcubeb/src/cubeb_sun.c new file mode 100644 index 000000000..b768bca56 --- /dev/null +++ b/media/libcubeb/src/cubeb_sun.c @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2013, 2017 Ginn Chen + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cubeb/cubeb.h" +#include "cubeb-internal.h" + +/* Macros copied from audio_oss.h */ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (C) 4Front Technologies 1996-2008. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#define OSSIOCPARM_MASK 0x1fff /* parameters must be < 8192 bytes */ +#define OSSIOC_VOID 0x00000000 /* no parameters */ +#define OSSIOC_OUT 0x20000000 /* copy out parameters */ +#define OSSIOC_IN 0x40000000 /* copy in parameters */ +#define OSSIOC_INOUT (OSSIOC_IN|OSSIOC_OUT) +#define OSSIOC_SZ(t) ((sizeof (t) & OSSIOCPARM_MASK) << 16) +#define __OSSIO(x, y) ((int)(OSSIOC_VOID|(x<<8)|y)) +#define __OSSIOR(x, y, t) ((int)(OSSIOC_OUT|OSSIOC_SZ(t)|(x<<8)|y)) +#define __OSSIOWR(x, y, t) ((int)(OSSIOC_INOUT|OSSIOC_SZ(t)|(x<<8)|y)) +#define SNDCTL_DSP_SPEED __OSSIOWR('P', 2, int) +#define SNDCTL_DSP_CHANNELS __OSSIOWR('P', 6, int) +#define SNDCTL_DSP_SETFMT __OSSIOWR('P', 5, int) /* Selects ONE fmt */ +#define SNDCTL_DSP_GETODELAY __OSSIOR('P', 23, int) +#define SNDCTL_DSP_HALT_OUTPUT __OSSIO('P', 34) +#define AFMT_S16_LE 0x00000010 +#define AFMT_S16_BE 0x00000020 + +#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) +#define AFMT_S16_NE AFMT_S16_BE +#else +#define AFMT_S16_NE AFMT_S16_LE +#endif + +#define DEFAULT_AUDIO_DEVICE "/dev/audio" +#define DEFAULT_DSP_DEVICE "/dev/dsp" + +#define BUF_SIZE_MS 10 + +#if defined(CUBEB_SUNAUDIO_DEBUG) +#define DPR(...) fprintf(stderr, __VA_ARGS__); +#else +#define DPR(...) do {} while(0) +#endif + +static struct cubeb_ops const sunaudio_ops; + +struct cubeb { + struct cubeb_ops const * ops; +}; + +struct cubeb_stream { + cubeb * context; + pthread_t th; /* to run real-time audio i/o */ + pthread_mutex_t mutex; /* protects fd and frm_played */ + int fd; /* link us to sunaudio */ + int active; /* cubec_start() called */ + int conv; /* need float->s16 conversion */ + int using_oss; + unsigned char *buf; /* data is prepared here */ + unsigned int rate; + unsigned int n_channles; + unsigned int bytes_per_ch; + unsigned int n_frm; + unsigned int buffer_size; + int64_t frm_played; + cubeb_data_callback data_cb; /* cb to preapare data */ + cubeb_state_callback state_cb; /* cb to notify about state changes */ + void *arg; /* user arg to {data,state}_cb */ +}; + +static void +float_to_s16(void *ptr, long nsamp) +{ + int16_t *dst = ptr; + float *src = ptr; + + while (nsamp-- > 0) + *(dst++) = *(src++) * 32767; +} + +static void * +sunaudio_mainloop(void *arg) +{ + struct cubeb_stream *s = arg; + int state; + + DPR("sunaudio_mainloop()\n"); + + s->state_cb(s, s->arg, CUBEB_STATE_STARTED); + + pthread_mutex_lock(&s->mutex); + DPR("sunaudio_mainloop(), started\n"); + + for (;;) { + if (!s->active) { + DPR("sunaudio_mainloop() stopped\n"); + state = CUBEB_STATE_STOPPED; + break; + } + + if (!s->using_oss) { + audio_info_t info; + ioctl(s->fd, AUDIO_GETINFO, &info); + if (s->frm_played > info.play.samples + 3 * s->n_frm) { + pthread_mutex_unlock(&s->mutex); + struct timespec ts = {0, 10000}; // 10 ms + nanosleep(&ts, NULL); + pthread_mutex_lock(&s->mutex); + continue; + } + } + + pthread_mutex_unlock(&s->mutex); + unsigned int got = s->data_cb(s, s->arg, NULL, s->buf, s->n_frm); + DPR("sunaudio_mainloop() ask %d got %d\n", s->n_frm, got); + pthread_mutex_lock(&s->mutex); + + if (got < 0) { + DPR("sunaudio_mainloop() cb err\n"); + state = CUBEB_STATE_ERROR; + break; + } + + if (s->conv) { + float_to_s16(s->buf, got * s->n_channles); + } + + unsigned int avail = got * 2 * s->n_channles; // coverted to s16 + unsigned int pos = 0; + + while (avail > 0 && s->active) { + int written = write(s->fd, s->buf + pos, avail); + if (written == -1) { + if (errno != EINTR && errno != EWOULDBLOCK) { + DPR("sunaudio_mainloop() write err\n"); + state = CUBEB_STATE_ERROR; + break; + } + pthread_mutex_unlock(&s->mutex); + struct timespec ts = {0, 10000}; // 10 ms + nanosleep(&ts, NULL); + pthread_mutex_lock(&s->mutex); + } else { + pos += written; + DPR("sunaudio_mainloop() write %d pos %d\n", written, pos); + s->frm_played += written / 2 / s->n_channles; + avail -= written; + } + } + + if ((got < s->n_frm)) { + DPR("sunaudio_mainloop() drained\n"); + state = CUBEB_STATE_DRAINED; + break; + } + } + + pthread_mutex_unlock(&s->mutex); + s->state_cb(s, s->arg, state); + + return NULL; +} + +/*static*/ int +sunaudio_init(cubeb **context, char const *context_name) +{ + DPR("sunaudio_init(%s)\n", context_name); + *context = malloc(sizeof(*context)); + (*context)->ops = &sunaudio_ops; + (void)context_name; + return CUBEB_OK; +} + +static char const * +sunaudio_get_backend_id(cubeb *context) +{ + return "sunaudio"; +} + +static void +sunaudio_destroy(cubeb *context) +{ + DPR("sunaudio_destroy()\n"); + free(context); +} + +static int +sunaudio_stream_init(cubeb *context, + cubeb_stream **stream, + char const *stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void *user_ptr) +{ + struct cubeb_stream *s; + DPR("sunaudio_stream_init(%s)\n", stream_name); + size_t size; + + s = malloc(sizeof(struct cubeb_stream)); + if (s == NULL) + return CUBEB_ERROR; + s->context = context; + + // If UTAUDIODEV is set, use it with Sun Audio interface + char * sa_device_name = getenv("UTAUDIODEV"); + char * dsp_device_name = NULL; + if (!sa_device_name) { + dsp_device_name = getenv("AUDIODSP"); + if (!dsp_device_name) { + dsp_device_name = DEFAULT_DSP_DEVICE; + } + sa_device_name = getenv("AUDIODEV"); + if (!sa_device_name) { + sa_device_name = DEFAULT_AUDIO_DEVICE; + } + } + + s->using_oss = 0; + // Try to use OSS if available + if (dsp_device_name) { + s->fd = open(dsp_device_name, O_WRONLY | O_NONBLOCK); + if (s->fd >= 0) { + s->using_oss = 1; + } + } + + // Try Sun Audio + if (!s->using_oss) { + s->fd = open(sa_device_name, O_WRONLY | O_NONBLOCK); + } + + if (s->fd < 0) { + free(s); + DPR("sunaudio_stream_init(), open() failed\n"); + return CUBEB_ERROR; + } + + if (s->using_oss) { + if (ioctl(s->fd, SNDCTL_DSP_SPEED, &output_stream_params->rate) < 0) { + DPR("ioctl SNDCTL_DSP_SPEED failed.\n"); + close(s->fd); + free(s); + return CUBEB_ERROR_INVALID_FORMAT; + } + + if (ioctl(s->fd, SNDCTL_DSP_CHANNELS, &output_stream_params->channels) < 0) { + DPR("ioctl SNDCTL_DSP_CHANNELS failed.\n"); + close(s->fd); + free(s); + return CUBEB_ERROR_INVALID_FORMAT; + } + + int format = AFMT_S16_NE; + if (ioctl(s->fd, SNDCTL_DSP_SETFMT, &format) < 0) { + DPR("ioctl SNDCTL_DSP_SETFMT failed.\n"); + close(s->fd); + free(s); + return CUBEB_ERROR_INVALID_FORMAT; + } + } else { + audio_info_t audio_info; + AUDIO_INITINFO(&audio_info) + audio_info.play.sample_rate = output_stream_params->rate; + audio_info.play.channels = output_stream_params->channels; + audio_info.play.encoding = AUDIO_ENCODING_LINEAR; + audio_info.play.precision = 16; + if (ioctl(s->fd, AUDIO_SETINFO, &audio_info) == -1) { + DPR("ioctl AUDIO_SETINFO failed.\n"); + close(s->fd); + free(s); + return CUBEB_ERROR_INVALID_FORMAT; + } + } + + s->conv = 0; + switch (output_stream_params->format) { + case CUBEB_SAMPLE_S16NE: + s->bytes_per_ch = 2; + break; + case CUBEB_SAMPLE_FLOAT32NE: + s->bytes_per_ch = 4; + s->conv = 1; + break; + default: + DPR("sunaudio_stream_init() unsupported format\n"); + close(s->fd); + free(s); + return CUBEB_ERROR_INVALID_FORMAT; + } + + s->active = 0; + s->rate = output_stream_params->rate; + s->n_channles = output_stream_params->channels; + s->data_cb = data_callback; + s->state_cb = state_callback; + s->arg = user_ptr; + if (pthread_mutex_init(&s->mutex, NULL) != 0) { + free(s); + return CUBEB_ERROR; + } + s->frm_played = 0; + s->n_frm = s->rate * BUF_SIZE_MS / 1000; + s->buffer_size = s->bytes_per_ch * s->n_channles * s->n_frm; + s->buf = malloc(s->buffer_size); + if (s->buf == NULL) { + close(s->fd); + free(s); + return CUBEB_ERROR; + } + + *stream = s; + DPR("sunaudio_stream_init() end, ok\n"); + return CUBEB_OK; +} + +static void +sunaudio_stream_destroy(cubeb_stream *s) +{ + DPR("sunaudio_stream_destroy()\n"); + if (s->fd > 0) { + // Flush buffer + if (s->using_oss) { + ioctl(s->fd, SNDCTL_DSP_HALT_OUTPUT); + } else { + ioctl(s->fd, I_FLUSH); + } + close(s->fd); + } + free(s->buf); + free(s); +} + +static int +sunaudio_stream_start(cubeb_stream *s) +{ + int err; + + DPR("sunaudio_stream_start()\n"); + s->active = 1; + err = pthread_create(&s->th, NULL, sunaudio_mainloop, s); + if (err) { + s->active = 0; + return CUBEB_ERROR; + } + return CUBEB_OK; +} + +static int +sunaudio_stream_stop(cubeb_stream *s) +{ + void *dummy; + + DPR("sunaudio_stream_stop()\n"); + if (s->active) { + s->active = 0; + pthread_join(s->th, &dummy); + } + return CUBEB_OK; +} + +static int +sunaudio_stream_get_position(cubeb_stream *s, uint64_t *p) +{ + int rv = CUBEB_OK; + pthread_mutex_lock(&s->mutex); + if (s->active && s->fd > 0) { + if (s->using_oss) { + int delay; + ioctl(s->fd, SNDCTL_DSP_GETODELAY, &delay); + int64_t t = s->frm_played - delay / s->n_channles / 2; + if (t < 0) { + *p = 0; + } else { + *p = t; + } + } else { + audio_info_t info; + ioctl(s->fd, AUDIO_GETINFO, &info); + *p = info.play.samples; + } + DPR("sunaudio_stream_get_position() %lld\n", *p); + } else { + rv = CUBEB_ERROR; + } + pthread_mutex_unlock(&s->mutex); + return rv; +} + +static int +sunaudio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) +{ + if (!ctx || !max_channels) + return CUBEB_ERROR; + + *max_channels = 2; + + return CUBEB_OK; +} + +static int +sunaudio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) +{ + if (!ctx || !rate) + return CUBEB_ERROR; + + // XXX Not yet implemented. + *rate = 44100; + + return CUBEB_OK; +} + +static int +sunaudio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) +{ + if (!ctx || !latency_ms) + return CUBEB_ERROR; + + // XXX Not yet implemented. + *latency_ms = 20; + + return CUBEB_OK; +} + +static int +sunaudio_stream_get_latency(cubeb_stream * s, uint32_t * latency) +{ + if (!s || !latency) + return CUBEB_ERROR; + + int rv = CUBEB_OK; + pthread_mutex_lock(&s->mutex); + if (s->active && s->fd > 0) { + if (s->using_oss) { + int delay; + ioctl(s->fd, SNDCTL_DSP_GETODELAY, &delay); + *latency = delay / s->n_channles / 2 / s->rate; + } else { + audio_info_t info; + ioctl(s->fd, AUDIO_GETINFO, &info); + *latency = (s->frm_played - info.play.samples) / s->rate; + } + DPR("sunaudio_stream_get_position() %lld\n", *p); + } else { + rv = CUBEB_ERROR; + } + pthread_mutex_unlock(&s->mutex); + return rv; +} + +static struct cubeb_ops const sunaudio_ops = { + .init = sunaudio_init, + .get_backend_id = sunaudio_get_backend_id, + .destroy = sunaudio_destroy, + .get_preferred_sample_rate = sunaudio_get_preferred_sample_rate, + .stream_init = sunaudio_stream_init, + .stream_destroy = sunaudio_stream_destroy, + .stream_start = sunaudio_stream_start, + .stream_stop = sunaudio_stream_stop, + .stream_get_position = sunaudio_stream_get_position, + .get_max_channel_count = sunaudio_get_max_channel_count, + .get_min_latency = sunaudio_get_min_latency, + .stream_get_latency = sunaudio_stream_get_latency +}; diff --git a/media/libcubeb/src/moz.build b/media/libcubeb/src/moz.build index 2ca3a2f54..b6d07126a 100644 --- a/media/libcubeb/src/moz.build +++ b/media/libcubeb/src/moz.build @@ -45,6 +45,12 @@ if CONFIG['OS_ARCH'] == 'OpenBSD': ] DEFINES['USE_SNDIO'] = True +if CONFIG['OS_ARCH'] == 'SunOS': + SOURCES += [ + 'cubeb_sun.c', + ] + DEFINES['USE_SUN'] = True + if CONFIG['OS_TARGET'] == 'Darwin': SOURCES += [ 'cubeb_audiounit.cpp', -- cgit v1.2.3 From 3647f42c27761472e4ee204bade964e8ffad4679 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Tue, 1 Oct 2019 21:36:29 -0500 Subject: MoonchildProductions#1251 - Part 7: All the posix_m* memory-related stuff, gathered together. https://bugzilla.mozilla.org/show_bug.cgi?id=1158445 https://bugzilla.mozilla.org/show_bug.cgi?id=963983 https://bugzilla.mozilla.org/show_bug.cgi?id=1542758 Solaris madvise and malign don't perfectly map to their POSIX counterparts, and some Linux versions (especially Android) don't define the POSIX counterparts at all, so options are limited. Ideally posix_madvise and posix_malign should be the safer and more portable options for all platforms. --- build/moz.configure/memory.configure | 3 +++ js/src/gc/Memory.cpp | 6 +++++- memory/mozjemalloc/jemalloc.c | 18 ++++++++++++++---- mfbt/Poison.cpp | 4 ++++ mfbt/tests/TestPoisonArea.cpp | 4 ++++ modules/libjar/nsZipArchive.cpp | 4 +++- 6 files changed, 33 insertions(+), 6 deletions(-) diff --git a/build/moz.configure/memory.configure b/build/moz.configure/memory.configure index 398413b62..1292d7273 100644 --- a/build/moz.configure/memory.configure +++ b/build/moz.configure/memory.configure @@ -58,6 +58,9 @@ def jemalloc_os_define(jemalloc, target): return 'MOZ_MEMORY_DARWIN' if target.kernel in ('kFreeBSD', 'FreeBSD', 'NetBSD'): return 'MOZ_MEMORY_BSD' + if target.kernel == 'SunOS': + return 'MOZ_MEMORY_SOLARIS' + die('--enable-jemalloc is not supported on %s', target.kernel) set_define(jemalloc_os_define, '1') diff --git a/js/src/gc/Memory.cpp b/js/src/gc/Memory.cpp index 268e1e489..5cf0cd2f7 100644 --- a/js/src/gc/Memory.cpp +++ b/js/src/gc/Memory.cpp @@ -678,7 +678,11 @@ MarkPagesUnused(void* p, size_t size) return false; MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0); - int result = madvise(p, size, MADV_DONTNEED); +#if defined(XP_SOLARIS) + int result = posix_madvise(p, size, POSIX_MADV_DONTNEED); +#else + int result = madvise(p, size, MADV_DONTNEED); +#endif return result != -1; } diff --git a/memory/mozjemalloc/jemalloc.c b/memory/mozjemalloc/jemalloc.c index c54a85959..acaf2572c 100644 --- a/memory/mozjemalloc/jemalloc.c +++ b/memory/mozjemalloc/jemalloc.c @@ -410,8 +410,8 @@ void *_mmap(void *addr, size_t length, int prot, int flags, #endif #endif -#if defined(MOZ_MEMORY_SOLARIS && defined(MAP_ALIGN) && !defined(JEMALLOC_NEVER_USES_MAP_ALIGN) -#define JEMALLOC_USES_MAP_ALIGN /* Required on Solaris 10. Might improve performance elsewhere. */ +#if defined(MOZ_MEMORY_SOLARIS) && defined(MAP_ALIGN) && !defined(JEMALLOC_NEVER_USES_MAP_ALIGN) +#define JEMALLOC_USES_MAP_ALIGN /* Required on Solaris 10. Might improve performance elsewhere. */ #endif #ifndef __DECONST @@ -1040,7 +1040,7 @@ static const bool config_recycle = false; * will abort. * Platform specific page size conditions copied from js/public/HeapAPI.h */ -#if (defined(__FreeBSD__)) && \ +#if (defined(SOLARIS) || defined(__FreeBSD__)) && \ (defined(__sparc) || defined(__sparcv9) || defined(__ia64)) #define pagesize_2pow ((size_t) 13) #elif defined(__powerpc64__) @@ -2646,8 +2646,13 @@ pages_purge(void *addr, size_t length) # define JEMALLOC_MADV_PURGE MADV_FREE # define JEMALLOC_MADV_ZEROS false # endif +#ifdef MOZ_MEMORY_SOLARIS + int err = posix_madvise(addr, length, JEMALLOC_MADV_PURGE); + unzeroed = (JEMALLOC_MADV_ZEROS == false || err != 0); +#else int err = madvise(addr, length, JEMALLOC_MADV_PURGE); unzeroed = (JEMALLOC_MADV_ZEROS == false || err != 0); +#endif # undef JEMALLOC_MADV_PURGE # undef JEMALLOC_MADV_ZEROS # endif @@ -3603,9 +3608,14 @@ arena_purge(arena_t *arena, bool all) #endif #ifndef MALLOC_DECOMMIT +#ifdef MOZ_MEMORY_SOLARIS + posix_madvise((void*)((uintptr_t)chunk + (i << pagesize_2pow)), + (npages << pagesize_2pow),MADV_FREE); +#else madvise((void *)((uintptr_t)chunk + (i << pagesize_2pow)), (npages << pagesize_2pow), MADV_FREE); +#endif # ifdef MALLOC_DOUBLE_PURGE madvised = true; # endif @@ -5131,7 +5141,7 @@ malloc_ncpus(void) static inline unsigned malloc_ncpus(void) { - return sysconf(_SC_NPROCESSORS_ONLN); + return sysconf(_SC_NPROCESSORS_ONLN); } #elif (defined(MOZ_MEMORY_WINDOWS)) static inline unsigned diff --git a/mfbt/Poison.cpp b/mfbt/Poison.cpp index b2767011d..7972dbea3 100644 --- a/mfbt/Poison.cpp +++ b/mfbt/Poison.cpp @@ -129,7 +129,11 @@ ReleaseRegion(void* aRegion, uintptr_t aSize) static bool ProbeRegion(uintptr_t aRegion, uintptr_t aSize) { +#ifdef XP_SOLARIS + if (posix_madvise(reinterpret_cast(aRegion), aSize, POSIX_MADV_NORMAL)) { +#else if (madvise(reinterpret_cast(aRegion), aSize, MADV_NORMAL)) { +#endif return true; } else { return false; diff --git a/mfbt/tests/TestPoisonArea.cpp b/mfbt/tests/TestPoisonArea.cpp index 6f1b61ed3..fb39ccf79 100644 --- a/mfbt/tests/TestPoisonArea.cpp +++ b/mfbt/tests/TestPoisonArea.cpp @@ -266,7 +266,11 @@ ReleaseRegion(void* aPage) static bool ProbeRegion(uintptr_t aPage) { +#ifdef XP_SOLARIS + return !!posix_madvise(reinterpret_cast(aPage), PageSize(), POSIX_MADV_NORMAL); +#else return !!madvise(reinterpret_cast(aPage), PageSize(), MADV_NORMAL); +#endif } static int diff --git a/modules/libjar/nsZipArchive.cpp b/modules/libjar/nsZipArchive.cpp index 429de1011..24da13072 100644 --- a/modules/libjar/nsZipArchive.cpp +++ b/modules/libjar/nsZipArchive.cpp @@ -688,7 +688,9 @@ MOZ_WIN_MEM_TRY_BEGIN // Success means optimized jar layout from bug 559961 is in effect uint32_t readaheadLength = xtolong(startp); if (readaheadLength) { -#if defined(XP_UNIX) +#if defined(XP_SOLARIS) + posix_madvise(const_cast(startp), readaheadLength, POSIX_MADV_WILLNEED); +#elif defined(XP_UNIX) madvise(const_cast(startp), readaheadLength, MADV_WILLNEED); #elif defined(XP_WIN) if (aFd) { -- cgit v1.2.3 From c0d814c1d52b0e9ae884b83f214218b954133acb Mon Sep 17 00:00:00 2001 From: athenian200 Date: Tue, 1 Oct 2019 22:59:38 -0500 Subject: MoonchildProductions#1251 - Part 8: Align pointer for char_16t. https://bugzilla.mozilla.org/show_bug.cgi?id=1352449 Mozilla patch that's been in the code since Firefox 55. Seems like there have been no ill effects from implementing it, and it would only increase the portability of the UXP code. All the Solaris Firefox repos I've seen implement some variation on the jsexn patch, and this seems to be the cleanest version of it. I can add ifdefs if needed or there are performance concerns associated with this patch, but I get the impression this alignment backlog issue might affect a few platforms other than Solaris, though none were named. Otherwise I think they wouldn't have used "platforms that need it" in plural form or failed to ifdef it. --- js/src/jsexn.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index 65cc81a1a..3fc9200c1 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -205,7 +205,12 @@ size_t ExtraMallocSize(JSErrorReport* report) { if (report->linebuf()) - return (report->linebufLength() + 1) * sizeof(char16_t); + /* + * Mozilla bug 1352449. Count with null + * terminator and alignment. See CopyExtraData for + * the details about alignment. + */ + return (report->linebufLength() + 1) * sizeof(char16_t) + 1; return 0; } @@ -220,10 +225,20 @@ bool CopyExtraData(JSContext* cx, uint8_t** cursor, JSErrorReport* copy, JSErrorReport* report) { if (report->linebuf()) { + /* + * Make sure cursor is properly aligned for char16_t for platforms + * which need it and it's at the end of the buffer on exit. + */ + size_t alignment_backlog = 0; + if (size_t(*cursor) % 2) + (*cursor)++; + else + alignment_backlog = 1; + size_t linebufSize = (report->linebufLength() + 1) * sizeof(char16_t); const char16_t* linebufCopy = (const char16_t*)(*cursor); js_memcpy(*cursor, report->linebuf(), linebufSize); - *cursor += linebufSize; + *cursor += linebufSize + alignment_backlog; copy->initBorrowedLinebuf(linebufCopy, report->linebufLength(), report->tokenOffset()); } -- cgit v1.2.3 From cf75ede0de7cc8febc12d666783631836c6b8e43 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Tue, 1 Oct 2019 23:39:11 -0500 Subject: MoonchildProductions#1251 - Part 9: Look for hypot in the math library (libm). https://bugzilla.mozilla.org/show_bug.cgi?id=1351309 https://bugzilla.mozilla.org/show_bug.cgi?id=1309157 I assess this change to be low-risk for the following reasons: 1. It has been in Firefox since version 55 without issues. 2. It's nearly identical to the fix for bug 1309157 which is already in our tree, so that one would be causing problems if this one were going to. 3. Linux, Windows, and BSD are known to have a hypot function in their math libraries. 4. Even if it does break something, it should only break a test and not critical functionality. --- js/src/jit-test/tests/ctypes/function-definition.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/jit-test/tests/ctypes/function-definition.js b/js/src/jit-test/tests/ctypes/function-definition.js index 4df317a09..5882ba889 100644 --- a/js/src/jit-test/tests/ctypes/function-definition.js +++ b/js/src/jit-test/tests/ctypes/function-definition.js @@ -27,7 +27,7 @@ function test() { let lib; try { - lib = ctypes.open(ctypes.libraryName("c")); + lib = ctypes.open(ctypes.libraryName("m")); } catch (e) { } if (!lib) -- cgit v1.2.3 From 52f2321cba8486169779773dc13a213410fc853b Mon Sep 17 00:00:00 2001 From: athenian200 Date: Wed, 2 Oct 2019 01:16:38 -0500 Subject: MoonchildProductions#1251 - Part 10: ipc_channel_posix.cc should use IOV_MAX. https://bugzilla.mozilla.org/show_bug.cgi?id=1345102 I assess this change to be low-risk for the following reasons: 1. It has been in Firefox since version 55 without issues. 2. The current behavior is not POSIX compliant, and is retained in the one instance where the new functionality causes issues. 3. It makes safer assumptions about implementation details than what we have now. --- ipc/chromium/src/chrome/common/ipc_channel_posix.cc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ipc/chromium/src/chrome/common/ipc_channel_posix.cc b/ipc/chromium/src/chrome/common/ipc_channel_posix.cc index 0d3a2b16c..9a8858656 100644 --- a/ipc/chromium/src/chrome/common/ipc_channel_posix.cc +++ b/ipc/chromium/src/chrome/common/ipc_channel_posix.cc @@ -8,6 +8,7 @@ #include #include +#include #if defined(OS_MACOSX) #include #endif @@ -39,8 +40,16 @@ #include "mozilla/ipc/Faulty.h" #endif -// Work around possible OS limitations. +// Use OS specific iovec array limit where it's possible +#if defined(IOV_MAX) +static const size_t kMaxIOVecSize = IOV_MAX; +// IOV_MAX isn't defined on Android, but the hard-coded 256 works well. +#elif defined(ANDROID) static const size_t kMaxIOVecSize = 256; +// On all other platforms, fallback to 16 (_XOPEN_IOV_MAX) as a safe bet. +#else +static const size_t kMaxIOVecSize = 16; +#endif #ifdef MOZ_TASK_TRACER #include "GeckoTaskTracerImpl.h" -- cgit v1.2.3 From 7966d22df36adfd418c49558611fbd092180ea9b Mon Sep 17 00:00:00 2001 From: athenian200 Date: Wed, 2 Oct 2019 13:25:24 -0500 Subject: MoonchildProductions#1251 - Part 11: libpng uses C89 now to avoid buffer overflows. https://bugzilla.mozilla.org/show_bug.cgi?id=1371266 https://github.com/glennrp/libpng/commit/12e63e91af1378225993b36e25ce3252b54e751a This sounds absurd, and the fact that we had to change a C++ comment to a C-style comment on account of this may seem hilarious and inconsequential, but this isapparently not a joke and leaving it as it is now may be a bad idea. --- media/libpng/moz.build | 3 +++ mozilla-config.h.in | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/media/libpng/moz.build b/media/libpng/moz.build index 9146a8d5a..782aa93df 100644 --- a/media/libpng/moz.build +++ b/media/libpng/moz.build @@ -51,3 +51,6 @@ FINAL_LIBRARY = 'gkmedias' # We allow warnings for third-party code that can be updated from upstream. ALLOW_COMPILER_WARNINGS = True + +if CONFIG['GNU_CC'] + CFLAGS += ['std=c89'] diff --git a/mozilla-config.h.in b/mozilla-config.h.in index 7484180b0..6883a7be2 100644 --- a/mozilla-config.h.in +++ b/mozilla-config.h.in @@ -13,7 +13,7 @@ #endif #endif -// Expands to all the defines from configure. +/* Expands to all the defines from configure. */ #undef ALLDEFINES /* -- cgit v1.2.3 From 76c55f747ce5878d1d37d8bf3bd0a50c1b478bec Mon Sep 17 00:00:00 2001 From: athenian200 Date: Wed, 2 Oct 2019 14:08:55 -0500 Subject: MoonchildProductions#1251 - Part 12: Add Solaris/illumos support to WasmSignalHandlers. https://www.illumos.org/issues/5876 https://bugzilla.mozilla.org/show_bug.cgi?id=135050 --- js/src/wasm/WasmSignalHandlers.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/js/src/wasm/WasmSignalHandlers.cpp b/js/src/wasm/WasmSignalHandlers.cpp index c4733cc96..21093ca9a 100644 --- a/js/src/wasm/WasmSignalHandlers.cpp +++ b/js/src/wasm/WasmSignalHandlers.cpp @@ -130,11 +130,16 @@ class AutoSetHandlingSegFault # define EPC_sig(p) ((p)->sc_pc) # define RFP_sig(p) ((p)->sc_regs[30]) # endif -#elif defined(__linux__) +#elif defined(__linux__) || defined(__sun) # if defined(__linux__) # define XMM_sig(p,i) ((p)->uc_mcontext.fpregs->_xmm[i]) # define EIP_sig(p) ((p)->uc_mcontext.gregs[REG_EIP]) -# else +# else // defined(__sun) +/* See https://www.illumos.org/issues/5876. They keep arguing over whether + * should provide the register index defines in regset.h or + * require applications to request them specifically, and we need them here. */ +#include +#include # define XMM_sig(p,i) ((p)->uc_mcontext.fpregs.fp_reg_set.fpchip_state.xmm[i]) # define EIP_sig(p) ((p)->uc_mcontext.gregs[REG_PC]) # endif -- cgit v1.2.3 From 5c28f10c144026aba25044410510ba8418978698 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Wed, 2 Oct 2019 14:44:26 -0500 Subject: MoonchildProductions#1251 - Part 13: Redefining abort in C++ requires extern "C" https://bugzilla.mozilla.org/show_bug.cgi?id=1375467 I would ifdef this, but it's been in Firefox since version of 56, and Petr Sumbara's explanation as to why it's wrong in the first place is so detailed that it's pretty obvious the code wasn't technically doing things properly to begin with. Basically, they tried to redefine a system function after including the header file that declares it, and it caused problems on Solaris because libc functions are imported into the C++ std namespace in a different way that also complies with standards. So the existing implementation is technically bad code on all platforms, the Solaris implementation just uncovered the lack of standards compliance in the Mozilla code. --- memory/mozalloc/mozalloc_abort.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/memory/mozalloc/mozalloc_abort.cpp b/memory/mozalloc/mozalloc_abort.cpp index a998d8164..85e566db0 100644 --- a/memory/mozalloc/mozalloc_abort.cpp +++ b/memory/mozalloc/mozalloc_abort.cpp @@ -68,7 +68,7 @@ void fillAbortMessage(char (&msg)[N], uintptr_t retAddress) { // // That segmentation fault will be interpreted as another bug by ASan and as a // result, ASan will just exit(1) instead of aborting. -void abort(void) +extern "C" void abort(void) { #ifdef MOZ_WIDGET_ANDROID char msg[64] = {}; -- cgit v1.2.3 From e3fb8bd24dcbc8357c325e3b24d0ad410bf0b15d Mon Sep 17 00:00:00 2001 From: athenian200 Date: Wed, 2 Oct 2019 16:46:20 -0500 Subject: MoonchildProductions#1251 - Part 14: libstagefright build should avoid _GLIBCXX_OS_DEFINES. https://bugzilla.mozilla.org/show_bug.cgi?id=1367775 Windows already requires this ifdef, Solaris needs it too. Not much more to say, but I can do research and follow up on this if asked. --- media/libstagefright/moz.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/libstagefright/moz.build b/media/libstagefright/moz.build index 5a8c9521a..87e162112 100644 --- a/media/libstagefright/moz.build +++ b/media/libstagefright/moz.build @@ -7,7 +7,7 @@ DEFINES['ANDROID_SMP'] = 0 DEFINES['LOG_NDEBUG'] = 1 -if CONFIG['OS_TARGET'] != 'WINNT': +if CONFIG['OS_TARGET'] != 'WINNT' and CONFIG['OS_TARGET'] != 'SunOS': DEFINES['_GLIBCXX_OS_DEFINES'] = True if CONFIG['OS_TARGET'] == 'WINNT': -- cgit v1.2.3 From 4f6639a1b35d4941f52eb4c8901adf45b35bc10d Mon Sep 17 00:00:00 2001 From: athenian200 Date: Wed, 2 Oct 2019 17:38:39 -0500 Subject: MoonchildProductions#1251 - Part 15: fdlibm should provide definition for u_int32_t and u_int64_t. https://bugzilla.mozilla.org/show_bug.cgi?id=1350355 u_int32_t is not an stdint.h type. Windows already requires this, Solaris needs it too. If someone has a nit with this approach, the alternatives include: 1. Just replacing every instance of u_int32_t with uint32_t. 2. Including for Solaris only, which does define this. 3. Changing the original ifdef to be WIN32 || XP_SOLARIS But it really doesn't matter how you solve this problem, all of the approaches are functionally equivalent, and this one has been used in Firefox since version 55. As far as I can tell, all it does is apply a fix that was being done for Windows already to any platform that needs it. --- .../fdlibm/patches/12_define_u_int32_t_and_u_int64_t_on_windows.patch | 4 +++- modules/fdlibm/src/math_private.h | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/fdlibm/patches/12_define_u_int32_t_and_u_int64_t_on_windows.patch b/modules/fdlibm/patches/12_define_u_int32_t_and_u_int64_t_on_windows.patch index b8f238c74..c0e9814aa 100644 --- a/modules/fdlibm/patches/12_define_u_int32_t_and_u_int64_t_on_windows.patch +++ b/modules/fdlibm/patches/12_define_u_int32_t_and_u_int64_t_on_windows.patch @@ -10,8 +10,10 @@ diff --git a/modules/fdlibm/src/math_private.h b/modules/fdlibm/src/math_private * endianness at run time. */ -+#ifdef WIN32 ++#ifndef u_int32_t +#define u_int32_t uint32_t ++#endif ++#ifndef u_int64_t +#define u_int64_t uint64_t +#endif + diff --git a/modules/fdlibm/src/math_private.h b/modules/fdlibm/src/math_private.h index 6947cecc0..86597e75f 100644 --- a/modules/fdlibm/src/math_private.h +++ b/modules/fdlibm/src/math_private.h @@ -38,8 +38,10 @@ * endianness at run time. */ -#ifdef WIN32 +#ifndef u_int32_t #define u_int32_t uint32_t +#endif +#ifndef u_int64_t #define u_int64_t uint64_t #endif -- cgit v1.2.3 From fca7c45a62542e0f625122222386cbee9b76243f Mon Sep 17 00:00:00 2001 From: athenian200 Date: Wed, 2 Oct 2019 18:26:54 -0500 Subject: MoonchildProductions#1251 - Part 16: Resolve namespace conflicts with dbm on Solaris. https://bugzilla.mozilla.org/show_bug.cgi?id=1513913 Mozilla's solution to this is arguably overkill, since the namespace issue on Solaris only required them to change (or temporarily undefine) __log2. Instead they changed ALL the functions to be something along the lines of dbm_log2. They haven't changed the external interface at all, though. If you're unhappy with this patch, I think I could also use XP_SOLARIS ifdefs to undefine __log2 prior to where it's declared in the dbm headers. The good thing about Mozilla's solution is that it guarantees this namespace issue never occurs again on any platform, though. --- security/nss/lib/dbm/include/extern.h | 48 ++++++++++----------- security/nss/lib/dbm/include/hash.h | 50 +++++++++++----------- security/nss/lib/dbm/include/mcom_db.h | 24 +++++------ security/nss/lib/dbm/include/ncompat.h | 6 +-- security/nss/lib/dbm/src/db.c | 25 +++++------ security/nss/lib/dbm/src/h_bigkey.c | 61 ++++++++++++++------------- security/nss/lib/dbm/src/h_func.c | 3 +- security/nss/lib/dbm/src/h_log2.c | 3 +- security/nss/lib/dbm/src/h_page.c | 77 +++++++++++++++++----------------- security/nss/lib/dbm/src/hash.c | 71 +++++++++++++++---------------- security/nss/lib/dbm/src/hash_buf.c | 25 +++++------ 11 files changed, 200 insertions(+), 193 deletions(-) diff --git a/security/nss/lib/dbm/include/extern.h b/security/nss/lib/dbm/include/extern.h index 897369fb8..4fbdc2d80 100644 --- a/security/nss/lib/dbm/include/extern.h +++ b/security/nss/lib/dbm/include/extern.h @@ -31,32 +31,32 @@ * @(#)extern.h 8.4 (Berkeley) 6/16/94 */ -BUFHEAD *__add_ovflpage(HTAB *, BUFHEAD *); -int __addel(HTAB *, BUFHEAD *, const DBT *, const DBT *); -int __big_delete(HTAB *, BUFHEAD *); -int __big_insert(HTAB *, BUFHEAD *, const DBT *, const DBT *); -int __big_keydata(HTAB *, BUFHEAD *, DBT *, DBT *, int); -int __big_return(HTAB *, BUFHEAD *, int, DBT *, int); -int __big_split(HTAB *, BUFHEAD *, BUFHEAD *, BUFHEAD *, - uint32, uint32, SPLIT_RETURN *); -int __buf_free(HTAB *, int, int); -void __buf_init(HTAB *, int); -uint32 __call_hash(HTAB *, char *, size_t); -int __delpair(HTAB *, BUFHEAD *, int); -int __expand_table(HTAB *); -int __find_bigpair(HTAB *, BUFHEAD *, int, char *, int); -uint16 __find_last_page(HTAB *, BUFHEAD **); -void __free_ovflpage(HTAB *, BUFHEAD *); -BUFHEAD *__get_buf(HTAB *, uint32, BUFHEAD *, int); -int __get_page(HTAB *, char *, uint32, int, int, int); -int __ibitmap(HTAB *, int, int, int); -uint32 __log2(uint32); -int __put_page(HTAB *, char *, uint32, int, int); -void __reclaim_buf(HTAB *, BUFHEAD *); -int __split_page(HTAB *, uint32, uint32); +BUFHEAD *dbm_add_ovflpage(HTAB *, BUFHEAD *); +int dbm_addel(HTAB *, BUFHEAD *, const DBT *, const DBT *); +int dbm_big_delete(HTAB *, BUFHEAD *); +int dbm_big_insert(HTAB *, BUFHEAD *, const DBT *, const DBT *); +int dbm_big_keydata(HTAB *, BUFHEAD *, DBT *, DBT *, int); +int dbm_big_return(HTAB *, BUFHEAD *, int, DBT *, int); +int dbm_big_split(HTAB *, BUFHEAD *, BUFHEAD *, BUFHEAD *, + uint32, uint32, SPLIT_RETURN *); +int dbm_buf_free(HTAB *, int, int); +void dbm_buf_init(HTAB *, int); +uint32 dbm_call_hash(HTAB *, char *, size_t); +int dbm_delpair(HTAB *, BUFHEAD *, int); +int dbm_expand_table(HTAB *); +int dbm_find_bigpair(HTAB *, BUFHEAD *, int, char *, int); +uint16 dbm_find_last_page(HTAB *, BUFHEAD **); +void dbm_free_ovflpage(HTAB *, BUFHEAD *); +BUFHEAD *dbm_get_buf(HTAB *, uint32, BUFHEAD *, int); +int dbm_get_page(HTAB *, char *, uint32, int, int, int); +int dbm_ibitmap(HTAB *, int, int, int); +uint32 dbm_log2(uint32); +int dbm_put_page(HTAB *, char *, uint32, int, int); +void dbm_reclaim_buf(HTAB *, BUFHEAD *); +int dbm_split_page(HTAB *, uint32, uint32); /* Default hash routine. */ -extern uint32 (*__default_hash)(const void *, size_t); +extern uint32 (*dbm_default_hash)(const void *, size_t); #ifdef HASH_STATISTICS extern int hash_accesses, hash_collisions, hash_expansions, hash_overflows; diff --git a/security/nss/lib/dbm/include/hash.h b/security/nss/lib/dbm/include/hash.h index 7da51dc64..0ce3c3ff2 100644 --- a/security/nss/lib/dbm/include/hash.h +++ b/security/nss/lib/dbm/include/hash.h @@ -190,7 +190,7 @@ typedef struct htab { /* Memory resident data structure */ #define OADDR_OF(S, O) ((uint32)((uint32)(S) << SPLITSHIFT) + (O)) #define BUCKET_TO_PAGE(B) \ - (B) + hashp->HDRPAGES + ((B) ? hashp->SPARES[__log2((uint32)((B) + 1)) - 1] : 0) + (B) + hashp->HDRPAGES + ((B) ? hashp->SPARES[dbm_log2((uint32)((B) + 1)) - 1] : 0) #define OADDR_TO_PAGE(B) \ BUCKET_TO_PAGE((1 << SPLITNUM((B))) - 1) + OPAGENUM((B)); @@ -314,28 +314,28 @@ typedef struct htab { /* Memory resident data structure */ #define NEXT_FREE hdr.next_free #define H_CHARKEY hdr.h_charkey -extern uint32 (*__default_hash)(const void *, size_t); -void __buf_init(HTAB *hashp, int32 nbytes); -int __big_delete(HTAB *hashp, BUFHEAD *bufp); -BUFHEAD *__get_buf(HTAB *hashp, uint32 addr, BUFHEAD *prev_bp, int newpage); -uint32 __call_hash(HTAB *hashp, char *k, size_t len); +extern uint32 (*dbm_default_hash)(const void *, size_t); +void dbm_buf_init(HTAB *hashp, int32 nbytes); +int dbm_big_delete(HTAB *hashp, BUFHEAD *bufp); +BUFHEAD *dbm_get_buf(HTAB *hashp, uint32 addr, BUFHEAD *prev_bp, int newpage); +uint32 dbm_call_hash(HTAB *hashp, char *k, size_t len); #include "page.h" -extern int __big_split(HTAB *hashp, BUFHEAD *op, BUFHEAD *np, - BUFHEAD *big_keyp, uint32 addr, uint32 obucket, SPLIT_RETURN *ret); -void __free_ovflpage(HTAB *hashp, BUFHEAD *obufp); -BUFHEAD *__add_ovflpage(HTAB *hashp, BUFHEAD *bufp); -int __big_insert(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val); -int __expand_table(HTAB *hashp); -uint32 __log2(uint32 num); -void __reclaim_buf(HTAB *hashp, BUFHEAD *bp); -int __get_page(HTAB *hashp, char *p, uint32 bucket, int is_bucket, int is_disk, int is_bitmap); -int __put_page(HTAB *hashp, char *p, uint32 bucket, int is_bucket, int is_bitmap); -int __ibitmap(HTAB *hashp, int pnum, int nbits, int ndx); -int __buf_free(HTAB *hashp, int do_free, int to_disk); -int __find_bigpair(HTAB *hashp, BUFHEAD *bufp, int ndx, char *key, int size); -uint16 __find_last_page(HTAB *hashp, BUFHEAD **bpp); -int __addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val); -int __big_return(HTAB *hashp, BUFHEAD *bufp, int ndx, DBT *val, int set_current); -int __delpair(HTAB *hashp, BUFHEAD *bufp, int ndx); -int __big_keydata(HTAB *hashp, BUFHEAD *bufp, DBT *key, DBT *val, int set); -int __split_page(HTAB *hashp, uint32 obucket, uint32 nbucket); +extern int dbm_big_split(HTAB *hashp, BUFHEAD *op, BUFHEAD *np, + BUFHEAD *big_keyp, uint32 addr, uint32 obucket, SPLIT_RETURN *ret); +void dbm_free_ovflpage(HTAB *hashp, BUFHEAD *obufp); +BUFHEAD *dbm_add_ovflpage(HTAB *hashp, BUFHEAD *bufp); +int dbm_big_insert(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val); +int dbm_expand_table(HTAB *hashp); +uint32 dbm_log2(uint32 num); +void dbm_reclaim_buf(HTAB *hashp, BUFHEAD *bp); +int dbm_get_page(HTAB *hashp, char *p, uint32 bucket, int is_bucket, int is_disk, int is_bitmap); +int dbm_put_page(HTAB *hashp, char *p, uint32 bucket, int is_bucket, int is_bitmap); +int dbm_ibitmap(HTAB *hashp, int pnum, int nbits, int ndx); +int dbm_buf_free(HTAB *hashp, int do_free, int to_disk); +int dbm_find_bigpair(HTAB *hashp, BUFHEAD *bufp, int ndx, char *key, int size); +uint16 dbm_find_last_page(HTAB *hashp, BUFHEAD **bpp); +int dbm_addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val); +int dbm_big_return(HTAB *hashp, BUFHEAD *bufp, int ndx, DBT *val, int set_current); +int dbm_delpair(HTAB *hashp, BUFHEAD *bufp, int ndx); +int dbm_big_keydata(HTAB *hashp, BUFHEAD *bufp, DBT *key, DBT *val, int set); +int dbm_split_page(HTAB *hashp, uint32 obucket, uint32 nbucket); diff --git a/security/nss/lib/dbm/include/mcom_db.h b/security/nss/lib/dbm/include/mcom_db.h index 0a4f6dc14..e961dd1b4 100644 --- a/security/nss/lib/dbm/include/mcom_db.h +++ b/security/nss/lib/dbm/include/mcom_db.h @@ -287,16 +287,16 @@ typedef enum { LockOutDatabase, #endif /* Access method description structure. */ -typedef struct __db { +typedef struct dbm_db { DBTYPE type; /* Underlying db type. */ - int (*close)(struct __db *); - int (*del)(const struct __db *, const DBT *, uint); - int (*get)(const struct __db *, const DBT *, DBT *, uint); - int (*put)(const struct __db *, DBT *, const DBT *, uint); - int (*seq)(const struct __db *, DBT *, DBT *, uint); - int (*sync)(const struct __db *, uint); + int (*close)(struct dbm_db *); + int (*del)(const struct dbm_db *, const DBT *, uint); + int (*get)(const struct dbm_db *, const DBT *, DBT *, uint); + int (*put)(const struct dbm_db *, DBT *, const DBT *, uint); + int (*seq)(const struct dbm_db *, DBT *, DBT *, uint); + int (*sync)(const struct dbm_db *, uint); void *internal; /* Access method private. */ - int (*fd)(const struct __db *); + int (*fd)(const struct dbm_db *); } DB; #define BTREEMAGIC 0x053162 @@ -412,10 +412,10 @@ dbopen(const char *, int, int, DBTYPE, const void *); void dbSetOrClearDBLock(DBLockFlagEnum type); #ifdef __DBINTERFACE_PRIVATE -DB *__bt_open(const char *, int, int, const BTREEINFO *, int); -DB *__hash_open(const char *, int, int, const HASHINFO *, int); -DB *__rec_open(const char *, int, int, const RECNOINFO *, int); -void __dbpanic(DB *dbp); +DB *dbm_bt_open(const char *, int, int, const BTREEINFO *, int); +DB *dbm_hash_open(const char *, int, int, const HASHINFO *, int); +DB *dbm_rec_open(const char *, int, int, const RECNOINFO *, int); +void dbm_dbpanic(DB *dbp); #endif PR_END_EXTERN_C diff --git a/security/nss/lib/dbm/include/ncompat.h b/security/nss/lib/dbm/include/ncompat.h index 9fd434799..f9f631622 100644 --- a/security/nss/lib/dbm/include/ncompat.h +++ b/security/nss/lib/dbm/include/ncompat.h @@ -89,13 +89,13 @@ typedef unsigned int sigset_t; #define SIG_UNBLOCK 2 #define SIG_SETMASK 3 -static int __sigtemp; /* For the use of sigprocmask */ +static int dbm_sigtemp; /* For the use of sigprocmask */ /* Repeated test of oset != NULL is to avoid "*0". */ #define sigprocmask(how, set, oset) \ - ((__sigtemp = \ + ((dbm_sigtemp = \ (((how) == SIG_BLOCK) ? sigblock(0) | *(set) : (((how) == SIG_UNBLOCK) ? sigblock(0) & ~(*(set)) : ((how) == SIG_SETMASK ? *(set) : sigblock(0))))), \ - ((oset) ? (*(oset ? oset : set) = sigsetmask(__sigtemp)) : sigsetmask(__sigtemp)), 0) + ((oset) ? (*(oset ? oset : set) = sigsetmask(dbm_sigtemp)) : sigsetmask(dbm_sigtemp)), 0) #endif /* diff --git a/security/nss/lib/dbm/src/db.c b/security/nss/lib/dbm/src/db.c index 5c35bbd48..4b5810760 100644 --- a/security/nss/lib/dbm/src/db.c +++ b/security/nss/lib/dbm/src/db.c @@ -92,16 +92,16 @@ dbopen(const char *fname, int flags, int mode, DBTYPE type, const void *openinfo /* we don't need btree and recno right now */ #if 0 case DB_BTREE: - return (__bt_open(fname, flags & USE_OPEN_FLAGS, + return (dbm_bt_open(fname, flags & USE_OPEN_FLAGS, mode, openinfo, flags & DB_FLAGS)); case DB_RECNO: - return (__rec_open(fname, flags & USE_OPEN_FLAGS, + return (dbm_rec_open(fname, flags & USE_OPEN_FLAGS, mode, openinfo, flags & DB_FLAGS)); #endif case DB_HASH: - return (__hash_open(fname, flags & USE_OPEN_FLAGS, - mode, (const HASHINFO *)openinfo, flags & DB_FLAGS)); + return (dbm_hash_open(fname, flags & USE_OPEN_FLAGS, + mode, (const HASHINFO *)openinfo, flags & DB_FLAGS)); default: break; } @@ -110,7 +110,7 @@ dbopen(const char *fname, int flags, int mode, DBTYPE type, const void *openinfo } static int -__dberr() +dbm_dberr() { return (RET_ERROR); } @@ -122,13 +122,14 @@ __dberr() * dbp: pointer to the DB structure. */ void -__dbpanic(DB *dbp) +dbm_dbpanic(DB *dbp) { /* The only thing that can succeed is a close. */ - dbp->del = (int (*)(const struct __db *, const DBT *, uint))__dberr; - dbp->fd = (int (*)(const struct __db *))__dberr; - dbp->get = (int (*)(const struct __db *, const DBT *, DBT *, uint))__dberr; - dbp->put = (int (*)(const struct __db *, DBT *, const DBT *, uint))__dberr; - dbp->seq = (int (*)(const struct __db *, DBT *, DBT *, uint))__dberr; - dbp->sync = (int (*)(const struct __db *, uint))__dberr; + dbp->del = (int (*)(const struct dbm_db *, const DBT *, uint))dbm_dberr; + dbp->fd = (int (*)(const struct dbm_db *))dbm_dberr; + dbp->get = (int (*)(const struct dbm_db *, const DBT *, DBT *, uint))dbm_dberr; + dbp->put = (int (*)(const struct dbm_db *, DBT *, const DBT *, uint))dbm_dberr; + dbp->seq = (int (*)(const struct dbm_db *, DBT *, DBT *, uint))dbm_dberr; + dbp->sync = (int (*)(const struct dbm_db *, uint))dbm_dberr; + } diff --git a/security/nss/lib/dbm/src/h_bigkey.c b/security/nss/lib/dbm/src/h_bigkey.c index 6edfe7f5a..795c7a09d 100644 --- a/security/nss/lib/dbm/src/h_bigkey.c +++ b/security/nss/lib/dbm/src/h_bigkey.c @@ -85,7 +85,7 @@ static int collect_data(HTAB *, BUFHEAD *, int, int); *-1 ==> ERROR */ extern int -__big_insert(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) +dbm_big_insert(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) { register uint16 *p; uint key_size, n, val_size; @@ -114,7 +114,7 @@ __big_insert(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) FREESPACE(p) = off - PAGE_META(n); OFFSET(p) = off; p[n] = PARTIAL_KEY; - bufp = __add_ovflpage(hashp, bufp); + bufp = dbm_add_ovflpage(hashp, bufp); if (!bufp) return (-1); n = p[0]; @@ -158,7 +158,7 @@ __big_insert(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) OFFSET(p) = off; if (val_size) { p[n] = FULL_KEY; - bufp = __add_ovflpage(hashp, bufp); + bufp = dbm_add_ovflpage(hashp, bufp); if (!bufp) return (-1); cp = bufp->page; @@ -182,7 +182,7 @@ __big_insert(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) *-1 => ERROR */ extern int -__big_delete(HTAB *hashp, BUFHEAD *bufp) +dbm_big_delete(HTAB *hashp, BUFHEAD *bufp) { register BUFHEAD *last_bfp, *rbufp; uint16 *bp, pageno; @@ -207,9 +207,9 @@ __big_delete(HTAB *hashp, BUFHEAD *bufp) break; pageno = bp[bp[0] - 1]; rbufp->flags |= BUF_MOD; - rbufp = __get_buf(hashp, pageno, rbufp, 0); + rbufp = dbm_get_buf(hashp, pageno, rbufp, 0); if (last_bfp) - __free_ovflpage(hashp, last_bfp); + dbm_free_ovflpage(hashp, last_bfp); last_bfp = rbufp; if (!rbufp) return (-1); /* Error. */ @@ -244,9 +244,9 @@ __big_delete(HTAB *hashp, BUFHEAD *bufp) bufp->flags |= BUF_MOD; if (rbufp) - __free_ovflpage(hashp, rbufp); + dbm_free_ovflpage(hashp, rbufp); if (last_bfp != rbufp) - __free_ovflpage(hashp, last_bfp); + dbm_free_ovflpage(hashp, last_bfp); hashp->NKEYS--; return (0); @@ -259,7 +259,7 @@ __big_delete(HTAB *hashp, BUFHEAD *bufp) * -3 error */ extern int -__find_bigpair(HTAB *hashp, BUFHEAD *bufp, int ndx, char *key, int size) +dbm_find_bigpair(HTAB *hashp, BUFHEAD *bufp, int ndx, char *key, int size) { register uint16 *bp; register char *p; @@ -279,7 +279,7 @@ __find_bigpair(HTAB *hashp, BUFHEAD *bufp, int ndx, char *key, int size) return (-2); kkey += bytes; ksize -= bytes; - bufp = __get_buf(hashp, bp[ndx + 2], bufp, 0); + bufp = dbm_get_buf(hashp, bp[ndx + 2], bufp, 0); if (!bufp) return (-3); p = bufp->page; @@ -306,7 +306,7 @@ __find_bigpair(HTAB *hashp, BUFHEAD *bufp, int ndx, char *key, int size) * bucket) */ extern uint16 -__find_last_page(HTAB *hashp, BUFHEAD **bpp) +dbm_find_last_page(HTAB *hashp, BUFHEAD **bpp) { BUFHEAD *bufp; uint16 *bp, pageno; @@ -332,7 +332,7 @@ __find_last_page(HTAB *hashp, BUFHEAD **bpp) return (0); pageno = bp[n - 1]; - bufp = __get_buf(hashp, pageno, bufp, 0); + bufp = dbm_get_buf(hashp, pageno, bufp, 0); if (!bufp) return (0); /* Need to indicate an error! */ bp = (uint16 *)bufp->page; @@ -350,7 +350,7 @@ __find_last_page(HTAB *hashp, BUFHEAD **bpp) * index (index should always be 1). */ extern int -__big_return( +dbm_big_return( HTAB *hashp, BUFHEAD *bufp, int ndx, @@ -364,7 +364,7 @@ __big_return( bp = (uint16 *)bufp->page; while (bp[ndx + 1] == PARTIAL_KEY) { - bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + bufp = dbm_get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!bufp) return (-1); bp = (uint16 *)bufp->page; @@ -372,7 +372,7 @@ __big_return( } if (bp[ndx + 1] == FULL_KEY) { - bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + bufp = dbm_get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!bufp) return (-1); bp = (uint16 *)bufp->page; @@ -392,7 +392,7 @@ __big_return( len = bp[1] - off; save_p = bufp; save_addr = bufp->addr; - bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + bufp = dbm_get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!bufp) return (-1); bp = (uint16 *)bufp->page; @@ -409,8 +409,8 @@ __big_return( hashp->cbucket++; hashp->cndx = 1; } else { - hashp->cpage = __get_buf(hashp, - bp[bp[0] - 1], bufp, 0); + hashp->cpage = dbm_get_buf(hashp, + bp[bp[0] - 1], bufp, 0); if (!hashp->cpage) return (-1); hashp->cndx = 1; @@ -470,7 +470,7 @@ collect_data( save_bufp->flags |= BUF_PIN; /* read the length of the buffer */ - for (totlen = len; bufp; bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0)) { + for (totlen = len; bufp; bufp = dbm_get_buf(hashp, bp[bp[0] - 1], bufp, 0)) { bp = (uint16 *)bufp->page; mylen = hashp->BSIZE - bp[1]; @@ -502,7 +502,7 @@ collect_data( /* copy the buffers back into temp buf */ for (bufp = save_bufp; bufp; - bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0)) { + bufp = dbm_get_buf(hashp, bp[bp[0] - 1], bufp, 0)) { bp = (uint16 *)bufp->page; mylen = hashp->BSIZE - bp[1]; memmove(&hashp->tmp_buf[len], (bufp->page) + bp[1], (size_t)mylen); @@ -522,7 +522,7 @@ collect_data( hashp->cpage = NULL; hashp->cbucket++; } else { - hashp->cpage = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + hashp->cpage = dbm_get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!hashp->cpage) return (-1); else if (!((uint16 *)hashp->cpage->page)[0]) { @@ -538,7 +538,7 @@ collect_data( * Fill in the key and data for this big pair. */ extern int -__big_keydata( +dbm_big_keydata( HTAB *hashp, BUFHEAD *bufp, DBT *key, DBT *val, @@ -579,10 +579,10 @@ collect_key( free(hashp->tmp_key); if ((hashp->tmp_key = (char *)malloc((size_t)totlen)) == NULL) return (-1); - if (__big_return(hashp, bufp, 1, val, set)) + if (dbm_big_return(hashp, bufp, 1, val, set)) return (-1); } else { - xbp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + xbp = dbm_get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!xbp || ((totlen = collect_key(hashp, xbp, totlen, val, set)) < 1)) return (-1); @@ -601,7 +601,7 @@ collect_key( * -1 => error */ extern int -__big_split( +dbm_big_split( HTAB *hashp, BUFHEAD *op, /* Pointer to where to put keys that go in old bucket */ BUFHEAD *np, /* Pointer to new bucket page */ @@ -621,13 +621,13 @@ __big_split( bp = big_keyp; /* Now figure out where the big key/data goes */ - if (__big_keydata(hashp, big_keyp, &key, &val, 0)) + if (dbm_big_keydata(hashp, big_keyp, &key, &val, 0)) return (-1); - change = (__call_hash(hashp, (char *)key.data, key.size) != obucket); + change = (dbm_call_hash(hashp, (char *)key.data, key.size) != obucket); - if ((ret->next_addr = __find_last_page(hashp, &big_keyp))) { + if ((ret->next_addr = dbm_find_last_page(hashp, &big_keyp))) { if (!(ret->nextp = - __get_buf(hashp, ret->next_addr, big_keyp, 0))) + dbm_get_buf(hashp, ret->next_addr, big_keyp, 0))) return (-1); ; } else @@ -692,7 +692,7 @@ __big_split( tp[0] -= 2; FREESPACE(tp) = free_space + OVFLSIZE; OFFSET(tp) = off; - tmpp = __add_ovflpage(hashp, big_keyp); + tmpp = dbm_add_ovflpage(hashp, big_keyp); if (!tmpp) return (-1); tp[4] = n; @@ -704,4 +704,5 @@ __big_split( else ret->oldp = tmpp; return (0); + } diff --git a/security/nss/lib/dbm/src/h_func.c b/security/nss/lib/dbm/src/h_func.c index 0d8734e8b..897060992 100644 --- a/security/nss/lib/dbm/src/h_func.c +++ b/security/nss/lib/dbm/src/h_func.c @@ -52,7 +52,7 @@ static uint32 hash3(const void *, size_t); static uint32 hash4(const void *, size_t); /* Global default hash function */ -uint32 (*__default_hash)(const void *, size_t) = hash4; +uint32 (*dbm_default_hash)(const void *, size_t) = hash4; /* * HASH FUNCTIONS @@ -205,3 +205,4 @@ hash4(const void *keyarg, register size_t len) } return (h); } + diff --git a/security/nss/lib/dbm/src/h_log2.c b/security/nss/lib/dbm/src/h_log2.c index a42b51a99..0e91fd042 100644 --- a/security/nss/lib/dbm/src/h_log2.c +++ b/security/nss/lib/dbm/src/h_log2.c @@ -43,7 +43,7 @@ static char sccsid[] = "@(#)hash_log2.c 8.2 (Berkeley) 5/31/94"; #include "mcom_db.h" uint32 -__log2(uint32 num) +dbm_log2(uint32 num) { register uint32 i, limit; @@ -51,4 +51,5 @@ __log2(uint32 num) for (i = 0; limit < num; limit = limit << 1, i++) { } return (i); + } diff --git a/security/nss/lib/dbm/src/h_page.c b/security/nss/lib/dbm/src/h_page.c index e5623224b..e6f61c623 100644 --- a/security/nss/lib/dbm/src/h_page.c +++ b/security/nss/lib/dbm/src/h_page.c @@ -204,7 +204,7 @@ putpair(char *p, const DBT *key, DBT *val) * -1 error */ extern int -__delpair(HTAB *hashp, BUFHEAD *bufp, int ndx) +dbm_delpair(HTAB *hashp, BUFHEAD *bufp, int ndx) { register uint16 *bp, newoff; register int n; @@ -214,7 +214,7 @@ __delpair(HTAB *hashp, BUFHEAD *bufp, int ndx) n = bp[0]; if (bp[ndx + 1] < REAL_KEY) - return (__big_delete(hashp, bufp)); + return (dbm_big_delete(hashp, bufp)); if (ndx != 1) newoff = bp[ndx - 1]; else @@ -277,7 +277,7 @@ __delpair(HTAB *hashp, BUFHEAD *bufp, int ndx) * -1 ==> Error */ extern int -__split_page(HTAB *hashp, uint32 obucket, uint32 nbucket) +dbm_split_page(HTAB *hashp, uint32 obucket, uint32 nbucket) { register BUFHEAD *new_bufp, *old_bufp; register uint16 *ino; @@ -292,10 +292,10 @@ __split_page(HTAB *hashp, uint32 obucket, uint32 nbucket) copyto = (uint16)hashp->BSIZE; off = (uint16)hashp->BSIZE; - old_bufp = __get_buf(hashp, obucket, NULL, 0); + old_bufp = dbm_get_buf(hashp, obucket, NULL, 0); if (old_bufp == NULL) return (-1); - new_bufp = __get_buf(hashp, nbucket, NULL, 0); + new_bufp = dbm_get_buf(hashp, nbucket, NULL, 0); if (new_bufp == NULL) return (-1); @@ -331,7 +331,7 @@ __split_page(HTAB *hashp, uint32 obucket, uint32 nbucket) assert(((int)key.size) > -1); #endif - if (__call_hash(hashp, (char *)key.data, key.size) == obucket) { + if (dbm_call_hash(hashp, (char *)key.data, key.size) == obucket) { /* Don't switch page */ diff = copyto - off; if (diff) { @@ -443,8 +443,8 @@ ugly_split(HTAB *hashp, uint32 obucket, BUFHEAD *old_bufp, return DATABASE_CORRUPTED_ERROR; if (ino[2] < REAL_KEY && ino[2] != OVFLPAGE) { - if ((status = __big_split(hashp, old_bufp, - new_bufp, bufp, bufp->addr, obucket, &ret))) + if ((status = dbm_big_split(hashp, old_bufp, + new_bufp, bufp, bufp->addr, obucket, &ret))) return (status); old_bufp = ret.oldp; if (!old_bufp) @@ -477,7 +477,7 @@ ugly_split(HTAB *hashp, uint32 obucket, BUFHEAD *old_bufp, scopyto - sizeof(uint16) * (ino[0] + 3); OFFSET(ino) = scopyto; - bufp = __get_buf(hashp, ov_addr, bufp, 0); + bufp = dbm_get_buf(hashp, ov_addr, bufp, 0); if (!bufp) return (-1); @@ -487,7 +487,7 @@ ugly_split(HTAB *hashp, uint32 obucket, BUFHEAD *old_bufp, moved = 0; if (last_bfp) - __free_ovflpage(hashp, last_bfp); + dbm_free_ovflpage(hashp, last_bfp); last_bfp = bufp; } /* Move regular sized pairs of there are any */ @@ -506,13 +506,13 @@ ugly_split(HTAB *hashp, uint32 obucket, BUFHEAD *old_bufp, val.size = ino[n] - ino[n + 1]; off = ino[n + 1]; - if (__call_hash(hashp, (char *)key.data, key.size) == obucket) { + if (dbm_call_hash(hashp, (char *)key.data, key.size) == obucket) { /* Keep on old page */ if (PAIRFITS(op, (&key), (&val))) putpair((char *)op, &key, &val); else { old_bufp = - __add_ovflpage(hashp, old_bufp); + dbm_add_ovflpage(hashp, old_bufp); if (!old_bufp) return (-1); op = (uint16 *)old_bufp->page; @@ -525,7 +525,7 @@ ugly_split(HTAB *hashp, uint32 obucket, BUFHEAD *old_bufp, putpair((char *)np, &key, &val); else { new_bufp = - __add_ovflpage(hashp, new_bufp); + dbm_add_ovflpage(hashp, new_bufp); if (!new_bufp) return (-1); np = (uint16 *)new_bufp->page; @@ -536,7 +536,7 @@ ugly_split(HTAB *hashp, uint32 obucket, BUFHEAD *old_bufp, } } if (last_bfp) - __free_ovflpage(hashp, last_bfp); + dbm_free_ovflpage(hashp, last_bfp); return (0); } @@ -548,7 +548,7 @@ ugly_split(HTAB *hashp, uint32 obucket, BUFHEAD *old_bufp, * 1 ==> failure */ extern int -__addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) +dbm_addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) { register uint16 *bp, *sop; int do_expand; @@ -562,7 +562,7 @@ __addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) and we need to add another page */ break; else if (bp[2] < REAL_KEY && bp[bp[0]] != OVFLPAGE) { - bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + bufp = dbm_get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!bufp) { #ifdef DEBUG assert(0); @@ -585,7 +585,7 @@ __addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) return (0); } } else { - bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + bufp = dbm_get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!bufp) { #ifdef DEBUG assert(0); @@ -599,7 +599,7 @@ __addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) putpair(bufp->page, key, (DBT *)val); else { do_expand = 1; - bufp = __add_ovflpage(hashp, bufp); + bufp = dbm_add_ovflpage(hashp, bufp); if (!bufp) { #ifdef DEBUG assert(0); @@ -610,7 +610,7 @@ __addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) if (PAIRFITS(sop, key, val)) putpair((char *)sop, key, (DBT *)val); - else if (__big_insert(hashp, bufp, key, val)) { + else if (dbm_big_insert(hashp, bufp, key, val)) { #ifdef DEBUG assert(0); #endif @@ -625,7 +625,7 @@ __addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) hashp->NKEYS++; if (do_expand || (hashp->NKEYS / (hashp->MAX_BUCKET + 1) > hashp->FFACTOR)) - return (__expand_table(hashp)); + return (dbm_expand_table(hashp)); return (0); } @@ -636,7 +636,7 @@ __addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) * NULL on error */ extern BUFHEAD * -__add_ovflpage(HTAB *hashp, BUFHEAD *bufp) +dbm_add_ovflpage(HTAB *hashp, BUFHEAD *bufp) { register uint16 *sp; uint16 ndx, ovfl_num; @@ -657,7 +657,7 @@ __add_ovflpage(HTAB *hashp, BUFHEAD *bufp) tmp1 = bufp->addr; tmp2 = bufp->ovfl ? bufp->ovfl->addr : 0; #endif - if (!ovfl_num || !(bufp->ovfl = __get_buf(hashp, ovfl_num, bufp, 1))) + if (!ovfl_num || !(bufp->ovfl = dbm_get_buf(hashp, ovfl_num, bufp, 1))) return (NULL); bufp->ovfl->flags |= BUF_MOD; #ifdef DEBUG1 @@ -687,12 +687,12 @@ __add_ovflpage(HTAB *hashp, BUFHEAD *bufp) * -1 indicates FAILURE */ extern int -__get_page(HTAB *hashp, - char *p, - uint32 bucket, - int is_bucket, - int is_disk, - int is_bitmap) +dbm_get_page(HTAB *hashp, + char *p, + uint32 bucket, + int is_bucket, + int is_disk, + int is_bitmap) { register int fd, page; size_t size; @@ -805,7 +805,7 @@ __get_page(HTAB *hashp, * -1 ==>failure */ extern int -__put_page(HTAB *hashp, char *p, uint32 bucket, int is_bucket, int is_bitmap) +dbm_put_page(HTAB *hashp, char *p, uint32 bucket, int is_bucket, int is_bitmap) { register int fd, page; size_t size; @@ -895,7 +895,7 @@ __put_page(HTAB *hashp, char *p, uint32 bucket, int is_bucket, int is_bitmap) * once they are read in. */ extern int -__ibitmap(HTAB *hashp, int pnum, int nbits, int ndx) +dbm_ibitmap(HTAB *hashp, int pnum, int nbits, int ndx) { uint32 *ip; size_t clearbytes, clearints; @@ -1011,8 +1011,8 @@ overflow_page(HTAB *hashp) * don't have to if we tell init_bitmap not to leave it clear * in the first place. */ - if (__ibitmap(hashp, - (int)OADDR_OF(splitnum, offset), 1, free_page)) + if (dbm_ibitmap(hashp, + (int)OADDR_OF(splitnum, offset), 1, free_page)) return (0); hashp->SPARES[splitnum]++; #ifdef DEBUG2 @@ -1084,7 +1084,7 @@ found: * Mark this overflow page as free. */ extern void -__free_ovflpage(HTAB *hashp, BUFHEAD *obufp) +dbm_free_ovflpage(HTAB *hashp, BUFHEAD *obufp) { uint16 addr; uint32 *freep; @@ -1125,7 +1125,7 @@ __free_ovflpage(HTAB *hashp, BUFHEAD *obufp) (void)fprintf(stderr, "FREE_OVFLPAGE: ADDR: %d BIT: %d PAGE %d\n", obufp->addr, free_bit, free_page); #endif - __reclaim_buf(hashp, obufp); + dbm_reclaim_buf(hashp, obufp); } /* @@ -1236,8 +1236,8 @@ fetch_bitmap(HTAB *hashp, uint32 ndx) return (NULL); if ((hashp->mapp[ndx] = (uint32 *)malloc((size_t)hashp->BSIZE)) == NULL) return (NULL); - if (__get_page(hashp, - (char *)hashp->mapp[ndx], hashp->BITMAPS[ndx], 0, 1, 1)) { + if (dbm_get_page(hashp, + (char *)hashp->mapp[ndx], hashp->BITMAPS[ndx], 0, 1, 1)) { free(hashp->mapp[ndx]); hashp->mapp[ndx] = NULL; /* NEW: 9-11-95 */ return (NULL); @@ -1253,15 +1253,16 @@ print_chain(int addr) short *bp, oaddr; (void)fprintf(stderr, "%d ", addr); - bufp = __get_buf(hashp, addr, NULL, 0); + bufp = dbm_get_buf(hashp, addr, NULL, 0); bp = (short *)bufp->page; while (bp[0] && ((bp[bp[0]] == OVFLPAGE) || ((bp[0] > 2) && bp[2] < REAL_KEY))) { oaddr = bp[bp[0] - 1]; (void)fprintf(stderr, "%d ", (int)oaddr); - bufp = __get_buf(hashp, (int)oaddr, bufp, 0); + bufp = dbm_get_buf(hashp, (int)oaddr, bufp, 0); bp = (short *)bufp->page; } (void)fprintf(stderr, "\n"); } + #endif diff --git a/security/nss/lib/dbm/src/hash.c b/security/nss/lib/dbm/src/hash.c index 98b1c07c7..100bbad27 100644 --- a/security/nss/lib/dbm/src/hash.c +++ b/security/nss/lib/dbm/src/hash.c @@ -118,7 +118,7 @@ int hash_accesses, hash_collisions, hash_expansions, hash_overflows; * This closes the file, flushing buffers as appropriate. */ static void -__remove_database(DB *dbp) +dbm_remove_database(DB *dbp) { HTAB *hashp = (HTAB *)dbp->internal; @@ -134,7 +134,7 @@ __remove_database(DB *dbp) /* OPEN/CLOSE */ extern DB * -__hash_open(const char *file, int flags, int mode, const HASHINFO *info, int dflags) +dbm_hash_open(const char *file, int flags, int mode, const HASHINFO *info, int dflags) { HTAB *hashp = NULL; struct stat statbuf; @@ -199,7 +199,7 @@ __hash_open(const char *file, int flags, int mode, const HASHINFO *info, int dfl if (info && info->hash) hashp->hash = info->hash; else - hashp->hash = __default_hash; + hashp->hash = dbm_default_hash; hdrsize = read(hashp->fp, (char *)&hashp->hdr, sizeof(HASHHDR)); if (hdrsize == -1) @@ -243,9 +243,9 @@ __hash_open(const char *file, int flags, int mode, const HASHINFO *info, int dfl /* Initialize Buffer Manager */ if (info && info->cachesize) - __buf_init(hashp, (int32)info->cachesize); + dbm_buf_init(hashp, (int32)info->cachesize); else - __buf_init(hashp, DEF_BUFSIZE); + dbm_buf_init(hashp, DEF_BUFSIZE); hashp->new_file = new_table; #ifdef macintosh @@ -331,7 +331,7 @@ init_hash(HTAB *hashp, const char *file, HASHINFO *info) hashp->SSHIFT = DEF_SEGSIZE_SHIFT; hashp->DSIZE = DEF_DIRSIZE; hashp->FFACTOR = DEF_FFACTOR; - hashp->hash = __default_hash; + hashp->hash = dbm_default_hash; memset(hashp->SPARES, 0, sizeof(hashp->SPARES)); memset(hashp->BITMAPS, 0, sizeof(hashp->BITMAPS)); @@ -353,13 +353,13 @@ init_hash(HTAB *hashp, const char *file, HASHINFO *info) if (hashp->BSIZE > MAX_BSIZE) hashp->BSIZE = MAX_BSIZE; #endif - hashp->BSHIFT = __log2((uint32)hashp->BSIZE); + hashp->BSHIFT = dbm_log2((uint32)hashp->BSIZE); } if (info) { if (info->bsize) { /* Round pagesize up to power of 2 */ - hashp->BSHIFT = __log2(info->bsize); + hashp->BSHIFT = dbm_log2(info->bsize); hashp->BSIZE = 1 << hashp->BSHIFT; if (hashp->BSIZE > MAX_BSIZE) { errno = EINVAL; @@ -406,7 +406,7 @@ init_htab(HTAB *hashp, int nelem) */ nelem = (nelem - 1) / hashp->FFACTOR + 1; - l2 = __log2((uint32)PR_MAX(nelem, 2)); + l2 = dbm_log2((uint32)PR_MAX(nelem, 2)); nbuckets = 1 << l2; hashp->SPARES[l2] = l2 + 1; @@ -415,7 +415,7 @@ init_htab(HTAB *hashp, int nelem) hashp->LAST_FREED = 2; /* First bitmap page is at: splitpoint l2 page offset 1 */ - if (__ibitmap(hashp, (int)OADDR_OF(l2, 1), l2 + 1, 0)) + if (dbm_ibitmap(hashp, (int)OADDR_OF(l2, 1), l2 + 1, 0)) return (-1); hashp->MAX_BUCKET = hashp->LOW_MASK = nbuckets - 1; @@ -425,7 +425,7 @@ init_htab(HTAB *hashp, int nelem) 1; nsegs = (nbuckets - 1) / hashp->SGSIZE + 1; - nsegs = 1 << __log2((uint32)nsegs); + nsegs = 1 << dbm_log2((uint32)nsegs); if (nsegs > hashp->DSIZE) hashp->DSIZE = nsegs; @@ -463,7 +463,7 @@ hdestroy(HTAB *hashp) * Call on buffer manager to free buffers, and if required, * write them to disk. */ - if (__buf_free(hashp, 1, hashp->save_file)) + if (dbm_buf_free(hashp, 1, hashp->save_file)) save_errno = errno; if (hashp->dir) { free(*hashp->dir); /* Free initial segments */ @@ -585,7 +585,7 @@ hash_sync(const DB *dbp, uint flags) if (!hashp->save_file) return (0); - if (__buf_free(hashp, 0, 1) || flush_meta(hashp)) + if (dbm_buf_free(hashp, 0, 1) || flush_meta(hashp)) return (DBM_ERROR); #if defined(_WIN32) || defined(_WINDOWS) if (hashp->updateEOF && hashp->filename && !hashp->is_temp) { @@ -635,8 +635,8 @@ flush_meta(HTAB *hashp) } for (i = 0; i < NCACHED; i++) if (hashp->mapp[i]) - if (__put_page(hashp, (char *)hashp->mapp[i], - hashp->BITMAPS[i], 0, 1)) + if (dbm_put_page(hashp, (char *)hashp->mapp[i], + hashp->BITMAPS[i], 0, 1)) return (-1); return (0); } @@ -675,7 +675,7 @@ hash_get( #if defined(unix) && defined(DEBUG) printf("\n\nDBM Database has been corrupted, tell Lou...\n\n"); #endif - __remove_database((DB *)dbp); + dbm_remove_database((DB *)dbp); } return (rv); @@ -711,7 +711,7 @@ hash_put( #if defined(unix) && defined(DEBUG) printf("\n\nDBM Database has been corrupted, tell Lou...\n\n"); #endif - __remove_database((DB *)dbp); + dbm_remove_database((DB *)dbp); } return (rv); @@ -744,7 +744,7 @@ hash_delete( #if defined(unix) && defined(DEBUG) printf("\n\nDBM Database has been corrupted, tell Lou...\n\n"); #endif - __remove_database((DB *)dbp); + dbm_remove_database((DB *)dbp); } return (rv); @@ -777,7 +777,7 @@ hash_access( off = hashp->BSIZE; size = key->size; kp = (char *)key->data; - rbufp = __get_buf(hashp, __call_hash(hashp, kp, size), NULL, 0); + rbufp = dbm_get_buf(hashp, dbm_call_hash(hashp, kp, size), NULL, 0); if (!rbufp) return (DATABASE_CORRUPTED_ERROR); save_bufp = rbufp; @@ -805,7 +805,7 @@ hash_access( last_overflow_page_no = *bp; - rbufp = __get_buf(hashp, *bp, rbufp, 0); + rbufp = dbm_get_buf(hashp, *bp, rbufp, 0); if (!rbufp) { save_bufp->flags &= ~BUF_PIN; return (DBM_ERROR); @@ -822,17 +822,17 @@ hash_access( off = hashp->BSIZE; } else if (bp[1] < REAL_KEY) { if ((ndx = - __find_bigpair(hashp, rbufp, ndx, kp, (int)size)) > 0) + dbm_find_bigpair(hashp, rbufp, ndx, kp, (int)size)) > 0) goto found; if (ndx == -2) { bufp = rbufp; if (!(pageno = - __find_last_page(hashp, &bufp))) { + dbm_find_last_page(hashp, &bufp))) { ndx = 0; rbufp = bufp; break; /* FOR */ } - rbufp = __get_buf(hashp, pageno, bufp, 0); + rbufp = dbm_get_buf(hashp, pageno, bufp, 0); if (!rbufp) { save_bufp->flags &= ~BUF_PIN; return (DBM_ERROR); @@ -853,7 +853,7 @@ hash_access( switch (action) { case HASH_PUT: case HASH_PUTNEW: - if (__addel(hashp, rbufp, key, val)) { + if (dbm_addel(hashp, rbufp, key, val)) { save_bufp->flags &= ~BUF_PIN; return (DBM_ERROR); } else { @@ -875,7 +875,7 @@ found: case HASH_GET: bp = (uint16 *)rbufp->page; if (bp[ndx + 1] < REAL_KEY) { - if (__big_return(hashp, rbufp, ndx, val, 0)) + if (dbm_big_return(hashp, rbufp, ndx, val, 0)) return (DBM_ERROR); } else { val->data = (uint8 *)rbufp->page + (int)bp[ndx + 1]; @@ -883,14 +883,14 @@ found: } break; case HASH_PUT: - if ((__delpair(hashp, rbufp, ndx)) || - (__addel(hashp, rbufp, key, val))) { + if ((dbm_delpair(hashp, rbufp, ndx)) || + (dbm_addel(hashp, rbufp, key, val))) { save_bufp->flags &= ~BUF_PIN; return (DBM_ERROR); } break; case HASH_DELETE: - if (__delpair(hashp, rbufp, ndx)) + if (dbm_delpair(hashp, rbufp, ndx)) return (DBM_ERROR); break; default: @@ -933,7 +933,7 @@ hash_seq( for (bucket = hashp->cbucket; bucket <= (uint32)hashp->MAX_BUCKET; bucket++, hashp->cndx = 1) { - bufp = __get_buf(hashp, bucket, NULL, 0); + bufp = dbm_get_buf(hashp, bucket, NULL, 0); if (!bufp) return (DBM_ERROR); hashp->cpage = bufp; @@ -955,7 +955,7 @@ hash_seq( #endif while (bp[hashp->cndx + 1] == OVFLPAGE) { bufp = hashp->cpage = - __get_buf(hashp, bp[hashp->cndx], bufp, 0); + dbm_get_buf(hashp, bp[hashp->cndx], bufp, 0); if (!bufp) return (DBM_ERROR); bp = (uint16 *)(bufp->page); @@ -968,7 +968,7 @@ hash_seq( } ndx = hashp->cndx; if (bp[ndx + 1] < REAL_KEY) { - if (__big_keydata(hashp, bufp, key, data, 1)) + if (dbm_big_keydata(hashp, bufp, key, data, 1)) return (DBM_ERROR); } else { key->data = (uint8 *)hashp->cpage->page + bp[ndx]; @@ -994,7 +994,7 @@ hash_seq( * -1 ==> Error */ extern int -__expand_table(HTAB *hashp) +dbm_expand_table(HTAB *hashp) { uint32 old_bucket, new_bucket; int new_segnum, spare_ndx; @@ -1029,7 +1029,7 @@ __expand_table(HTAB *hashp) * * increases), we need to copy the current contents of the spare * split bucket to the next bucket. */ - spare_ndx = __log2((uint32)(hashp->MAX_BUCKET + 1)); + spare_ndx = dbm_log2((uint32)(hashp->MAX_BUCKET + 1)); if (spare_ndx > hashp->OVFL_POINT) { hashp->SPARES[spare_ndx] = hashp->SPARES[hashp->OVFL_POINT]; hashp->OVFL_POINT = spare_ndx; @@ -1041,7 +1041,7 @@ __expand_table(HTAB *hashp) hashp->HIGH_MASK = new_bucket | hashp->LOW_MASK; } /* Relocate records to the new bucket */ - return (__split_page(hashp, old_bucket, new_bucket)); + return (dbm_split_page(hashp, old_bucket, new_bucket)); } /* @@ -1065,7 +1065,7 @@ hash_realloc( } extern uint32 -__call_hash(HTAB *hashp, char *k, size_t len) +dbm_call_hash(HTAB *hashp, char *k, size_t len) { uint32 n, bucket; @@ -1168,4 +1168,5 @@ swap_header(HTAB *hashp) M_16_SWAP(hdrp->bitmaps[i]); } } + #endif diff --git a/security/nss/lib/dbm/src/hash_buf.c b/security/nss/lib/dbm/src/hash_buf.c index a7cd2d076..02deb81c5 100644 --- a/security/nss/lib/dbm/src/hash_buf.c +++ b/security/nss/lib/dbm/src/hash_buf.c @@ -104,7 +104,7 @@ static BUFHEAD *newbuf(HTAB *, uint32, BUFHEAD *); * address you are seeking. */ extern BUFHEAD * -__get_buf(HTAB *hashp, uint32 addr, BUFHEAD *prev_bp, int newpage) +dbm_get_buf(HTAB *hashp, uint32 addr, BUFHEAD *prev_bp, int newpage) /* If prev_bp set, indicates a new overflow page. */ { register BUFHEAD *bp; @@ -124,7 +124,7 @@ __get_buf(HTAB *hashp, uint32 addr, BUFHEAD *prev_bp, int newpage) /* Grab buffer out of directory */ segment_ndx = addr & (hashp->SGSIZE - 1); - /* valid segment ensured by __call_hash() */ + /* valid segment ensured by dbm_call_hash() */ segp = hashp->dir[addr >> hashp->SSHIFT]; #ifdef DEBUG assert(segp != NULL); @@ -140,7 +140,7 @@ __get_buf(HTAB *hashp, uint32 addr, BUFHEAD *prev_bp, int newpage) bp = newbuf(hashp, addr, prev_bp); if (!bp) return (NULL); - if (__get_page(hashp, bp->page, addr, !prev_bp, is_disk, 0)) { + if (dbm_get_page(hashp, bp->page, addr, !prev_bp, is_disk, 0)) { /* free bp and its page */ if (prev_bp) { /* if prev_bp is set then the new page that @@ -242,8 +242,8 @@ newbuf(HTAB *hashp, uint32 addr, BUFHEAD *prev_bp) } oaddr = shortp[shortp[0] - 1]; } - if ((bp->flags & BUF_MOD) && __put_page(hashp, bp->page, - bp->addr, (int)IS_BUCKET(bp->flags), 0)) + if ((bp->flags & BUF_MOD) && dbm_put_page(hashp, bp->page, + bp->addr, (int)IS_BUCKET(bp->flags), 0)) return (NULL); /* * Update the pointer to this page (i.e. invalidate it). @@ -298,8 +298,8 @@ newbuf(HTAB *hashp, uint32 addr, BUFHEAD *prev_bp) /* set before __put_page */ oaddr = shortp[shortp[0] - 1]; } - if ((xbp->flags & BUF_MOD) && __put_page(hashp, - xbp->page, xbp->addr, 0, 0)) + if ((xbp->flags & BUF_MOD) && dbm_put_page(hashp, + xbp->page, xbp->addr, 0, 0)) return (NULL); xbp->addr = 0; xbp->flags = 0; @@ -335,7 +335,7 @@ newbuf(HTAB *hashp, uint32 addr, BUFHEAD *prev_bp) } extern void -__buf_init(HTAB *hashp, int32 nbytes) +dbm_buf_init(HTAB *hashp, int32 nbytes) { BUFHEAD *bfp; int npages; @@ -358,7 +358,7 @@ __buf_init(HTAB *hashp, int32 nbytes) } extern int -__buf_free(HTAB *hashp, int do_free, int to_disk) +dbm_buf_free(HTAB *hashp, int do_free, int to_disk) { BUFHEAD *bp; int status = -1; @@ -370,8 +370,8 @@ __buf_free(HTAB *hashp, int do_free, int to_disk) /* Check that the buffer is valid */ if (bp->addr || IS_BUCKET(bp->flags)) { if (to_disk && (bp->flags & BUF_MOD) && - (status = __put_page(hashp, bp->page, - bp->addr, IS_BUCKET(bp->flags), 0))) { + (status = dbm_put_page(hashp, bp->page, + bp->addr, IS_BUCKET(bp->flags), 0))) { if (do_free) { if (bp->page) @@ -397,11 +397,12 @@ __buf_free(HTAB *hashp, int do_free, int to_disk) } extern void -__reclaim_buf(HTAB *hashp, BUFHEAD *bp) +dbm_reclaim_buf(HTAB *hashp, BUFHEAD *bp) { bp->ovfl = 0; bp->addr = 0; bp->flags = 0; BUF_REMOVE(bp); LRU_INSERT(bp); + } -- cgit v1.2.3 From beea314ffe6c9cefeb232e9b38fcecf66154854f Mon Sep 17 00:00:00 2001 From: athenian200 Date: Wed, 2 Oct 2019 19:51:00 -0500 Subject: MoonchildProductions#1251 - Part 17: All the libffi and libxul.so issues, resolved. https://bugzilla.mozilla.org/show_bug.cgi?id=1185424 http://www.mindfruit.co.uk/2012/06/relocations-relocations.html The libxul.so fix was implemented by Mozilla in Firefox 57 and personally recommended to me by an Oracle employee on the OpenIndiana mailing list. It can easily be made ifdef XP_SOLARIS, but it seems like the new way is considered a better solution overall by the original author of the code that had it use that null pointer hack to begin with. I can't link where I found the fix for libffi because I came up with it myself while looking at the way sysv.S does things. Something clicked in my brain while reading that mindfruit link above, though, and gave me enough of a sense of what was going on to be able to fix libffi. The libffi fix looks a bit hairy because of all the FDE_ENCODE statements, but if you examine the code closely, you should find that it does exactly what it did before on all platforms besides Solaris. I later discovered that people who originally ported Firefox to Solaris never figured this out during the Firefox 52 era and had to use GNU LD for linking libxul.so while using the Sun LD for the rest of the build to make it work. For whatever reason, it works for me now without the GNU LD trick. --- config/external/ffi/moz.build | 21 +++++++++++++--- js/src/ctypes/libffi/src/x86/win32.S | 28 +++++++++++++++++----- .../StaticXULComponentsEnd.cpp | 5 +++- toolkit/library/StaticXULComponentsStart.cpp | 5 +++- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/config/external/ffi/moz.build b/config/external/ffi/moz.build index e9ef07de3..3a5478967 100644 --- a/config/external/ffi/moz.build +++ b/config/external/ffi/moz.build @@ -35,9 +35,17 @@ else: 'FFI_NO_RAW_API': True, 'HAVE_AS_ASCII_PSEUDO_OP': True, 'HAVE_AS_STRING_PSEUDO_OP': True, - 'HAVE_AS_X86_64_UNWIND_SECTION_TYPE': True, }) +# This should NEVER be true on 32-bit x86 systems. It's called x86_64 unwind +# section type for a reason. By rights the way it was before should have broken +# all 32-bit builds on x86. + + if CONFIG['FFI_TARGET'] == 'X86': + DEFINES['HAVE_AS_X86_64_UNWIND_SECTION_TYPE'] = False + else: + DEFINES['HAVE_AS_X86_64_UNWIND_SECTION_TYPE'] = True + if CONFIG['MOZ_DEBUG']: DEFINES['FFI_DEBUG'] = True if not CONFIG['MOZ_NO_DEBUG_RTL']: @@ -49,13 +57,20 @@ else: if CONFIG['OS_TARGET'] not in ('WINNT', 'Darwin'): DEFINES['HAVE_HIDDEN_VISIBILITY_ATTRIBUTE'] = True - if CONFIG['INTEL_ARCHITECTURE']: +# Solaris uses datarel encoding for x86. This causes a lot of really stupid +# problems, like the vast majority of x86 assembler not being considered PIC +# on Solaris. + + if CONFIG['INTEL_ARCHITECTURE'] and CONFIG['OS_TARGET'] != 'SunOS': DEFINES['HAVE_AS_X86_PCREL'] = True # Don't bother setting EH_FRAME_FLAGS on Windows. # Quoted defines confuse msvcc.sh, and the value isn't used there. if CONFIG['OS_TARGET'] != 'WINNT': - if CONFIG['FFI_TARGET'] == 'ARM': + # Solaris seems to require EH_FRAME to be writable even on x86. + # It works fine most of the time and there's no rule against it, + # but it causes a lot of weird problems. + if CONFIG['FFI_TARGET'] == 'ARM' or CONFIG['OS_ARCH'] == 'SunOS': DEFINES['EH_FRAME_FLAGS'] = '"aw"' else: DEFINES['EH_FRAME_FLAGS'] = '"a"' diff --git a/js/src/ctypes/libffi/src/x86/win32.S b/js/src/ctypes/libffi/src/x86/win32.S index daf0e799c..4f702e8b1 100644 --- a/js/src/ctypes/libffi/src/x86/win32.S +++ b/js/src/ctypes/libffi/src/x86/win32.S @@ -1158,8 +1158,24 @@ L_ffi_closure_SYSV_inner$stub: .byte 0x7c /* .sleb128 -4; CIE Data Alignment Factor */ .byte 0x8 /* CIE RA Column */ #ifdef __PIC__ - .byte 0x1 /* .uleb128 0x1; Augmentation size */ - .byte 0x1b /* FDE Encoding (pcrel sdata4) */ +# if defined __sun__ && defined __svr4__ +/* 32-bit Solaris 2/x86 uses datarel encoding for PIC. GNU ld before 2.22 + doesn't correctly sort .eh_frame_hdr with mixed encodings, so match this. */ +# define FDE_ENCODING 0x30 /* datarel */ +# define FDE_ENCODE(X) X@GOTOFF +# else +# define FDE_ENCODING 0x1b /* pcrel sdata4 */ +# if defined HAVE_AS_X86_PCREL +# define FDE_ENCODE(X) X-. +# else +# define FDE_ENCODE(X) X@rel +# endif +# endif +#else +# define FDE_ENCODING 0 /* absolute */ +# define FDE_ENCODE(X) X +.byte 0x1 /* .uleb128 0x1; Augmentation size */ +.byte FDE_ENCODING #endif .byte 0xc /* DW_CFA_def_cfa CFA = r4 + 4 = 4(%esp) */ .byte 0x4 /* .uleb128 0x4 */ @@ -1176,7 +1192,7 @@ L_ffi_closure_SYSV_inner$stub: #if defined __PIC__ && defined HAVE_AS_X86_PCREL .long .LFB1-. /* FDE initial location */ #else - .long .LFB1 + .long FDE_ENCODE(.LFB1) #endif .long .LFE1-.LFB1 /* FDE address range */ #ifdef __PIC__ @@ -1207,7 +1223,7 @@ L_ffi_closure_SYSV_inner$stub: #if defined __PIC__ && defined HAVE_AS_X86_PCREL .long .LFB3-. /* FDE initial location */ #else - .long .LFB3 + .long FDE_ENCODE(.LFB3) #endif .long .LFE3-.LFB3 /* FDE address range */ #ifdef __PIC__ @@ -1240,7 +1256,7 @@ L_ffi_closure_SYSV_inner$stub: #if defined __PIC__ && defined HAVE_AS_X86_PCREL .long .LFB4-. /* FDE initial location */ #else - .long .LFB4 + .long FDE_ENCODE(.LFB4) #endif .long .LFE4-.LFB4 /* FDE address range */ #ifdef __PIC__ @@ -1278,7 +1294,7 @@ L_ffi_closure_SYSV_inner$stub: #if defined __PIC__ && defined HAVE_AS_X86_PCREL .long .LFB5-. /* FDE initial location */ #else - .long .LFB5 + .long FDE_ENCODE(.LFB5) #endif .long .LFE5-.LFB5 /* FDE address range */ #ifdef __PIC__ diff --git a/toolkit/library/StaticXULComponentsEnd/StaticXULComponentsEnd.cpp b/toolkit/library/StaticXULComponentsEnd/StaticXULComponentsEnd.cpp index 28fd9d484..8b5b1f4cb 100644 --- a/toolkit/library/StaticXULComponentsEnd/StaticXULComponentsEnd.cpp +++ b/toolkit/library/StaticXULComponentsEnd/StaticXULComponentsEnd.cpp @@ -10,4 +10,7 @@ # undef NSMODULE_SECTION # define NSMODULE_SECTION __declspec(allocate(".kPStaticModules$Z"), dllexport) #endif -NSMODULE_DEFN(end_kPStaticModules) = nullptr; +/* This could be null, but this needs a dummy value to ensure it actually ends + * up in the same section as other NSMODULE_DEFNs, instead of being moved to a + * separate readonly section. */ +NSMODULE_DEFN(end_kPStaticModules) = (mozilla::Module*)&NSMODULE_NAME(end_kPStaticModules); diff --git a/toolkit/library/StaticXULComponentsStart.cpp b/toolkit/library/StaticXULComponentsStart.cpp index 1738aa810..d2e9a8828 100644 --- a/toolkit/library/StaticXULComponentsStart.cpp +++ b/toolkit/library/StaticXULComponentsStart.cpp @@ -1,3 +1,6 @@ #include "mozilla/Module.h" -NSMODULE_DEFN(start_kPStaticModules) = nullptr; +/* This could be null, but this needs a dummy value to ensure it actually ends + * up in the same section as other NSMODULE_DEFNs, instead of being moved to a + * separate readonly section. */ +NSMODULE_DEFN(start_kPStaticModules) = (mozilla::Module*)&NSMODULE_NAME(start_kPStaticModules); -- cgit v1.2.3 From 7e5ff857eefd66008c78f2bcda62b28316e71f09 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Wed, 2 Oct 2019 20:36:49 -0500 Subject: MoonchildProductions#1251 - Part 18: (Hopefully) final version of build system fixes. I hope I can just apply these nice, clean changes and have everything work without having to make a mess again. --- old-configure.in | 4 ++++ toolkit/library/libxul.mk | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/old-configure.in b/old-configure.in index 1525ecc9b..f8fb9245b 100644 --- a/old-configure.in +++ b/old-configure.in @@ -1197,6 +1197,10 @@ case "$target" in fi ;; +*-solaris* + MOZ_FIX_LINK_PATHS="-L${DIST}/bin" + ;; + esac AC_SUBST_LIST(MMX_FLAGS) diff --git a/toolkit/library/libxul.mk b/toolkit/library/libxul.mk index 9e7e8beee..8533a015d 100644 --- a/toolkit/library/libxul.mk +++ b/toolkit/library/libxul.mk @@ -4,7 +4,7 @@ EXTRA_DEPS += $(topsrcdir)/toolkit/library/libxul.mk -ifeq (Linux,$(OS_ARCH)) +ifeq (Linux,$(OS_ARCH)) ifneq (Android,$(OS_TARGET)) OS_LDFLAGS += -Wl,-version-script,symverscript @@ -16,10 +16,10 @@ EXTRA_DEPS += symverscript endif endif -# Generate GDB pretty printer-autoload files only on Linux. OSX's GDB is +# Generate GDB pretty printer-autoload files on Linux and Solaris. OSX's GDB is # too old to support Python pretty-printers; if this changes, we could make # this 'ifdef GNU_CC'. -ifeq (Linux,$(OS_ARCH)) +ifeq (,$(filter-out SunOS Linux,$(OS_ARCH))) # Create a GDB Python auto-load file alongside the libxul shared library in # the build directory. PP_TARGETS += LIBXUL_AUTOLOAD @@ -27,6 +27,10 @@ LIBXUL_AUTOLOAD = $(topsrcdir)/toolkit/library/libxul.so-gdb.py.in LIBXUL_AUTOLOAD_FLAGS := -Dtopsrcdir=$(abspath $(topsrcdir)) endif +ifeq ($(OS_ARCH),SunOS) +OS_LDFLAGS += -Wl,-z,defs +endif + # BFD ld doesn't create multiple PT_LOADs as usual when an unknown section # exists. Using an implicit linker script to make it fold that section in # .data.rel.ro makes it create multiple PT_LOADs. That implicit linker @@ -50,6 +54,6 @@ endif LOCAL_CHECKS = test "$$($(get_first_and_last) | xargs echo)" != "start_kPStaticModules_NSModule end_kPStaticModules_NSModule" && echo "NSModules are not ordered appropriately" && exit 1 || exit 0 -ifeq (Linux,$(OS_ARCH)) +ifeq (,$(filter-out SunOS Linux,$(OS_ARCH))) LOCAL_CHECKS += ; test "$$($(TOOLCHAIN_PREFIX)readelf -l $1 | awk '$1 == "LOAD" { t += 1 } END { print t }')" -le 1 && echo "Only one PT_LOAD segment" && exit 1 || exit 0 endif -- cgit v1.2.3 From e51afbcc2fe7360bbcf5654f6e31752c48098ca0 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Fri, 4 Oct 2019 02:59:26 -0500 Subject: MoonchildProductions#1251 - Part 19: Make the unpreprocessed file script work on Solaris. https://www.tachytelic.net/2019/01/grep-recursively/ During testing, I tried simply replacing grep with ggrep, which was non-portable but worked fine. Using an environment variable with os.getenv set to 'g' also worked, but the problem with that approach is that you have to set it manually and some times the script will mess up if you don't explictly define it to an empty string for platforms that don't need it. Setting TOOLCHAIN_PREFIX to 'g' seemed to solve all of my problems except this one, and it appears to be the only non-portable use of GNU grep in the whole tree. To understand what I tried to do here, let me present with you with two commands that yield the same output on my machine: find . -name '*.xul' | xargs grep -E "#include" ggrep -E -r "#include" --include="*.xul" . I just tried to make the Python script follow the logic of those two commands, though I'm not sure how well I succeeded since I got no errors. I visualized everything this way: ggrep -E -r "" --include="" find -name '' | xargs grep -E "" And arranged it all accordingly to the best of my ability, though I should point out that Python is not my strong suit. --- python/mozbuild/mozbuild/mach_commands.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/python/mozbuild/mozbuild/mach_commands.py b/python/mozbuild/mozbuild/mach_commands.py index a45656b37..c2e1a3e89 100644 --- a/python/mozbuild/mozbuild/mach_commands.py +++ b/python/mozbuild/mozbuild/mach_commands.py @@ -544,9 +544,14 @@ class Build(MachCommandBase): # Check if there are any unpreprocessed files in '@MOZ_OBJDIR@/dist/bin' # See python/mozbuild/mozbuild/preprocessor.py#L293-L309 for the list of directives # We skip if, ifdef, ifndef, else, elif, elifdef and elifndef, because they are never used alone - grepcmd = 'grep -E -r "^(#|%)(define|endif|error|expand|filter|include|literal|undef|unfilter)" '\ - + '--include=\*.{css,dtd,html,js,jsm,xhtml,xml,xul,manifest,properties,rdf} '\ - + self.topobjdir + '/dist/bin | awk "/\.css:%/ || (!/\.css/ && /:#/)"' + # + # The original version of this script only worked with GNU grep because of the --include flag. + # Not a problem in and of itself, except that it didn't take TOOLCHAIN_PREFIX and simply assumed + # all operating systems use GNU grep as the system grep (often it's called ggrep or something). + # This script is a bit slower, but should do the same thing on all Unix platforms. + + grepcmd = 'find ' + self.topobjdir + '/dist/bin' + ' -name \'\*.{css,dtd,html,js,jsm,xhtml,xml,xul,manifest,properties,rdf}\' ' + '| xargs grep -E "^(#|%)(define|endif|error|expand|filter|include|literal|undef|unfilter)" '\ + + '| awk "/\.css:%/ || (!/\.css/ && /:#/)"' grepresult = subprocess.Popen(grepcmd, stdout=subprocess.PIPE, shell=True).communicate()[0] if grepresult: print('\nERROR: preprocessor was not applied to the following files:\n\n' + grepresult) -- cgit v1.2.3 From db34ef993c3b0a25619ec56d98303933b61169bc Mon Sep 17 00:00:00 2001 From: athenian200 Date: Fri, 4 Oct 2019 04:35:10 -0500 Subject: MoonchildProductions#1251 - Part 20: Add atomic.h to system-headers. https://bugzilla.mozilla.org/show_bug.cgi?id=1369061 OpenIndiana used a much messier fix for this, but this one was used by Mozilla and looks a lot cleaner. It shouldn't interfere with any other targets, but if it does, the messy version of this fix basically involves rewriting the Solaris version of atomicops_internals_solaris.h to use GCC compiler intrinsics for atomic operations directly. It absolutely works, but it's gross to look at. https://github.com/OpenIndiana/oi-userland/blob/3b246b0b385735b092a88f58f9baa9799ee34761/components/web/firefox/patches/01-FF43.0b3_OpenSXCE_x86_x64.patch Another fix may be possible by preventing config/gcc-hidden.h from being included, or possibly using well-placed GCC pragmas to solve the visibility issues. --- config/system-headers | 1 + 1 file changed, 1 insertion(+) diff --git a/config/system-headers b/config/system-headers index 683c9d337..b4f901792 100644 --- a/config/system-headers +++ b/config/system-headers @@ -1,3 +1,4 @@ +atomic.h nspr.h plarena.h plarenas.h -- cgit v1.2.3 From 575f51a27d6b3627ae5675cc8e920c8dcae073bd Mon Sep 17 00:00:00 2001 From: athenian200 Date: Fri, 4 Oct 2019 04:37:51 -0500 Subject: Fix a bunch of dumb typos and omissions. --- config/external/nspr/pr/moz.build | 2 +- dom/plugins/ipc/PluginModuleChild.cpp | 2 +- ipc/chromium/src/base/message_pump_glib.cc | 2 +- ipc/chromium/src/chrome/common/transport_dib.h | 2 +- js/src/jsnativestack.cpp | 3 ++- media/libpng/moz.build | 4 ++-- old-configure.in | 2 +- toolkit/library/moz.build | 1 + toolkit/xre/nsSigHandlers.cpp | 1 - xpcom/base/nsMemoryReporterManager.cpp | 2 +- xpcom/io/nsLocalFileUnix.cpp | 1 - xpcom/reflect/xptcall/md/unix/moz.build | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/config/external/nspr/pr/moz.build b/config/external/nspr/pr/moz.build index 02811aae8..84d6e7903 100644 --- a/config/external/nspr/pr/moz.build +++ b/config/external/nspr/pr/moz.build @@ -63,7 +63,7 @@ elif CONFIG['OS_TARGET'] == 'SunOS': if CONFIG['CPU_ARCH'] == 'x86_64': SOURCES += ['/nsprpub/pr/src/md/unix/os_SunOS_x86_64.s'] elif CONFIG['CPU_ARCH'] == 'x86': - SOURCES += ['/nsprpub/pr/src/md/unix/os_SunOS/x86.s'] + SOURCES += ['/nsprpub/pr/src/md/unix/os_SunOS_x86.s'] elif CONFIG['OS_TARGET'] == 'WINNT': OS_LIBS += [ 'advapi32', diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp index 102c44a5e..f943dfc42 100644 --- a/dom/plugins/ipc/PluginModuleChild.cpp +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -286,7 +286,7 @@ PluginModuleChild::InitForChrome(const std::string& aPluginFilename, // TODO: use PluginPRLibrary here -#if defined(OS_LINUX) || defined(OS_BSD) +#if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) mShutdownFunc = (NP_PLUGINSHUTDOWN) PR_FindFunctionSymbol(mLibrary, "NP_Shutdown"); diff --git a/ipc/chromium/src/base/message_pump_glib.cc b/ipc/chromium/src/base/message_pump_glib.cc index a5e719c72..f92378f3b 100644 --- a/ipc/chromium/src/base/message_pump_glib.cc +++ b/ipc/chromium/src/base/message_pump_glib.cc @@ -138,7 +138,7 @@ MessagePumpForUI::MessagePumpForUI() int flags = fcntl(fds[0], F_GETFL,0); if (flags == -1) flags = 0; - fntl(fds[0], F_SETFL, flags | O_NDELAY); + fcntl(fds[0], F_SETFL, flags | O_NDELAY); #endif wakeup_pipe_read_ = fds[0]; wakeup_pipe_write_ = fds[1]; diff --git a/ipc/chromium/src/chrome/common/transport_dib.h b/ipc/chromium/src/chrome/common/transport_dib.h index e3c40768c..f40a97d17 100644 --- a/ipc/chromium/src/chrome/common/transport_dib.h +++ b/ipc/chromium/src/chrome/common/transport_dib.h @@ -15,7 +15,7 @@ #if defined(OS_WIN) #include -#elif defined(OS_LINUX) +#elif defined(OS_LINUX) || defined(OS_SOLARIS) #include "chrome/common/x11_util.h" #endif diff --git a/js/src/jsnativestack.cpp b/js/src/jsnativestack.cpp index 389d7e657..4e96e01e8 100644 --- a/js/src/jsnativestack.cpp +++ b/js/src/jsnativestack.cpp @@ -73,7 +73,8 @@ js::GetNativeStackBaseImpl() JS_STATIC_ASSERT(JS_STACK_GROWTH_DIRECTION < 0); -void js::GetNativeStackBaseImpl() +void* +js::GetNativeStackBaseImpl() { stack_t st; stack_getbounds(&st); diff --git a/media/libpng/moz.build b/media/libpng/moz.build index 782aa93df..f2538484b 100644 --- a/media/libpng/moz.build +++ b/media/libpng/moz.build @@ -52,5 +52,5 @@ FINAL_LIBRARY = 'gkmedias' # We allow warnings for third-party code that can be updated from upstream. ALLOW_COMPILER_WARNINGS = True -if CONFIG['GNU_CC'] - CFLAGS += ['std=c89'] +if CONFIG['GNU_CC']: + CFLAGS += ['-std=c89'] diff --git a/old-configure.in b/old-configure.in index f8fb9245b..c5b4f1efb 100644 --- a/old-configure.in +++ b/old-configure.in @@ -1197,7 +1197,7 @@ case "$target" in fi ;; -*-solaris* +*-solaris*) MOZ_FIX_LINK_PATHS="-L${DIST}/bin" ;; diff --git a/toolkit/library/moz.build b/toolkit/library/moz.build index cd900a615..293c2feaf 100644 --- a/toolkit/library/moz.build +++ b/toolkit/library/moz.build @@ -274,6 +274,7 @@ if CONFIG['OS_ARCH'] == 'SunOS': OS_LIBS += [ 'elf', 'demangle', + 'sendfile', ] if CONFIG['OS_ARCH'] == 'FreeBSD': diff --git a/toolkit/xre/nsSigHandlers.cpp b/toolkit/xre/nsSigHandlers.cpp index bcbcb6e3d..a3a366f95 100644 --- a/toolkit/xre/nsSigHandlers.cpp +++ b/toolkit/xre/nsSigHandlers.cpp @@ -225,7 +225,6 @@ static void fpehandler(int signum, siginfo_t *si, void *context) #endif #endif } -} #endif void InstallSignalHandlers(const char *aProgname) diff --git a/xpcom/base/nsMemoryReporterManager.cpp b/xpcom/base/nsMemoryReporterManager.cpp index 9c2d620cc..dcbad171e 100644 --- a/xpcom/base/nsMemoryReporterManager.cpp +++ b/xpcom/base/nsMemoryReporterManager.cpp @@ -1151,7 +1151,7 @@ ResidentPeakDistinguishedAmount(int64_t* aN) #ifdef XP_MACOSX *aN = usage.ru_maxrss; #elif defined(XP_SOLARIS) - *aN = usage.ru.maxrss * getpagesize(); + *aN = usage.ru_maxrss * getpagesize(); #else *aN = usage.ru_maxrss * 1024; #endif diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp index c0b5c0f48..06706235d 100644 --- a/xpcom/io/nsLocalFileUnix.cpp +++ b/xpcom/io/nsLocalFileUnix.cpp @@ -1607,7 +1607,6 @@ nsLocalFile::IsExecutable(bool* aResult) return NSRESULT_FOR_ERRNO(); } #endif - if (*aResult || errno == EACCES) { if (*aResult || errno == EACCES) { return NS_OK; } diff --git a/xpcom/reflect/xptcall/md/unix/moz.build b/xpcom/reflect/xptcall/md/unix/moz.build index c5cba0c8d..a00588028 100644 --- a/xpcom/reflect/xptcall/md/unix/moz.build +++ b/xpcom/reflect/xptcall/md/unix/moz.build @@ -56,7 +56,7 @@ if CONFIG['OS_ARCH'] == 'SunOS': 'xptcinvoke_x86_64_unix.cpp', 'xptcstubs_x86_64_linux.cpp' ] - elif CONFIG['OS_TEST'] == 'x86': + elif '86' in CONFIG['OS_TEST']: SOURCES += [ 'xptcinvoke_gcc_x86_unix.cpp', 'xptcstubs_gcc_x86_unix.cpp' -- cgit v1.2.3 From 5a4a4990cd9f5d0b56d515b0246bc09da7109eb3 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Mon, 7 Oct 2019 03:09:28 -0500 Subject: MoonchildProductions#1251 - Part 21: Fix Flash player and some extensions being incompatible. https://github.com/oracle/solaris-userland/blob/82dd4adb0eca729372074d62435e00a783d95b1f/components/desktop/firefox/patches/firefox-49-npapi.patch The first fix was something I found on Oracle's patchset and allowed me to use the last Flash Player compiled for Solaris, from all the way back in 2012. Still works with most Flash content. The second is an evolution of what I had to do to get Interlink to compile. For Interlink, I basically had to copy the contents of any boolean values from confvars.sh into the empty moz.configure file, otherwise nothing would get configured. I decided to test whether Pale Moon had the same issue, and it turned out that it wasn't as bad as on Interlink, but it was still pure luck that the browser component built at all, because MOZ_PHOENIX and other important flags were apparently not being defined at all, hence why I couldn't get half the extensions to be compatible at first. I don't know why this is the case, but apparently configure.in isn't able to import values from confvars.sh. old-configure.in seems immune to the problem that application-specific configure.in files were experiencing, though. confvars.sh itself seems to work fine with values that aren't passed along via configure.in, though. So it's the interface between those two files that is messed up. --- application/palemoon/moz.configure | 46 ++++++++++++++++++++++++++++++++++++++ dom/plugins/base/npapi.h | 7 ++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/application/palemoon/moz.configure b/application/palemoon/moz.configure index 72236254f..30ad4bfe5 100644 --- a/application/palemoon/moz.configure +++ b/application/palemoon/moz.configure @@ -5,3 +5,49 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. include('../../toolkit/moz.configure') + +# configure.in is unreliable on Solaris. The build system only picks up about +# half the boolean values defined there. Doing this instead +# works consistently. Most of the ones that use AC_DEFINE without a +# MOZ_ARG_BOOL in this directory's configure.in fail, but all the ones in +# old-configure.in work just fine and say there's a duplicate if I try and +# define them here. + +if target_is_solaris: + + set_define('HAVE_SIDEBAR', True) + set_config('HAVE_SIDEBAR', True) + + set_define('MC_PALEMOON', True) + set_config('MC_PALEMOON', True) + + set_define('MOZ_PHOENIX', True) + set_config('MOZ_PHOENIX', True) + + set_define('MOZ_PERSONAS', True) + set_config('MOZ_PERSONAS', True) + +# set_define('MOZ_DEVTOOLS', True) +# set_config('MOZ_DEVTOOLS', True) + + set_define('MOZ_PHOENIX_EXTENSIONS', True) + set_config('MOZ_PHOENIX_EXTENSIONS', True) + + set_define('MOZ_SERVICES_COMMON', True) + set_config('MOZ_SERVICES_COMMON', True) + +# set_define('MOZ_SERVICES_SYNC', True) +# set_config('MOZ_SERVICES_SYNC', True) + +# set_define('MOZ_JSDOWNLOADS', True) +# set_config('MOZ_JSDOWNLOADS', True) + +# set_define('MOZ_WEBGL_CONFORMANT', True) +# set_config('MOZ_WEBGL_CONFORMANT', True) + +# set_define('MOZ_ADDON_SIGNING', False) +# set_config('MOZ_ADDON_SIGNING', False) + +# set_define('MOZ_REQUIRE_SIGNING', False) +# set_config('MOZ_REQUIRE_SIGNING', False) + diff --git a/dom/plugins/base/npapi.h b/dom/plugins/base/npapi.h index 12ac635c7..e554aaabc 100644 --- a/dom/plugins/base/npapi.h +++ b/dom/plugins/base/npapi.h @@ -327,9 +327,12 @@ typedef enum { #define NP_ABI_GCC3_MASK 0x10000000 /* * gcc 3.x generated vtables on UNIX and OSX are incompatible with - * previous compilers. + * previous compilers. Flash plugin binaries for Solaris were compiled + * with Sun Studio, so this has to be false to make things work. This may + * become a problem in the future when/if new plugins are compiled with + * GCC, however. */ -#if (defined(XP_UNIX) && defined(__GNUC__) && (__GNUC__ >= 3)) +#if (defined(XP_UNIX) && defined(__GNUC__) && (__GNUC__ >= 3) && !defined(XP_SOLARIS)) #define _NP_ABI_MIXIN_FOR_GCC3 NP_ABI_GCC3_MASK #else #define _NP_ABI_MIXIN_FOR_GCC3 0 -- cgit v1.2.3 From 7d65eb2b3a345abe22f42361e00c97da2e968009 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Mon, 7 Oct 2019 03:27:01 -0500 Subject: MoonchildProductions#1251 - Part 22: Remove some unused type declarations from IPC process_util. https://bugzilla.mozilla.org/show_bug.cgi?id=1397928 Was looking into that _POSIX_PATH_MAX/NAME_MAX issue earlier because it didn't make a lot of sense and I was thinking of other approaches besides char arrays, and I wanted to make sure it didn't cause problems after they did it. Turns out that one commit after this was added, Mozilla determined the code I was working on fixing to be dead code as of Firefox 58. I don't know if it's dead code in Pale Moon as well, but given that it compiles fine without it and I can't find any other references to szExeFile in the IPC code, that seems like a safe bet. Besides, I determined config/pathsub.c already seems to do what this code looks like it's trying to do, and implements the solution of just defining NAME_MAX to 256 and having done with it that I nearly adopted after realizing that even OS/2 and BeOS, let alone Unix/Linux systems, all basically use that value and there's just disagreement on which system header to check for it. --- ipc/chromium/src/base/process_util.h | 23 +---------------------- ipc/chromium/src/base/process_util_linux.cc | 5 ----- widget/GfxInfoX11.cpp | 2 +- 3 files changed, 2 insertions(+), 28 deletions(-) diff --git a/ipc/chromium/src/base/process_util.h b/ipc/chromium/src/base/process_util.h index 9b1e4fed5..4bb125681 100644 --- a/ipc/chromium/src/base/process_util.h +++ b/ipc/chromium/src/base/process_util.h @@ -39,28 +39,7 @@ #include "base/command_line.h" #include "base/process.h" -#if defined(OS_WIN) -typedef PROCESSENTRY32 ProcessEntry; -typedef IO_COUNTERS IoCounters; -#elif defined(OS_POSIX) -// TODO(port): we should not rely on a Win32 structure. -// Using NAME_MAX here would raise POSIX compliance issues -// (see Mozilla bug 1364865). -struct ProcessEntry { - int pid; - int ppid; - char szExeFile[_POSIX_PATH_MAX + 1]; -}; - -struct IoCounters { - unsigned long long ReadOperationCount; - unsigned long long WriteOperationCount; - unsigned long long OtherOperationCount; - unsigned long long ReadTransferCount; - unsigned long long WriteTransferCount; - unsigned long long OtherTransferCount; -}; - +#if defined(OS_POSIX) #include "base/file_descriptor_shuffle.h" #endif diff --git a/ipc/chromium/src/base/process_util_linux.cc b/ipc/chromium/src/base/process_util_linux.cc index 57388ccb0..a0d5ae816 100644 --- a/ipc/chromium/src/base/process_util_linux.cc +++ b/ipc/chromium/src/base/process_util_linux.cc @@ -34,11 +34,6 @@ namespace { -enum ParsingState { - KEY_NAME, - KEY_VALUE -}; - static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG"); } // namespace diff --git a/widget/GfxInfoX11.cpp b/widget/GfxInfoX11.cpp index 98a4bb965..05a2cab1a 100644 --- a/widget/GfxInfoX11.cpp +++ b/widget/GfxInfoX11.cpp @@ -192,7 +192,7 @@ GfxInfo::GetData() // and a buffer as some kind of workaround. They've been implementing // missing functions lately, though, so this may not be needed much longer. -#if defined(XP_SOLARIS) +#ifndef strcasestr #define strcasestr PL_strcasestr #endif -- cgit v1.2.3 From 2f4488521db663520c703a9a836d5549d679266c Mon Sep 17 00:00:00 2001 From: athenian200 Date: Thu, 10 Oct 2019 15:38:27 -0500 Subject: MoonchildProductions#1251 - Part 23: Allow AMD64 build to work. https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Compiling_32-bit_Firefox_on_a_Linux_64-bit_OS Setting this up turned out to be easier than I thought it would be. All I had to do was apply these instructions in reverse and add the following to my .mozconfig file: CC="gcc -m64" CXX="g++ -m64" AS="gas --64" ac_add_options --target=x86_64-pc-solaris2.11 export PKG_CONFIG_PATH=/usr/lib/amd64/pkgconfig ac_add_options --libdir=/usr/lib/amd64 ac_add_options --x-libraries=/usr/lib/amd64 Most of these changes were fairly trivial, just requiring me to make a few of the changes I made earlier conditional on a 32-bit build. The biggest challenge was figuring out why the JavaScript engine triggered a segfault everytime it tried to allocate memory. But this patch fixes it: https://github.com/OpenIndiana/oi-userland/blob/oi/hipster/components/web/firefox/patches/patch-js_src_gc_Memory.cpp.patch Turns out that Solaris on AMD64 handles memory management in a fairly unusual way with a segmented memory model, but it's not that different from what we see on other 64-bit processors. In fact, I saw a SPARC crash for a similar reason, and noticed that it looked just like mine except the numbers in the first segment were reversed. Having played around with hex editors before, I had a feeling I might be dealing with a little-endian version of a big-endian problem, but I didn't expect that knowledge to actually yield an easy solution. https://bugzilla.mozilla.org/show_bug.cgi?id=577056 https://www.oracle.com/technetwork/server-storage/solaris10/solaris-memory-135224.html As far as I can tell, this was the last barrier to an AMD64 Solaris build of Pale Moon. --- config/external/ffi/moz.build | 9 ++- config/external/nspr/pr/moz.build | 1 + ipc/chromium/src/base/message_pump_libevent.cc | 2 +- .../libevent/solaris/event2/event-config.h | 4 -- js/src/gc/Memory.cpp | 11 +++- xpcom/reflect/xptcall/md/unix/moz.build | 2 +- .../xptcall/md/unix/xptcinvoke_x86_64_solaris.cpp | 75 ++++++++++++++++++++++ xpcom/string/nsTSubstring.h | 9 ++- 8 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_solaris.cpp diff --git a/config/external/ffi/moz.build b/config/external/ffi/moz.build index 3a5478967..01ccd0547 100644 --- a/config/external/ffi/moz.build +++ b/config/external/ffi/moz.build @@ -64,13 +64,20 @@ else: if CONFIG['INTEL_ARCHITECTURE'] and CONFIG['OS_TARGET'] != 'SunOS': DEFINES['HAVE_AS_X86_PCREL'] = True +# Which is why they apparently don't do this anymore on amd64. + + if CONFIG['FFI_TARGET'] == 'X86_64' and CONFIG['OS_TARGET'] == 'SunOS': + DEFINES['HAVE_AS_X86_PCREL'] = True + # Don't bother setting EH_FRAME_FLAGS on Windows. # Quoted defines confuse msvcc.sh, and the value isn't used there. if CONFIG['OS_TARGET'] != 'WINNT': # Solaris seems to require EH_FRAME to be writable even on x86. # It works fine most of the time and there's no rule against it, # but it causes a lot of weird problems. - if CONFIG['FFI_TARGET'] == 'ARM' or CONFIG['OS_ARCH'] == 'SunOS': + if CONFIG['FFI_TARGET'] == 'ARM': + DEFINES['EH_FRAME_FLAGS'] = '"aw"' + elif CONFIG['FFI_TARGET'] == 'X86' and CONFIG['OS_TARGET'] == 'SunOS': DEFINES['EH_FRAME_FLAGS'] = '"aw"' else: DEFINES['EH_FRAME_FLAGS'] = '"a"' diff --git a/config/external/nspr/pr/moz.build b/config/external/nspr/pr/moz.build index 84d6e7903..1d2df3099 100644 --- a/config/external/nspr/pr/moz.build +++ b/config/external/nspr/pr/moz.build @@ -62,6 +62,7 @@ elif CONFIG['OS_TARGET'] == 'SunOS': SOURCES += ['/nsprpub/pr/src/md/unix/solaris.c'] if CONFIG['CPU_ARCH'] == 'x86_64': SOURCES += ['/nsprpub/pr/src/md/unix/os_SunOS_x86_64.s'] + DEFINES['USE_64'] = True elif CONFIG['CPU_ARCH'] == 'x86': SOURCES += ['/nsprpub/pr/src/md/unix/os_SunOS_x86.s'] elif CONFIG['OS_TARGET'] == 'WINNT': diff --git a/ipc/chromium/src/base/message_pump_libevent.cc b/ipc/chromium/src/base/message_pump_libevent.cc index daba7c2fe..20d529707 100644 --- a/ipc/chromium/src/base/message_pump_libevent.cc +++ b/ipc/chromium/src/base/message_pump_libevent.cc @@ -24,7 +24,7 @@ #include "mozilla/UniquePtr.h" // This macro checks that the _EVENT_SIZEOF_* constants defined in -// ipc/chromiume/src/third_party//event2/event-config.h are correct. +// ipc/chromium/src/third_party//event2/event-config.h are correct. #if defined(_EVENT_SIZEOF_SHORT) #define CHECK_EVENT_SIZEOF(TYPE, type) \ static_assert(_EVENT_SIZEOF_##TYPE == sizeof(type), \ diff --git a/ipc/chromium/src/third_party/libevent/solaris/event2/event-config.h b/ipc/chromium/src/third_party/libevent/solaris/event2/event-config.h index 73000106b..cc4fb1eb1 100644 --- a/ipc/chromium/src/third_party/libevent/solaris/event2/event-config.h +++ b/ipc/chromium/src/third_party/libevent/solaris/event2/event-config.h @@ -417,11 +417,7 @@ #define _EVENT_SIZEOF_LONG_LONG 8 /* The size of `pthread_t', as computed by sizeof. */ -#ifdef __LP64__ -#define _EVENT_SIZEOF_PTHREAD_T 8 -#else #define _EVENT_SIZEOF_PTHREAD_T 4 -#endif /* The size of `short', as computed by sizeof. */ #define _EVENT_SIZEOF_SHORT 2 diff --git a/js/src/gc/Memory.cpp b/js/src/gc/Memory.cpp index 5cf0cd2f7..2bc1b9f50 100644 --- a/js/src/gc/Memory.cpp +++ b/js/src/gc/Memory.cpp @@ -415,8 +415,15 @@ InitMemorySubsystem() static inline void* MapMemoryAt(void* desired, size_t length, int prot = PROT_READ | PROT_WRITE, int flags = MAP_PRIVATE | MAP_ANON, int fd = -1, off_t offset = 0) + +// Solaris manages 64-bit address space in a different manner from every other +// AMD64 operating system, but fortunately the fix is the same one +// required for every operating system on 64-bit SPARC, Itanium, and ARM. +// Most people's intuition failed them here and they thought this couldn't +// possibly be correct on AMD64, but for Solaris/illumos it is. + { -#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) || defined(__aarch64__) +#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) || defined(__aarch64__) || (defined(__sun) && defined(__x86_64__)) MOZ_ASSERT((0xffff800000000000ULL & (uintptr_t(desired) + length - 1)) == 0); #endif void* region = mmap(desired, length, prot, flags, fd, offset); @@ -466,7 +473,7 @@ MapMemory(size_t length, int prot = PROT_READ | PROT_WRITE, return nullptr; } return region; -#elif defined(__aarch64__) +#elif defined(__aarch64__) || (defined(__sun) && defined(__x86_64__)) /* * There might be similar virtual address issue on arm64 which depends on * hardware and kernel configurations. But the work around is slightly diff --git a/xpcom/reflect/xptcall/md/unix/moz.build b/xpcom/reflect/xptcall/md/unix/moz.build index a00588028..8ee7b3181 100644 --- a/xpcom/reflect/xptcall/md/unix/moz.build +++ b/xpcom/reflect/xptcall/md/unix/moz.build @@ -53,7 +53,7 @@ if CONFIG['OS_ARCH'] == 'SunOS': if CONFIG['OS_TEST'] == 'x86_64': SOURCES += [ 'xptcinvoke_asm_x86_64_unix.S', - 'xptcinvoke_x86_64_unix.cpp', + 'xptcinvoke_x86_64_solaris.cpp', 'xptcstubs_x86_64_linux.cpp' ] elif '86' in CONFIG['OS_TEST']: diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_solaris.cpp new file mode 100644 index 000000000..a9db2a693 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_solaris.cpp @@ -0,0 +1,75 @@ +/* -*- 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/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +#include "xptcprivate.h" + +// 6 integral parameters are passed in registers, but 1 is |this| which isn't +// considered here. +const uint32_t GPR_COUNT = 5; + +// 8 floating point parameters are passed in SSE registers +const uint32_t FPR_COUNT = 8; + +extern "C" void +InvokeCopyToStack(uint64_t * gpregs, double * fpregs, + uint32_t paramCount, nsXPTCVariant * s, + uint64_t* d) +{ + uint32_t nr_gpr = 0u; // skip one GP register for 'that' + uint32_t nr_fpr = 0u; + uint64_t value = 0u; + + for (uint32_t i = 0; i < paramCount; i++, s++) { + if (s->IsPtrData()) + value = (uint64_t) s->ptr; + else { + switch (s->type) { + case nsXPTType::T_FLOAT: break; + case nsXPTType::T_DOUBLE: break; + case nsXPTType::T_I8: value = s->val.i8; break; + case nsXPTType::T_I16: value = s->val.i16; break; + case nsXPTType::T_I32: value = s->val.i32; break; + case nsXPTType::T_I64: value = s->val.i64; break; + case nsXPTType::T_U8: value = s->val.u8; break; + case nsXPTType::T_U16: value = s->val.u16; break; + case nsXPTType::T_U32: value = s->val.u32; break; + case nsXPTType::T_U64: value = s->val.u64; break; + case nsXPTType::T_BOOL: value = s->val.b; break; + case nsXPTType::T_CHAR: value = s->val.c; break; + case nsXPTType::T_WCHAR: value = s->val.wc; break; + default: value = (uint64_t) s->val.p; break; + } + } + + if (!s->IsPtrData() && s->type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) + fpregs[nr_fpr++] = s->val.d; + else { + *((double *)d) = s->val.d; + d++; + } + } + else if (!s->IsPtrData() && s->type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) + // The value in %xmm register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + fpregs[nr_fpr++] = s->val.d; + else { + *((float *)d) = s->val.f; + d++; + } + } + else { + if (nr_gpr < GPR_COUNT) + gpregs[nr_gpr++] = value; + else + *d++ = value; + } + } +} diff --git a/xpcom/string/nsTSubstring.h b/xpcom/string/nsTSubstring.h index 2b0723c04..537889d5c 100644 --- a/xpcom/string/nsTSubstring.h +++ b/xpcom/string/nsTSubstring.h @@ -9,7 +9,12 @@ #include "mozilla/MemoryReporting.h" #include "mozilla/IntegerTypeTraits.h" #include "mozilla/Span.h" -#ifdef XP_SOLARIS + +// Solaris defines pid_t to be long on ILP32 and int on LP64. I checked in +// sys/types.h. AMD64 and SPARC64 builds don't need this fix at all, +// while all 32-bit builds do. + +#if defined(XP_SOLARIS) && !defined(__LP64__) #include #endif @@ -590,7 +595,7 @@ public: const char* fmt = aRadix == 10 ? "%d" : aRadix == 8 ? "%o" : "%x"; AppendPrintf(fmt, aInteger); } -#ifdef XP_SOLARIS +#if defined(XP_SOLARIS) && !defined(__LP64__) void AppendInt(pid_t aInteger) { AppendPrintf("%lu", aInteger); -- cgit v1.2.3 From 9cd92944e2c772fbb2e9856d5948553ae9233c9a Mon Sep 17 00:00:00 2001 From: athenian200 Date: Fri, 11 Oct 2019 15:36:33 -0500 Subject: MoonchildProductions#1251 - Part 24: Remove temporary GNU M4 workaround. We finally found where configure was failing. Apparently they just invoked m4 without regard for TOOLCHAIN_PREFIX. Easy to fix, difficult to find. --- application/palemoon/moz.configure | 44 -------------------------------------- old-configure.in | 4 ++-- 2 files changed, 2 insertions(+), 46 deletions(-) diff --git a/application/palemoon/moz.configure b/application/palemoon/moz.configure index 30ad4bfe5..de9b10b16 100644 --- a/application/palemoon/moz.configure +++ b/application/palemoon/moz.configure @@ -6,48 +6,4 @@ include('../../toolkit/moz.configure') -# configure.in is unreliable on Solaris. The build system only picks up about -# half the boolean values defined there. Doing this instead -# works consistently. Most of the ones that use AC_DEFINE without a -# MOZ_ARG_BOOL in this directory's configure.in fail, but all the ones in -# old-configure.in work just fine and say there's a duplicate if I try and -# define them here. - -if target_is_solaris: - - set_define('HAVE_SIDEBAR', True) - set_config('HAVE_SIDEBAR', True) - - set_define('MC_PALEMOON', True) - set_config('MC_PALEMOON', True) - - set_define('MOZ_PHOENIX', True) - set_config('MOZ_PHOENIX', True) - - set_define('MOZ_PERSONAS', True) - set_config('MOZ_PERSONAS', True) - -# set_define('MOZ_DEVTOOLS', True) -# set_config('MOZ_DEVTOOLS', True) - - set_define('MOZ_PHOENIX_EXTENSIONS', True) - set_config('MOZ_PHOENIX_EXTENSIONS', True) - - set_define('MOZ_SERVICES_COMMON', True) - set_config('MOZ_SERVICES_COMMON', True) - -# set_define('MOZ_SERVICES_SYNC', True) -# set_config('MOZ_SERVICES_SYNC', True) - -# set_define('MOZ_JSDOWNLOADS', True) -# set_config('MOZ_JSDOWNLOADS', True) - -# set_define('MOZ_WEBGL_CONFORMANT', True) -# set_config('MOZ_WEBGL_CONFORMANT', True) - -# set_define('MOZ_ADDON_SIGNING', False) -# set_config('MOZ_ADDON_SIGNING', False) - -# set_define('MOZ_REQUIRE_SIGNING', False) -# set_config('MOZ_REQUIRE_SIGNING', False) diff --git a/old-configure.in b/old-configure.in index c5b4f1efb..253084216 100644 --- a/old-configure.in +++ b/old-configure.in @@ -761,7 +761,7 @@ case "$host" in HOST_CFLAGS="$HOST_CFLAGS -DXP_UNIX" HOST_OPTIMIZE_FLAGS="${HOST_OPTIMIZE_FLAGS=-O3}" ;; - + *) HOST_CFLAGS="$HOST_CFLAGS -DXP_UNIX" HOST_OPTIMIZE_FLAGS="${HOST_OPTIMIZE_FLAGS=-O2}" @@ -4146,7 +4146,7 @@ if test -f "${srcdir}/${MOZ_BUILD_APP}/configure.in" ; then _subconfigure_config_args="$ac_configure_args" } tmpscript=`$PYTHON -c 'import os, tempfile; print tempfile.mktemp(prefix="subscript.").replace(os.sep, "/")'` || exit 1 - m4 "${srcdir}/build/autoconf/subconfigure.m4" \ + ${TOOLCHAIN_PREFIX}m4 "${srcdir}/build/autoconf/subconfigure.m4" \ "${srcdir}/build/autoconf/altoptions.m4" \ "${srcdir}/${MOZ_BUILD_APP}/configure.in" > $tmpscript . $tmpscript -- cgit v1.2.3 From 21146d0d5e4b6c985a3dce26460a5f495f1deb6e Mon Sep 17 00:00:00 2001 From: athenian200 Date: Thu, 17 Oct 2019 17:26:58 -0500 Subject: MoonchildProductions#1251 - Part 25: Fix link paths. This fix is a bit ugly and may need to be changed later if we switch a new GCC version, but the fact is that we use an architecture-specific path for GCC libraries on Solaris, so knowing the right prefix for GCC would only help so much, because it would still need to decide between ${gccdir}/lib and ${gccdir}/lib/amd64. The MOZ_FIX_LINK_PATHS variable puts the search paths into the right order without the need for me to use elfedit on the binaries afterwards. --- js/src/old-configure.in | 8 ++++++++ old-configure.in | 8 ++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/js/src/old-configure.in b/js/src/old-configure.in index 45108ee59..6566ce05e 100644 --- a/js/src/old-configure.in +++ b/js/src/old-configure.in @@ -923,6 +923,14 @@ case "$target" in fi ;; +i*86-*-solaris*) + MOZ_FIX_LINK_PATHS="-L${DIST}/bin -R'\$\$ORIGIN':/usr/gcc/7/lib" + ;; + +x86_64-*-solaris*) + MOZ_FIX_LINK_PATHS="-L${DIST}/bin -R'\$\$ORIGIN':/usr/gcc/7/lib/amd64" + ;; + esac dnl Only one oddball right now (QNX), but this gives us flexibility diff --git a/old-configure.in b/old-configure.in index 253084216..dae43d6f1 100644 --- a/old-configure.in +++ b/old-configure.in @@ -1197,8 +1197,12 @@ case "$target" in fi ;; -*-solaris*) - MOZ_FIX_LINK_PATHS="-L${DIST}/bin" +i*86-*-solaris*) + MOZ_FIX_LINK_PATHS="-L${DIST}/bin -R'\$\$ORIGIN':/usr/gcc/7/lib" + ;; + +x86_64-*-solaris*) + MOZ_FIX_LINK_PATHS="-L${DIST}/bin -R'\$\$ORIGIN':/usr/gcc/7/lib/amd64" ;; esac -- cgit v1.2.3 From e4f90cbb56b3cdcde52396b1aaa476cc8c5f07e5 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Thu, 17 Oct 2019 19:55:19 -0500 Subject: MoonchildProductions#1251 - Part 26: Oracle Solaris gsed/ICU fix. OpenIndiana didn't need this for some reason, but on Oracle Solaris, we need this to make sure we're using gsed (GNU sed) here. It's probably a safer bet anyway. --- build/autoconf/icu.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/autoconf/icu.m4 b/build/autoconf/icu.m4 index 89c53c52c..d3cc3113d 100644 --- a/build/autoconf/icu.m4 +++ b/build/autoconf/icu.m4 @@ -35,7 +35,7 @@ if test -n "$USE_ICU"; then fi fi - version=`sed -n 's/^[[[:space:]]]*#[[:space:]]*define[[:space:]][[:space:]]*U_ICU_VERSION_MAJOR_NUM[[:space:]][[:space:]]*\([0-9][0-9]*\)[[:space:]]*$/\1/p' "$icudir/common/unicode/uvernum.h"` + version=`${TOOLCHAIN_PREFIX}sed -n 's/^[[[:space:]]]*#[[:space:]]*define[[:space:]][[:space:]]*U_ICU_VERSION_MAJOR_NUM[[:space:]][[:space:]]*\([0-9][0-9]*\)[[:space:]]*$/\1/p' "$icudir/common/unicode/uvernum.h"` if test x"$version" = x; then AC_MSG_ERROR([cannot determine icu version number from uvernum.h header file $lineno]) fi -- cgit v1.2.3 From 687a798e6dedacb8b42826debcd8e89baa69ce94 Mon Sep 17 00:00:00 2001 From: athenian200 Date: Sat, 19 Oct 2019 14:24:49 -0500 Subject: MoonchildProductions#1251 - Part 27: Fix ifdef style. This should do it for all the commits to files I changed, but while I'm in here I could probably go ahead and turn ALL the singular if defined statements into ifdef statements by using grep/find on the tree. On the other hand, perhaps we should do that as a separate issue so that this doesn't become a case of scope creep. --- dom/base/nsContentUtils.h | 4 ++-- dom/media/AudioStream.h | 2 +- dom/plugins/base/nsPluginsDirUnix.cpp | 4 ++-- ipc/chromium/src/base/message_pump_glib.cc | 6 +++--- ipc/chromium/src/base/message_pump_libevent.cc | 2 +- ipc/chromium/src/base/sys_info_posix.cc | 4 ++-- ipc/chromium/src/base/time.h | 8 ++++---- ipc/chromium/src/base/time_posix.cc | 2 +- ipc/glue/MessageLink.cpp | 4 ++-- js/src/builtin/TestingFunctions.cpp | 2 +- js/src/gc/Memory.cpp | 2 +- modules/libjar/nsZipArchive.cpp | 2 +- toolkit/components/terminator/nsTerminator.cpp | 4 ++-- toolkit/xre/nsSigHandlers.cpp | 4 ++-- xpcom/ds/nsMathUtils.h | 2 +- 15 files changed, 26 insertions(+), 26 deletions(-) diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 26635acd6..606d67de9 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -9,11 +9,11 @@ #ifndef nsContentUtils_h___ #define nsContentUtils_h___ -#if defined(XP_WIN) +#ifdef XP_WIN #include #endif -#if defined(XP_SOLARIS) +#ifdef XP_SOLARIS #include #endif diff --git a/dom/media/AudioStream.h b/dom/media/AudioStream.h index 13cbb0c75..199314d4b 100644 --- a/dom/media/AudioStream.h +++ b/dom/media/AudioStream.h @@ -17,7 +17,7 @@ #include "mozilla/UniquePtr.h" #include "CubebUtils.h" #include "soundtouch/SoundTouchFactory.h" -#if defined(XP_SOLARIS) +#ifdef XP_SOLARIS #include "soundtouch/SoundTouch.h" #endif diff --git a/dom/plugins/base/nsPluginsDirUnix.cpp b/dom/plugins/base/nsPluginsDirUnix.cpp index 22d6bd44b..de3b7a2d1 100644 --- a/dom/plugins/base/nsPluginsDirUnix.cpp +++ b/dom/plugins/base/nsPluginsDirUnix.cpp @@ -19,7 +19,7 @@ #include "nsIPrefService.h" #define LOCAL_PLUGIN_DLL_SUFFIX ".so" -#if defined(XP_SOLARIS) +#ifdef XP_SOLARIS #define DEFAULT_X11_PATH "/usr/openwin/lib" #elif defined(LINUX) #define DEFAULT_X11_PATH "/usr/X11R6/lib/" @@ -94,7 +94,7 @@ static bool LoadExtraSharedLib(const char *name, char **soname, bool tryToGetSon #define PLUGIN_MAX_NUMBER_OF_EXTRA_LIBS 32 #define PREF_PLUGINS_SONAME "plugin.soname.list" -#if defined (XP_SOLARIS) +#ifdef XP_SOLARIS #define DEFAULT_EXTRA_LIBS_LIST "libXt" LOCAL_PLUGIN_DLL_SUFFIX ":libXext" LOCAL_PLUGIN_DLL_SUFFIX ":libXm" LOCAL_PLUGIN_DLL_SUFFIX #else #define DEFAULT_EXTRA_LIBS_LIST "libXt" LOCAL_PLUGIN_DLL_SUFFIX ":libXext" LOCAL_PLUGIN_DLL_SUFFIX diff --git a/ipc/chromium/src/base/message_pump_glib.cc b/ipc/chromium/src/base/message_pump_glib.cc index f92378f3b..24081bd8b 100644 --- a/ipc/chromium/src/base/message_pump_glib.cc +++ b/ipc/chromium/src/base/message_pump_glib.cc @@ -9,7 +9,7 @@ #include #include -#if defined(OS_SOLARIS) +#ifdef OS_SOLARIS #include #endif #include @@ -134,7 +134,7 @@ MessagePumpForUI::MessagePumpForUI() // Create our wakeup pipe, which is used to flag when work was scheduled. int fds[2]; CHECK(pipe(fds) == 0); -#if defined(OS_SOLARIS) +#ifdef OS_SOLARIS int flags = fcntl(fds[0], F_GETFL,0); if (flags == -1) flags = 0; @@ -324,7 +324,7 @@ void MessagePumpForUI::ScheduleWork() { // variables as we would then need locks all over. This ensures that if // we are sleeping in a poll that we will wake up. char msg = '!'; -#if defined(OS_SOLARIS) +#ifdef OS_SOLARIS char buf[32]; while (HANDLE_EINTR(read(wakeup_pipe_read_, &buf, 32))); #endif diff --git a/ipc/chromium/src/base/message_pump_libevent.cc b/ipc/chromium/src/base/message_pump_libevent.cc index 20d529707..51d3205b8 100644 --- a/ipc/chromium/src/base/message_pump_libevent.cc +++ b/ipc/chromium/src/base/message_pump_libevent.cc @@ -8,7 +8,7 @@ #include #include -#if defined(OS_SOLARIS) +#ifdef OS_SOLARIS #include #endif #if defined(ANDROID) || defined(OS_POSIX) diff --git a/ipc/chromium/src/base/sys_info_posix.cc b/ipc/chromium/src/base/sys_info_posix.cc index 6adbb1c06..f29c85092 100644 --- a/ipc/chromium/src/base/sys_info_posix.cc +++ b/ipc/chromium/src/base/sys_info_posix.cc @@ -121,7 +121,7 @@ std::wstring SysInfo::GetEnvVar(const wchar_t* var) { // static std::string SysInfo::OperatingSystemName() { -#if !defined(XP_SOLARIS) +#ifndef XP_SOLARIS) utsname info; #else struct utsname info; @@ -139,7 +139,7 @@ std::string SysInfo::OperatingSystemName() { // static std::string SysInfo::CPUArchitecture() { -#if !defined(XP_SOLARIS) +#ifndef XP_SOLARIS utsname info; #else struct utsname info; diff --git a/ipc/chromium/src/base/time.h b/ipc/chromium/src/base/time.h index 6a5c92d14..cee463579 100644 --- a/ipc/chromium/src/base/time.h +++ b/ipc/chromium/src/base/time.h @@ -28,7 +28,7 @@ #include "base/basictypes.h" -#if defined(OS_WIN) +#ifdef OS_WIN // For FILETIME in FromFileTime, until it moves to a new converter class. // See TODO(iyengar) below. #include @@ -64,7 +64,7 @@ class TimeDelta { return delta_; } -#if defined(OS_SOLARIS) +#ifdef OS_SOLARIS struct timespec ToTimeSpec() const; #endif @@ -230,11 +230,11 @@ class Time { static Time FromDoubleT(double dt); double ToDoubleT() const; -#if defined(OS_SOLARIS) +#ifdef OS_SOLARIS struct timeval ToTimeVal() const; #endif -#if defined(OS_WIN) +#ifdef OS_WIN static Time FromFileTime(FILETIME ft); FILETIME ToFileTime() const; #endif diff --git a/ipc/chromium/src/base/time_posix.cc b/ipc/chromium/src/base/time_posix.cc index 419cc8afb..5e0a922d9 100644 --- a/ipc/chromium/src/base/time_posix.cc +++ b/ipc/chromium/src/base/time_posix.cc @@ -202,7 +202,7 @@ TimeTicks TimeTicks::HighResNow() { return Now(); } -#if defined(OS_SOLARIS) +#ifdef OS_SOLARIS struct timespec TimeDelta::ToTimeSpec() const { int64_t microseconds = InMicroseconds(); time_t seconds = 0; diff --git a/ipc/glue/MessageLink.cpp b/ipc/glue/MessageLink.cpp index 8c6d9b890..14d885544 100644 --- a/ipc/glue/MessageLink.cpp +++ b/ipc/glue/MessageLink.cpp @@ -278,7 +278,7 @@ ProcessLink::OnChannelOpened() #ifdef DEBUG if (mExistingListener) { -#if defined(XP_SOLARIS) +#ifdef XP_SOLARIS std::queue pending; #else queue pending; @@ -298,7 +298,7 @@ void ProcessLink::OnTakeConnectedChannel() { AssertIOThread(); -#if defined(XP_SOLARIS) +#ifdef XP_SOLARIS std::queue pending; #else queue pending; diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 6beb82932..4363c7aed 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -239,7 +239,7 @@ GetBuildConfiguration(JSContext* cx, unsigned argc, Value* vp) value = BooleanValue(true); if (!JS_SetProperty(cx, info, "intl-api", value)) return false; -#if defined(XP_SOLARIS) +#ifdef XP_SOLARIS value = BooleanValue(false); #else value = BooleanValue(true); diff --git a/js/src/gc/Memory.cpp b/js/src/gc/Memory.cpp index 2bc1b9f50..418984057 100644 --- a/js/src/gc/Memory.cpp +++ b/js/src/gc/Memory.cpp @@ -685,7 +685,7 @@ MarkPagesUnused(void* p, size_t size) return false; MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0); -#if defined(XP_SOLARIS) +#ifdef XP_SOLARIS int result = posix_madvise(p, size, POSIX_MADV_DONTNEED); #else int result = madvise(p, size, MADV_DONTNEED); diff --git a/modules/libjar/nsZipArchive.cpp b/modules/libjar/nsZipArchive.cpp index 24da13072..841503ebf 100644 --- a/modules/libjar/nsZipArchive.cpp +++ b/modules/libjar/nsZipArchive.cpp @@ -688,7 +688,7 @@ MOZ_WIN_MEM_TRY_BEGIN // Success means optimized jar layout from bug 559961 is in effect uint32_t readaheadLength = xtolong(startp); if (readaheadLength) { -#if defined(XP_SOLARIS) +#ifdef XP_SOLARIS posix_madvise(const_cast(startp), readaheadLength, POSIX_MADV_WILLNEED); #elif defined(XP_UNIX) madvise(const_cast(startp), readaheadLength, MADV_WILLNEED); diff --git a/toolkit/components/terminator/nsTerminator.cpp b/toolkit/components/terminator/nsTerminator.cpp index 007885b8d..9f6a90b57 100644 --- a/toolkit/components/terminator/nsTerminator.cpp +++ b/toolkit/components/terminator/nsTerminator.cpp @@ -30,7 +30,7 @@ #include "nsIObserverService.h" #include "nsIPrefService.h" -#if defined(XP_WIN) +#ifdef XP_WIN #include #else #include @@ -385,7 +385,7 @@ nsTerminator::StartWatchdog() } UniquePtr options(new Options()); -#if !defined(XP_SOLARIS) +#ifdef XP_SOLARIS const PRIntervalTime ticksDuration = PR_MillisecondsToInterval(1000); #else const PRIntervalTime ticksDuration = 1000; diff --git a/toolkit/xre/nsSigHandlers.cpp b/toolkit/xre/nsSigHandlers.cpp index a3a366f95..660af4522 100644 --- a/toolkit/xre/nsSigHandlers.cpp +++ b/toolkit/xre/nsSigHandlers.cpp @@ -32,7 +32,7 @@ #endif #endif -#if defined(XP_SOLARIS) +#ifdef XP_SOLARIS #include #include #endif @@ -286,7 +286,7 @@ void InstallSignalHandlers(const char *aProgname) } #endif -#if defined(XP_SOLARIS) +#ifdef XP_SOLARIS #define NOFILES 512 // Boost Solaris file descriptors diff --git a/xpcom/ds/nsMathUtils.h b/xpcom/ds/nsMathUtils.h index 61eb47501..e3f423c8f 100644 --- a/xpcom/ds/nsMathUtils.h +++ b/xpcom/ds/nsMathUtils.h @@ -11,7 +11,7 @@ #include #include -#if defined(XP_SOLARIS) +#ifdef XP_SOLARIS #include #include #endif -- cgit v1.2.3 From a0ab17b2103aecfaafdbe0c18a98e358c990ad8d Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Mon, 21 Oct 2019 22:29:42 +0200 Subject: Issue #1253 - Reset performance object on navigation This also addresses clearing of document dependent JS slots which might get out of sync with innerWindow navigation; relevant comments added. This resolves #1253 --- dom/base/nsGlobalWindow.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index afaa24f09..ec546f068 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -3224,6 +3224,12 @@ nsGlobalWindow::SetNewDocument(nsIDocument* aDocument, newInnerWindow->mLocalStorage = nullptr; newInnerWindow->mSessionStorage = nullptr; + newInnerWindow->mPerformance = nullptr; + + // This must be called after nulling the internal objects because + // we might recreate them here by calling the getter methods, and + // store them into the JS slots. If we null them after, the slot + // values and the objects will be out of sync. newInnerWindow->ClearDocumentDependentSlots(cx); } } else { @@ -3364,10 +3370,16 @@ nsGlobalWindow::InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument) } mDoc = aDocument; - ClearDocumentDependentSlots(aCx); mFocusedNode = nullptr; mLocalStorage = nullptr; mSessionStorage = nullptr; + mPerformance = nullptr; + + // This must be called after nulling the internal objects because we might + // recreate them here by calling the getter methods, and store them into the JS + // slots. If we null them after, the slot values and the objects will be + // out of sync. + ClearDocumentDependentSlots(aCx); #ifdef DEBUG mLastOpenedURI = aDocument->GetDocumentURI(); -- cgit v1.2.3 From dee457e63aa52ac83c0545ae87dc273cbdd072f1 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Tue, 22 Oct 2019 20:57:58 +0200 Subject: Avoid uint32_t overflow in js shell by checking size of file before trying to stuff something insanely large into a Uint8Array. See also: BMO 1571911 --- js/src/shell/OSObject.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/js/src/shell/OSObject.cpp b/js/src/shell/OSObject.cpp index 846ec7b15..4fb3d4e77 100644 --- a/js/src/shell/OSObject.cpp +++ b/js/src/shell/OSObject.cpp @@ -184,6 +184,11 @@ FileAsTypedArray(JSContext* cx, JS::HandleString pathnameStr) return nullptr; JS_ReportErrorUTF8(cx, "can't seek start of %s", pathname.ptr()); } else { + if (len > INT32_MAX) { + JS_ReportErrorUTF8(cx, "file %s is too large for a Uint8Array", + pathname.ptr()); + return nullptr; + } obj = JS_NewUint8Array(cx, len); if (!obj) return nullptr; -- cgit v1.2.3 From 245bbebbc248151a335c8ab78f3e30438a808379 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 23 Oct 2019 10:32:13 +0200 Subject: Issue #1255 - Port upstream fix from libexpat --- parser/expat/lib/xmlparse.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/parser/expat/lib/xmlparse.c b/parser/expat/lib/xmlparse.c index 93a817764..6ab140c89 100644 --- a/parser/expat/lib/xmlparse.c +++ b/parser/expat/lib/xmlparse.c @@ -335,7 +335,7 @@ initializeEncoding(XML_Parser parser); static enum XML_Error doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, int tok, const char *next, const char **nextPtr, - XML_Bool haveMore); + XML_Bool haveMore, XML_Bool allowClosingDoctype); static enum XML_Error processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl); @@ -3760,7 +3760,7 @@ externalParEntProcessor(XML_Parser parser, processor = prologProcessor; return doProlog(parser, encoding, s, end, tok, next, - nextPtr, (XML_Bool)!ps_finalBuffer); + nextPtr, (XML_Bool)!ps_finalBuffer, XML_TRUE); } static enum XML_Error PTRCALL @@ -3810,7 +3810,7 @@ prologProcessor(XML_Parser parser, const char *next = s; int tok = XmlPrologTok(encoding, s, end, &next); return doProlog(parser, encoding, s, end, tok, next, - nextPtr, (XML_Bool)!ps_finalBuffer); + nextPtr, (XML_Bool)!ps_finalBuffer, XML_TRUE); } static enum XML_Error @@ -3821,7 +3821,8 @@ doProlog(XML_Parser parser, int tok, const char *next, const char **nextPtr, - XML_Bool haveMore) + XML_Bool haveMore, + XML_Bool allowClosingDoctype) { #ifdef XML_DTD static const XML_Char externalSubsetName[] = { '#' , '\0' }; @@ -3987,6 +3988,11 @@ doProlog(XML_Parser parser, } break; case XML_ROLE_DOCTYPE_CLOSE: + if (allowClosingDoctype != XML_TRUE) { + /* Must not close doctype from within expanded parameter entities */ + return XML_ERROR_INVALID_TOKEN; + } + if (doctypeName) { startDoctypeDeclHandler(handlerArg, doctypeName, doctypeSysid, doctypePubid, 0); @@ -4892,7 +4898,7 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, if (entity->is_param) { int tok = XmlPrologTok(internalEncoding, textStart, textEnd, &next); result = doProlog(parser, internalEncoding, textStart, textEnd, tok, - next, &next, XML_FALSE); + next, &next, XML_FALSE, XML_FALSE); } else #endif /* XML_DTD */ @@ -4959,7 +4965,7 @@ internalEntityProcessor(XML_Parser parser, if (entity->is_param) { int tok = XmlPrologTok(internalEncoding, textStart, textEnd, &next); result = doProlog(parser, internalEncoding, textStart, textEnd, tok, - next, &next, XML_FALSE); + next, &next, XML_FALSE, XML_TRUE); } else #endif /* XML_DTD */ @@ -4986,7 +4992,7 @@ internalEntityProcessor(XML_Parser parser, processor = prologProcessor; tok = XmlPrologTok(encoding, s, end, &next); return doProlog(parser, encoding, s, end, tok, next, nextPtr, - (XML_Bool)!ps_finalBuffer); + (XML_Bool)!ps_finalBuffer, XML_TRUE); } else #endif /* XML_DTD */ -- cgit v1.2.3 From cb762d71af8bf7ab7e69468d2d91167c1e7d8ab7 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 23 Oct 2019 10:50:55 +0200 Subject: Add null check in Http2Session::RecvAltSvc --- netwerk/protocol/http/Http2Session.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/netwerk/protocol/http/Http2Session.cpp b/netwerk/protocol/http/Http2Session.cpp index 86e8c74f6..726b39f74 100644 --- a/netwerk/protocol/http/Http2Session.cpp +++ b/netwerk/protocol/http/Http2Session.cpp @@ -2182,6 +2182,7 @@ Http2Session::RecvAltSvc(Http2Session *self) } if (NS_FAILED(self->SetInputFrameDataStream(self->mInputFrameID)) || + !self->mInputFrameDataStream || !self->mInputFrameDataStream->Transaction() || !self->mInputFrameDataStream->Transaction()->RequestHead()) { LOG3(("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream", self)); -- cgit v1.2.3 From 2e22601d0059fddea55339363416290f4365afdd Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 23 Oct 2019 11:29:38 +0200 Subject: Avoid following the prototype chain No longer follow the value's prototype chain when creating index updates in IndexedDB. --- dom/indexedDB/IDBObjectStore.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index f86c619a7..cbac30894 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -1114,7 +1114,7 @@ IDBObjectStore::AppendIndexUpdateInfo( } bool isArray; - if (!JS_IsArrayObject(aCx, val, &isArray)) { + if (NS_WARN_IF(!JS_IsArrayObject(aCx, val, &isArray))) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } @@ -1127,8 +1127,25 @@ IDBObjectStore::AppendIndexUpdateInfo( } for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) { - JS::Rooted arrayItem(aCx); - if (NS_WARN_IF(!JS_GetOwnElement(aCx, array, arrayIndex, &arrayItem))) { + JS::RootedId indexId(aCx); + if (NS_WARN_IF(!JS_IndexToId(aCx, arrayIndex, &indexId))) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + bool hasOwnProperty; + if (NS_WARN_IF( + !JS_HasOwnPropertyById(aCx, array, indexId, &hasOwnProperty))) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + if (!hasOwnProperty) { + continue; + } + + JS::RootedValue arrayItem(aCx); + if (NS_WARN_IF(!JS_GetPropertyById(aCx, array, indexId, &arrayItem))) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } -- cgit v1.2.3 From 7aaec46911d28e91c05a218628eaae0a54eb51ec Mon Sep 17 00:00:00 2001 From: Simon Giesecke Date: Wed, 23 Oct 2019 12:19:15 +0200 Subject: Ensure that file actors created after the database was closed are expired. --- dom/indexedDB/IDBDatabase.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dom/indexedDB/IDBDatabase.cpp b/dom/indexedDB/IDBDatabase.cpp index 5592e7f93..6ef352801 100644 --- a/dom/indexedDB/IDBDatabase.cpp +++ b/dom/indexedDB/IDBDatabase.cpp @@ -1257,6 +1257,9 @@ IDBDatabase::LastRelease() AssertIsOnOwningThread(); CloseInternal(); + + // Make sure that file actors created after the database was closed are expired. + ExpireFileActors(/* aExpireAll */ true); if (mBackgroundActor) { mBackgroundActor->SendDeleteMeInternal(); -- cgit v1.2.3 From f9ea088b4f36511e2bda26cdca1e7ddd8eb1f502 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Wed, 23 Oct 2019 16:40:29 +0200 Subject: Leverage strings to get working dirs in nsUpdateDriver. --- toolkit/xre/nsUpdateDriver.cpp | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp index aac856d6e..be11fb158 100644 --- a/toolkit/xre/nsUpdateDriver.cpp +++ b/toolkit/xre/nsUpdateDriver.cpp @@ -75,21 +75,26 @@ GetUpdateLog() #endif static nsresult -GetCurrentWorkingDir(char *buf, size_t size) +GetCurrentWorkingDir(nsACString& aOutPath) { // Cannot use NS_GetSpecialDirectory because XPCOM is not yet initialized. - // This code is duplicated from xpcom/io/SpecialSystemDirectory.cpp: - + + // Just in case junk has been passed in. + aOutPath.Truncate(); + #if defined(XP_WIN) wchar_t wpath[MAX_PATH]; - if (!_wgetcwd(wpath, size)) + if (!_wgetcwd(wpath, ArrayLength(wpath))) return NS_ERROR_FAILURE; - NS_ConvertUTF16toUTF8 path(wpath); - strncpy(buf, path.get(), size); + CopyUTF16toUTF8(nsDependentString(wpath), aOutPath); #else - if(!getcwd(buf, size)) + char path[MAXPATHLEN]; + if (!getcwd(path, ArrayLength(path))) { return NS_ERROR_FAILURE; + } + aOutPath = path; #endif + return NS_OK; } @@ -535,8 +540,8 @@ SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir, return; // Get the current working directory. - char workingDirPath[MAXPATHLEN]; - rv = GetCurrentWorkingDir(workingDirPath, sizeof(workingDirPath)); + nsAutoCString workingDirPath; + rv = GetCurrentWorkingDir(workingDirPath); if (NS_FAILED(rv)) return; @@ -565,7 +570,7 @@ SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir, argv[3] = (char*) applyToDir.get(); argv[4] = (char*) pid.get(); if (appArgc) { - argv[5] = workingDirPath; + argv[5] = (char*) workingDirPath.get(); argv[6] = (char*) appFilePath.get(); for (int i = 1; i < appArgc; ++i) argv[6 + i] = appArgv[i]; @@ -743,8 +748,8 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile, } // Get the current working directory. - char workingDirPath[MAXPATHLEN]; - rv = GetCurrentWorkingDir(workingDirPath, sizeof(workingDirPath)); + nsAutoCString workingDirPath; + rv = GetCurrentWorkingDir(workingDirPath); if (NS_FAILED(rv)) return; @@ -786,7 +791,7 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile, argv[3] = (char*) applyToDir.get(); argv[4] = (char*) pid.get(); if (restart && appArgc) { - argv[5] = workingDirPath; + argv[5] = (char*) workingDirPath.get(); argv[6] = (char*) appFilePath.get(); for (int i = 1; i < appArgc; ++i) argv[6 + i] = appArgv[i]; -- cgit v1.2.3 From 8c2501ffe9c7310d985396544f51a4ada029bb69 Mon Sep 17 00:00:00 2001 From: Byron Campen Date: Thu, 24 Oct 2019 10:28:26 +0200 Subject: Prevent nr_ice_component_insert_pair from leaking. --- media/mtransport/third_party/nICEr/src/ice/ice_component.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_component.c b/media/mtransport/third_party/nICEr/src/ice/ice_component.c index 2be25efca..11b4fcbc1 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c +++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c @@ -909,7 +909,6 @@ static int nr_ice_component_process_incoming_check(nr_ice_component *comp, nr_tr nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_FROZEN); if(r=nr_ice_component_insert_pair(comp,pair)) { *error=(r==R_NO_MEMORY)?500:400; - nr_ice_candidate_pair_destroy(&pair); ABORT(r); } @@ -1615,6 +1614,7 @@ int nr_ice_component_finalize(nr_ice_component *lcomp, nr_ice_component *rcomp) int nr_ice_component_insert_pair(nr_ice_component *pcomp, nr_ice_cand_pair *pair) { int r,_status; + int pair_inserted=0; /* Pairs for peer reflexive are marked SUCCEEDED immediately */ if (pair->state != NR_ICE_PAIR_STATE_FROZEN && @@ -1626,6 +1626,8 @@ int nr_ice_component_insert_pair(nr_ice_component *pcomp, nr_ice_cand_pair *pair if(r=nr_ice_candidate_pair_insert(&pair->remote->stream->check_list,pair)) ABORT(r); + pair_inserted=1; + /* Make sure the check timer is running, if the stream was previously * started. We will not start streams just because a pair was created, * unless it is the first pair to be created across all streams. */ @@ -1642,6 +1644,9 @@ int nr_ice_component_insert_pair(nr_ice_component *pcomp, nr_ice_cand_pair *pair _status=0; abort: + if (_status && !pair_inserted) { + nr_ice_candidate_pair_destroy(&pair); + } return(_status); } -- cgit v1.2.3 From 7bccd87695b792ec54fe0cb027cc6b6dc8baf454 Mon Sep 17 00:00:00 2001 From: Henri Sivonen Date: Thu, 24 Oct 2019 11:43:25 +0200 Subject: Adjust tokenization of U+0000 --- parser/html/nsHtml5Tokenizer.cpp | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/parser/html/nsHtml5Tokenizer.cpp b/parser/html/nsHtml5Tokenizer.cpp index 4c815b0c0..5464d211d 100644 --- a/parser/html/nsHtml5Tokenizer.cpp +++ b/parser/html/nsHtml5Tokenizer.cpp @@ -1054,9 +1054,6 @@ nsHtml5Tokenizer::stateLoop(int32_t state, char16_t c, int32_t pos, char16_t* bu } c = checkChar(buf, pos); switch(c) { - case '\0': { - NS_HTML5_BREAK(stateloop); - } case '-': { clearStrBufAfterOneHyphen(); state = P::transition(mViewSource, NS_HTML5TOKENIZER_COMMENT_START, reconsume, pos); @@ -1461,9 +1458,6 @@ nsHtml5Tokenizer::stateLoop(int32_t state, char16_t c, int32_t pos, char16_t* bu NS_HTML5_BREAK(stateloop); } c = checkChar(buf, pos); - if (c == '\0') { - NS_HTML5_BREAK(stateloop); - } switch(c) { case ' ': case '\t': @@ -1471,7 +1465,8 @@ nsHtml5Tokenizer::stateLoop(int32_t state, char16_t c, int32_t pos, char16_t* bu case '\r': case '\f': case '<': - case '&': { + case '&': + case '\0': { emitOrAppendCharRefBuf(returnState); if (!(returnState & NS_HTML5TOKENIZER_DATA_AND_RCDATA_MASK)) { cstart = pos; @@ -1519,9 +1514,6 @@ nsHtml5Tokenizer::stateLoop(int32_t state, char16_t c, int32_t pos, char16_t* bu NS_HTML5_BREAK(stateloop); } c = checkChar(buf, pos); - if (c == '\0') { - NS_HTML5_BREAK(stateloop); - } int32_t hilo = 0; if (c <= 'z') { const int32_t* row = nsHtml5NamedCharactersAccel::HILO_ACCEL[c]; @@ -1556,9 +1548,6 @@ nsHtml5Tokenizer::stateLoop(int32_t state, char16_t c, int32_t pos, char16_t* bu NS_HTML5_BREAK(stateloop); } c = checkChar(buf, pos); - if (c == '\0') { - NS_HTML5_BREAK(stateloop); - } entCol++; for (; ; ) { if (hi < lo) { -- cgit v1.2.3 From e8c5582bec49058508b2de9f2f292f3a25e5278e Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 24 Oct 2019 12:13:26 +0200 Subject: Fix type barrier in IonBuilder::jsop_getimport. --- js/src/jit/IonBuilder.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 2d053de5a..0c69729a4 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -8895,10 +8895,8 @@ IonBuilder::jsop_getimport(PropertyName* name) if (!emitted) { // This can happen if we don't have type information. - TypeSet::ObjectKey* staticKey = TypeSet::ObjectKey::get(targetEnv); TemporaryTypeSet* types = bytecodeTypes(pc); - BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), staticKey, - name, types, /* updateObserved = */ true); + BarrierKind barrier = BarrierKind::TypeSet; if (!loadStaticSlot(targetEnv, barrier, types, shape->slot())) return false; -- cgit v1.2.3 From f1308011aef318f40d05d93353db17d059db83a0 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 24 Oct 2019 12:17:39 +0200 Subject: Add size checks to WebGLContext::BufferData() On MacOS, particularly large allocations within the platform limits (1.2G+) will fail and crash. This adds a specific size check for that when working around driver bugs (default). While there, added a generic size_t limited size check for the platform, and reporting OOM if too large. --- dom/canvas/WebGLContextBuffers.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dom/canvas/WebGLContextBuffers.cpp b/dom/canvas/WebGLContextBuffers.cpp index af506c01c..f53f9d7d7 100644 --- a/dom/canvas/WebGLContextBuffers.cpp +++ b/dom/canvas/WebGLContextBuffers.cpp @@ -9,6 +9,8 @@ #include "WebGLBuffer.h" #include "WebGLVertexArray.h" +#include "mozilla/CheckedInt.h" + namespace mozilla { WebGLRefPtr* @@ -345,6 +347,16 @@ WebGLContext::BufferData(GLenum target, WebGLsizeiptr size, GLenum usage) //// + const auto checkedSize = CheckedInt(size); + if (!checkedSize.isValid()) + return ErrorOutOfMemory("%s: Size too large for platform.", funcName); + +#if defined(XP_MACOSX) + if (gl->WorkAroundDriverBugs() && size > 1200000000) { + return ErrorOutOfMemory("Allocations larger than 1200000000 fail on MacOS."); + } +#endif + const UniqueBuffer zeroBuffer(calloc(size, 1)); if (!zeroBuffer) return ErrorOutOfMemory("%s: Failed to allocate zeros.", funcName); -- cgit v1.2.3 From edfba06ce39f155f9394381d4f445a0c986bac77 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 24 Oct 2019 16:14:41 +0200 Subject: Support longer (up to RFC maximum) HKDF outputs HKDF-Expand enforces a maximum output length much shorter than stated in the RFC. This patch aligns the implementation with the RFC by allocating more output space when necessary. --- security/nss/lib/softoken/pkcs11c.c | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/security/nss/lib/softoken/pkcs11c.c b/security/nss/lib/softoken/pkcs11c.c index 884702cc1..327a67d5c 100644 --- a/security/nss/lib/softoken/pkcs11c.c +++ b/security/nss/lib/softoken/pkcs11c.c @@ -7668,9 +7668,11 @@ NSC_DeriveKey(CK_SESSION_HANDLE hSession, const SECHashObject *rawHash; unsigned hashLen; CK_BYTE hashbuf[HASH_LENGTH_MAX]; - CK_BYTE *prk; /* psuedo-random key */ + CK_BYTE *prk; /* psuedo-random key */ CK_ULONG prkLen; - CK_BYTE *okm; /* output keying material */ + CK_BYTE *okm; /* output keying material */ + unsigned allocated_space = 0; /* If we need more work space, track it */ + unsigned char *key_buf = &key_block[0]; rawHash = HASH_GetRawHashObject(hashType); if (rawHash == NULL || rawHash->length > sizeof(hashbuf)) { @@ -7686,7 +7688,7 @@ NSC_DeriveKey(CK_SESSION_HANDLE hSession, crv = CKR_MECHANISM_PARAM_INVALID; break; } - if (keySize == 0 || keySize > sizeof key_block || + if (keySize == 0 || (!params->bExpand && keySize > hashLen) || (params->bExpand && keySize > 255 * hashLen)) { crv = CKR_TEMPLATE_INCONSISTENT; @@ -7736,34 +7738,49 @@ NSC_DeriveKey(CK_SESSION_HANDLE hSession, /* T(1) = HMAC-Hash(prk, "" | info | 0x01) * T(n) = HMAC-Hash(prk, T(n-1) | info | n * key material = T(1) | ... | T(n) + * + * If the requested output length does not fit + * within |key_block|, allocate space for expansion. */ HMACContext *hmac; CK_BYTE bi; - unsigned iterations = PR_ROUNDUP(keySize, hashLen) / hashLen; + unsigned n_bytes = PR_ROUNDUP(keySize, hashLen); + unsigned iterations = n_bytes / hashLen; hmac = HMAC_Create(rawHash, prk, prkLen, isFIPS); if (hmac == NULL) { crv = CKR_HOST_MEMORY; break; } - for (bi = 1; bi <= iterations; ++bi) { + if (n_bytes > sizeof(key_block)) { + key_buf = PORT_Alloc(n_bytes); + if (key_buf == NULL) { + crv = CKR_HOST_MEMORY; + break; + } + allocated_space = n_bytes; + } + for (bi = 1; bi <= iterations && bi > 0; ++bi) { unsigned len; HMAC_Begin(hmac); if (bi > 1) { - HMAC_Update(hmac, key_block + ((bi - 2) * hashLen), hashLen); + HMAC_Update(hmac, key_buf + ((bi - 2) * hashLen), hashLen); } if (params->ulInfoLen != 0) { HMAC_Update(hmac, params->pInfo, params->ulInfoLen); } HMAC_Update(hmac, &bi, 1); - HMAC_Finish(hmac, key_block + ((bi - 1) * hashLen), &len, + HMAC_Finish(hmac, key_buf + ((bi - 1) * hashLen), &len, hashLen); PORT_Assert(len == hashLen); } HMAC_Destroy(hmac, PR_TRUE); - okm = key_block; + okm = key_buf; } /* key material = prk */ crv = sftk_forceAttribute(key, CKA_VALUE, okm, keySize); + if (allocated_space) { + PORT_ZFree(key_buf, allocated_space); + } break; } /* end of CKM_NSS_HKDF_* */ -- cgit v1.2.3 From c525bb791873ecdcce59c0da4ceafc8f5f557b2f Mon Sep 17 00:00:00 2001 From: Kevin Jacobs Date: Thu, 24 Oct 2019 16:47:28 +0200 Subject: Add length checks for cryptographic primitives This rollup patch adds additional length checks around cryptographic primitives. --- security/nss/lib/freebl/chacha20poly1305.c | 5 +++++ security/nss/lib/freebl/ctr.c | 12 ++++++++++++ security/nss/lib/freebl/gcm.c | 6 ++++++ security/nss/lib/freebl/intel-gcm-wrap.c | 22 ++++++++++++++++++++++ security/nss/lib/freebl/rsapkcs.c | 20 +++++++++++--------- 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/security/nss/lib/freebl/chacha20poly1305.c b/security/nss/lib/freebl/chacha20poly1305.c index 302f0db9e..8fdaf3fec 100644 --- a/security/nss/lib/freebl/chacha20poly1305.c +++ b/security/nss/lib/freebl/chacha20poly1305.c @@ -234,6 +234,11 @@ ChaCha20Poly1305_Open(const ChaCha20Poly1305Context *ctx, unsigned char *output, PORT_SetError(SEC_ERROR_OUTPUT_LEN); return SECFailure; } + // ChaCha has a 64 octet block, with a 32-bit block counter. + if (inputLen >= (1ULL << (6 + 32)) + ctx->tagLen) { + PORT_SetError(SEC_ERROR_INPUT_LEN); + return SECFailure; + } PORT_Memset(block, 0, sizeof(block)); // Generate a block of keystream. The first 32 bytes will be the poly1305 diff --git a/security/nss/lib/freebl/ctr.c b/security/nss/lib/freebl/ctr.c index d7652c060..4d26a5b06 100644 --- a/security/nss/lib/freebl/ctr.c +++ b/security/nss/lib/freebl/ctr.c @@ -128,6 +128,12 @@ CTR_Update(CTRContext *ctr, unsigned char *outbuf, unsigned int tmp; SECStatus rv; + // Limit block count to 2^counterBits - 2 + if (ctr->counterBits < (sizeof(unsigned int) * 8) && + inlen > ((1 << ctr->counterBits) - 2) * AES_BLOCK_SIZE) { + PORT_SetError(SEC_ERROR_INPUT_LEN); + return SECFailure; + } if (maxout < inlen) { *outlen = inlen; PORT_SetError(SEC_ERROR_OUTPUT_LEN); @@ -199,6 +205,12 @@ CTR_Update_HW_AES(CTRContext *ctr, unsigned char *outbuf, unsigned int tmp; SECStatus rv; + // Limit block count to 2^counterBits - 2 + if (ctr->counterBits < (sizeof(unsigned int) * 8) && + inlen > ((1 << ctr->counterBits) - 2) * AES_BLOCK_SIZE) { + PORT_SetError(SEC_ERROR_INPUT_LEN); + return SECFailure; + } if (maxout < inlen) { *outlen = inlen; PORT_SetError(SEC_ERROR_OUTPUT_LEN); diff --git a/security/nss/lib/freebl/gcm.c b/security/nss/lib/freebl/gcm.c index f1e16da78..e93970b88 100644 --- a/security/nss/lib/freebl/gcm.c +++ b/security/nss/lib/freebl/gcm.c @@ -469,6 +469,12 @@ gcmHash_Reset(gcmHashContext *ghash, const unsigned char *AAD, { SECStatus rv; + // Limit AADLen in accordance with SP800-38D + if (sizeof(AADLen) >= 8 && AADLen > (1ULL << 61) - 1) { + PORT_SetError(SEC_ERROR_INPUT_LEN); + return SECFailure; + } + ghash->cLen = 0; PORT_Memset(ghash->counterBuf, 0, GCM_HASH_LEN_LEN * 2); ghash->bufLen = 0; diff --git a/security/nss/lib/freebl/intel-gcm-wrap.c b/security/nss/lib/freebl/intel-gcm-wrap.c index 37a1af765..f69bc7c7a 100644 --- a/security/nss/lib/freebl/intel-gcm-wrap.c +++ b/security/nss/lib/freebl/intel-gcm-wrap.c @@ -62,6 +62,12 @@ intel_AES_GCM_CreateContext(void *context, PORT_SetError(SEC_ERROR_INVALID_ARGS); return NULL; } + // Limit AADLen in accordance with SP800-38D + if (sizeof(AAD_whole_len) >= 8 && AAD_whole_len > (1ULL << 61) - 1) { + PORT_SetError(SEC_ERROR_INPUT_LEN); + return NULL; + } + gcm = PORT_ZNew(intel_AES_GCMContext); if (gcm == NULL) { return NULL; @@ -159,6 +165,14 @@ intel_AES_GCM_EncryptUpdate(intel_AES_GCMContext *gcm, unsigned char T[AES_BLOCK_SIZE]; unsigned int j; + // GCM has a 16 octet block, with a 32-bit block counter + // Limit in accordance with SP800-38D + if (sizeof(inlen) > 4 && + inlen >= ((1ULL << 32) - 2) * AES_BLOCK_SIZE) { + PORT_SetError(SEC_ERROR_INPUT_LEN); + return SECFailure; + } + tagBytes = (gcm->tagBits + (PR_BITS_PER_BYTE - 1)) / PR_BITS_PER_BYTE; if (UINT_MAX - inlen < tagBytes) { PORT_SetError(SEC_ERROR_INPUT_LEN); @@ -216,6 +230,14 @@ intel_AES_GCM_DecryptUpdate(intel_AES_GCMContext *gcm, inlen -= tagBytes; intag = inbuf + inlen; + // GCM has a 16 octet block, with a 32-bit block counter + // Limit in accordance with SP800-38D + if (sizeof(inlen) > 4 && + inlen >= ((1ULL << 32) - 2) * AES_BLOCK_SIZE) { + PORT_SetError(SEC_ERROR_INPUT_LEN); + return SECFailure; + } + if (maxout < inlen) { *outlen = inlen; PORT_SetError(SEC_ERROR_OUTPUT_LEN); diff --git a/security/nss/lib/freebl/rsapkcs.c b/security/nss/lib/freebl/rsapkcs.c index 875e4e28d..6f94770ad 100644 --- a/security/nss/lib/freebl/rsapkcs.c +++ b/security/nss/lib/freebl/rsapkcs.c @@ -115,7 +115,7 @@ rsa_FormatOneBlock(unsigned modulusLen, { unsigned char *block; unsigned char *bp; - int padLen; + unsigned int padLen; int i, j; SECStatus rv; @@ -135,14 +135,14 @@ rsa_FormatOneBlock(unsigned modulusLen, switch (blockType) { /* - * Blocks intended for private-key operation. - */ + * Blocks intended for private-key operation. + */ case RSA_BlockPrivate: /* preferred method */ /* - * 0x00 || BT || Pad || 0x00 || ActualData - * 1 1 padLen 1 data->len - * Pad is either all 0x00 or all 0xff bytes, depending on blockType. - */ + * 0x00 || BT || Pad || 0x00 || ActualData + * 1 1 padLen 1 data->len + * Pad is either all 0x00 or all 0xff bytes, depending on blockType. + */ padLen = modulusLen - data->len - 3; PORT_Assert(padLen >= RSA_BLOCK_MIN_PAD_LEN); if (padLen < RSA_BLOCK_MIN_PAD_LEN) { @@ -162,7 +162,7 @@ rsa_FormatOneBlock(unsigned modulusLen, /* * 0x00 || BT || Pad || 0x00 || ActualData * 1 1 padLen 1 data->len - * Pad is all non-zero random bytes. + * Pad is 8 or more non-zero random bytes. * * Build the block left to right. * Fill the entire block from Pad to the end with random bytes. @@ -236,7 +236,9 @@ rsa_FormatBlock(SECItem *result, * The "3" below is the first octet + the second octet + the 0x00 * octet that always comes just before the ActualData. */ - PORT_Assert(data->len <= (modulusLen - (3 + RSA_BLOCK_MIN_PAD_LEN))); + if (data->len > (modulusLen - (3 + RSA_BLOCK_MIN_PAD_LEN))) { + return SECFailure; + } result->data = rsa_FormatOneBlock(modulusLen, blockType, data); if (result->data == NULL) { -- cgit v1.2.3 From 29317adcbc182f769074c39a7c1191529a356b24 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 24 Oct 2019 16:52:46 +0200 Subject: Update NSS version --- security/nss/coreconf/coreconf.dep | 1 + security/nss/lib/nss/nss.h | 4 ++-- security/nss/lib/softoken/softkver.h | 4 ++-- security/nss/lib/util/nssutil.h | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/security/nss/coreconf/coreconf.dep b/security/nss/coreconf/coreconf.dep index 5182f7555..590d1bfae 100644 --- a/security/nss/coreconf/coreconf.dep +++ b/security/nss/coreconf/coreconf.dep @@ -10,3 +10,4 @@ */ #error "Do not include this header file." + diff --git a/security/nss/lib/nss/nss.h b/security/nss/lib/nss/nss.h index ea54ce0cd..f6b83a01c 100644 --- a/security/nss/lib/nss/nss.h +++ b/security/nss/lib/nss/nss.h @@ -22,10 +22,10 @@ * The format of the version string should be * ".[.[.]][ ][ ]" */ -#define NSS_VERSION "3.41.2" _NSS_CUSTOMIZED +#define NSS_VERSION "3.41.3" _NSS_CUSTOMIZED #define NSS_VMAJOR 3 #define NSS_VMINOR 41 -#define NSS_VPATCH 2 +#define NSS_VPATCH 3 #define NSS_VBUILD 0 #define NSS_BETA PR_FALSE diff --git a/security/nss/lib/softoken/softkver.h b/security/nss/lib/softoken/softkver.h index 73a38b010..ab2e91018 100644 --- a/security/nss/lib/softoken/softkver.h +++ b/security/nss/lib/softoken/softkver.h @@ -17,10 +17,10 @@ * The format of the version string should be * ".[.[.]][ ][ ]" */ -#define SOFTOKEN_VERSION "3.41.2" SOFTOKEN_ECC_STRING +#define SOFTOKEN_VERSION "3.41.3" SOFTOKEN_ECC_STRING #define SOFTOKEN_VMAJOR 3 #define SOFTOKEN_VMINOR 41 -#define SOFTOKEN_VPATCH 2 +#define SOFTOKEN_VPATCH 3 #define SOFTOKEN_VBUILD 0 #define SOFTOKEN_BETA PR_FALSE diff --git a/security/nss/lib/util/nssutil.h b/security/nss/lib/util/nssutil.h index a2be260b0..f880fb55e 100644 --- a/security/nss/lib/util/nssutil.h +++ b/security/nss/lib/util/nssutil.h @@ -19,10 +19,10 @@ * The format of the version string should be * ".[.[.]][ ]" */ -#define NSSUTIL_VERSION "3.41.2" +#define NSSUTIL_VERSION "3.41.3" #define NSSUTIL_VMAJOR 3 #define NSSUTIL_VMINOR 41 -#define NSSUTIL_VPATCH 2 +#define NSSUTIL_VPATCH 3 #define NSSUTIL_VBUILD 0 #define NSSUTIL_BETA PR_FALSE -- cgit v1.2.3 From fbf84d5155f7d47df85e1d389955a9529a04158f Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 24 Oct 2019 17:58:40 +0200 Subject: Force clobber. --- CLOBBER | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLOBBER b/CLOBBER index ec7253408..e67882096 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Clobber for SpiderMonkey Update +Clobber for NSS Update -- cgit v1.2.3 From c59c9682d351e641f184e1f8095f399fee179f62 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sat, 26 Oct 2019 13:08:45 +0200 Subject: Issue #1257 - Part1: Remove watch class-hook and proxy trap. --- dom/base/nsGlobalWindow.cpp | 19 ------------------- dom/bindings/BindingUtils.cpp | 2 -- dom/bindings/Codegen.py | 6 +++--- dom/bindings/DOMJSProxyHandler.cpp | 13 ------------- dom/bindings/DOMJSProxyHandler.h | 5 ----- dom/plugins/base/nsJSNPRuntime.cpp | 1 - js/public/Class.h | 14 ++------------ js/public/Proxy.h | 6 ------ js/src/builtin/TypedObject.cpp | 1 - js/src/jsobj.cpp | 6 ------ js/src/jsobj.h | 2 -- js/src/jswrapper.h | 7 ------- js/src/proxy/BaseProxyHandler.cpp | 14 -------------- js/src/proxy/Proxy.cpp | 27 --------------------------- js/src/proxy/Proxy.h | 3 --- js/src/proxy/SecurityWrapper.cpp | 19 ------------------- js/src/vm/EnvironmentObject.cpp | 3 --- js/xpconnect/src/XPCWrappedNativeJSOps.cpp | 2 -- 18 files changed, 5 insertions(+), 145 deletions(-) diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index ec546f068..1288b3435 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1026,11 +1026,6 @@ public: return false; } - virtual bool watch(JSContext *cx, JS::Handle proxy, - JS::Handle id, JS::Handle callable) const override; - virtual bool unwatch(JSContext *cx, JS::Handle proxy, - JS::Handle id) const override; - static void ObjectMoved(JSObject *obj, const JSObject *old); static const nsOuterWindowProxy singleton; @@ -1398,20 +1393,6 @@ nsOuterWindowProxy::AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy, return true; } -bool -nsOuterWindowProxy::watch(JSContext *cx, JS::Handle proxy, - JS::Handle id, JS::Handle callable) const -{ - return js::WatchGuts(cx, proxy, id, callable); -} - -bool -nsOuterWindowProxy::unwatch(JSContext *cx, JS::Handle proxy, - JS::Handle id) const -{ - return js::UnwatchGuts(cx, proxy, id); -} - void nsOuterWindowProxy::ObjectMoved(JSObject *obj, const JSObject *old) { diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index a26fc4422..6e0db29b1 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -1968,8 +1968,6 @@ const js::ObjectOps sInterfaceObjectClassObjectOps = { nullptr, /* setProperty */ nullptr, /* getOwnPropertyDescriptor */ nullptr, /* deleteProperty */ - nullptr, /* watch */ - nullptr, /* unwatch */ nullptr, /* getElements */ nullptr, /* enumerate */ InterfaceObjectToString, /* funToString */ diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index d7d700a96..924241aa0 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -13169,9 +13169,9 @@ class CGDictionary(CGThing): # continues to match the list in test_Object.prototype_props.html if (member.identifier.name in ["constructor", "toSource", "toString", "toLocaleString", "valueOf", - "watch", "unwatch", "hasOwnProperty", "isPrototypeOf", - "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", - "__lookupGetter__", "__lookupSetter__", "__proto__"]): + "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", + "__defineGetter__", "__defineSetter__", "__lookupGetter__", + "__lookupSetter__", "__proto__"]): raise TypeError("'%s' member of %s dictionary shadows " "a property of Object.prototype, and Xrays to " "Object can't handle that.\n" diff --git a/dom/bindings/DOMJSProxyHandler.cpp b/dom/bindings/DOMJSProxyHandler.cpp index 23f0abd88..49281c1c2 100644 --- a/dom/bindings/DOMJSProxyHandler.cpp +++ b/dom/bindings/DOMJSProxyHandler.cpp @@ -274,19 +274,6 @@ DOMProxyHandler::delete_(JSContext* cx, JS::Handle proxy, return result.succeed(); } -bool -BaseDOMProxyHandler::watch(JSContext* cx, JS::Handle proxy, JS::Handle id, - JS::Handle callable) const -{ - return js::WatchGuts(cx, proxy, id, callable); -} - -bool -BaseDOMProxyHandler::unwatch(JSContext* cx, JS::Handle proxy, JS::Handle id) const -{ - return js::UnwatchGuts(cx, proxy, id); -} - bool BaseDOMProxyHandler::ownPropertyKeys(JSContext* cx, JS::Handle proxy, diff --git a/dom/bindings/DOMJSProxyHandler.h b/dom/bindings/DOMJSProxyHandler.h index 1781649cc..e3e151b7a 100644 --- a/dom/bindings/DOMJSProxyHandler.h +++ b/dom/bindings/DOMJSProxyHandler.h @@ -72,11 +72,6 @@ public: virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle proxy, JS::AutoIdVector &props) const override; - bool watch(JSContext* cx, JS::Handle proxy, JS::Handle id, - JS::Handle callable) const override; - bool unwatch(JSContext* cx, JS::Handle proxy, - JS::Handle id) const override; - protected: // Hook for subclasses to implement shared ownPropertyKeys()/keys() // functionality. The "flags" argument is either JSITER_OWNONLY (for keys()) diff --git a/dom/plugins/base/nsJSNPRuntime.cpp b/dom/plugins/base/nsJSNPRuntime.cpp index 05e0ec4ba..1d42c18d6 100644 --- a/dom/plugins/base/nsJSNPRuntime.cpp +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -248,7 +248,6 @@ const static js::ObjectOps sNPObjectJSWrapperObjectOps = { nullptr, // setProperty nullptr, // getOwnPropertyDescriptor nullptr, // deleteProperty - nullptr, nullptr, // watch/unwatch nullptr, // getElements NPObjWrapper_Enumerate, nullptr, diff --git a/js/public/Class.h b/js/public/Class.h index 3b5023875..f7533654b 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -425,12 +425,6 @@ typedef bool (* DeletePropertyOp)(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::ObjectOpResult& result); -typedef bool -(* WatchOp)(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable); - -typedef bool -(* UnwatchOp)(JSContext* cx, JS::HandleObject obj, JS::HandleId id); - class JS_FRIEND_API(ElementAdder) { public: @@ -670,8 +664,6 @@ struct ObjectOps SetPropertyOp setProperty; GetOwnPropertyOp getOwnPropertyDescriptor; DeletePropertyOp deleteProperty; - WatchOp watch; - UnwatchOp unwatch; GetElementsOp getElements; JSNewEnumerateOp enumerate; JSFunToStringOp funToString; @@ -822,8 +814,8 @@ struct Class * Objects of this class aren't native objects. They don't have Shapes that * describe their properties and layout. Classes using this flag must * provide their own property behavior, either by being proxy classes (do - * this) or by overriding all the ObjectOps except getElements, watch and - * unwatch (don't do this). + * this) or by overriding all the ObjectOps except getElements + * (don't do this). */ static const uint32_t NON_NATIVE = JSCLASS_INTERNAL_FLAG2; @@ -900,8 +892,6 @@ struct Class const { return oOps ? oOps->getOwnPropertyDescriptor : nullptr; } DeletePropertyOp getOpsDeleteProperty() const { return oOps ? oOps->deleteProperty : nullptr; } - WatchOp getOpsWatch() const { return oOps ? oOps->watch : nullptr; } - UnwatchOp getOpsUnwatch() const { return oOps ? oOps->unwatch : nullptr; } GetElementsOp getOpsGetElements() const { return oOps ? oOps->getElements : nullptr; } JSNewEnumerateOp getOpsEnumerate() const { return oOps ? oOps->enumerate : nullptr; } JSFunToStringOp getOpsFunToString() const { return oOps ? oOps->funToString : nullptr; } diff --git a/js/public/Proxy.h b/js/public/Proxy.h index 5acb91b26..f40772fb0 100644 --- a/js/public/Proxy.h +++ b/js/public/Proxy.h @@ -341,12 +341,6 @@ class JS_FRIEND_API(BaseProxyHandler) virtual bool isCallable(JSObject* obj) const; virtual bool isConstructor(JSObject* obj) const; - // These two hooks must be overridden, or not overridden, in tandem -- no - // overriding just one! - virtual bool watch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, - JS::HandleObject callable) const; - virtual bool unwatch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id) const; - virtual bool getElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end, ElementAdder* adder) const; diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index ff3680774..50bf0b836 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -2215,7 +2215,6 @@ const ObjectOps TypedObject::objectOps_ = { TypedObject::obj_setProperty, TypedObject::obj_getOwnPropertyDescriptor, TypedObject::obj_deleteProperty, - nullptr, nullptr, /* watch/unwatch */ nullptr, /* getElements */ TypedObject::obj_enumerate, nullptr, /* thisValue */ diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 6f9596924..ee66addc9 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -2836,9 +2836,6 @@ js::UnwatchGuts(JSContext* cx, JS::HandleObject origObj, JS::HandleId id) bool js::WatchProperty(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable) { - if (WatchOp op = obj->getOpsWatch()) - return op(cx, obj, id, callable); - if (!obj->isNative() || obj->is()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_WATCH, obj->getClass()->name); @@ -2851,9 +2848,6 @@ js::WatchProperty(JSContext* cx, HandleObject obj, HandleId id, HandleObject cal bool js::UnwatchProperty(JSContext* cx, HandleObject obj, HandleId id) { - if (UnwatchOp op = obj->getOpsUnwatch()) - return op(cx, obj, id); - return UnwatchGuts(cx, obj, id); } diff --git a/js/src/jsobj.h b/js/src/jsobj.h index db2c22b76..8a41d155e 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -141,8 +141,6 @@ class JSObject : public js::gc::Cell js::GetOwnPropertyOp getOpsGetOwnPropertyDescriptor() const { return getClass()->getOpsGetOwnPropertyDescriptor(); } js::DeletePropertyOp getOpsDeleteProperty() const { return getClass()->getOpsDeleteProperty(); } - js::WatchOp getOpsWatch() const { return getClass()->getOpsWatch(); } - js::UnwatchOp getOpsUnwatch() const { return getClass()->getOpsUnwatch(); } js::GetElementsOp getOpsGetElements() const { return getClass()->getOpsGetElements(); } JSNewEnumerateOp getOpsEnumerate() const { return getClass()->getOpsEnumerate(); } JSFunToStringOp getOpsFunToString() const { return getClass()->getOpsFunToString(); } diff --git a/js/src/jswrapper.h b/js/src/jswrapper.h index 5f3704e32..32336c68b 100644 --- a/js/src/jswrapper.h +++ b/js/src/jswrapper.h @@ -315,13 +315,6 @@ class JS_FRIEND_API(SecurityWrapper) : public Base virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override; - // Allow isCallable and isConstructor. They used to be class-level, and so could not be guarded - // against. - - virtual bool watch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, - JS::HandleObject callable) const override; - virtual bool unwatch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id) const override; - /* * Allow our subclasses to select the superclass behavior they want without * needing to specify an exact superclass. diff --git a/js/src/proxy/BaseProxyHandler.cpp b/js/src/proxy/BaseProxyHandler.cpp index 8d5f30a19..423aa8671 100644 --- a/js/src/proxy/BaseProxyHandler.cpp +++ b/js/src/proxy/BaseProxyHandler.cpp @@ -418,20 +418,6 @@ BaseProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* return true; } -bool -BaseProxyHandler::watch(JSContext* cx, HandleObject proxy, HandleId id, HandleObject callable) const -{ - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_WATCH, - proxy->getClass()->name); - return false; -} - -bool -BaseProxyHandler::unwatch(JSContext* cx, HandleObject proxy, HandleId id) const -{ - return true; -} - bool BaseProxyHandler::getElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end, ElementAdder* adder) const diff --git a/js/src/proxy/Proxy.cpp b/js/src/proxy/Proxy.cpp index 2c1cffb77..6b3a3d1e9 100644 --- a/js/src/proxy/Proxy.cpp +++ b/js/src/proxy/Proxy.cpp @@ -504,20 +504,6 @@ Proxy::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp JSObject * const TaggedProto::LazyProto = reinterpret_cast(0x1); -/* static */ bool -Proxy::watch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleObject callable) -{ - JS_CHECK_RECURSION(cx, return false); - return proxy->as().handler()->watch(cx, proxy, id, callable); -} - -/* static */ bool -Proxy::unwatch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id) -{ - JS_CHECK_RECURSION(cx, return false); - return proxy->as().handler()->unwatch(cx, proxy, id); -} - /* static */ bool Proxy::getElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end, ElementAdder* adder) @@ -698,18 +684,6 @@ js::proxy_Construct(JSContext* cx, unsigned argc, Value* vp) return Proxy::construct(cx, proxy, args); } -bool -js::proxy_Watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable) -{ - return Proxy::watch(cx, obj, id, callable); -} - -bool -js::proxy_Unwatch(JSContext* cx, HandleObject obj, HandleId id) -{ - return Proxy::unwatch(cx, obj, id); -} - bool js::proxy_GetElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end, ElementAdder* adder) @@ -750,7 +724,6 @@ const ObjectOps js::ProxyObjectOps = { js::proxy_SetProperty, js::proxy_GetOwnPropertyDescriptor, js::proxy_DeleteProperty, - js::proxy_Watch, js::proxy_Unwatch, js::proxy_GetElements, nullptr, /* enumerate */ js::proxy_FunToString diff --git a/js/src/proxy/Proxy.h b/js/src/proxy/Proxy.h index 89909a085..4a8ecf8ab 100644 --- a/js/src/proxy/Proxy.h +++ b/js/src/proxy/Proxy.h @@ -65,9 +65,6 @@ class Proxy static bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g); static bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp); - static bool watch(JSContext* cx, HandleObject proxy, HandleId id, HandleObject callable); - static bool unwatch(JSContext* cx, HandleObject proxy, HandleId id); - static bool getElements(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t end, ElementAdder* adder); diff --git a/js/src/proxy/SecurityWrapper.cpp b/js/src/proxy/SecurityWrapper.cpp index 710faf9b0..71d22fca6 100644 --- a/js/src/proxy/SecurityWrapper.cpp +++ b/js/src/proxy/SecurityWrapper.cpp @@ -130,24 +130,5 @@ SecurityWrapper::defineProperty(JSContext* cx, HandleObject wrapper, Handl return Base::defineProperty(cx, wrapper, id, desc, result); } -template -bool -SecurityWrapper::watch(JSContext* cx, HandleObject proxy, - HandleId id, HandleObject callable) const -{ - ReportUnwrapDenied(cx); - return false; -} - -template -bool -SecurityWrapper::unwatch(JSContext* cx, HandleObject proxy, - HandleId id) const -{ - ReportUnwrapDenied(cx); - return false; -} - - template class js::SecurityWrapper; template class js::SecurityWrapper; diff --git a/js/src/vm/EnvironmentObject.cpp b/js/src/vm/EnvironmentObject.cpp index a5aac2ab4..3680c5b7b 100644 --- a/js/src/vm/EnvironmentObject.cpp +++ b/js/src/vm/EnvironmentObject.cpp @@ -408,7 +408,6 @@ const ObjectOps ModuleEnvironmentObject::objectOps_ = { ModuleEnvironmentObject::setProperty, ModuleEnvironmentObject::getOwnPropertyDescriptor, ModuleEnvironmentObject::deleteProperty, - nullptr, nullptr, /* watch/unwatch */ nullptr, /* getElements */ ModuleEnvironmentObject::enumerate, nullptr @@ -790,7 +789,6 @@ static const ObjectOps WithEnvironmentObjectOps = { with_SetProperty, with_GetOwnPropertyDescriptor, with_DeleteProperty, - nullptr, nullptr, /* watch/unwatch */ nullptr, /* getElements */ nullptr, /* enumerate (native enumeration of target doesn't work) */ nullptr, @@ -1159,7 +1157,6 @@ static const ObjectOps RuntimeLexicalErrorObjectObjectOps = { lexicalError_SetProperty, lexicalError_GetOwnPropertyDescriptor, lexicalError_DeleteProperty, - nullptr, nullptr, /* watch/unwatch */ nullptr, /* getElements */ nullptr, /* enumerate (native enumeration of target doesn't work) */ nullptr, /* this */ diff --git a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp index 12b203b70..08ba8241a 100644 --- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp +++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp @@ -924,8 +924,6 @@ const js::ObjectOps XPC_WN_ObjectOpsWithEnumerate = { nullptr, // setProperty nullptr, // getOwnPropertyDescriptor nullptr, // deleteProperty - nullptr, // watch - nullptr, // unwatch nullptr, // getElements XPC_WN_JSOp_Enumerate, nullptr, // funToString -- cgit v1.2.3 From 44a077980abb92dcea9ed374fd9719eaaf2f1458 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 27 Oct 2019 02:51:11 +0200 Subject: Issue #1257 - Part 2: Remove watch/unwatch and JS watchpoint class. --- js/src/builtin/Object.cpp | 95 ----------------- js/src/gc/Marking.cpp | 7 +- js/src/gc/RootMarking.cpp | 1 - js/src/jit/BaselineIC.cpp | 6 -- js/src/jit/IonCaches.cpp | 5 +- js/src/js.msg | 2 - js/src/jsapi.cpp | 1 - js/src/jscntxt.cpp | 1 - js/src/jscompartment.cpp | 12 --- js/src/jscompartment.h | 3 - js/src/jsfriendapi.cpp | 2 - js/src/jsfriendapi.h | 24 ----- js/src/jsgc.cpp | 14 --- js/src/jsobj.cpp | 66 +----------- js/src/jsobj.h | 20 ---- js/src/jsobjinlines.h | 6 -- js/src/jsversion.h | 1 - js/src/jswatchpoint.cpp | 246 ------------------------------------------- js/src/jswatchpoint.h | 90 ---------------- js/src/moz.build | 1 - js/src/vm/NativeObject-inl.h | 8 +- js/src/vm/NativeObject.cpp | 14 +-- js/src/vm/Runtime.cpp | 1 - js/src/vm/Shape.h | 2 +- js/src/vm/TypeInference.cpp | 114 ++++++++++---------- 25 files changed, 65 insertions(+), 677 deletions(-) delete mode 100644 js/src/jswatchpoint.cpp delete mode 100644 js/src/jswatchpoint.h diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index 56c77f304..bfcc8d20e 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -568,97 +568,6 @@ obj_setPrototypeOf(JSContext* cx, unsigned argc, Value* vp) return true; } -#if JS_HAS_OBJ_WATCHPOINT - -bool -js::WatchHandler(JSContext* cx, JSObject* obj_, jsid id_, const JS::Value& old, - JS::Value* nvp, void* closure) -{ - RootedObject obj(cx, obj_); - RootedId id(cx, id_); - - /* Avoid recursion on (obj, id) already being watched on cx. */ - AutoResolving resolving(cx, obj, id, AutoResolving::WATCH); - if (resolving.alreadyStarted()) - return true; - - FixedInvokeArgs<3> args(cx); - - args[0].set(IdToValue(id)); - args[1].set(old); - args[2].set(*nvp); - - RootedValue callable(cx, ObjectValue(*static_cast(closure))); - RootedValue thisv(cx, ObjectValue(*obj)); - RootedValue rv(cx); - if (!Call(cx, callable, thisv, args, &rv)) - return false; - - *nvp = rv; - return true; -} - -static bool -obj_watch(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - RootedObject obj(cx, ToObject(cx, args.thisv())); - if (!obj) - return false; - - if (!GlobalObject::warnOnceAboutWatch(cx, obj)) - return false; - - if (args.length() <= 1) { - ReportMissingArg(cx, args.calleev(), 1); - return false; - } - - RootedObject callable(cx, ValueToCallable(cx, args[1], args.length() - 2)); - if (!callable) - return false; - - RootedId propid(cx); - if (!ValueToId(cx, args[0], &propid)) - return false; - - if (!WatchProperty(cx, obj, propid, callable)) - return false; - - args.rval().setUndefined(); - return true; -} - -static bool -obj_unwatch(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - RootedObject obj(cx, ToObject(cx, args.thisv())); - if (!obj) - return false; - - if (!GlobalObject::warnOnceAboutWatch(cx, obj)) - return false; - - RootedId id(cx); - if (args.length() != 0) { - if (!ValueToId(cx, args[0], &id)) - return false; - } else { - id = JSID_VOID; - } - - if (!UnwatchProperty(cx, obj, id)) - return false; - - args.rval().setUndefined(); - return true; -} - -#endif /* JS_HAS_OBJ_WATCHPOINT */ - /* ECMA 15.2.4.5. */ bool js::obj_hasOwnProperty(JSContext* cx, unsigned argc, Value* vp) @@ -1290,10 +1199,6 @@ static const JSFunctionSpec object_methods[] = { JS_FN(js_toString_str, obj_toString, 0,0), JS_SELF_HOSTED_FN(js_toLocaleString_str, "Object_toLocaleString", 0, 0), JS_SELF_HOSTED_FN(js_valueOf_str, "Object_valueOf", 0,0), -#if JS_HAS_OBJ_WATCHPOINT - JS_FN(js_watch_str, obj_watch, 2,0), - JS_FN(js_unwatch_str, obj_unwatch, 1,0), -#endif JS_FN(js_hasOwnProperty_str, obj_hasOwnProperty, 1,0), JS_FN(js_isPrototypeOf_str, obj_isPrototypeOf, 1,0), JS_FN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1,0), diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index da3ef7d0d..262fc8cbc 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -2846,10 +2846,9 @@ struct UnmarkGrayTracer : public JS::CallbackTracer * * There is an additional complication for certain kinds of edges that are not * contained explicitly in the source object itself, such as from a weakmap key - * to its value, and from an object being watched by a watchpoint to the - * watchpoint's closure. These "implicit edges" are represented in some other - * container object, such as the weakmap or the watchpoint itself. In these - * cases, calling unmark gray on an object won't find all of its children. + * to its value. These "implicit edges" are represented in some other + * container object, such as the weakmap itself. In these cases, calling unmark + * gray on an object won't find all of its children. * * Handling these implicit edges has two parts: * - A special pass enumerating all of the containers that know about the diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp index f5969bc1f..7d665e8eb 100644 --- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -14,7 +14,6 @@ #include "jsgc.h" #include "jsprf.h" #include "jstypes.h" -#include "jswatchpoint.h" #include "builtin/MapObject.h" #include "frontend/BytecodeCompiler.h" diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index a001357f8..9530f65fa 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -4053,9 +4053,6 @@ TryAttachSetValuePropStub(JSContext* cx, HandleScript script, jsbytecode* pc, IC { MOZ_ASSERT(!*attached); - if (obj->watched()) - return true; - RootedShape shape(cx); RootedObject holder(cx); if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape)) @@ -4151,9 +4148,6 @@ TryAttachSetAccessorPropStub(JSContext* cx, HandleScript script, jsbytecode* pc, MOZ_ASSERT(!*attached); MOZ_ASSERT(!*isTemporarilyUnoptimizable); - if (obj->watched()) - return true; - RootedShape shape(cx); RootedObject holder(cx); if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape)) diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index 48e0792bb..fb4291188 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -3232,7 +3232,7 @@ SetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* MOZ_ASSERT(!*emitted); MOZ_ASSERT(!*tryNativeAddSlot); - if (!canAttachStub() || obj->watched()) + if (!canAttachStub()) return true; // Fail cache emission if the object is frozen @@ -3897,9 +3897,6 @@ IsDenseElementSetInlineable(JSObject* obj, const Value& idval, const ConstantOrR if (!obj->is()) return false; - if (obj->watched()) - return false; - if (!idval.isInt32()) return false; diff --git a/js/src/js.msg b/js/src/js.msg index f57b36a90..1612c831d 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -47,7 +47,6 @@ MSG_DEF(JSMSG_MORE_ARGS_NEEDED, 3, JSEXN_TYPEERR, "{0} requires more than MSG_DEF(JSMSG_INCOMPATIBLE_PROTO, 3, JSEXN_TYPEERR, "{0}.prototype.{1} called on incompatible {2}") MSG_DEF(JSMSG_NO_CONSTRUCTOR, 1, JSEXN_TYPEERR, "{0} has no constructor") MSG_DEF(JSMSG_BAD_SORT_ARG, 0, JSEXN_TYPEERR, "invalid Array.prototype.sort argument") -MSG_DEF(JSMSG_CANT_WATCH, 1, JSEXN_TYPEERR, "can't watch non-native objects of class {0}") MSG_DEF(JSMSG_READ_ONLY, 1, JSEXN_TYPEERR, "{0} is read-only") MSG_DEF(JSMSG_CANT_DELETE, 1, JSEXN_TYPEERR, "property {0} is non-configurable and can't be deleted") MSG_DEF(JSMSG_CANT_TRUNCATE_ARRAY, 0, JSEXN_TYPEERR, "can't delete non-configurable array element") @@ -72,7 +71,6 @@ MSG_DEF(JSMSG_UNDEFINED_PROP, 1, JSEXN_REFERENCEERR, "reference to unde MSG_DEF(JSMSG_INVALID_MAP_ITERABLE, 1, JSEXN_TYPEERR, "iterable for {0} should have array-like objects") MSG_DEF(JSMSG_NESTING_GENERATOR, 0, JSEXN_TYPEERR, "already executing generator") MSG_DEF(JSMSG_INCOMPATIBLE_METHOD, 3, JSEXN_TYPEERR, "{0} {1} called on incompatible {2}") -MSG_DEF(JSMSG_OBJECT_WATCH_DEPRECATED, 0, JSEXN_WARN, "Object.prototype.watch and unwatch are very slow, non-standard, and deprecated; use a getter/setter instead") MSG_DEF(JSMSG_ARRAYBUFFER_SLICE_DEPRECATED, 0, JSEXN_WARN, "ArrayBuffer.slice is deprecated; use ArrayBuffer.prototype.slice instead") MSG_DEF(JSMSG_BAD_SURROGATE_CHAR, 1, JSEXN_TYPEERR, "bad surrogate character {0}") MSG_DEF(JSMSG_UTF8_CHAR_TOO_LARGE, 1, JSEXN_TYPEERR, "UTF-8 character {0} too large") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index cad0840e0..f9a0c6a6b 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -39,7 +39,6 @@ #include "jsstr.h" #include "jstypes.h" #include "jsutil.h" -#include "jswatchpoint.h" #include "jsweakmap.h" #include "jswrapper.h" diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index e505a4b34..23b9d27ae 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -37,7 +37,6 @@ #include "jsscript.h" #include "jsstr.h" #include "jstypes.h" -#include "jswatchpoint.h" #include "gc/Marking.h" #include "jit/Ion.h" diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index d0caeb558..8ba186b08 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -13,7 +13,6 @@ #include "jsfriendapi.h" #include "jsgc.h" #include "jsiter.h" -#include "jswatchpoint.h" #include "jswrapper.h" #include "gc/Marking.h" @@ -76,7 +75,6 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options = gcIncomingGrayPointers(nullptr), debugModeBits(0), randomKeyGenerator_(runtime_->forkRandomKeyGenerator()), - watchpointMap(nullptr), scriptCountsMap(nullptr), debugScriptMap(nullptr), debugEnvs(nullptr), @@ -103,7 +101,6 @@ JSCompartment::~JSCompartment() rt->lcovOutput.writeLCovResult(lcovOutput); js_delete(jitCompartment_); - js_delete(watchpointMap); js_delete(scriptCountsMap); js_delete(debugScriptMap); js_delete(debugEnvs); @@ -662,12 +659,6 @@ JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime t if (traceOrMark == js::gc::GCRuntime::MarkRuntime && !zone()->isCollecting()) return; - // During a GC, these are treated as weak pointers. - if (traceOrMark == js::gc::GCRuntime::TraceRuntime) { - if (watchpointMap) - watchpointMap->markAll(trc); - } - /* Mark debug scopes, if present */ if (debugEnvs) debugEnvs->mark(trc); @@ -712,9 +703,6 @@ JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime t void JSCompartment::finishRoots() { - if (watchpointMap) - watchpointMap->clear(); - if (debugEnvs) debugEnvs->finish(); diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 83c15da3b..05ff40b43 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -282,7 +282,6 @@ class MOZ_RAII AutoSetNewObjectMetadata : private JS::CustomAutoRooter namespace js { class DebugEnvironments; class ObjectWeakMap; -class WatchpointMap; class WeakMapBase; } // namespace js @@ -811,8 +810,6 @@ struct JSCompartment void sweepBreakpoints(js::FreeOp* fop); public: - js::WatchpointMap* watchpointMap; - js::ScriptCountsMap* scriptCountsMap; js::DebugScriptMap* debugScriptMap; diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index f7622cb44..bdb3c0a4d 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -15,7 +15,6 @@ #include "jsgc.h" #include "jsobj.h" #include "jsprf.h" -#include "jswatchpoint.h" #include "jsweakmap.h" #include "jswrapper.h" @@ -579,7 +578,6 @@ void js::TraceWeakMaps(WeakMapTracer* trc) { WeakMapBase::traceAllMappings(trc); - WatchpointMap::traceAll(trc); } extern JS_FRIEND_API(bool) diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 351667fb3..491215456 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -2110,30 +2110,6 @@ JS_FRIEND_API(void*) JS_GetDataViewData(JSObject* obj, const JS::AutoCheckCannotGC&); namespace js { - -/** - * Add a watchpoint -- in the Object.prototype.watch sense -- to |obj| for the - * property |id|, using the callable object |callable| as the function to be - * called for notifications. - * - * This is an internal function exposed -- temporarily -- only so that DOM - * proxies can be watchable. Don't use it! We'll soon kill off the - * Object.prototype.{,un}watch functions, at which point this will go too. - */ -extern JS_FRIEND_API(bool) -WatchGuts(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable); - -/** - * Remove a watchpoint -- in the Object.prototype.watch sense -- from |obj| for - * the property |id|. - * - * This is an internal function exposed -- temporarily -- only so that DOM - * proxies can be watchable. Don't use it! We'll soon kill off the - * Object.prototype.{,un}watch functions, at which point this will go too. - */ -extern JS_FRIEND_API(bool) -UnwatchGuts(JSContext* cx, JS::HandleObject obj, JS::HandleId id); - namespace jit { enum class InlinableNative : uint16_t; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index b2ee8d67b..5a9d732b6 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -207,7 +207,6 @@ #include "jsscript.h" #include "jstypes.h" #include "jsutil.h" -#include "jswatchpoint.h" #include "jsweakmap.h" #ifdef XP_WIN # include "jswin.h" @@ -2392,11 +2391,6 @@ GCRuntime::updatePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess Debugger::markIncomingCrossCompartmentEdges(&trc); WeakMapBase::markAll(zone, &trc); - for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) { - c->trace(&trc); - if (c->watchpointMap) - c->watchpointMap->markAll(&trc); - } // Mark all gray roots, making sure we call the trace callback to get the // current set. @@ -2405,7 +2399,6 @@ GCRuntime::updatePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess } // Sweep everything to fix up weak pointers - WatchpointMap::sweepAll(rt); Debugger::sweepAll(rt->defaultFreeOp()); jit::JitRuntime::SweepJitcodeGlobalTable(rt); rt->gc.sweepZoneAfterCompacting(zone); @@ -3850,10 +3843,6 @@ GCRuntime::markWeakReferences(gcstats::Phase phase) for (ZoneIterT zone(rt); !zone.done(); zone.next()) markedAny |= WeakMapBase::markZoneIteratively(zone, &marker); } - for (CompartmentsIterT c(rt); !c.done(); c.next()) { - if (c->watchpointMap) - markedAny |= c->watchpointMap->markIteratively(&marker); - } markedAny |= Debugger::markAllIteratively(&marker); markedAny |= jit::JitRuntime::MarkJitcodeGlobalTableIteratively(&marker); @@ -4625,9 +4614,6 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock) // Bug 1071218: the following two methods have not yet been // refactored to work on a single zone-group at once. - // Collect watch points associated with unreachable objects. - WatchpointMap::sweepAll(rt); - // Detach unreachable debuggers and global objects from each other. Debugger::sweepAll(&fop); diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index ee66addc9..ef1291079 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -33,7 +33,6 @@ #include "jsstr.h" #include "jstypes.h" #include "jsutil.h" -#include "jswatchpoint.h" #include "jswin.h" #include "jswrapper.h" @@ -1011,13 +1010,7 @@ js::CreateThisForFunction(JSContext* cx, HandleObject callee, HandleObject newTa JSObject::nonNativeSetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) { - RootedValue value(cx, v); - if (MOZ_UNLIKELY(obj->watched())) { - WatchpointMap* wpmap = cx->compartment()->watchpointMap; - if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, &value)) - return false; - } - return obj->getOpsSetProperty()(cx, obj, id, value, receiver, result); + return obj->getOpsSetProperty()(cx, obj, id, v, receiver, result); } /* static */ bool @@ -2795,62 +2788,6 @@ js::GetPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, return true; } -bool -js::WatchGuts(JSContext* cx, JS::HandleObject origObj, JS::HandleId id, JS::HandleObject callable) -{ - RootedObject obj(cx, ToWindowIfWindowProxy(origObj)); - if (obj->isNative()) { - // Use sparse indexes for watched objects, as dense elements can be - // written to without checking the watchpoint map. - if (!NativeObject::sparsifyDenseElements(cx, obj.as())) - return false; - - MarkTypePropertyNonData(cx, obj, id); - } - - WatchpointMap* wpmap = cx->compartment()->watchpointMap; - if (!wpmap) { - wpmap = cx->runtime()->new_(); - if (!wpmap || !wpmap->init()) { - ReportOutOfMemory(cx); - js_delete(wpmap); - return false; - } - cx->compartment()->watchpointMap = wpmap; - } - - return wpmap->watch(cx, obj, id, js::WatchHandler, callable); -} - -bool -js::UnwatchGuts(JSContext* cx, JS::HandleObject origObj, JS::HandleId id) -{ - // Looking in the map for an unsupported object will never hit, so we don't - // need to check for nativeness or watchable-ness here. - RootedObject obj(cx, ToWindowIfWindowProxy(origObj)); - if (WatchpointMap* wpmap = cx->compartment()->watchpointMap) - wpmap->unwatch(obj, id, nullptr, nullptr); - return true; -} - -bool -js::WatchProperty(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable) -{ - if (!obj->isNative() || obj->is()) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_WATCH, - obj->getClass()->name); - return false; - } - - return WatchGuts(cx, obj, id, callable); -} - -bool -js::UnwatchProperty(JSContext* cx, HandleObject obj, HandleId id) -{ - return UnwatchGuts(cx, obj, id); -} - const char* js::GetObjectClassName(JSContext* cx, HandleObject obj) { @@ -3410,7 +3347,6 @@ JSObject::dump(FILE* fp) const if (obj->isBoundFunction()) fprintf(fp, " bound_function"); if (obj->isQualifiedVarObj()) fprintf(fp, " varobj"); if (obj->isUnqualifiedVarObj()) fprintf(fp, " unqualified_varobj"); - if (obj->watched()) fprintf(fp, " watched"); if (obj->isIteratedSingleton()) fprintf(fp, " iterated_singleton"); if (obj->isNewGroupUnknown()) fprintf(fp, " new_type_unknown"); if (obj->hasUncacheableProto()) fprintf(fp, " has_uncacheable_proto"); diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 8a41d155e..01845d7e6 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -219,11 +219,6 @@ class JSObject : public js::gc::Cell inline bool isBoundFunction() const; inline bool hasSpecialEquality() const; - inline bool watched() const; - static bool setWatched(js::ExclusiveContext* cx, JS::HandleObject obj) { - return setFlags(cx, obj, js::BaseShape::WATCHED, GENERATE_SHAPE); - } - // A "qualified" varobj is the object on which "qualified" variable // declarations (i.e., those defined with "var") are kept. // @@ -1030,21 +1025,6 @@ extern bool DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs, DefineAsIntrinsic intrinsic); -/* - * Set a watchpoint: a synchronous callback when the given property of the - * given object is set. - * - * Watchpoints are nonstandard and do not fit in well with the way ES6 - * specifies [[Set]]. They are also insufficient for implementing - * Object.observe. - */ -extern bool -WatchProperty(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable); - -/* Clear a watchpoint. */ -extern bool -UnwatchProperty(JSContext* cx, HandleObject obj, HandleId id); - /* ES6 draft rev 36 (2015 March 17) 7.1.1 ToPrimitive(vp[, preferredType]) */ extern bool ToPrimitiveSlow(JSContext* cx, JSType hint, MutableHandleValue vp); diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index c132ee6b2..98e740142 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -463,12 +463,6 @@ JSObject::isBoundFunction() const return is() && as().isBoundFunction(); } -inline bool -JSObject::watched() const -{ - return hasAllFlags(js::BaseShape::WATCHED); -} - inline bool JSObject::isDelegate() const { diff --git a/js/src/jsversion.h b/js/src/jsversion.h index 8bdfe47b6..cf4c6e73a 100644 --- a/js/src/jsversion.h +++ b/js/src/jsversion.h @@ -12,7 +12,6 @@ */ #define JS_HAS_STR_HTML_HELPERS 1 /* (no longer used) */ #define JS_HAS_OBJ_PROTO_PROP 1 /* has o.__proto__ etc. */ -#define JS_HAS_OBJ_WATCHPOINT 1 /* has o.watch and o.unwatch */ #define JS_HAS_TOSOURCE 1 /* has Object/Array toSource method */ #define JS_HAS_CATCH_GUARD 1 /* has exception handling catch guard */ #define JS_HAS_UNEVAL 1 /* has uneval() top-level function */ diff --git a/js/src/jswatchpoint.cpp b/js/src/jswatchpoint.cpp deleted file mode 100644 index 34479a990..000000000 --- a/js/src/jswatchpoint.cpp +++ /dev/null @@ -1,246 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sts=4 et sw=4 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 "jswatchpoint.h" - -#include "jsatom.h" -#include "jscompartment.h" -#include "jsfriendapi.h" - -#include "gc/Marking.h" -#include "vm/Shape.h" - -#include "jsgcinlines.h" - -using namespace js; -using namespace js::gc; - -inline HashNumber -WatchKeyHasher::hash(const Lookup& key) -{ - return MovableCellHasher::hash(key.object) ^ HashId(key.id); -} - -namespace { - -class AutoEntryHolder { - typedef WatchpointMap::Map Map; - Generation gen; - Map& map; - Map::Ptr p; - RootedObject obj; - RootedId id; - - public: - AutoEntryHolder(JSContext* cx, Map& map, Map::Ptr p) - : gen(map.generation()), map(map), p(p), obj(cx, p->key().object), id(cx, p->key().id) - { - MOZ_ASSERT(!p->value().held); - p->value().held = true; - } - - ~AutoEntryHolder() { - if (gen != map.generation()) - p = map.lookup(WatchKey(obj, id)); - if (p) - p->value().held = false; - } -}; - -} /* anonymous namespace */ - -bool -WatchpointMap::init() -{ - return map.init(); -} - -bool -WatchpointMap::watch(JSContext* cx, HandleObject obj, HandleId id, - JSWatchPointHandler handler, HandleObject closure) -{ - MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id) || JSID_IS_SYMBOL(id)); - - if (!JSObject::setWatched(cx, obj)) - return false; - - Watchpoint w(handler, closure, false); - if (!map.put(WatchKey(obj, id), w)) { - ReportOutOfMemory(cx); - return false; - } - /* - * For generational GC, we don't need to post-barrier writes to the - * hashtable here because we mark all watchpoints as part of root marking in - * markAll(). - */ - return true; -} - -void -WatchpointMap::unwatch(JSObject* obj, jsid id, - JSWatchPointHandler* handlerp, JSObject** closurep) -{ - if (Map::Ptr p = map.lookup(WatchKey(obj, id))) { - if (handlerp) - *handlerp = p->value().handler; - if (closurep) { - // Read barrier to prevent an incorrectly gray closure from escaping the - // watchpoint. See the comment before UnmarkGrayChildren in gc/Marking.cpp - JS::ExposeObjectToActiveJS(p->value().closure); - *closurep = p->value().closure; - } - map.remove(p); - } -} - -void -WatchpointMap::unwatchObject(JSObject* obj) -{ - for (Map::Enum e(map); !e.empty(); e.popFront()) { - Map::Entry& entry = e.front(); - if (entry.key().object == obj) - e.removeFront(); - } -} - -void -WatchpointMap::clear() -{ - map.clear(); -} - -bool -WatchpointMap::triggerWatchpoint(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp) -{ - Map::Ptr p = map.lookup(WatchKey(obj, id)); - if (!p || p->value().held) - return true; - - AutoEntryHolder holder(cx, map, p); - - /* Copy the entry, since GC would invalidate p. */ - JSWatchPointHandler handler = p->value().handler; - RootedObject closure(cx, p->value().closure); - - /* Determine the property's old value. */ - Value old; - old.setUndefined(); - if (obj->isNative()) { - NativeObject* nobj = &obj->as(); - if (Shape* shape = nobj->lookup(cx, id)) { - if (shape->hasSlot()) - old = nobj->getSlot(shape->slot()); - } - } - - // Read barrier to prevent an incorrectly gray closure from escaping the - // watchpoint. See the comment before UnmarkGrayChildren in gc/Marking.cpp - JS::ExposeObjectToActiveJS(closure); - - /* Call the handler. */ - return handler(cx, obj, id, old, vp.address(), closure); -} - -bool -WatchpointMap::markIteratively(JSTracer* trc) -{ - bool marked = false; - for (Map::Enum e(map); !e.empty(); e.popFront()) { - Map::Entry& entry = e.front(); - JSObject* priorKeyObj = entry.key().object; - jsid priorKeyId(entry.key().id.get()); - bool objectIsLive = - IsMarked(trc->runtime(), const_cast(&entry.key().object)); - if (objectIsLive || entry.value().held) { - if (!objectIsLive) { - TraceEdge(trc, const_cast(&entry.key().object), - "held Watchpoint object"); - marked = true; - } - - MOZ_ASSERT(JSID_IS_STRING(priorKeyId) || - JSID_IS_INT(priorKeyId) || - JSID_IS_SYMBOL(priorKeyId)); - TraceEdge(trc, const_cast(&entry.key().id), "WatchKey::id"); - - if (entry.value().closure && !IsMarked(trc->runtime(), &entry.value().closure)) { - TraceEdge(trc, &entry.value().closure, "Watchpoint::closure"); - marked = true; - } - - /* We will sweep this entry in sweepAll if !objectIsLive. */ - if (priorKeyObj != entry.key().object || priorKeyId != entry.key().id) - e.rekeyFront(WatchKey(entry.key().object, entry.key().id)); - } - } - return marked; -} - -void -WatchpointMap::markAll(JSTracer* trc) -{ - for (Map::Enum e(map); !e.empty(); e.popFront()) { - Map::Entry& entry = e.front(); - JSObject* object = entry.key().object; - jsid id = entry.key().id; - JSObject* priorObject = object; - jsid priorId = id; - MOZ_ASSERT(JSID_IS_STRING(priorId) || JSID_IS_INT(priorId) || JSID_IS_SYMBOL(priorId)); - - TraceManuallyBarrieredEdge(trc, &object, "held Watchpoint object"); - TraceManuallyBarrieredEdge(trc, &id, "WatchKey::id"); - TraceEdge(trc, &entry.value().closure, "Watchpoint::closure"); - - if (priorObject != object || priorId != id) - e.rekeyFront(WatchKey(object, id)); - } -} - -void -WatchpointMap::sweepAll(JSRuntime* rt) -{ - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - if (WatchpointMap* wpmap = c->watchpointMap) - wpmap->sweep(); - } -} - -void -WatchpointMap::sweep() -{ - for (Map::Enum e(map); !e.empty(); e.popFront()) { - Map::Entry& entry = e.front(); - JSObject* obj(entry.key().object); - if (IsAboutToBeFinalizedUnbarriered(&obj)) { - MOZ_ASSERT(!entry.value().held); - e.removeFront(); - } else if (obj != entry.key().object) { - e.rekeyFront(WatchKey(obj, entry.key().id)); - } - } -} - -void -WatchpointMap::traceAll(WeakMapTracer* trc) -{ - JSRuntime* rt = trc->context; - for (CompartmentsIter comp(rt, SkipAtoms); !comp.done(); comp.next()) { - if (WatchpointMap* wpmap = comp->watchpointMap) - wpmap->trace(trc); - } -} - -void -WatchpointMap::trace(WeakMapTracer* trc) -{ - for (Map::Range r = map.all(); !r.empty(); r.popFront()) { - Map::Entry& entry = r.front(); - trc->trace(nullptr, - JS::GCCellPtr(entry.key().object.get()), - JS::GCCellPtr(entry.value().closure.get())); - } -} diff --git a/js/src/jswatchpoint.h b/js/src/jswatchpoint.h deleted file mode 100644 index bba6c38ce..000000000 --- a/js/src/jswatchpoint.h +++ /dev/null @@ -1,90 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sts=4 et sw=4 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 jswatchpoint_h -#define jswatchpoint_h - -#include "jsalloc.h" - -#include "gc/Barrier.h" -#include "js/HashTable.h" - -namespace js { - -struct WeakMapTracer; - -struct WatchKey { - WatchKey() {} - WatchKey(JSObject* obj, jsid id) : object(obj), id(id) {} - WatchKey(const WatchKey& key) : object(key.object.get()), id(key.id.get()) {} - - // These are traced unconditionally during minor GC, so do not require - // post-barriers. - PreBarrieredObject object; - PreBarrieredId id; - - bool operator!=(const WatchKey& other) const { - return object != other.object || id != other.id; - } -}; - -typedef bool -(* JSWatchPointHandler)(JSContext* cx, JSObject* obj, jsid id, const JS::Value& old, - JS::Value* newp, void* closure); - -struct Watchpoint { - JSWatchPointHandler handler; - PreBarrieredObject closure; /* This is always marked in minor GCs and so doesn't require a postbarrier. */ - bool held; /* true if currently running handler */ - Watchpoint(JSWatchPointHandler handler, JSObject* closure, bool held) - : handler(handler), closure(closure), held(held) {} -}; - -struct WatchKeyHasher -{ - typedef WatchKey Lookup; - static inline js::HashNumber hash(const Lookup& key); - - static bool match(const WatchKey& k, const Lookup& l) { - return MovableCellHasher::match(k.object, l.object) && - DefaultHasher::match(k.id, l.id); - } - - static void rekey(WatchKey& k, const WatchKey& newKey) { - k.object.unsafeSet(newKey.object); - k.id.unsafeSet(newKey.id); - } -}; - -class WatchpointMap { - public: - typedef HashMap Map; - - bool init(); - bool watch(JSContext* cx, HandleObject obj, HandleId id, - JSWatchPointHandler handler, HandleObject closure); - void unwatch(JSObject* obj, jsid id, - JSWatchPointHandler* handlerp, JSObject** closurep); - void unwatchObject(JSObject* obj); - void clear(); - - bool triggerWatchpoint(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp); - - bool markIteratively(JSTracer* trc); - void markAll(JSTracer* trc); - static void sweepAll(JSRuntime* rt); - void sweep(); - - static void traceAll(WeakMapTracer* trc); - void trace(WeakMapTracer* trc); - - private: - Map map; -}; - -} // namespace js - -#endif /* jswatchpoint_h */ diff --git a/js/src/moz.build b/js/src/moz.build index a0f074d1c..5ce0dfd98 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -291,7 +291,6 @@ UNIFIED_SOURCES += [ 'jspropertytree.cpp', 'jsscript.cpp', 'jsstr.cpp', - 'jswatchpoint.cpp', 'jsweakmap.cpp', 'perf/jsperf.cpp', 'proxy/BaseProxyHandler.cpp', diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h index e55e3db04..030d92c12 100644 --- a/js/src/vm/NativeObject-inl.h +++ b/js/src/vm/NativeObject-inl.h @@ -158,11 +158,11 @@ NativeObject::extendDenseElements(ExclusiveContext* cx, MOZ_ASSERT(!denseElementsAreFrozen()); /* - * Don't grow elements for non-extensible objects or watched objects. Dense - * elements can be added/written with no extensible or watchpoint checks as - * long as there is capacity for them. + * Don't grow elements for non-extensible objects. Dense elements can be + * added/written with no extensible checks as long as there is capacity + * for them. */ - if (!nonProxyIsExtensible() || watched()) { + if (!nonProxyIsExtensible()) { MOZ_ASSERT(getDenseCapacity() == 0); return DenseElementResult::Incomplete; } diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index da0f59fe2..d801fad06 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -9,8 +9,6 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/Casting.h" -#include "jswatchpoint.h" - #include "gc/Marking.h" #include "js/Value.h" #include "vm/Debugger.h" @@ -602,7 +600,7 @@ NativeObject::maybeDensifySparseElements(js::ExclusiveContext* cx, HandleNativeO return DenseElementResult::Incomplete; /* Watch for conditions under which an object's elements cannot be dense. */ - if (!obj->nonProxyIsExtensible() || obj->watched()) + if (!obj->nonProxyIsExtensible()) return DenseElementResult::Incomplete; /* @@ -2410,17 +2408,9 @@ SetExistingProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleVa } bool -js::NativeSetProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue value, +js::NativeSetProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue v, HandleValue receiver, QualifiedBool qualified, ObjectOpResult& result) { - // Fire watchpoints, if any. - RootedValue v(cx, value); - if (MOZ_UNLIKELY(obj->watched())) { - WatchpointMap* wpmap = cx->compartment()->watchpointMap; - if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, &v)) - return false; - } - // Step numbers below reference ES6 rev 27 9.1.9, the [[Set]] internal // method for ordinary objects. We substitute our own names for these names // used in the spec: O -> pobj, P -> id, ownDesc -> shape. diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 5fc8e0e17..284a4f3d7 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -34,7 +34,6 @@ #include "jsnativestack.h" #include "jsobj.h" #include "jsscript.h" -#include "jswatchpoint.h" #include "jswin.h" #include "jswrapper.h" diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h index fd6d843e0..85bc044a5 100644 --- a/js/src/vm/Shape.h +++ b/js/src/vm/Shape.h @@ -387,7 +387,7 @@ class BaseShape : public gc::TenuredCell INDEXED = 0x20, /* (0x40 is unused) */ HAD_ELEMENTS_ACCESS = 0x80, - WATCHED = 0x100, + /* (0x100 is unused) */ ITERATED_SINGLETON = 0x200, NEW_GROUP_UNKNOWN = 0x400, UNCACHEABLE_PROTO = 0x800, diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index 88327b47e..2b1fa0e3b 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -2670,14 +2670,6 @@ ObjectGroup::updateNewPropertyTypes(ExclusiveContext* cx, JSObject* objArg, jsid if (shape) UpdatePropertyType(cx, types, obj, shape, false); } - - if (obj->watched()) { - /* - * Mark the property as non-data, to inhibit optimizations on it - * and avoid bypassing the watchpoint handler. - */ - types->setNonDataProperty(cx); - } } void @@ -3622,42 +3614,42 @@ struct DestroyTypeNewScript } // namespace -bool DPAConstraintInfo::finishConstraints(JSContext* cx, ObjectGroup* group) { - for (const ProtoConstraint& constraint : protoConstraints_) { - ObjectGroup* protoGroup = constraint.proto->group(); - - // Note: we rely on the group's type information being unchanged since - // AddClearDefiniteGetterSetterForPrototypeChain. - - bool unknownProperties = protoGroup->unknownProperties(); - MOZ_RELEASE_ASSERT(!unknownProperties); - - HeapTypeSet* protoTypes = - protoGroup->getProperty(cx, constraint.proto, constraint.id); - MOZ_RELEASE_ASSERT(protoTypes); - - MOZ_ASSERT(!protoTypes->nonDataProperty()); - MOZ_ASSERT(!protoTypes->nonWritableProperty()); - - if (!protoTypes->addConstraint( - cx, - cx->typeLifoAlloc().new_( - group))) { - ReportOutOfMemory(cx); - return false; - } - } - - for (const InliningConstraint& constraint : inliningConstraints_) { - if (!AddClearDefiniteFunctionUsesInScript(cx, group, constraint.caller, - constraint.callee)) { - ReportOutOfMemory(cx); - return false; - } - } - - return true; -} +bool DPAConstraintInfo::finishConstraints(JSContext* cx, ObjectGroup* group) { + for (const ProtoConstraint& constraint : protoConstraints_) { + ObjectGroup* protoGroup = constraint.proto->group(); + + // Note: we rely on the group's type information being unchanged since + // AddClearDefiniteGetterSetterForPrototypeChain. + + bool unknownProperties = protoGroup->unknownProperties(); + MOZ_RELEASE_ASSERT(!unknownProperties); + + HeapTypeSet* protoTypes = + protoGroup->getProperty(cx, constraint.proto, constraint.id); + MOZ_RELEASE_ASSERT(protoTypes); + + MOZ_ASSERT(!protoTypes->nonDataProperty()); + MOZ_ASSERT(!protoTypes->nonWritableProperty()); + + if (!protoTypes->addConstraint( + cx, + cx->typeLifoAlloc().new_( + group))) { + ReportOutOfMemory(cx); + return false; + } + } + + for (const InliningConstraint& constraint : inliningConstraints_) { + if (!AddClearDefiniteFunctionUsesInScript(cx, group, constraint.caller, + constraint.callee)) { + ReportOutOfMemory(cx); + return false; + } + } + + return true; +} bool TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, bool force) @@ -3826,13 +3818,13 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, // The definite properties analysis found exactly the properties that // are held in common by the preliminary objects. No further analysis // is needed. - - if (!constraintInfo.finishConstraints(cx, group)) { - return false; - } - if (!group->newScript()) { - return true; - } + + if (!constraintInfo.finishConstraints(cx, group)) { + return false; + } + if (!group->newScript()) { + return true; + } group->addDefiniteProperties(cx, templateObject()->lastProperty()); @@ -3854,16 +3846,16 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, initialFlags); if (!initialGroup) return false; - - // Add the constraints. Use the initialGroup as group referenced by the - // constraints because that's the group that will have the TypeNewScript - // associated with it. See the detachNewScript and setNewScript calls below. - if (!constraintInfo.finishConstraints(cx, initialGroup)) { - return false; - } - if (!group->newScript()) { - return true; - } + + // Add the constraints. Use the initialGroup as group referenced by the + // constraints because that's the group that will have the TypeNewScript + // associated with it. See the detachNewScript and setNewScript calls below. + if (!constraintInfo.finishConstraints(cx, initialGroup)) { + return false; + } + if (!group->newScript()) { + return true; + } initialGroup->addDefiniteProperties(cx, templateObject()->lastProperty()); group->addDefiniteProperties(cx, prefixShape); -- cgit v1.2.3 From 24027f0df9d23304709a80c22c6bfdbd27a95046 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 27 Oct 2019 02:14:16 +0100 Subject: Issue #1257 - Part 3: Remove/update tests. This removes a ton of tests that are no longer relevant with (un)watch removed (e.g. testing stability/bugs in the watchpoint system itself which has never been the most stable), and updates others that would previously rely on watch/unwatch, so that they don't unexpectedly fail. --- ...wser_webconsole_bug_585991_autocomplete_keys.js | 58 +++++---- dom/bindings/test/test_Object.prototype_props.html | 6 +- dom/html/test/mochitest.ini | 1 - dom/html/test/test_document.watch.html | 129 --------------------- dom/svg/crashtests/880544-1.svg | 15 --- dom/svg/crashtests/880544-2.svg | 15 --- dom/svg/crashtests/880544-3.svg | 15 --- dom/svg/crashtests/880544-4.svg | 15 --- dom/svg/crashtests/880544-5.svg | 15 --- dom/svg/crashtests/crashtests.list | 5 - dom/tests/mochitest/bugs/iframe_bug38959-1.html | 14 --- dom/tests/mochitest/bugs/iframe_bug38959-2.html | 14 --- dom/tests/mochitest/bugs/mochitest.ini | 3 - dom/tests/mochitest/bugs/test_bug38959.html | 57 --------- js/src/jit-test/tests/auto-regress/bug466654.js | 8 -- js/src/jit-test/tests/auto-regress/bug516897.js | 6 - js/src/jit-test/tests/auto-regress/bug537854.js | 4 - js/src/jit-test/tests/auto-regress/bug560796.js | 9 -- js/src/jit-test/tests/auto-regress/bug638735.js | 1 - js/src/jit-test/tests/auto-regress/bug639413.js | 9 -- js/src/jit-test/tests/auto-regress/bug698899.js | 9 -- js/src/jit-test/tests/auto-regress/bug746397.js | 10 -- js/src/jit-test/tests/auto-regress/bug769192.js | 6 - js/src/jit-test/tests/baseline/bug843444.js | 8 -- js/src/jit-test/tests/basic/bug510437.js | 13 --- js/src/jit-test/tests/basic/bug605015.js | 9 -- js/src/jit-test/tests/basic/bug631305.js | 9 -- js/src/jit-test/tests/basic/bug662562.js | 6 - js/src/jit-test/tests/basic/bug690292.js | 12 -- js/src/jit-test/tests/basic/bug696748.js | 3 - js/src/jit-test/tests/basic/bug831846.js | 3 - .../basic/testAssigningWatchedDeletedProperty.js | 7 -- js/src/jit-test/tests/basic/testBug566556.js | 9 -- js/src/jit-test/tests/basic/testBug578044.js | 13 --- js/src/jit-test/tests/basic/testBug584650.js | 9 -- js/src/jit-test/tests/basic/testBug780288-1.js | 20 ---- js/src/jit-test/tests/basic/testBug780288-2.js | 20 ---- .../basic/testEvalCalledFromWatchOverSetter.js | 3 - js/src/jit-test/tests/basic/testNonStubGetter.js | 7 -- .../basic/testSettingWatchPointOnReadOnlyProp.js | 7 -- js/src/jit-test/tests/basic/testTrueShiftTrue.js | 16 --- js/src/jit-test/tests/basic/testWatchRecursion.js | 63 ---------- js/src/jit-test/tests/gc/bug-900405.js | 3 - js/src/jit-test/tests/gc/bug-913261.js | 5 - js/src/jit-test/tests/gc/bug-986864.js | 8 -- js/src/jit-test/tests/ion/bug1063182.js | 8 -- js/src/jit-test/tests/ion/bug772901.js | 2 +- js/src/jit-test/tests/ion/bug774257-1.js | 8 -- js/src/jit-test/tests/ion/bug774257-2.js | 10 -- js/src/jit-test/tests/ion/bug779631.js | 9 -- js/src/jit-test/tests/ion/bug783590.js | 1 - js/src/jit-test/tests/jaeger/bug550665.js | 8 -- js/src/jit-test/tests/jaeger/bug557063.js | 7 -- js/src/jit-test/tests/jaeger/bug588338.js | 1 - js/src/jit-test/tests/jaeger/bug625438.js | 10 -- js/src/jit-test/tests/jaeger/bug630366.js | 7 -- .../jit-test/tests/jaeger/recompile/bug641225.js | 1 - js/src/jit-test/tests/pic/fuzz1.js | 4 - js/src/jit-test/tests/pic/fuzz3.js | 3 - js/src/jit-test/tests/pic/watch1.js | 7 -- js/src/jit-test/tests/pic/watch1a.js | 17 --- js/src/jit-test/tests/pic/watch2.js | 8 -- js/src/jit-test/tests/pic/watch2a.js | 18 --- js/src/jit-test/tests/pic/watch3.js | 7 -- js/src/jit-test/tests/pic/watch3a.js | 19 --- js/src/jit-test/tests/pic/watch3b.js | 20 ---- js/src/jit-test/tests/pic/watch4.js | 9 -- js/src/jit-test/tests/pic/watch5.js | 27 ----- js/src/jit-test/tests/profiler/bug1140643.js | 14 --- js/src/tests/ecma_5/Array/frozen-dense-array.js | 19 --- .../tests/ecma_5/extensions/watch-array-length.js | 41 ------- .../ecma_5/extensions/watch-inherited-property.js | 38 ------ .../ecma_5/extensions/watch-replaced-setter.js | 46 -------- .../extensions/watch-setter-become-setter.js | 44 ------- .../extensions/watch-value-prop-becoming-setter.js | 43 ------- .../watchpoint-deletes-JSPropertyOp-setter.js | 56 --------- js/src/tests/js1_5/Object/regress-362872-01.js | 41 ------- js/src/tests/js1_5/Object/regress-362872-02.js | 24 ---- js/src/tests/js1_5/Regress/regress-127243.js | 75 ------------ js/src/tests/js1_5/Regress/regress-213482.js | 29 ----- js/src/tests/js1_5/Regress/regress-240577.js | 37 ------ js/src/tests/js1_5/Regress/regress-355341.js | 29 ----- js/src/tests/js1_5/Regress/regress-355344.js | 49 -------- js/src/tests/js1_5/Regress/regress-361467.js | 32 ----- js/src/tests/js1_5/Regress/regress-361617.js | 35 ------ js/src/tests/js1_5/Regress/regress-385393-06.js | 28 ----- js/src/tests/js1_5/Regress/regress-506567.js | 45 ------- js/src/tests/js1_5/extensions/regress-303277.js | 19 --- js/src/tests/js1_5/extensions/regress-355339.js | 32 ----- js/src/tests/js1_5/extensions/regress-361346.js | 22 ---- js/src/tests/js1_5/extensions/regress-361360.js | 32 ----- js/src/tests/js1_5/extensions/regress-361552.js | 27 ----- js/src/tests/js1_5/extensions/regress-361558.js | 19 --- js/src/tests/js1_5/extensions/regress-361571.js | 38 ------ js/src/tests/js1_5/extensions/regress-361856.js | 35 ------ js/src/tests/js1_5/extensions/regress-361964.js | 54 --------- js/src/tests/js1_5/extensions/regress-385134.js | 38 ------ js/src/tests/js1_5/extensions/regress-385393-09.js | 18 --- js/src/tests/js1_5/extensions/regress-390597.js | 42 ------- js/src/tests/js1_5/extensions/regress-420612.js | 21 ---- js/src/tests/js1_5/extensions/regress-435345-01.js | 100 ---------------- js/src/tests/js1_5/extensions/regress-454040.js | 25 ---- js/src/tests/js1_5/extensions/regress-454142.js | 30 ----- js/src/tests/js1_5/extensions/regress-455413.js | 24 ---- js/src/tests/js1_5/extensions/regress-465145.js | 24 ---- js/src/tests/js1_5/extensions/regress-472787.js | 31 ----- js/src/tests/js1_5/extensions/regress-488995.js | 46 -------- js/src/tests/js1_6/Regress/regress-476655.js | 40 ------- js/src/tests/js1_6/extensions/regress-457521.js | 24 ---- js/src/tests/js1_6/extensions/regress-479567.js | 33 ------ js/src/tests/js1_7/extensions/regress-453955.js | 31 ----- js/src/tests/js1_7/extensions/regress-473282.js | 20 ---- js/src/tests/js1_7/regress/regress-385133-01.js | 37 ------ js/src/tests/js1_8/extensions/regress-394709.js | 51 -------- js/src/tests/js1_8/extensions/regress-481989.js | 19 --- .../tests/js1_8_1/extensions/regress-452498-193.js | 34 ------ .../tests/js1_8_1/extensions/regress-452498-196.js | 9 -- js/src/tests/js1_8_1/extensions/regress-520572.js | 41 ------- js/src/tests/js1_8_1/regress/regress-452498-160.js | 5 - .../tests/js1_8_5/extensions/regress-604781-1.js | 24 ---- .../tests/js1_8_5/extensions/regress-604781-2.js | 13 --- .../tests/js1_8_5/extensions/regress-627984-1.js | 16 --- .../tests/js1_8_5/extensions/regress-627984-2.js | 15 --- .../tests/js1_8_5/extensions/regress-627984-3.js | 14 --- .../tests/js1_8_5/extensions/regress-627984-4.js | 15 --- .../tests/js1_8_5/extensions/regress-627984-5.js | 13 --- .../tests/js1_8_5/extensions/regress-627984-6.js | 15 --- .../tests/js1_8_5/extensions/regress-627984-7.js | 9 -- js/src/tests/js1_8_5/extensions/regress-631723.js | 10 -- js/src/tests/js1_8_5/extensions/regress-636697.js | 11 -- js/src/tests/js1_8_5/extensions/regress-637985.js | 8 -- js/src/tests/js1_8_5/extensions/regress-691746.js | 11 -- .../js1_8_5/extensions/watch-undefined-setter.js | 19 --- js/src/tests/js1_8_5/regress/regress-533876.js | 23 ---- js/src/tests/js1_8_5/regress/regress-548276.js | 10 -- js/src/tests/js1_8_5/regress/regress-584648.js | 16 --- js/src/tests/js1_8_5/regress/regress-635195.js | 8 -- js/src/tests/js1_8_5/regress/regress-636394.js | 10 -- js/xpconnect/tests/chrome/chrome.ini | 1 - js/xpconnect/tests/chrome/test_watchpoints.xul | 75 ------------ js/xpconnect/tests/chrome/test_xrayToJS.xul | 4 +- 141 files changed, 34 insertions(+), 2940 deletions(-) delete mode 100644 dom/html/test/test_document.watch.html delete mode 100644 dom/svg/crashtests/880544-1.svg delete mode 100644 dom/svg/crashtests/880544-2.svg delete mode 100644 dom/svg/crashtests/880544-3.svg delete mode 100644 dom/svg/crashtests/880544-4.svg delete mode 100644 dom/svg/crashtests/880544-5.svg delete mode 100644 dom/tests/mochitest/bugs/iframe_bug38959-1.html delete mode 100644 dom/tests/mochitest/bugs/iframe_bug38959-2.html delete mode 100644 dom/tests/mochitest/bugs/test_bug38959.html delete mode 100644 js/src/jit-test/tests/auto-regress/bug466654.js delete mode 100644 js/src/jit-test/tests/auto-regress/bug516897.js delete mode 100644 js/src/jit-test/tests/auto-regress/bug537854.js delete mode 100644 js/src/jit-test/tests/auto-regress/bug560796.js delete mode 100644 js/src/jit-test/tests/auto-regress/bug639413.js delete mode 100644 js/src/jit-test/tests/auto-regress/bug698899.js delete mode 100644 js/src/jit-test/tests/auto-regress/bug746397.js delete mode 100644 js/src/jit-test/tests/auto-regress/bug769192.js delete mode 100644 js/src/jit-test/tests/baseline/bug843444.js delete mode 100644 js/src/jit-test/tests/basic/bug510437.js delete mode 100644 js/src/jit-test/tests/basic/bug605015.js delete mode 100644 js/src/jit-test/tests/basic/bug631305.js delete mode 100644 js/src/jit-test/tests/basic/bug662562.js delete mode 100644 js/src/jit-test/tests/basic/bug690292.js delete mode 100644 js/src/jit-test/tests/basic/bug831846.js delete mode 100644 js/src/jit-test/tests/basic/testAssigningWatchedDeletedProperty.js delete mode 100644 js/src/jit-test/tests/basic/testBug566556.js delete mode 100644 js/src/jit-test/tests/basic/testBug578044.js delete mode 100644 js/src/jit-test/tests/basic/testBug584650.js delete mode 100644 js/src/jit-test/tests/basic/testBug780288-1.js delete mode 100644 js/src/jit-test/tests/basic/testBug780288-2.js delete mode 100644 js/src/jit-test/tests/basic/testEvalCalledFromWatchOverSetter.js delete mode 100644 js/src/jit-test/tests/basic/testNonStubGetter.js delete mode 100644 js/src/jit-test/tests/basic/testSettingWatchPointOnReadOnlyProp.js delete mode 100644 js/src/jit-test/tests/basic/testTrueShiftTrue.js delete mode 100644 js/src/jit-test/tests/basic/testWatchRecursion.js delete mode 100644 js/src/jit-test/tests/gc/bug-900405.js delete mode 100644 js/src/jit-test/tests/gc/bug-913261.js delete mode 100644 js/src/jit-test/tests/gc/bug-986864.js delete mode 100644 js/src/jit-test/tests/ion/bug1063182.js delete mode 100644 js/src/jit-test/tests/ion/bug774257-1.js delete mode 100644 js/src/jit-test/tests/ion/bug774257-2.js delete mode 100644 js/src/jit-test/tests/ion/bug779631.js delete mode 100644 js/src/jit-test/tests/jaeger/bug550665.js delete mode 100644 js/src/jit-test/tests/jaeger/bug557063.js delete mode 100644 js/src/jit-test/tests/jaeger/bug625438.js delete mode 100644 js/src/jit-test/tests/jaeger/bug630366.js delete mode 100644 js/src/jit-test/tests/pic/fuzz1.js delete mode 100644 js/src/jit-test/tests/pic/fuzz3.js delete mode 100644 js/src/jit-test/tests/pic/watch1.js delete mode 100644 js/src/jit-test/tests/pic/watch1a.js delete mode 100644 js/src/jit-test/tests/pic/watch2.js delete mode 100644 js/src/jit-test/tests/pic/watch2a.js delete mode 100644 js/src/jit-test/tests/pic/watch3.js delete mode 100644 js/src/jit-test/tests/pic/watch3a.js delete mode 100644 js/src/jit-test/tests/pic/watch3b.js delete mode 100644 js/src/jit-test/tests/pic/watch4.js delete mode 100644 js/src/jit-test/tests/pic/watch5.js delete mode 100644 js/src/jit-test/tests/profiler/bug1140643.js delete mode 100644 js/src/tests/ecma_5/extensions/watch-array-length.js delete mode 100644 js/src/tests/ecma_5/extensions/watch-inherited-property.js delete mode 100644 js/src/tests/ecma_5/extensions/watch-replaced-setter.js delete mode 100644 js/src/tests/ecma_5/extensions/watch-setter-become-setter.js delete mode 100644 js/src/tests/ecma_5/extensions/watch-value-prop-becoming-setter.js delete mode 100644 js/src/tests/ecma_5/extensions/watchpoint-deletes-JSPropertyOp-setter.js delete mode 100644 js/src/tests/js1_5/Object/regress-362872-01.js delete mode 100644 js/src/tests/js1_5/Object/regress-362872-02.js delete mode 100644 js/src/tests/js1_5/Regress/regress-127243.js delete mode 100644 js/src/tests/js1_5/Regress/regress-213482.js delete mode 100644 js/src/tests/js1_5/Regress/regress-240577.js delete mode 100644 js/src/tests/js1_5/Regress/regress-355341.js delete mode 100644 js/src/tests/js1_5/Regress/regress-355344.js delete mode 100644 js/src/tests/js1_5/Regress/regress-361467.js delete mode 100644 js/src/tests/js1_5/Regress/regress-361617.js delete mode 100644 js/src/tests/js1_5/Regress/regress-385393-06.js delete mode 100644 js/src/tests/js1_5/Regress/regress-506567.js delete mode 100644 js/src/tests/js1_5/extensions/regress-303277.js delete mode 100644 js/src/tests/js1_5/extensions/regress-355339.js delete mode 100644 js/src/tests/js1_5/extensions/regress-361346.js delete mode 100644 js/src/tests/js1_5/extensions/regress-361360.js delete mode 100644 js/src/tests/js1_5/extensions/regress-361552.js delete mode 100644 js/src/tests/js1_5/extensions/regress-361558.js delete mode 100644 js/src/tests/js1_5/extensions/regress-361571.js delete mode 100644 js/src/tests/js1_5/extensions/regress-361856.js delete mode 100644 js/src/tests/js1_5/extensions/regress-361964.js delete mode 100644 js/src/tests/js1_5/extensions/regress-385134.js delete mode 100644 js/src/tests/js1_5/extensions/regress-385393-09.js delete mode 100644 js/src/tests/js1_5/extensions/regress-390597.js delete mode 100644 js/src/tests/js1_5/extensions/regress-420612.js delete mode 100644 js/src/tests/js1_5/extensions/regress-435345-01.js delete mode 100644 js/src/tests/js1_5/extensions/regress-454040.js delete mode 100644 js/src/tests/js1_5/extensions/regress-454142.js delete mode 100644 js/src/tests/js1_5/extensions/regress-455413.js delete mode 100644 js/src/tests/js1_5/extensions/regress-465145.js delete mode 100755 js/src/tests/js1_5/extensions/regress-472787.js delete mode 100644 js/src/tests/js1_5/extensions/regress-488995.js delete mode 100644 js/src/tests/js1_6/Regress/regress-476655.js delete mode 100644 js/src/tests/js1_6/extensions/regress-457521.js delete mode 100644 js/src/tests/js1_6/extensions/regress-479567.js delete mode 100644 js/src/tests/js1_7/extensions/regress-453955.js delete mode 100644 js/src/tests/js1_7/extensions/regress-473282.js delete mode 100644 js/src/tests/js1_7/regress/regress-385133-01.js delete mode 100644 js/src/tests/js1_8/extensions/regress-394709.js delete mode 100644 js/src/tests/js1_8/extensions/regress-481989.js delete mode 100644 js/src/tests/js1_8_1/extensions/regress-452498-193.js delete mode 100644 js/src/tests/js1_8_1/extensions/regress-520572.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-604781-1.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-604781-2.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-627984-1.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-627984-2.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-627984-3.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-627984-4.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-627984-5.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-627984-6.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-627984-7.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-631723.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-636697.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-637985.js delete mode 100644 js/src/tests/js1_8_5/extensions/regress-691746.js delete mode 100644 js/src/tests/js1_8_5/extensions/watch-undefined-setter.js delete mode 100644 js/src/tests/js1_8_5/regress/regress-533876.js delete mode 100644 js/src/tests/js1_8_5/regress/regress-548276.js delete mode 100644 js/src/tests/js1_8_5/regress/regress-584648.js delete mode 100644 js/src/tests/js1_8_5/regress/regress-635195.js delete mode 100644 js/src/tests/js1_8_5/regress/regress-636394.js delete mode 100644 js/xpconnect/tests/chrome/test_watchpoints.xul diff --git a/devtools/client/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js b/devtools/client/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js index 0021a8cc1..b96dc0c8e 100644 --- a/devtools/client/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js +++ b/devtools/client/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js @@ -57,8 +57,8 @@ var consoleOpened = Task.async(function* (hud) { // 4 values, and the following properties: // __defineGetter__ __defineSetter__ __lookupGetter__ __lookupSetter__ // __proto__ hasOwnProperty isPrototypeOf propertyIsEnumerable - // toLocaleString toString toSource unwatch valueOf watch constructor. - is(popup.itemCount, 19, "popup.itemCount is correct"); + // toLocaleString toString toSource valueOfconstructor. + is(popup.itemCount, 17, "popup.itemCount is correct"); let sameItems = popup.getItems().reverse().map(function (e) { return e.label; @@ -82,36 +82,34 @@ var consoleOpened = Task.async(function* (hud) { "toLocaleString", "toSource", "toString", - "unwatch", "valueOf", - "watch", ][index] === prop; }), "getItems returns the items we expect"); - is(popup.selectedIndex, 18, + is(popup.selectedIndex, 16, "Index of the first item from bottom is selected."); EventUtils.synthesizeKey("VK_DOWN", {}); let prefix = jsterm.getInputValue().replace(/[\S]/g, " "); is(popup.selectedIndex, 0, "index 0 is selected"); - is(popup.selectedItem.label, "watch", "watch is selected"); - is(completeNode.value, prefix + "watch", - "completeNode.value holds watch"); + is(popup.selectedItem.label, "valueOf", "valueOf is selected"); + is(completeNode.value, prefix + "valueOf", + "completeNode.value holds valueOf"); EventUtils.synthesizeKey("VK_DOWN", {}); is(popup.selectedIndex, 1, "index 1 is selected"); - is(popup.selectedItem.label, "valueOf", "valueOf is selected"); - is(completeNode.value, prefix + "valueOf", - "completeNode.value holds valueOf"); + is(popup.selectedItem.label, "toString", "toString is selected"); + is(completeNode.value, prefix + "toString", + "completeNode.value holds toString"); EventUtils.synthesizeKey("VK_UP", {}); is(popup.selectedIndex, 0, "index 0 is selected"); - is(popup.selectedItem.label, "watch", "watch is selected"); - is(completeNode.value, prefix + "watch", - "completeNode.value holds watch"); + is(popup.selectedItem.label, "valueOf", "valueOf is selected"); + is(completeNode.value, prefix + "valueOf", + "completeNode.value holds valueOf"); let currentSelectionIndex = popup.selectedIndex; @@ -127,7 +125,7 @@ var consoleOpened = Task.async(function* (hud) { "Index is less after Page UP"); EventUtils.synthesizeKey("VK_END", {}); - is(popup.selectedIndex, 18, "index is last after End"); + is(popup.selectedIndex, 16, "index is last after End"); EventUtils.synthesizeKey("VK_HOME", {}); is(popup.selectedIndex, 0, "index is first after Home"); @@ -151,7 +149,7 @@ function popupHideAfterTab() { // At this point the completion suggestion should be accepted. ok(!popup.isOpen, "popup is not open"); - is(jsterm.getInputValue(), "window.foobarBug585991.watch", + is(jsterm.getInputValue(), "window.foobarBug585991.valueOf", "completion was successful after VK_TAB"); ok(!completeNode.value, "completeNode is empty"); @@ -159,17 +157,17 @@ function popupHideAfterTab() { popup.once("popup-opened", function onShown() { ok(popup.isOpen, "popup is open"); - is(popup.itemCount, 19, "popup.itemCount is correct"); + is(popup.itemCount, 17, "popup.itemCount is correct"); - is(popup.selectedIndex, 18, "First index from bottom is selected"); + is(popup.selectedIndex, 16, "First index from bottom is selected"); EventUtils.synthesizeKey("VK_DOWN", {}); let prefix = jsterm.getInputValue().replace(/[\S]/g, " "); is(popup.selectedIndex, 0, "index 0 is selected"); - is(popup.selectedItem.label, "watch", "watch is selected"); - is(completeNode.value, prefix + "watch", - "completeNode.value holds watch"); + is(popup.selectedItem.label, "valueOf", "valueOf is selected"); + is(completeNode.value, prefix + "valueOf", + "completeNode.value holds valueOf"); popup.once("popup-closed", function onHidden() { ok(!popup.isOpen, "popup is not open after VK_ESCAPE"); @@ -203,29 +201,29 @@ function testReturnKey() { popup.once("popup-opened", function onShown() { ok(popup.isOpen, "popup is open"); - is(popup.itemCount, 19, "popup.itemCount is correct"); + is(popup.itemCount, 17, "popup.itemCount is correct"); - is(popup.selectedIndex, 18, "First index from bottom is selected"); + is(popup.selectedIndex, 16, "First index from bottom is selected"); EventUtils.synthesizeKey("VK_DOWN", {}); let prefix = jsterm.getInputValue().replace(/[\S]/g, " "); is(popup.selectedIndex, 0, "index 0 is selected"); - is(popup.selectedItem.label, "watch", "watch is selected"); - is(completeNode.value, prefix + "watch", - "completeNode.value holds watch"); + is(popup.selectedItem.label, "valueOf", "valueOf is selected"); + is(completeNode.value, prefix + "valueOf", + "completeNode.value holds valueOf"); EventUtils.synthesizeKey("VK_DOWN", {}); is(popup.selectedIndex, 1, "index 1 is selected"); - is(popup.selectedItem.label, "valueOf", "valueOf is selected"); - is(completeNode.value, prefix + "valueOf", - "completeNode.value holds valueOf"); + is(popup.selectedItem.label, "toString", "toString is selected"); + is(completeNode.value, prefix + "toString", + "completeNode.value holds toString"); popup.once("popup-closed", function onHidden() { ok(!popup.isOpen, "popup is not open after VK_RETURN"); - is(jsterm.getInputValue(), "window.foobarBug585991.valueOf", + is(jsterm.getInputValue(), "window.foobarBug585991.toString", "completion was successful after VK_RETURN"); ok(!completeNode.value, "completeNode is empty"); diff --git a/dom/bindings/test/test_Object.prototype_props.html b/dom/bindings/test/test_Object.prototype_props.html index 03147eb03..3ab27c5e4 100644 --- a/dom/bindings/test/test_Object.prototype_props.html +++ b/dom/bindings/test/test_Object.prototype_props.html @@ -11,9 +11,9 @@ test(function() { // Codegen.py's CGDictionary.getMemberDefinition method. var expected = [ "constructor", "toSource", "toString", "toLocaleString", "valueOf", - "watch", "unwatch", "hasOwnProperty", "isPrototypeOf", - "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", - "__lookupGetter__", "__lookupSetter__", "__proto__" + "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", + "__defineGetter__", "__defineSetter__", "__lookupGetter__", + "__lookupSetter__", "__proto__" ]; assert_array_equals(props.sort(), expected.sort()); }, "Own properties of Object.prototype"); diff --git a/dom/html/test/mochitest.ini b/dom/html/test/mochitest.ini index 99b425df8..f619be5df 100644 --- a/dom/html/test/mochitest.ini +++ b/dom/html/test/mochitest.ini @@ -553,7 +553,6 @@ skip-if = true # Disabled for timeouts. [test_viewport.html] [test_documentAll.html] [test_document-element-inserted.html] -[test_document.watch.html] [test_bug445004.html] skip-if = true || toolkit == 'android' # Disabled permanently (bug 559932). [test_bug446483.html] diff --git a/dom/html/test/test_document.watch.html b/dom/html/test/test_document.watch.html deleted file mode 100644 index 54509823b..000000000 --- a/dom/html/test/test_document.watch.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - Test for Bug 903332 - - - - - -Mozilla Bug 903332 -

- -
-
- - diff --git a/dom/svg/crashtests/880544-1.svg b/dom/svg/crashtests/880544-1.svg deleted file mode 100644 index 9052d2396..000000000 --- a/dom/svg/crashtests/880544-1.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - diff --git a/dom/svg/crashtests/880544-2.svg b/dom/svg/crashtests/880544-2.svg deleted file mode 100644 index 7570c7cbf..000000000 --- a/dom/svg/crashtests/880544-2.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - foo - - diff --git a/dom/svg/crashtests/880544-3.svg b/dom/svg/crashtests/880544-3.svg deleted file mode 100644 index 5791b8ec6..000000000 --- a/dom/svg/crashtests/880544-3.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - foo - - diff --git a/dom/svg/crashtests/880544-4.svg b/dom/svg/crashtests/880544-4.svg deleted file mode 100644 index 7bdb80f47..000000000 --- a/dom/svg/crashtests/880544-4.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - diff --git a/dom/svg/crashtests/880544-5.svg b/dom/svg/crashtests/880544-5.svg deleted file mode 100644 index ef7f468f8..000000000 --- a/dom/svg/crashtests/880544-5.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - diff --git a/dom/svg/crashtests/crashtests.list b/dom/svg/crashtests/crashtests.list index 147838bbe..b2e920152 100644 --- a/dom/svg/crashtests/crashtests.list +++ b/dom/svg/crashtests/crashtests.list @@ -66,11 +66,6 @@ load 837450-1.svg load 842463-1.html load 847138-1.svg load 864509.svg -load 880544-1.svg -load 880544-2.svg -load 880544-3.svg -load 880544-4.svg -load 880544-5.svg load 898915-1.svg load 1035248-1.svg load 1035248-2.svg diff --git a/dom/tests/mochitest/bugs/iframe_bug38959-1.html b/dom/tests/mochitest/bugs/iframe_bug38959-1.html deleted file mode 100644 index d4c16c47a..000000000 --- a/dom/tests/mochitest/bugs/iframe_bug38959-1.html +++ /dev/null @@ -1,14 +0,0 @@ - - - Iframe test for bug 38959 - - - - - diff --git a/dom/tests/mochitest/bugs/iframe_bug38959-2.html b/dom/tests/mochitest/bugs/iframe_bug38959-2.html deleted file mode 100644 index 36cd0c156..000000000 --- a/dom/tests/mochitest/bugs/iframe_bug38959-2.html +++ /dev/null @@ -1,14 +0,0 @@ - - - Iframe test for bug 38959 - - - - - diff --git a/dom/tests/mochitest/bugs/mochitest.ini b/dom/tests/mochitest/bugs/mochitest.ini index e0c71f857..309aab6e0 100644 --- a/dom/tests/mochitest/bugs/mochitest.ini +++ b/dom/tests/mochitest/bugs/mochitest.ini @@ -23,8 +23,6 @@ support-files = grandchild_bug260264.html iframe_bug304459-1.html iframe_bug304459-2.html - iframe_bug38959-1.html - iframe_bug38959-2.html iframe_bug430276-2.html iframe_bug430276.html iframe_bug440572.html @@ -64,7 +62,6 @@ skip-if = toolkit == 'android' #TIMED_OUT [test_bug377539.html] [test_bug384122.html] [test_bug389366.html] -[test_bug38959.html] [test_bug393974.html] [test_bug394769.html] [test_bug396843.html] diff --git a/dom/tests/mochitest/bugs/test_bug38959.html b/dom/tests/mochitest/bugs/test_bug38959.html deleted file mode 100644 index a8d07d1a6..000000000 --- a/dom/tests/mochitest/bugs/test_bug38959.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - Test for Bug 38959 - - - - -Mozilla Bug 38959 -

- -
-
-
- - diff --git a/js/src/jit-test/tests/auto-regress/bug466654.js b/js/src/jit-test/tests/auto-regress/bug466654.js deleted file mode 100644 index 6c82c425b..000000000 --- a/js/src/jit-test/tests/auto-regress/bug466654.js +++ /dev/null @@ -1,8 +0,0 @@ -// |jit-test| error:TypeError - -// Binary: cache/js-dbg-32-29add08d84ae-linux -// Flags: -j -// -this.watch('y', /x/g ); -for each (y in ['q', 'q', 'q']) continue; -gc(); diff --git a/js/src/jit-test/tests/auto-regress/bug516897.js b/js/src/jit-test/tests/auto-regress/bug516897.js deleted file mode 100644 index e3caf4e6e..000000000 --- a/js/src/jit-test/tests/auto-regress/bug516897.js +++ /dev/null @@ -1,6 +0,0 @@ -// Binary: cache/js-dbg-64-38754465ffde-linux -// Flags: -// -this.__defineSetter__("x", gc); -this.watch("x",function(){return}); -x = 3; diff --git a/js/src/jit-test/tests/auto-regress/bug537854.js b/js/src/jit-test/tests/auto-regress/bug537854.js deleted file mode 100644 index 80fb3c14a..000000000 --- a/js/src/jit-test/tests/auto-regress/bug537854.js +++ /dev/null @@ -1,4 +0,0 @@ -// Binary: cache/js-dbg-64-9d51f2a931f7-linux -// Flags: -// -({x:function(){}}).watch('x',function(){}); diff --git a/js/src/jit-test/tests/auto-regress/bug560796.js b/js/src/jit-test/tests/auto-regress/bug560796.js deleted file mode 100644 index 4ab93567e..000000000 --- a/js/src/jit-test/tests/auto-regress/bug560796.js +++ /dev/null @@ -1,9 +0,0 @@ -// Binary: cache/js-dbg-64-a6d7a5677b4c-linux -// Flags: -// -this.__defineSetter__("x", function(){}) -this.watch("x", "".localeCompare) -window = x -Object.defineProperty(this, "x", ({ - set: window -})) diff --git a/js/src/jit-test/tests/auto-regress/bug638735.js b/js/src/jit-test/tests/auto-regress/bug638735.js index 63071aa7c..c941f5369 100644 --- a/js/src/jit-test/tests/auto-regress/bug638735.js +++ b/js/src/jit-test/tests/auto-regress/bug638735.js @@ -4,7 +4,6 @@ var o9 = Function.prototype; var o13 = Array; function f5(o) { -o.watch('p3', function() {}); ox1 = new Proxy(o, {}); } f5(o9); diff --git a/js/src/jit-test/tests/auto-regress/bug639413.js b/js/src/jit-test/tests/auto-regress/bug639413.js deleted file mode 100644 index d8dd58eaf..000000000 --- a/js/src/jit-test/tests/auto-regress/bug639413.js +++ /dev/null @@ -1,9 +0,0 @@ -// |jit-test| error:TypeError - -// Binary: cache/js-dbg-32-1c8e91b2e3a4-linux -// Flags: -// -a = evalcx("lazy"); -a.watch("x", function() {}); -({}).watch("x", function() {}); -a.__defineGetter__("y", {}); diff --git a/js/src/jit-test/tests/auto-regress/bug698899.js b/js/src/jit-test/tests/auto-regress/bug698899.js deleted file mode 100644 index 644f45ec2..000000000 --- a/js/src/jit-test/tests/auto-regress/bug698899.js +++ /dev/null @@ -1,9 +0,0 @@ -// Binary: cache/js-dbg-32-f951e9151626-linux -// Flags: -m -n -// -o = evalcx("lazy").__proto__ -gc() -try { - o.watch() -} catch (e) {} -o.constructor() diff --git a/js/src/jit-test/tests/auto-regress/bug746397.js b/js/src/jit-test/tests/auto-regress/bug746397.js deleted file mode 100644 index d915ca7bb..000000000 --- a/js/src/jit-test/tests/auto-regress/bug746397.js +++ /dev/null @@ -1,10 +0,0 @@ -// |jit-test| error:ReferenceError - -// Binary: cache/js-dbg-64-67bf9a4a1f77-linux -// Flags: --ion-eager -// - -(function () { - var a = ['x', 'y']; - obj.watch(a[+("0")], counter); -})(); diff --git a/js/src/jit-test/tests/auto-regress/bug769192.js b/js/src/jit-test/tests/auto-regress/bug769192.js deleted file mode 100644 index 531e37912..000000000 --- a/js/src/jit-test/tests/auto-regress/bug769192.js +++ /dev/null @@ -1,6 +0,0 @@ -// |jit-test| error:TypeError - -// Binary: cache/js-dbg-64-bf8f2961d0cc-linux -// Flags: -// -Object.watch.call(new Uint8ClampedArray, "length", function() {}); diff --git a/js/src/jit-test/tests/baseline/bug843444.js b/js/src/jit-test/tests/baseline/bug843444.js deleted file mode 100644 index 3a77402ac..000000000 --- a/js/src/jit-test/tests/baseline/bug843444.js +++ /dev/null @@ -1,8 +0,0 @@ -gczeal(8, 1) -function recurse(x) { - recurse; - if (x < 20) - recurse(x + 1); -}; -this.watch(5, (function () {})) -recurse(0) diff --git a/js/src/jit-test/tests/basic/bug510437.js b/js/src/jit-test/tests/basic/bug510437.js deleted file mode 100644 index 2418b9b11..000000000 --- a/js/src/jit-test/tests/basic/bug510437.js +++ /dev/null @@ -1,13 +0,0 @@ -// Don't crash or assert. - -var d; -this.watch("d", eval); -(function () { - (eval("\ - (function () {\ - for (let x = 0; x < 2; ++x) {\ - d = x\ - }\ - })\ -"))() -})() diff --git a/js/src/jit-test/tests/basic/bug605015.js b/js/src/jit-test/tests/basic/bug605015.js deleted file mode 100644 index a35f7b6c7..000000000 --- a/js/src/jit-test/tests/basic/bug605015.js +++ /dev/null @@ -1,9 +0,0 @@ -// |jit-test| error: TypeError -// don't assert - -print(this.watch("x", -function() { - Object.defineProperty(this, "x", ({ - get: (Int8Array) - })) -}))(x = /x/) diff --git a/js/src/jit-test/tests/basic/bug631305.js b/js/src/jit-test/tests/basic/bug631305.js deleted file mode 100644 index b0cbbbb24..000000000 --- a/js/src/jit-test/tests/basic/bug631305.js +++ /dev/null @@ -1,9 +0,0 @@ -var n = 0; -var a = []; -for (var i = 0; i < 20; i++) - a[i] = {}; -a[18].watch("p", function () { n++; }); -delete a[18].p; -for (var i = 0; i < 20; i++) - a[i].p = 0; -assertEq(n, 1); diff --git a/js/src/jit-test/tests/basic/bug662562.js b/js/src/jit-test/tests/basic/bug662562.js deleted file mode 100644 index 45b48589a..000000000 --- a/js/src/jit-test/tests/basic/bug662562.js +++ /dev/null @@ -1,6 +0,0 @@ -// |jit-test| error: TypeError -function f(o) { - o.watch("x", this); -} -var c = evalcx(""); -f(c); diff --git a/js/src/jit-test/tests/basic/bug690292.js b/js/src/jit-test/tests/basic/bug690292.js deleted file mode 100644 index 43ab56dd7..000000000 --- a/js/src/jit-test/tests/basic/bug690292.js +++ /dev/null @@ -1,12 +0,0 @@ - -done = false; -try { - function x() {} - print(this.watch("d", Object.create)) - var d = {} -} catch (e) {} -try { - eval("d = ''") - done = true; -} catch (e) {} -assertEq(done, false); diff --git a/js/src/jit-test/tests/basic/bug696748.js b/js/src/jit-test/tests/basic/bug696748.js index fe171f976..33fb4d52f 100644 --- a/js/src/jit-test/tests/basic/bug696748.js +++ b/js/src/jit-test/tests/basic/bug696748.js @@ -1,6 +1,3 @@ -try { -this.watch("b", "".substring); -} catch(exc1) {} eval("\ var URI = '';\ test();\ diff --git a/js/src/jit-test/tests/basic/bug831846.js b/js/src/jit-test/tests/basic/bug831846.js deleted file mode 100644 index 30bb3aa86..000000000 --- a/js/src/jit-test/tests/basic/bug831846.js +++ /dev/null @@ -1,3 +0,0 @@ -// |jit-test| error:TypeError - -evalcx('').watch("", /()/); diff --git a/js/src/jit-test/tests/basic/testAssigningWatchedDeletedProperty.js b/js/src/jit-test/tests/basic/testAssigningWatchedDeletedProperty.js deleted file mode 100644 index c22eabed0..000000000 --- a/js/src/jit-test/tests/basic/testAssigningWatchedDeletedProperty.js +++ /dev/null @@ -1,7 +0,0 @@ -var o = {}; -o.watch("p", function() { }); - -for (var i = 0; i < 10; i++) { - o.p = 123; - delete o.p; -} diff --git a/js/src/jit-test/tests/basic/testBug566556.js b/js/src/jit-test/tests/basic/testBug566556.js deleted file mode 100644 index 244be57d2..000000000 --- a/js/src/jit-test/tests/basic/testBug566556.js +++ /dev/null @@ -1,9 +0,0 @@ -var msg = ""; -try { - this.__defineSetter__('x', Object.create); - this.watch('x', function() {}); - x = 3; -} catch (e) { - msg = e.toString(); -} -assertEq(msg, "TypeError: undefined is not an object or null"); diff --git a/js/src/jit-test/tests/basic/testBug578044.js b/js/src/jit-test/tests/basic/testBug578044.js deleted file mode 100644 index c5b811dc9..000000000 --- a/js/src/jit-test/tests/basic/testBug578044.js +++ /dev/null @@ -1,13 +0,0 @@ -this.watch("x", Object.create) -try { - (function() { - this.__defineGetter__("x", - function() { - return this - }) - })() -} catch(e) {} -Object.defineProperty(x, "x", ({ - set: Uint16Array -})) - diff --git a/js/src/jit-test/tests/basic/testBug584650.js b/js/src/jit-test/tests/basic/testBug584650.js deleted file mode 100644 index b6c9d8ab7..000000000 --- a/js/src/jit-test/tests/basic/testBug584650.js +++ /dev/null @@ -1,9 +0,0 @@ -if (typeof gczeal != "function") - gczeal = function() {} - -// don't crash -x = (evalcx('lazy')) -x.watch("", function () {}) -gczeal(1) -for (w in x) {} - diff --git a/js/src/jit-test/tests/basic/testBug780288-1.js b/js/src/jit-test/tests/basic/testBug780288-1.js deleted file mode 100644 index 90746a04a..000000000 --- a/js/src/jit-test/tests/basic/testBug780288-1.js +++ /dev/null @@ -1,20 +0,0 @@ -s = newGlobal() -try { - evalcx("\ - Object.defineProperty(this,\"i\",{enumerable:true,get:function(){t}});\ - for each(y in this)true\ - ", s) -} catch (e) {} -try { - evalcx("\ - for(z=0,(7).watch(\"\",eval);;g){\ - if(z=1){({t:function(){}})\ - }\ - ", s) -} catch (e) {} -try { - evalcx("\ - Object.defineProperty(this,\"g2\",{get:function(){return this}});\ - g2.y()\ - ", s) -} catch (e) {} diff --git a/js/src/jit-test/tests/basic/testBug780288-2.js b/js/src/jit-test/tests/basic/testBug780288-2.js deleted file mode 100644 index 8c4c1737c..000000000 --- a/js/src/jit-test/tests/basic/testBug780288-2.js +++ /dev/null @@ -1,20 +0,0 @@ -s = newGlobal() -try { - evalcx("\ - Object.defineProperty(this,\"i\",{enumerable:true,get:function(){t}});\ - for each(y in this)true\ - ", s) -} catch (e) {} -try { - evalcx("\ - for(z=0,(7).watch(\"\",eval);;g){\ - if(z=1){({t:function(){}})\ - }\ - ", s) -} catch (e) {} -try { - evalcx("\ - Object.defineProperty(this,\"g2\",{get:function(){return this}});\ - g2.y(\"\")\ - ", s) -} catch (e) {} diff --git a/js/src/jit-test/tests/basic/testEvalCalledFromWatchOverSetter.js b/js/src/jit-test/tests/basic/testEvalCalledFromWatchOverSetter.js deleted file mode 100644 index bcd60fd80..000000000 --- a/js/src/jit-test/tests/basic/testEvalCalledFromWatchOverSetter.js +++ /dev/null @@ -1,3 +0,0 @@ -this.__defineSetter__("x", function(){}); -this.watch("x", eval); -x = 0; diff --git a/js/src/jit-test/tests/basic/testNonStubGetter.js b/js/src/jit-test/tests/basic/testNonStubGetter.js deleted file mode 100644 index 58a698eb4..000000000 --- a/js/src/jit-test/tests/basic/testNonStubGetter.js +++ /dev/null @@ -1,7 +0,0 @@ -function testNonStubGetter() { - { let [] = []; (this.watch("x", function(p, o, n) { return /a/g.exec(p, o, n); })); }; - (function () { (eval("(function(){for each (x in [1, 2, 2]);});"))(); })(); - this.unwatch("x"); - return "ok"; -} -assertEq(testNonStubGetter(), "ok"); diff --git a/js/src/jit-test/tests/basic/testSettingWatchPointOnReadOnlyProp.js b/js/src/jit-test/tests/basic/testSettingWatchPointOnReadOnlyProp.js deleted file mode 100644 index 78c281f05..000000000 --- a/js/src/jit-test/tests/basic/testSettingWatchPointOnReadOnlyProp.js +++ /dev/null @@ -1,7 +0,0 @@ -for (var i = 0; i < 5; ++i) { - var o = {} - Object.defineProperty(o, 'x', { value:"cow", writable:false }); - var r = o.watch('x', function() {}); - assertEq(r, undefined); - o.x = 4; -} diff --git a/js/src/jit-test/tests/basic/testTrueShiftTrue.js b/js/src/jit-test/tests/basic/testTrueShiftTrue.js deleted file mode 100644 index 44c1290d8..000000000 --- a/js/src/jit-test/tests/basic/testTrueShiftTrue.js +++ /dev/null @@ -1,16 +0,0 @@ -// Test no assert or crash from outer recorders (bug 465145) -function testBug465145() { - this.__defineSetter__("x", function(){}); - this.watch("x", function(){}); - y = this; - for (var z = 0; z < 2; ++z) { x = y }; - this.__defineSetter__("x", function(){}); - for (var z = 0; z < 2; ++z) { x = y }; -} - -function testTrueShiftTrue() { - var a = new Array(5); - for (var i=0;i<5;++i) a[i] = "" + (true << true); - return a.join(","); -} -assertEq(testTrueShiftTrue(), "2,2,2,2,2"); diff --git a/js/src/jit-test/tests/basic/testWatchRecursion.js b/js/src/jit-test/tests/basic/testWatchRecursion.js deleted file mode 100644 index e5d5877df..000000000 --- a/js/src/jit-test/tests/basic/testWatchRecursion.js +++ /dev/null @@ -1,63 +0,0 @@ -// Test that the watch handler is not called recursively for the same object -// and property. -(function() { - var obj1 = {}, obj2 = {}; - var handler_entry_count = 0; - var handler_exit_count = 0; - - obj1.watch('x', handler); - obj1.watch('y', handler); - obj2.watch('x', handler); - obj1.x = 1; - assertEq(handler_entry_count, 3); - assertEq(handler_exit_count, 3); - - function handler(id) { - handler_entry_count++; - assertEq(handler_exit_count, 0); - switch (true) { - case this === obj1 && id === "x": - assertEq(handler_entry_count, 1); - obj2.x = 3; - assertEq(handler_exit_count, 2); - break; - case this === obj2 && id === "x": - assertEq(handler_entry_count, 2); - obj1.y = 4; - assertEq(handler_exit_count, 1); - break; - default: - assertEq(this, obj1); - assertEq(id, "y"); - assertEq(handler_entry_count, 3); - - // We expect no more watch handler invocations - obj1.x = 5; - obj1.y = 6; - obj2.x = 7; - assertEq(handler_exit_count, 0); - break; - } - ++handler_exit_count; - assertEq(handler_entry_count, 3); - } -})(); - - -// Test that run-away recursion in watch handlers is properly handled. -(function() { - var obj = {}; - var i = 0; - try { - handler(); - throw new Error("Unreachable"); - } catch(e) { - assertEq(e instanceof InternalError, true); - } - - function handler() { - var prop = "a" + ++i; - obj.watch(prop, handler); - obj[prop] = 2; - } -})(); diff --git a/js/src/jit-test/tests/gc/bug-900405.js b/js/src/jit-test/tests/gc/bug-900405.js deleted file mode 100644 index eeec6f25f..000000000 --- a/js/src/jit-test/tests/gc/bug-900405.js +++ /dev/null @@ -1,3 +0,0 @@ -(function() { - [{ "9": [] }.watch([], function(){})] -})() diff --git a/js/src/jit-test/tests/gc/bug-913261.js b/js/src/jit-test/tests/gc/bug-913261.js deleted file mode 100644 index 43066053f..000000000 --- a/js/src/jit-test/tests/gc/bug-913261.js +++ /dev/null @@ -1,5 +0,0 @@ -// |jit-test| error: InternalError: too much recursion -(function f() { - "".watch(2, function() {}); - f(); -})() diff --git a/js/src/jit-test/tests/gc/bug-986864.js b/js/src/jit-test/tests/gc/bug-986864.js deleted file mode 100644 index abb8de6b2..000000000 --- a/js/src/jit-test/tests/gc/bug-986864.js +++ /dev/null @@ -1,8 +0,0 @@ -// |jit-test| slow -function x() {} -for (var j = 0; j < 9999; ++j) { - (function() { - x += x.watch("endsWith", ArrayBuffer); - return 0 >> Function(x) - })() -} diff --git a/js/src/jit-test/tests/ion/bug1063182.js b/js/src/jit-test/tests/ion/bug1063182.js deleted file mode 100644 index 9cda48099..000000000 --- a/js/src/jit-test/tests/ion/bug1063182.js +++ /dev/null @@ -1,8 +0,0 @@ -// |jit-test| error: ReferenceError - -eval("(function() { " + "\ -var o = {};\ -o.watch('p', function() { });\ -for (var i = 0; i < 10; \u5ede ++)\ - o.p = 123;\ -" + " })();"); diff --git a/js/src/jit-test/tests/ion/bug772901.js b/js/src/jit-test/tests/ion/bug772901.js index eb71f6afb..164afd151 100644 --- a/js/src/jit-test/tests/ion/bug772901.js +++ b/js/src/jit-test/tests/ion/bug772901.js @@ -4,4 +4,4 @@ function f(x) { delete ((x)++); arguments[0] !== undefined; } -f(1, x = [f.ArrayBuffer,unwatch.Int32Array], this, this, this) ; +f(1, x = [f.ArrayBuffer, undefined], this, this, this) ; diff --git a/js/src/jit-test/tests/ion/bug774257-1.js b/js/src/jit-test/tests/ion/bug774257-1.js deleted file mode 100644 index 9c998a028..000000000 --- a/js/src/jit-test/tests/ion/bug774257-1.js +++ /dev/null @@ -1,8 +0,0 @@ -Object.defineProperty(Object.prototype, 'x', { - set: function() { evalcx('lazy'); } -}); -var obj = {}; -obj.watch("x", function (id, oldval, newval) {}); -for (var str in 'A') { - obj.x = 1; -} diff --git a/js/src/jit-test/tests/ion/bug774257-2.js b/js/src/jit-test/tests/ion/bug774257-2.js deleted file mode 100644 index b31043b08..000000000 --- a/js/src/jit-test/tests/ion/bug774257-2.js +++ /dev/null @@ -1,10 +0,0 @@ -Object.defineProperty(Object.prototype, 'x', { - set: function() { evalcx('lazy'); } -}); -var obj = {}; -var prot = {}; -obj.__proto__ = prot; -obj.watch("x", function (id, oldval, newval) {}); -for (var str in 'A') { - obj.x = 1; -} diff --git a/js/src/jit-test/tests/ion/bug779631.js b/js/src/jit-test/tests/ion/bug779631.js deleted file mode 100644 index 087aa01ac..000000000 --- a/js/src/jit-test/tests/ion/bug779631.js +++ /dev/null @@ -1,9 +0,0 @@ -var flag = 0; -var a = {}; -Object.defineProperty(a, "value", {set: function(x) {}}); -a.watch("value", function(){flag++;}); - -for(var i = 0; i < 100; i++) { - a.value = i; - assertEq(flag, i+1); -} diff --git a/js/src/jit-test/tests/ion/bug783590.js b/js/src/jit-test/tests/ion/bug783590.js index d48cb609e..9d277e02c 100644 --- a/js/src/jit-test/tests/ion/bug783590.js +++ b/js/src/jit-test/tests/ion/bug783590.js @@ -7,7 +7,6 @@ Object.defineProperty(arr, 0, { glob.__proto__; }) }); -this.watch("s", function() {}); try { arr.pop(); } catch (e) {} diff --git a/js/src/jit-test/tests/jaeger/bug550665.js b/js/src/jit-test/tests/jaeger/bug550665.js deleted file mode 100644 index 816888b5e..000000000 --- a/js/src/jit-test/tests/jaeger/bug550665.js +++ /dev/null @@ -1,8 +0,0 @@ -(function () { - var a; - eval("for(w in ((function(x,y){b:0})())) ;"); -})(); - -this.__defineSetter__("l", function() { gc() }); -this.watch("l", function(x) { yield {} }); -l = true; diff --git a/js/src/jit-test/tests/jaeger/bug557063.js b/js/src/jit-test/tests/jaeger/bug557063.js deleted file mode 100644 index ea77fd2c8..000000000 --- a/js/src/jit-test/tests/jaeger/bug557063.js +++ /dev/null @@ -1,7 +0,0 @@ -(function() { - for (a = 0; a < 2; a++) - ''.watch("", function() {}) -})() - -/* Don't crash or assert. */ - diff --git a/js/src/jit-test/tests/jaeger/bug588338.js b/js/src/jit-test/tests/jaeger/bug588338.js index 457be238a..c400d7687 100644 --- a/js/src/jit-test/tests/jaeger/bug588338.js +++ b/js/src/jit-test/tests/jaeger/bug588338.js @@ -9,7 +9,6 @@ function f() { } } })(/x/))) -for (z = 0; z < 100; x.unwatch(), z++) for (e in [0]) { gczeal(2) } ( [1,2,3])("") diff --git a/js/src/jit-test/tests/jaeger/bug625438.js b/js/src/jit-test/tests/jaeger/bug625438.js deleted file mode 100644 index 32586d8ab..000000000 --- a/js/src/jit-test/tests/jaeger/bug625438.js +++ /dev/null @@ -1,10 +0,0 @@ -// vim: set ts=8 sts=4 et sw=4 tw=99: - -var count = 0; -this.watch("x", function() { - count++; -}); -for(var i=0; i<10; i++) { - x = 2; -} -assertEq(count, 10); diff --git a/js/src/jit-test/tests/jaeger/bug630366.js b/js/src/jit-test/tests/jaeger/bug630366.js deleted file mode 100644 index acac8d3ef..000000000 --- a/js/src/jit-test/tests/jaeger/bug630366.js +++ /dev/null @@ -1,7 +0,0 @@ -var o = {}; -for(var i=0; i<5; i++) { - o.p = 2; - o.watch("p", function() { }); - o.p = 2; - delete o.p; -} diff --git a/js/src/jit-test/tests/jaeger/recompile/bug641225.js b/js/src/jit-test/tests/jaeger/recompile/bug641225.js index a6e3a86c7..7b7978197 100644 --- a/js/src/jit-test/tests/jaeger/recompile/bug641225.js +++ b/js/src/jit-test/tests/jaeger/recompile/bug641225.js @@ -118,7 +118,6 @@ for(var o2 in f5) { f2(o5); f2(o5); f0(o3); - o9.watch('p3', function() {}); o8[o8] = o8; f0(o5); f1(o6); diff --git a/js/src/jit-test/tests/pic/fuzz1.js b/js/src/jit-test/tests/pic/fuzz1.js deleted file mode 100644 index 2481a1314..000000000 --- a/js/src/jit-test/tests/pic/fuzz1.js +++ /dev/null @@ -1,4 +0,0 @@ -(function() { - for (a = 0; a < 2; a++) - ''.watch("", function() {}) -})() diff --git a/js/src/jit-test/tests/pic/fuzz3.js b/js/src/jit-test/tests/pic/fuzz3.js deleted file mode 100644 index 17613b6f5..000000000 --- a/js/src/jit-test/tests/pic/fuzz3.js +++ /dev/null @@ -1,3 +0,0 @@ -for each(let w in [[], 0, [], 0]) { - w.unwatch() -} diff --git a/js/src/jit-test/tests/pic/watch1.js b/js/src/jit-test/tests/pic/watch1.js deleted file mode 100644 index 09d6347bf..000000000 --- a/js/src/jit-test/tests/pic/watch1.js +++ /dev/null @@ -1,7 +0,0 @@ -// assignments to watched objects must not be cached -var obj = {x: 0}; -var hits = 0; -obj.watch("x", function (id, oldval, newval) { hits++; return newval; }); -for (var i = 0; i < 10; i++) - obj.x = i; -assertEq(hits, 10); diff --git a/js/src/jit-test/tests/pic/watch1a.js b/js/src/jit-test/tests/pic/watch1a.js deleted file mode 100644 index 4b404f507..000000000 --- a/js/src/jit-test/tests/pic/watch1a.js +++ /dev/null @@ -1,17 +0,0 @@ -// assignments to watched objects must not be traced -var hits = 0; -function counter(id, oldval, newval) { - hits++; - return newval; -} - -(function () { - var obj = {x: 0, y: 0}; - var a = ['x', 'y']; - obj.watch('z', counter); - for (var i = 0; i < 14; i++) { - obj.watch(a[+(i > 8)], counter); - obj.y = i; - } -})(); -assertEq(hits, 5); diff --git a/js/src/jit-test/tests/pic/watch2.js b/js/src/jit-test/tests/pic/watch2.js deleted file mode 100644 index fb3e29617..000000000 --- a/js/src/jit-test/tests/pic/watch2.js +++ /dev/null @@ -1,8 +0,0 @@ -// assignments to watched properties via ++ must not be cached -var obj = {x: 0}; -var hits = 0; -obj.watch("x", function (id, oldval, newval) { hits++; return newval; }); -for (var i = 0; i < 10; i++) - obj.x++; -assertEq(hits, 10); - diff --git a/js/src/jit-test/tests/pic/watch2a.js b/js/src/jit-test/tests/pic/watch2a.js deleted file mode 100644 index ce3294ba3..000000000 --- a/js/src/jit-test/tests/pic/watch2a.js +++ /dev/null @@ -1,18 +0,0 @@ -// assignments to watched properties via ++ must not be traced -var hits = 0; -function counter(id, oldval, newval) { - hits++; - return newval; -} - -(function () { - var obj = {x: 0, y: 0}; - var a = ['x', 'y']; - obj.watch('z', counter); - for (var i = 0; i < 14; i++) { - obj.watch(a[+(i > 8)], counter); - obj.y++; - } -})(); -assertEq(hits, 5); - diff --git a/js/src/jit-test/tests/pic/watch3.js b/js/src/jit-test/tests/pic/watch3.js deleted file mode 100644 index 4c5c93d8b..000000000 --- a/js/src/jit-test/tests/pic/watch3.js +++ /dev/null @@ -1,7 +0,0 @@ -// assignment to watched global properties must not be cached -x = 0; -var hits = 0; -this.watch("x", function (id, oldval, newval) { hits++; return newval; }); -for (var i = 0; i < 10; i++) - x = i; -assertEq(hits, 10); diff --git a/js/src/jit-test/tests/pic/watch3a.js b/js/src/jit-test/tests/pic/watch3a.js deleted file mode 100644 index 700daf6af..000000000 --- a/js/src/jit-test/tests/pic/watch3a.js +++ /dev/null @@ -1,19 +0,0 @@ -// assignment to watched global properties must not be traced -var hits = 0; -function counter(id, oldval, newval) { - hits++; - return newval; -} - -var x = 0; -var y = 0; -(function () { - var a = ['x', 'y']; - this.watch('z', counter); - for (var i = 0; i < 14; i++) { - this.watch(a[+(i > 8)], counter); - y = 1; - } -})(); -assertEq(hits, 5); - diff --git a/js/src/jit-test/tests/pic/watch3b.js b/js/src/jit-test/tests/pic/watch3b.js deleted file mode 100644 index 9b0dc8cc9..000000000 --- a/js/src/jit-test/tests/pic/watch3b.js +++ /dev/null @@ -1,20 +0,0 @@ -// assignment to watched global properties must not be traced -var hits = 0; -function counter(id, oldval, newval) { - hits++; - return newval; -} - -var x = 0; -var y = 0; -function f() { - var a = [{}, this]; - for (var i = 0; i < 14; i++) { - print(shapeOf(this)); - Object.prototype.watch.call(a[+(i > 8)], "y", counter); - y++; - } -} -f(); -assertEq(hits, 5); - diff --git a/js/src/jit-test/tests/pic/watch4.js b/js/src/jit-test/tests/pic/watch4.js deleted file mode 100644 index 4b640c4df..000000000 --- a/js/src/jit-test/tests/pic/watch4.js +++ /dev/null @@ -1,9 +0,0 @@ -// adding assignment + watchpoint vs. caching -var hits = 0; -var obj = {}; -obj.watch("x", function (id, oldval, newval) { hits++; return newval; }); -for (var i = 0; i < 10; i++) { - obj.x = 1; - delete obj.x; -} -assertEq(hits, 10); diff --git a/js/src/jit-test/tests/pic/watch5.js b/js/src/jit-test/tests/pic/watch5.js deleted file mode 100644 index 6b22951a4..000000000 --- a/js/src/jit-test/tests/pic/watch5.js +++ /dev/null @@ -1,27 +0,0 @@ -// test against future pic support for symbols - -// assignments to watched objects must not be cached -var obj = {}; -var x = Symbol.for("x"); -obj[x] = 0; -var hits = 0; -obj.watch(x, function (id, oldval, newval) { hits++; return newval; }); -for (var i = 0; i < 10; i++) - obj[x] = i; -assertEq(hits, 10); - -// assignments to watched properties via ++ must not be cached -hits = 0; -for (var i = 0; i < 10; i++) - obj[x]++; -assertEq(hits, 10); - -// adding assignment + watchpoint vs. caching -hits = 0; -obj = {}; -obj.watch(x, function (id, oldval, newval) { hits++; return newval; }); -for (var i = 0; i < 10; i++) { - obj[x] = 1; - delete obj[x]; -} -assertEq(hits, 10); diff --git a/js/src/jit-test/tests/profiler/bug1140643.js b/js/src/jit-test/tests/profiler/bug1140643.js deleted file mode 100644 index 1b171aea2..000000000 --- a/js/src/jit-test/tests/profiler/bug1140643.js +++ /dev/null @@ -1,14 +0,0 @@ -// |jit-test| allow-oom -enableSPSProfiling(); -loadFile('\ -for (var i = 0; i < 2; i++) {\ - obj = { m: function () {} };\ - obj.watch("m", function () { float32 = 0 + obj.foo; });\ - obj.m = 0;\ -}\ -'); -gcparam("maxBytes", gcparam("gcBytes") + (1)*1024); -newGlobal("same-compartment"); -function loadFile(lfVarx) { - evaluate(lfVarx, { noScriptRval : true, isRunOnce : true }); -} diff --git a/js/src/tests/ecma_5/Array/frozen-dense-array.js b/js/src/tests/ecma_5/Array/frozen-dense-array.js index 9db63036f..cdb86daa3 100644 --- a/js/src/tests/ecma_5/Array/frozen-dense-array.js +++ b/js/src/tests/ecma_5/Array/frozen-dense-array.js @@ -38,24 +38,5 @@ assertEq(delete a[0], false); assertArrayIsExpected(); -var watchpointCalled = false; -// NOTE: Be careful with the position of this test, since this sparsifies the -// elements and you might not test what you think you're testing otherwise. -a.watch(2, function(prop, oldValue, newValue) { - watchpointCalled = true; - assertEq(prop, 2); - assertEq(oldValue, 1); - assertEq(newValue, "foo"); -}); - -assertArrayIsExpected(); - -a.length = 5; -a[2] = "foo"; -assertEq(watchpointCalled, true); -assertEq(delete a[0], false); - -assertArrayIsExpected(); - if (typeof reportCompare === "function") reportCompare(true, true); diff --git a/js/src/tests/ecma_5/extensions/watch-array-length.js b/js/src/tests/ecma_5/extensions/watch-array-length.js deleted file mode 100644 index e9b356efa..000000000 --- a/js/src/tests/ecma_5/extensions/watch-array-length.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - */ - -var hitCount; -function watcher(p,o,n) { hitCount++; return n; } - -var a = [1]; -a.watch('length', watcher); -hitCount = 0; -a.length = 0; -reportCompare(1, hitCount, "lenient; configurable: watchpoint hit"); - -var b = Object.defineProperty([1],'0',{configurable:false}); -b.watch('length', watcher); -hitCount = 0; -var result; -try { - b.length = 0; - result = "no error"; -} catch (x) { - result = x.toString(); -} -reportCompare(1, hitCount, "lenient; non-configurable: watchpoint hit"); -reportCompare(1, b.length, "lenient; non-configurable: length unchanged"); -reportCompare("no error", result, "lenient; non-configurable: no error thrown"); - -var c = Object.defineProperty([1],'0',{configurable:false}); -c.watch('length', watcher); -hitCount = 0; -var threwTypeError; -try { - (function(){'use strict'; c.length = 0;})(); - threwTypeError = false; -} catch (x) { - threwTypeError = x instanceof TypeError; -} -reportCompare(1, hitCount, "strict; non-configurable: watchpoint hit"); -reportCompare(1, c.length, "strict; non-configurable: length unchanged"); -reportCompare(true, threwTypeError, "strict; non-configurable: TypeError thrown"); diff --git a/js/src/tests/ecma_5/extensions/watch-inherited-property.js b/js/src/tests/ecma_5/extensions/watch-inherited-property.js deleted file mode 100644 index 1a0ad566b..000000000 --- a/js/src/tests/ecma_5/extensions/watch-inherited-property.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - */ - -/* Create a prototype object with a setter property. */ -var protoSetterCount; -var proto = ({ set x(v) { protoSetterCount++; } }); - -/* Put a watchpoint on that setter. */ -var protoWatchCount; -proto.watch('x', function() { protoWatchCount++; }); - -/* Make an object with the above as its prototype. */ -function C() { } -C.prototype = proto; -var o = new C(); - -/* - * Set a watchpoint on the property in the inheriting object. We have - * defined this to mean "duplicate the property, setter and all, in the - * inheriting object." I don't think debugging observation mechanisms - * should mutate the program being run, but that's what we've got. - */ -var oWatchCount; -o.watch('x', function() { oWatchCount++; }); - -/* - * Assign to the property. This should trip the watchpoint on the inheriting object and - * the setter. - */ -protoSetterCount = protoWatchCount = oWatchCount = 0; -o.x = 1; -assertEq(protoWatchCount, 0); -assertEq(oWatchCount, 1); -assertEq(protoSetterCount, 1); - -reportCompare(true, true); diff --git a/js/src/tests/ecma_5/extensions/watch-replaced-setter.js b/js/src/tests/ecma_5/extensions/watch-replaced-setter.js deleted file mode 100644 index 05cf60aff..000000000 --- a/js/src/tests/ecma_5/extensions/watch-replaced-setter.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - */ - -/* A stock watcher function. */ -var watcherCount; -function watcher(id, oldval, newval) { watcherCount++; return newval; } - -/* Create an object with a JavaScript setter. */ -var setterCount; -var o = { w:2, set x(v) { setterCount++; } }; - -/* - * Put the object in dictionary mode, so that JSObject::putProperty will - * mutate its shapes instead of creating new ones. - */ -delete o.w; - -/* - * Place a watchpoint on the property. The watchpoint structure holds the - * original JavaScript setter, and a pointer to the shape. - */ -o.watch('x', watcher); - -/* - * Replace the accessor property with a value property. The shape's setter - * should become a non-JS setter, js_watch_set, and the watchpoint - * structure's saved setter should be updated (in this case, cleared). - */ -Object.defineProperty(o, 'x', { value:3, - writable:true, - enumerable:true, - configurable:true }); - -/* - * Assign to the property. This should trigger js_watch_set, which should - * call the handler, and then see that there is no JS-level setter to pass - * control on to, and return. - */ -watcherCount = setterCount = 0; -o.x = 3; -assertEq(watcherCount, 1); -assertEq(setterCount, 0); - -reportCompare(true, true); diff --git a/js/src/tests/ecma_5/extensions/watch-setter-become-setter.js b/js/src/tests/ecma_5/extensions/watch-setter-become-setter.js deleted file mode 100644 index f5eff98b8..000000000 --- a/js/src/tests/ecma_5/extensions/watch-setter-become-setter.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - */ - -/* Create an object with a JavaScript setter. */ -var firstSetterCount; -var o = { w:2, set x(v) { firstSetterCount++; } }; - -/* - * Put the object in dictionary mode, so that JSObject::putProperty will - * mutate its shapes instead of creating new ones. - */ -delete o.w; - -/* A stock watcher function. */ -var watcherCount; -function watcher(id, oldval, newval) { watcherCount++; return newval; } - -/* - * Place a watchpoint on the property. The property's shape now has the - * watchpoint setter, with the original setter saved in the watchpoint - * structure. - */ -o.watch('x', watcher); - -/* - * Replace the setter with a new setter. The shape should get updated to - * refer to the new setter, and then the watchpoint setter should be - * re-established. - */ -var secondSetterCount; -Object.defineProperty(o, 'x', { set: function () { secondSetterCount++ } }); - -/* - * Assign to the property. This should trigger the watchpoint and the new setter. - */ -watcherCount = firstSetterCount = secondSetterCount = 0; -o.x = 3; -assertEq(watcherCount, 1); -assertEq(firstSetterCount, 0); -assertEq(secondSetterCount, 1); - -reportCompare(true, true); diff --git a/js/src/tests/ecma_5/extensions/watch-value-prop-becoming-setter.js b/js/src/tests/ecma_5/extensions/watch-value-prop-becoming-setter.js deleted file mode 100644 index 03cdd788e..000000000 --- a/js/src/tests/ecma_5/extensions/watch-value-prop-becoming-setter.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - */ - -/* A stock watcher function. */ -var watcherCount; -function watcher(id, old, newval) { - watcherCount++; - return newval; -} - -/* Create an object with a value property. */ -var o = { w:2, x:3 }; - -/* - * Place a watchpoint on the value property. The watchpoint structure holds - * the original JavaScript setter, and a pointer to the shape. - */ -o.watch('x', watcher); - -/* - * Put the object in dictionary mode, so that JSObject::putProperty will - * mutate its shapes instead of creating new ones. - */ -delete o.w; - -/* - * Replace the value property with a setter. - */ -var setterCount; -o.__defineSetter__('x', function() { setterCount++; }); - -/* - * Trigger the watchpoint. The watchpoint handler should run, and then the - * setter should run. - */ -watcherCount = setterCount = 0; -o.x = 4; -assertEq(watcherCount, 1); -assertEq(setterCount, 1); - -reportCompare(true, true); diff --git a/js/src/tests/ecma_5/extensions/watchpoint-deletes-JSPropertyOp-setter.js b/js/src/tests/ecma_5/extensions/watchpoint-deletes-JSPropertyOp-setter.js deleted file mode 100644 index 85410bbd4..000000000 --- a/js/src/tests/ecma_5/extensions/watchpoint-deletes-JSPropertyOp-setter.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - */ - -function make_watcher(name) { - return function (id, oldv, newv) { - print("watched " + name + "[0]"); - }; -} - -var o, p; -function f(flag) { - if (flag) { - o = arguments; - } else { - p = arguments; - o.watch(0, make_watcher('o')); - p.watch(0, make_watcher('p')); - - /* - * Previously, the watchpoint implementation actually substituted its magic setter - * functions for the setters of shared shapes, and then 1) carefully ignored calls - * to its magic setter from unrelated objects, and 2) avoided restoring the - * original setter until all watchpoints on that shape had been removed. - * - * However, when the watchpoint code began using JSObject::changeProperty and - * js_ChangeNativePropertyAttrs to change shapes' setters, the shape tree code - * became conscious of the presence of watchpoints, and shared shapes between - * objects only when their watchpoint nature coincided. Clearing the magic setter - * from one object's shape would not affect other objects, because the - * watchpointed and non-watchpointed shapes were distinct if they were shared. - * - * Thus, the first unwatch call must go ahead and fix p's shape, even though a - * watchpoint exists on the same shape in o. o's watchpoint's presence shouldn't - * cause 'unwatch' to leave p's magic setter in place. - */ - - /* DropWatchPointAndUnlock would see o's watchpoint, and not change p's property. */ - p.unwatch(0); - - /* DropWatchPointAndUnlock would fix o's property, but not p's; p's setter would be gone. */ - o.unwatch(0); - - /* This would fail to invoke the arguments object's setter. */ - p[0] = 4; - - /* And the formal parameter would not get updated. */ - assertEq(flag, 4); - } -} - -f(true); -f(false); - -reportCompare(true, true); diff --git a/js/src/tests/js1_5/Object/regress-362872-01.js b/js/src/tests/js1_5/Object/regress-362872-01.js deleted file mode 100644 index 0808ee82b..000000000 --- a/js/src/tests/js1_5/Object/regress-362872-01.js +++ /dev/null @@ -1,41 +0,0 @@ -/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 362872; -var summary = 'script should not drop watchpoint that is in use'; -var actual = 'No Crash'; -var expect = 'No Crash'; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - function exploit() { - var rooter = {}, object = {}, filler1 = "", filler2 = "\u5555"; - for(var i = 0; i < 32/2-2; i++) { filler1 += "\u5050"; } - object.watch("foo", function(){ - object.unwatch("foo"); - object.unwatch("foo"); - for(var i = 0; i < 8 * 1024; i++) { - rooter[i] = filler1 + filler2; - } - }); - object.foo = "bar"; - } - exploit(); - - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/Object/regress-362872-02.js b/js/src/tests/js1_5/Object/regress-362872-02.js deleted file mode 100644 index edee43a4a..000000000 --- a/js/src/tests/js1_5/Object/regress-362872-02.js +++ /dev/null @@ -1,24 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - * Contributor: Blake Kaplan - */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 362872; -var summary = 'script should not drop watchpoint that is in use'; -var actual = 'No Crash'; -var expect = 'No Crash'; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -this.watch('x', function f() { - print("before"); - x = 3; - print("after"); - }); -x = 3; - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/Regress/regress-127243.js b/js/src/tests/js1_5/Regress/regress-127243.js deleted file mode 100644 index 11779f803..000000000 --- a/js/src/tests/js1_5/Regress/regress-127243.js +++ /dev/null @@ -1,75 +0,0 @@ -// |reftest| skip-if(xulRuntime.OS=="WINNT"&&isDebugBuild) slow -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 127243; -var summary = 'Do not crash on watch'; -var actual = 'No Crash'; -var expect = 'No Crash'; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -if (typeof window != 'undefined' && typeof document != 'undefined') -{ - // delay test driver end - gDelayTestDriverEnd = true; - window.addEventListener('load', handleLoad, false); -} -else -{ - printStatus('This test must be run in the browser'); - reportCompare(expect, actual, summary); - -} - -var div; - -function handleLoad() -{ - div = document.createElement('div'); - document.body.appendChild(div); - div.setAttribute('id', 'id1'); - div.style.width = '50px'; - div.style.height = '100px'; - div.style.overflow = 'auto'; - - for (var i = 0; i < 5; i++) - { - var p = document.createElement('p'); - var t = document.createTextNode('blah'); - p.appendChild(t); - div.appendChild(p); - } - - div.watch('scrollTop', wee); - - setTimeout('setScrollTop()', 1000); -} - -function wee(id, oldval, newval) -{ - var t = document.createTextNode('setting ' + id + - ' value ' + div.scrollTop + - ' oldval ' + oldval + - ' newval ' + newval); - var p = document.createElement('p'); - p.appendChild(t); - document.body.appendChild(p); - - return newval; -} - -function setScrollTop() -{ - div.scrollTop = 42; - - reportCompare(expect, actual, summary); - - gDelayTestDriverEnd = false; - jsTestDriverEnd(); - -} diff --git a/js/src/tests/js1_5/Regress/regress-213482.js b/js/src/tests/js1_5/Regress/regress-213482.js deleted file mode 100644 index 26ed7e894..000000000 --- a/js/src/tests/js1_5/Regress/regress-213482.js +++ /dev/null @@ -1,29 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 213482; -var summary = 'Do not crash watching property when watcher sets property'; -var actual = 'No Crash'; -var expect = 'No Crash'; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -var testobj = {value: 'foo'}; - -function watched (a, b, c) { - testobj.value = (new Date()).getTime(); -} - -function setTest() { - testobj.value = 'b'; -} - -testobj.watch("value", watched); - -setTest(); - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/Regress/regress-240577.js b/js/src/tests/js1_5/Regress/regress-240577.js deleted file mode 100644 index ba8330aa0..000000000 --- a/js/src/tests/js1_5/Regress/regress-240577.js +++ /dev/null @@ -1,37 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - * Contributor: Bob Clary - */ - -//----------------------------------------------------------------------------- -// originally reported by Jens Thiele in -var BUGNUMBER = 240577; -var summary = 'object.watch execution context'; -var actual = ''; -var expect = ''; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -var createWatcher = function ( watchlabel ) -{ - var watcher = function (property, oldvalue, newvalue) - { - actual += watchlabel; return newvalue; - }; - return watcher; -}; - -var watcher1 = createWatcher('watcher1'); - -var object = {property: 'value'}; - -object.watch('property', watcher1); - -object.property = 'newvalue'; - -expect = 'watcher1'; - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/Regress/regress-355341.js b/js/src/tests/js1_5/Regress/regress-355341.js deleted file mode 100644 index ab2a4b884..000000000 --- a/js/src/tests/js1_5/Regress/regress-355341.js +++ /dev/null @@ -1,29 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 355341; -var summary = 'Do not crash with watch and setter'; -var actual = ''; -var expect = ''; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - Object.defineProperty(this, "x", { set: Function, enumerable: true, configurable: true }); - this.watch('x', function () { }); x = 3; - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/Regress/regress-355344.js b/js/src/tests/js1_5/Regress/regress-355344.js deleted file mode 100644 index 00bd39147..000000000 --- a/js/src/tests/js1_5/Regress/regress-355344.js +++ /dev/null @@ -1,49 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 355344; -var summary = 'Exceptions thrown by watch point'; -var actual = ''; -var expect = ''; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - var o = {}; - - expect = 'setter: yikes'; - - o.watch('x', function(){throw 'yikes'}); - try - { - o.x = 3; - } - catch(ex) - { - actual = "setter: " + ex; - } - - try - { - eval("") ; - } - catch(e) - { - actual = "eval: " + e; - } - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/Regress/regress-361467.js b/js/src/tests/js1_5/Regress/regress-361467.js deleted file mode 100644 index 371c0a8b5..000000000 --- a/js/src/tests/js1_5/Regress/regress-361467.js +++ /dev/null @@ -1,32 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 361467; -var summary = 'Do not crash with certain watchers'; -var actual = ''; -var expect = ''; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - expect = actual = 'No Crash'; - - var x; - this.watch('x', print); - x = 5; - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/Regress/regress-361617.js b/js/src/tests/js1_5/Regress/regress-361617.js deleted file mode 100644 index 5d20fd78f..000000000 --- a/js/src/tests/js1_5/Regress/regress-361617.js +++ /dev/null @@ -1,35 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 361617; -var summary = 'Do not crash with getter, watch and gc'; -var actual = 'No Crash'; -var expect = 'No Crash'; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - (function() { - Object.defineProperty(this, "x", { get: function(){}, enumerable: true, configurable: true }); - })(); - this.watch('x', print); - Object.defineProperty(this, "x", { get: function(){}, enumerable: true, configurable: true }); - gc(); - this.unwatch('x'); - x; - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/Regress/regress-385393-06.js b/js/src/tests/js1_5/Regress/regress-385393-06.js deleted file mode 100644 index 327103f39..000000000 --- a/js/src/tests/js1_5/Regress/regress-385393-06.js +++ /dev/null @@ -1,28 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - - -//----------------------------------------------------------------------------- -var BUGNUMBER = 385393; -var summary = 'Regression test for bug 385393'; -var actual = 'No Crash'; -var expect = 'No Crash'; - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - reportCompare(expect, actual, summary); - - true.watch("x", function(){}); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/Regress/regress-506567.js b/js/src/tests/js1_5/Regress/regress-506567.js deleted file mode 100644 index e78dfe1db..000000000 --- a/js/src/tests/js1_5/Regress/regress-506567.js +++ /dev/null @@ -1,45 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 506567; -var summary = 'Do not crash with watched variables'; -var actual = ''; -var expect = ''; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - if (typeof clearInterval == 'undefined') - { - clearInterval = (function () {}); - } - - var obj = new Object(); - obj.test = null; - obj.watch("test", (function(prop, oldval, newval) - { - if(false) - { - var test = newval % oldval; - var func = (function(){clearInterval(myInterval);}); - } - })); - - obj.test = 'null'; - print(obj.test); - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/extensions/regress-303277.js b/js/src/tests/js1_5/extensions/regress-303277.js deleted file mode 100644 index 319d9a2ed..000000000 --- a/js/src/tests/js1_5/extensions/regress-303277.js +++ /dev/null @@ -1,19 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 303277; -var summary = 'Do not crash with crash with a watchpoint for __proto__ property '; -var actual = 'No Crash'; -var expect = 'No Crash'; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -var o = {}; -o.watch("__proto__", function(){return null;}); -o.__proto__ = null; - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/extensions/regress-355339.js b/js/src/tests/js1_5/extensions/regress-355339.js deleted file mode 100644 index 9b15bd742..000000000 --- a/js/src/tests/js1_5/extensions/regress-355339.js +++ /dev/null @@ -1,32 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 355339; -var summary = 'Do not assert: sprop->setter != js_watch_set'; -var actual = ''; -var expect = ''; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - expect = actual = 'No Crash'; - o = {}; - o.watch("j", function(a,b,c) { print("*",a,b,c) }); - o.unwatch("j"); - o.watch("j", function(a,b,c) { print("*",a,b,c) }); - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/extensions/regress-361346.js b/js/src/tests/js1_5/extensions/regress-361346.js deleted file mode 100644 index 297c3b1f2..000000000 --- a/js/src/tests/js1_5/extensions/regress-361346.js +++ /dev/null @@ -1,22 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 361346; -var summary = 'Crash with setter, watch, GC'; -var actual = ''; -var expect = ''; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -expect = actual = 'No Crash'; - -Object.defineProperty(this, "x", { set: new Function, enumerable: true, configurable: true }); -this.watch('x', function(){}); -gc(); -x = {}; - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/extensions/regress-361360.js b/js/src/tests/js1_5/extensions/regress-361360.js deleted file mode 100644 index 98e6575d9..000000000 --- a/js/src/tests/js1_5/extensions/regress-361360.js +++ /dev/null @@ -1,32 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 361360; -var summary = 'Do not assert: !caller || caller->pc involving setter and watch'; -var actual = ''; -var expect = ''; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - expect = actual = 'No Crash'; - - this.__defineSetter__('x', eval); - this.watch('x', function(){}); - x = 3; - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/extensions/regress-361552.js b/js/src/tests/js1_5/extensions/regress-361552.js deleted file mode 100644 index eed54e6dd..000000000 --- a/js/src/tests/js1_5/extensions/regress-361552.js +++ /dev/null @@ -1,27 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 361552; -var summary = 'Crash with setter, watch, Script'; -var actual = ''; -var expect = ''; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -expect = actual = 'No Crash'; - -if (typeof Script == 'undefined') -{ - print('Test skipped. Script not defined.'); -} -else -{ - this.__defineSetter__('x', gc); - this.watch('x', new Script('')); - x = 3; -} -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/extensions/regress-361558.js b/js/src/tests/js1_5/extensions/regress-361558.js deleted file mode 100644 index a9a3ae725..000000000 --- a/js/src/tests/js1_5/extensions/regress-361558.js +++ /dev/null @@ -1,19 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 361558; -var summary = 'Do not assert: sprop->setter != js_watch_set'; -var actual = ''; -var expect = ''; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -expect = actual = 'No Crash'; - -({}.__proto__.watch('x', print)); ({}.watch('x', print)); - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/extensions/regress-361571.js b/js/src/tests/js1_5/extensions/regress-361571.js deleted file mode 100644 index bf89d794b..000000000 --- a/js/src/tests/js1_5/extensions/regress-361571.js +++ /dev/null @@ -1,38 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 361571; -var summary = 'Do not assert: fp->scopeChain == parent'; -var actual = 'No Crash'; -var expect = 'No Crash'; - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - try - { - o = {}; - o.__defineSetter__('y', eval); - o.watch('y', function () { return "";}); - o.y = 1; - } - catch(ex) - { - printStatus('Note eval can no longer be called directly'); - expect = 'EvalError: function eval must be called directly, and not by way of a function of another name'; - actual = ex + ''; - } - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/extensions/regress-361856.js b/js/src/tests/js1_5/extensions/regress-361856.js deleted file mode 100644 index e7e2f675b..000000000 --- a/js/src/tests/js1_5/extensions/regress-361856.js +++ /dev/null @@ -1,35 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 361856; -var summary = 'Do not assert: overwriting @ js_AddScopeProperty'; -var actual = ''; -var expect = ''; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - function testit() { - var obj = {}; - obj.watch("foo", function(){}); - delete obj.foo; - obj = null; - gc(); - } - testit(); - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/extensions/regress-361964.js b/js/src/tests/js1_5/extensions/regress-361964.js deleted file mode 100644 index fcb8bba01..000000000 --- a/js/src/tests/js1_5/extensions/regress-361964.js +++ /dev/null @@ -1,54 +0,0 @@ -// |reftest| skip -- slow, alert not dismissed, now busted by harness -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 361964; -var summary = 'Crash [@ MarkGCThingChildren] involving watch and setter'; -var actual = 'No Crash'; -var expect = 'No Crash'; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - var doc; - if (typeof document == 'undefined') - { - doc = {}; - } - else - { - doc = document; - } - - if (typeof alert == 'undefined') - { - alert = print; - } - -// Crash: - doc.watch("title", function(a,b,c,d) { - return { toString : function() { alert(1); } }; - }); - doc.title = "xxx"; - -// No crash: - doc.watch("title", function() { - return { toString : function() { alert(1); } }; - }); - doc.title = "xxx"; - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/extensions/regress-385134.js b/js/src/tests/js1_5/extensions/regress-385134.js deleted file mode 100644 index 041f4d6e7..000000000 --- a/js/src/tests/js1_5/extensions/regress-385134.js +++ /dev/null @@ -1,38 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 385134; -var summary = 'Do not crash with setter, watch, uneval'; -var actual = 'No Crash'; -var expect = 'No Crash'; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - if (typeof this.__defineSetter__ != 'undefined' && - typeof this.watch != 'undefined' && - typeof uneval != 'undefined') - { - try { - this.__defineSetter__(0, function(){}); - } catch (exc) { - // In the browser, this fails. Ignore the error. - } - this.watch(0, function(){}); - uneval(this); - } - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/extensions/regress-385393-09.js b/js/src/tests/js1_5/extensions/regress-385393-09.js deleted file mode 100644 index 42834824a..000000000 --- a/js/src/tests/js1_5/extensions/regress-385393-09.js +++ /dev/null @@ -1,18 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - - -//----------------------------------------------------------------------------- -var BUGNUMBER = 385393; -var summary = 'Regression test for bug 385393'; -var actual = 'No Crash'; -var expect = 'No Crash'; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -eval("this.__defineSetter__('x', gc); this.watch('x', [].slice); x = 1;"); - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/extensions/regress-390597.js b/js/src/tests/js1_5/extensions/regress-390597.js deleted file mode 100644 index 9f8596adc..000000000 --- a/js/src/tests/js1_5/extensions/regress-390597.js +++ /dev/null @@ -1,42 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 390597; -var summary = 'watch point + eval-as-setter allows access to dead JSStackFrame'; -var actual = 'No Crash'; -var expect = 'No Crash'; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - function exploit() { - try - { - var obj = this, args = null; - obj.__defineSetter__("evil", eval); - obj.watch("evil", function() { return "args = arguments;"; }); - obj.evil = null; - eval("print(args[0]);"); - } - catch(ex) - { - print('Caught ' + ex); - } - } - exploit(); - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_5/extensions/regress-420612.js b/js/src/tests/js1_5/extensions/regress-420612.js deleted file mode 100644 index a4491095e..000000000 --- a/js/src/tests/js1_5/extensions/regress-420612.js +++ /dev/null @@ -1,21 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 420612; -var summary = 'Do not assert: obj == pobj'; -var actual = 'No Crash'; -var expect = 'No Crash'; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -var obj = Object.create([]); -obj.unwatch("x"); - -if (typeof reportCompare === "function") - reportCompare(true, true); - -print("Tests complete"); diff --git a/js/src/tests/js1_5/extensions/regress-435345-01.js b/js/src/tests/js1_5/extensions/regress-435345-01.js deleted file mode 100644 index 28beab473..000000000 --- a/js/src/tests/js1_5/extensions/regress-435345-01.js +++ /dev/null @@ -1,100 +0,0 @@ -// |reftest| fails -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 435345; -var summary = 'Watch the length property of arrays'; -var actual = ''; -var expect = ''; - -// see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Object:watch - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - var arr; - - try - { - expect = 'watcher: propname=length, oldval=0, newval=1; '; - actual = ''; - arr = []; - arr.watch('length', watcher); - arr[0] = '0'; - } - catch(ex) - { - actual = ex + ''; - } - reportCompare(expect, actual, summary + ': 1'); - - try - { - expect = 'watcher: propname=length, oldval=1, newval=2; ' + - 'watcher: propname=length, oldval=2, newval=2; '; - actual = ''; - arr.push(5); - } - catch(ex) - { - actual = ex + ''; - } - reportCompare(expect, actual, summary + ': 2'); - - try - { - expect = 'watcher: propname=length, oldval=2, newval=1; '; - actual = ''; - arr.pop(); - } - catch(ex) - { - actual = ex + ''; - } - reportCompare(expect, actual, summary + ': 3'); - - try - { - expect = 'watcher: propname=length, oldval=1, newval=2; '; - actual = ''; - arr.length++; - } - catch(ex) - { - actual = ex + ''; - } - reportCompare(expect, actual, summary + ': 4'); - - try - { - expect = 'watcher: propname=length, oldval=2, newval=5; '; - actual = ''; - arr.length = 5; - } - catch(ex) - { - actual = ex + ''; - } - reportCompare(expect, actual, summary + ': 5'); - - exitFunc ('test'); -} - -function watcher(propname, oldval, newval) -{ - actual += 'watcher: propname=' + propname + ', oldval=' + oldval + - ', newval=' + newval + '; '; - - return newval; -} - diff --git a/js/src/tests/js1_5/extensions/regress-454040.js b/js/src/tests/js1_5/extensions/regress-454040.js deleted file mode 100644 index 63b5e1488..000000000 --- a/js/src/tests/js1_5/extensions/regress-454040.js +++ /dev/null @@ -1,25 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 454040; -var summary = 'Do not crash @ js_ComputeFilename'; -var actual = ''; -var expect = ''; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -try -{ - this.__defineGetter__("x", Function); - this.__defineSetter__("x", Function); - this.watch("x", x.__proto__); - x = 1; -} -catch(ex) -{ -} -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/extensions/regress-454142.js b/js/src/tests/js1_5/extensions/regress-454142.js deleted file mode 100644 index 05f331278..000000000 --- a/js/src/tests/js1_5/extensions/regress-454142.js +++ /dev/null @@ -1,30 +0,0 @@ -// |reftest| skip-if(Android) -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 454142; -var summary = 'Do not crash with gczeal, setter, watch'; -var actual = ''; -var expect = ''; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -this.watch("x", function(){}); -delete x; -if (typeof gczeal == 'function') -{ - gczeal(2); -} - -this.__defineSetter__("x", function(){}); - -if (typeof gczeal == 'function') -{ - gczeal(0); -} - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/extensions/regress-455413.js b/js/src/tests/js1_5/extensions/regress-455413.js deleted file mode 100644 index d00ab135b..000000000 --- a/js/src/tests/js1_5/extensions/regress-455413.js +++ /dev/null @@ -1,24 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 455413; -var summary = 'Do not assert with JIT: (m != JSVAL_INT) || isInt32(*vp)'; -var actual = 'No Crash'; -var expect = 'No Crash'; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -printBugNumber(BUGNUMBER); -printStatus (summary); - - -this.watch('x', Math.pow); (function() { for(var j=0;j<4;++j){x=1;} })(); - - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/extensions/regress-465145.js b/js/src/tests/js1_5/extensions/regress-465145.js deleted file mode 100644 index 84f81703b..000000000 --- a/js/src/tests/js1_5/extensions/regress-465145.js +++ /dev/null @@ -1,24 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 465145; -var summary = 'Do not crash with watched defined setter'; -var actual = ''; -var expect = ''; - -printBugNumber(BUGNUMBER); -printStatus (summary); - - -this.__defineSetter__("x", function(){}); -this.watch("x", function(){}); -y = this; -for (var z = 0; z < 2; ++z) { x = y }; -this.__defineSetter__("x", function(){}); -for (var z = 0; z < 2; ++z) { x = y }; - - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/extensions/regress-472787.js b/js/src/tests/js1_5/extensions/regress-472787.js deleted file mode 100755 index d3196f05f..000000000 --- a/js/src/tests/js1_5/extensions/regress-472787.js +++ /dev/null @@ -1,31 +0,0 @@ -// |reftest| skip-if(Android) -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 472787; -var summary = 'Do not hang with gczeal, watch and concat'; -var actual = ''; -var expect = ''; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -this.__defineSetter__("x", Math.sin); -this.watch("x", '' .concat); - -if (typeof gczeal == 'function') -{ - gczeal(2); -} - -x = 1; - -if (typeof gczeal == 'function') -{ - gczeal(0); -} - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/extensions/regress-488995.js b/js/src/tests/js1_5/extensions/regress-488995.js deleted file mode 100644 index f7abbe439..000000000 --- a/js/src/tests/js1_5/extensions/regress-488995.js +++ /dev/null @@ -1,46 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 488995; -var summary = 'Do not crash with watch, __defineSetter__ on svg'; -var actual = ''; -var expect = ''; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - if (typeof document == 'undefined') - { - print('Test skipped: requires browser.'); - } - else - { - try - { - var o=document.createElementNS("http://www.w3.org/2000/svg", "svg"); - var p=o.y; - p.watch('animVal', function(id, oldvar, newval) {} ); - p.type='xxx'; - p.__defineSetter__('animVal', function() {}); - p.animVal=0; - } - catch(ex) - { - } - } - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_6/Regress/regress-476655.js b/js/src/tests/js1_6/Regress/regress-476655.js deleted file mode 100644 index 9077ba3a0..000000000 --- a/js/src/tests/js1_6/Regress/regress-476655.js +++ /dev/null @@ -1,40 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 476655; -var summary = 'TM: Do not assert: count <= (size_t) (fp->regs->sp - StackBase(fp) - depth)'; -var actual = ''; -var expect = ''; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - - try - { - eval( - "(function (){ for (var y in this) {} })();" + - "[''.watch(\"\", function(){}) for each (x in ['', '', eval, '', '']) if " + - "(x)].map(Function)" - ); - } - catch(ex) - { - } - - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_6/extensions/regress-457521.js b/js/src/tests/js1_6/extensions/regress-457521.js deleted file mode 100644 index 6f4fbda50..000000000 --- a/js/src/tests/js1_6/extensions/regress-457521.js +++ /dev/null @@ -1,24 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 457521; -var summary = 'Do not crash @ js_DecompileValueGenerator'; -var actual = ''; -var expect = ''; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -try -{ - this.__defineSetter__("x", [,,,].map); - this.watch("x", (new Function("var y, eval"))); - x = true; -} -catch(ex) -{ -} -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_6/extensions/regress-479567.js b/js/src/tests/js1_6/extensions/regress-479567.js deleted file mode 100644 index 6f3525130..000000000 --- a/js/src/tests/js1_6/extensions/regress-479567.js +++ /dev/null @@ -1,33 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 479567; -var summary = 'Do not assert: thing'; -var actual = ''; -var expect = ''; - -printBugNumber(BUGNUMBER); -printStatus (summary); - - -function f() -{ - (eval("(function(){for each (y in [false, false, false]);});"))(); -} - -try -{ - this.__defineGetter__("x", gc); - uneval(this.watch("y", this.toSource)) - f(); - throw x; -} -catch(ex) -{ -} - - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_7/extensions/regress-453955.js b/js/src/tests/js1_7/extensions/regress-453955.js deleted file mode 100644 index 454f712f3..000000000 --- a/js/src/tests/js1_7/extensions/regress-453955.js +++ /dev/null @@ -1,31 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 453955; -var summary = 'Do not assert: sprop->setter != js_watch_set || pobj != obj'; -var actual = ''; -var expect = ''; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - for (var z = 0; z < 2; ++z) - { - [].filter.watch("9", function(y) { yield y; }); - } - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_7/extensions/regress-473282.js b/js/src/tests/js1_7/extensions/regress-473282.js deleted file mode 100644 index c50ac9234..000000000 --- a/js/src/tests/js1_7/extensions/regress-473282.js +++ /dev/null @@ -1,20 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 473282; -var summary = 'Do not assert: thing'; -var actual = ''; -var expect = ''; - -printBugNumber(BUGNUMBER); -printStatus (summary); - -this.watch("b", "".substring); -this.__defineGetter__("a", gc); -for each (b in [this, null, null]); -a; - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_7/regress/regress-385133-01.js b/js/src/tests/js1_7/regress/regress-385133-01.js deleted file mode 100644 index 03b09f535..000000000 --- a/js/src/tests/js1_7/regress/regress-385133-01.js +++ /dev/null @@ -1,37 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 385133; -var summary = 'Do not crash due to recursion with watch, setter, delete, generator'; -var actual = 'No Crash'; -var expect = 'No Crash'; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - try - { - Object.defineProperty(this, "x", { set: {}.watch, enumerable: true, configurable: true }); - this.watch('x', 'foo'.split); - delete x; - function g(){ x = 1; yield; } - for (i in g()) { } - } - catch(ex) - { - } - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_8/extensions/regress-394709.js b/js/src/tests/js1_8/extensions/regress-394709.js deleted file mode 100644 index d04d8832f..000000000 --- a/js/src/tests/js1_8/extensions/regress-394709.js +++ /dev/null @@ -1,51 +0,0 @@ -// |reftest| skip-if(!xulRuntime.shell) -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 394709; -var summary = 'Do not leak with object.watch and closure'; -var actual = 'No Leak'; -var expect = 'No Leak'; - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - assertEq(finalizeCount(), 0, "invalid initial state"); - - runtest(); - gc(); - assertEq(finalizeCount(), 1, "leaked"); - - runtest(); - gc(); - assertEq(finalizeCount(), 2, "leaked"); - - runtest(); - gc(); - assertEq(finalizeCount(), 3, "leaked"); - - - function runtest () { - var obj = { b: makeFinalizeObserver() }; - obj.watch('b', watcher); - - function watcher(id, old, value) { - ++obj.n; - return value; - } - } - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_8/extensions/regress-481989.js b/js/src/tests/js1_8/extensions/regress-481989.js deleted file mode 100644 index 36efb7567..000000000 --- a/js/src/tests/js1_8/extensions/regress-481989.js +++ /dev/null @@ -1,19 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 481989; -var summary = 'TM: Do not assert: SPROP_HAS_STUB_SETTER(sprop)'; -var actual = ''; -var expect = ''; - -printBugNumber(BUGNUMBER); -printStatus (summary); - - -y = this.watch("x", function(){}); for each (let y in ['', '']) x = y; - - -reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_8_1/extensions/regress-452498-193.js b/js/src/tests/js1_8_1/extensions/regress-452498-193.js deleted file mode 100644 index 1397bf4bb..000000000 --- a/js/src/tests/js1_8_1/extensions/regress-452498-193.js +++ /dev/null @@ -1,34 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 452498; -var summary = 'TM: upvar2 regression tests'; -var actual = ''; -var expect = ''; - -//------- Comment #193 From Gary Kwong - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - -// Assertion failure: afunbox->parent, at ../jsparse.cpp:1912 - - this.x = undefined; - this.watch("x", Function); - NaN = uneval({ get \u3056 (){ return undefined } }); - x+=NaN; - - reportCompare(expect, actual, summary); - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_8_1/extensions/regress-452498-196.js b/js/src/tests/js1_8_1/extensions/regress-452498-196.js index 69d5a3586..5b9191199 100644 --- a/js/src/tests/js1_8_1/extensions/regress-452498-196.js +++ b/js/src/tests/js1_8_1/extensions/regress-452498-196.js @@ -21,15 +21,6 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); -// Assertion failure: localKind == JSLOCAL_VAR || localKind == JSLOCAL_CONST, at ../jsfun.cpp:916 - - this.x = undefined; - this.watch("x", Function); - NaN = uneval({ get \u3056 (){ return undefined } }); - x+=NaN; - - reportCompare(expect, actual, summary + ': 1'); - // Assertion failure: lexdep->isLet(), at ../jsparse.cpp:1900 (function (){ diff --git a/js/src/tests/js1_8_1/extensions/regress-520572.js b/js/src/tests/js1_8_1/extensions/regress-520572.js deleted file mode 100644 index 97f00029a..000000000 --- a/js/src/tests/js1_8_1/extensions/regress-520572.js +++ /dev/null @@ -1,41 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - * Contributor: Blake Kaplan - */ - -//----------------------------------------------------------------------------- -var BUGNUMBER = 520572; -var summary = 'watch should innerize the object being watched'; -var actual = 0; -var expect = 2; - - -//----------------------------------------------------------------------------- -test(); -//----------------------------------------------------------------------------- - -function test() -{ - enterFunc ('test'); - printBugNumber(BUGNUMBER); - printStatus (summary); - - if ("evalcx" in this) { - // shell - let s = evalcx("lazy"); - s.n = 0; - evalcx('this.watch("x", function(){ n++; }); this.x = 4; x = 6', s); - actual = s.n; - reportCompare(expect, actual, summary); - } else { - // browser - this.watch('x', function(){ actual++; }); - this.x = 4; - x = 6; - reportCompare(expect, actual, summary); - } - - exitFunc ('test'); -} diff --git a/js/src/tests/js1_8_1/regress/regress-452498-160.js b/js/src/tests/js1_8_1/regress/regress-452498-160.js index 6498a0b5a..305b41795 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-160.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-160.js @@ -21,11 +21,6 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); -// crash [@ js_Interpret] - (eval("(function(){ this.watch(\"x\", function () { new function ()y } ); const y = undefined });"))(); - x = NaN; - reportCompare(expect, actual, summary + ': 2'); - // Assertion failure: JOF_OPTYPE(op) == JOF_ATOM, at ../jsemit.cpp:5916 ({ set z(v){}, set y(v)--x, set w(v)--w }); reportCompare(expect, actual, summary + ': 3'); diff --git a/js/src/tests/js1_8_5/extensions/regress-604781-1.js b/js/src/tests/js1_8_5/extensions/regress-604781-1.js deleted file mode 100644 index a7c43f95d..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-604781-1.js +++ /dev/null @@ -1,24 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -var watcherCount, setterCount; -function watcher(id, oldval, newval) { watcherCount++; return newval; } -function setter(newval) { setterCount++; } - -var p = { set x(v) { setter(v); } }; -p.watch('x', watcher); - -watcherCount = setterCount = 0; -p.x = 2; -assertEq(setterCount, 1); -assertEq(watcherCount, 1); - -var o = Object.defineProperty({}, 'x', { set:setter, enumerable:true, configurable:true }); -o.watch('x', watcher); - -watcherCount = setterCount = 0; -o.x = 2; -assertEq(setterCount, 1); -assertEq(watcherCount, 1); - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/regress-604781-2.js b/js/src/tests/js1_8_5/extensions/regress-604781-2.js deleted file mode 100644 index 7aba4a274..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-604781-2.js +++ /dev/null @@ -1,13 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -var log; -function watcher(id, old, newval) { log += 'watcher'; return newval; } -var o = { set x(v) { log += 'setter'; } }; -o.watch('x', watcher); -Object.defineProperty(o, 'x', {value: 3, writable: true}); -log = ''; -o.x = 3; -assertEq(log, 'watcher'); - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/regress-627984-1.js b/js/src/tests/js1_8_5/extensions/regress-627984-1.js deleted file mode 100644 index a3726630a..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-627984-1.js +++ /dev/null @@ -1,16 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -// See bug 627984, comment 17, item 1. -var obj; -var methods = []; -for (var i = 0; i < 2; i++) { - obj = {m: function () { return this.x; }}; - obj.watch("m", function (id, oldval, newval) { methods[i] = oldval; }); - obj.m = 0; -} -assertEq(typeof methods[0], "function"); -assertEq(typeof methods[1], "function"); -assertEq(methods[0] !== methods[1], true); - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/regress-627984-2.js b/js/src/tests/js1_8_5/extensions/regress-627984-2.js deleted file mode 100644 index c4f1b508c..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-627984-2.js +++ /dev/null @@ -1,15 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -// See bug 627984, comment 17, item 2. -var obj = {}; -var x; -obj.watch("m", function (id, oldval, newval) { - x = this.m; - return newval; - }); -delete obj.m; -obj.m = function () { return this.method; }; -obj.m = 2; - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/regress-627984-3.js b/js/src/tests/js1_8_5/extensions/regress-627984-3.js deleted file mode 100644 index cbe4e10fa..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-627984-3.js +++ /dev/null @@ -1,14 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -// Don't write string value to method slot. -// See bug 627984, comment 17, item 2. -var obj = {}; -obj.watch("m", function (id, oldval, newval) { - return 'ok'; - }); -delete obj.m; -obj.m = function () { return this.x; }; -assertEq(obj.m, 'ok'); - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/regress-627984-4.js b/js/src/tests/js1_8_5/extensions/regress-627984-4.js deleted file mode 100644 index bbc017ffb..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-627984-4.js +++ /dev/null @@ -1,15 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -// See bug 627984, comment 17, item 3. -var obj = {}; -obj.watch("m", function (id, oldval, newval) { - delete obj.m; - obj.m = function () {}; - return newval; - }); -delete obj.m; -obj.m = 1; -assertEq(obj.m, 1); - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/regress-627984-5.js b/js/src/tests/js1_8_5/extensions/regress-627984-5.js deleted file mode 100644 index 704d8421c..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-627984-5.js +++ /dev/null @@ -1,13 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -// Bug 627984 comment 11. -var o = ({}); -o.p = function() {}; -o.watch('p', function() { }); -o.q = function() {} -delete o.p; -o.p = function() {}; -assertEq(o.p, void 0); - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/regress-627984-6.js b/js/src/tests/js1_8_5/extensions/regress-627984-6.js deleted file mode 100644 index cb1a0fca9..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-627984-6.js +++ /dev/null @@ -1,15 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -// Bug 627984 description. -var o = Array; -o.p = function() {}; -o.watch('p', function() { }); -for(var x in o) { - o[x]; -} -delete o.p; -o.p = function() {}; -assertEq(o.p, void 0); - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/regress-627984-7.js b/js/src/tests/js1_8_5/extensions/regress-627984-7.js deleted file mode 100644 index b13a0e912..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-627984-7.js +++ /dev/null @@ -1,9 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -// See bug 627984 comment 20. -var obj = {m: function () {}}; -obj.watch("m", function () { throw 'FAIL'; }); -var f = obj.m; // don't call the watchpoint - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/regress-631723.js b/js/src/tests/js1_8_5/extensions/regress-631723.js deleted file mode 100644 index f7c755603..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-631723.js +++ /dev/null @@ -1,10 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -var o = {a:1, b:2}; -o.watch("p", function() { return 13; }); -delete o.p; -o.p = 0; -assertEq(o.p, 13); - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/regress-636697.js b/js/src/tests/js1_8_5/extensions/regress-636697.js deleted file mode 100644 index 6b3b1de37..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-636697.js +++ /dev/null @@ -1,11 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -var a = {set p(x) {}}; -a.watch('p', function () {}); -var b = Object.create(a); -b.watch('p', function () {}); -delete b.p; -b.p = 0; - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/regress-637985.js b/js/src/tests/js1_8_5/extensions/regress-637985.js deleted file mode 100644 index 305bfc820..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-637985.js +++ /dev/null @@ -1,8 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -var obj = {}; -obj.watch(-1, function(){}); -obj.unwatch("-1"); // don't assert - -reportCompare(0, 0, 'ok'); \ No newline at end of file diff --git a/js/src/tests/js1_8_5/extensions/regress-691746.js b/js/src/tests/js1_8_5/extensions/regress-691746.js deleted file mode 100644 index 26f732d07..000000000 --- a/js/src/tests/js1_8_5/extensions/regress-691746.js +++ /dev/null @@ -1,11 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -var obj = {}; -try { - obj.watch(QName(), function () {}); -} catch (exc) { -} -gc(); - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/watch-undefined-setter.js b/js/src/tests/js1_8_5/extensions/watch-undefined-setter.js deleted file mode 100644 index 92608de0e..000000000 --- a/js/src/tests/js1_8_5/extensions/watch-undefined-setter.js +++ /dev/null @@ -1,19 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - * Contributor: - * Gary Kwong - */ - -var gTestfile = 'watch-undefined-setter.js'; -//----------------------------------------------------------------------------- -var BUGNUMBER = 560277; -var summary = - 'Crash [@ JSObject::getParent] or [@ js_WrapWatchedSetter] or ' + - '[@ js_GetClassPrototype]'; - -this.watch("x", function() { }); -Object.defineProperty(this, "x", { set: undefined, configurable: true }); - -reportCompare(true, true); diff --git a/js/src/tests/js1_8_5/regress/regress-533876.js b/js/src/tests/js1_8_5/regress/regress-533876.js deleted file mode 100644 index e44bc8a4f..000000000 --- a/js/src/tests/js1_8_5/regress/regress-533876.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - * Contributors: Gary Kwong and Jason Orendorff - */ - -var savedEval = eval; -var x = [0]; -eval(); - -x.__proto__ = this; // x has non-dictionary scope -try { - DIE; -} catch(e) { -} - -delete eval; // force dictionary scope for global -gc(); -eval = savedEval; -var f = eval("(function () { return /x/; })"); -x.watch('x', f); // clone property from global to x, including SPROP_IN_DICTIONARY flag - -reportCompare("ok", "ok", "bug 533876"); diff --git a/js/src/tests/js1_8_5/regress/regress-548276.js b/js/src/tests/js1_8_5/regress/regress-548276.js deleted file mode 100644 index 5e306eba1..000000000 --- a/js/src/tests/js1_8_5/regress/regress-548276.js +++ /dev/null @@ -1,10 +0,0 @@ -// |reftest| skip -/* - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ - * Contributors: Gary Kwong and Jason Orendorff - */ -var obj = {}; -obj.__defineSetter__("x", function() {}); -obj.watch("x", function() {}); -obj.__defineSetter__("x", /a/); diff --git a/js/src/tests/js1_8_5/regress/regress-584648.js b/js/src/tests/js1_8_5/regress/regress-584648.js deleted file mode 100644 index a1635ea51..000000000 --- a/js/src/tests/js1_8_5/regress/regress-584648.js +++ /dev/null @@ -1,16 +0,0 @@ -// |reftest| skip -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ -// Contributors: Gary Kwong -// Jason Orendorff - -// on a non-global object -var x = {}; -x.watch("p", function () { evalcx(''); }); -x.p = 0; - -// on the global -watch("e", (function () { evalcx(''); })); -e = function () {}; - -reportCompare(0, 0, "ok"); diff --git a/js/src/tests/js1_8_5/regress/regress-635195.js b/js/src/tests/js1_8_5/regress/regress-635195.js deleted file mode 100644 index 89980b05a..000000000 --- a/js/src/tests/js1_8_5/regress/regress-635195.js +++ /dev/null @@ -1,8 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -var obj = {set x(v) {}}; -obj.watch("x", function() { delete obj.x; }); -obj.x = "hi"; // don't assert - -reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/regress/regress-636394.js b/js/src/tests/js1_8_5/regress/regress-636394.js deleted file mode 100644 index d1a249786..000000000 --- a/js/src/tests/js1_8_5/regress/regress-636394.js +++ /dev/null @@ -1,10 +0,0 @@ -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -var a = {p0: function () {}}; -var b = /f/; -b.__proto__ = a; -b.watch("p0", function () {}); -b.p0; - -reportCompare(0, 0, "ok"); diff --git a/js/xpconnect/tests/chrome/chrome.ini b/js/xpconnect/tests/chrome/chrome.ini index 5a7b98214..d89c89b54 100644 --- a/js/xpconnect/tests/chrome/chrome.ini +++ b/js/xpconnect/tests/chrome/chrome.ini @@ -106,7 +106,6 @@ skip-if = os == 'win' || os == 'mac' # bug 1131110 [test_precisegc.xul] [test_sandboxImport.xul] [test_scriptSettings.xul] -[test_watchpoints.xul] [test_weakmap_keys_preserved.xul] [test_weakmap_keys_preserved2.xul] [test_weakmaps.xul] diff --git a/js/xpconnect/tests/chrome/test_watchpoints.xul b/js/xpconnect/tests/chrome/test_watchpoints.xul deleted file mode 100644 index 2262b1a90..000000000 --- a/js/xpconnect/tests/chrome/test_watchpoints.xul +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - diff --git a/js/xpconnect/tests/chrome/test_xrayToJS.xul b/js/xpconnect/tests/chrome/test_xrayToJS.xul index 73de267a1..495b99607 100644 --- a/js/xpconnect/tests/chrome/test_xrayToJS.xul +++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul @@ -182,8 +182,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681 "toGMTString", Symbol.toPrimitive]; gConstructorProperties['Date'] = constructorProps(["UTC", "parse", "now"]); gPrototypeProperties['Object'] = - ["constructor", "toSource", "toString", "toLocaleString", "valueOf", "watch", - "unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", + ["constructor", "toSource", "toString", "toLocaleString", "valueOf", + "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__"]; gConstructorProperties['Object'] = -- cgit v1.2.3 From a9dc528a4a7b0aaad5308aff70963485ec3e9bec Mon Sep 17 00:00:00 2001 From: athenian200 Date: Thu, 31 Oct 2019 19:35:03 -0500 Subject: Fix nits. I hope this addresses everything. --- ipc/chromium/src/base/sys_info_posix.cc | 2 +- toolkit/library/libxul.mk | 2 +- .../xptcall/md/unix/xptcinvoke_sparc_solaris.cpp | 30 ++++++++++++++-------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/ipc/chromium/src/base/sys_info_posix.cc b/ipc/chromium/src/base/sys_info_posix.cc index f29c85092..632aa2f37 100644 --- a/ipc/chromium/src/base/sys_info_posix.cc +++ b/ipc/chromium/src/base/sys_info_posix.cc @@ -134,7 +134,7 @@ std::string SysInfo::OperatingSystemName() { } // Solaris contains "extern struct utsname utsname;" -// As a consequence, any use of utsname has to be proceeded with struct on +// As a consequence, any use of utsname has to be preceded with struct on // Solaris. See Mozilla bugs 758483 and 1353332. // static diff --git a/toolkit/library/libxul.mk b/toolkit/library/libxul.mk index 8533a015d..80f934e60 100644 --- a/toolkit/library/libxul.mk +++ b/toolkit/library/libxul.mk @@ -4,7 +4,7 @@ EXTRA_DEPS += $(topsrcdir)/toolkit/library/libxul.mk -ifeq (Linux,$(OS_ARCH)) +ifeq (Linux,$(OS_ARCH)) ifneq (Android,$(OS_TARGET)) OS_LDFLAGS += -Wl,-version-script,symverscript diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp index 126ef1dad..c1b679779 100644 --- a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp @@ -104,22 +104,32 @@ invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) } switch(l_s->type) { - case nsXPTType::T_I8 : *((int32_t*) l_d) = l_s->val.i8; break; - case nsXPTType::T_I16 : *((int32_t*) l_d) = l_s->val.i16; break; - case nsXPTType::T_I32 : *((int32_t*) l_d) = l_s->val.i32; break; + case nsXPTType::T_I8 : *((int32_t*) l_d) = l_s->val.i8; + break; + case nsXPTType::T_I16 : *((int32_t*) l_d) = l_s->val.i16; + break; + case nsXPTType::T_I32 : *((int32_t*) l_d) = l_s->val.i32; + break; case nsXPTType::T_I64 : case nsXPTType::T_U64 : case nsXPTType::T_DOUBLE : *((uint32_t*) l_d++) = ((DU *)l_s)->hi; if (regCount < 5) regCount++; *((uint32_t*) l_d) = ((DU *)l_s)->lo; break; - case nsXPTType::T_U8 : *((uint32_t*) l_d) = l_s->val.u8; break; - case nsXPTType::T_U16 : *((uint32_t*) l_d) = l_s->val.u16; break; - case nsXPTType::T_U32 : *((uint32_t*) l_d) = l_s->val.u32; break; - case nsXPTType::T_FLOAT : *((float*) l_d) = l_s->val.f; break; - case nsXPTType::T_BOOL : *((uint32_t*) l_d) = l_s->val.b; break; - case nsXPTType::T_CHAR : *((uint32_t*) l_d) = l_s->val.c; break; - case nsXPTType::T_WCHAR : *((int32_t*) l_d) = l_s->val.wc; break; + case nsXPTType::T_U8 : *((uint32_t*) l_d) = l_s->val.u8; + break; + case nsXPTType::T_U16 : *((uint32_t*) l_d) = l_s->val.u16; + break; + case nsXPTType::T_U32 : *((uint32_t*) l_d) = l_s->val.u32; + break; + case nsXPTType::T_FLOAT : *((float*) l_d) = l_s->val.f; + break; + case nsXPTType::T_BOOL : *((uint32_t*) l_d) = l_s->val.b; + break; + case nsXPTType::T_CHAR : *((uint32_t*) l_d) = l_s->val.c; + break; + case nsXPTType::T_WCHAR : *((int32_t*) l_d) = l_s->val.wc; + break; default: // all the others are plain pointer types *((void**)l_d) = l_s->val.p; -- cgit v1.2.3 From ff881bdb6795e0f307b93919f98f454bedde4bb6 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Fri, 1 Nov 2019 21:55:13 +0100 Subject: #1261 - Update status bar component for the removal of Object.(un)watch This fixes the regression from #1257 by adding in an Object.prototype.watch() shim, based on Eli Grey's polyfill. This resolves #1261. --- .../palemoon/components/statusbar/Status.jsm | 66 +++++++++++++++++----- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/application/palemoon/components/statusbar/Status.jsm b/application/palemoon/components/statusbar/Status.jsm index 19e12ddfd..dbdd1fc49 100644 --- a/application/palemoon/components/statusbar/Status.jsm +++ b/application/palemoon/components/statusbar/Status.jsm @@ -120,6 +120,57 @@ S4EStatusService.prototype = }, buildBinding: function() { + + // Object.prototype.watch() shim, based on Eli Grey's polyfill + // object.watch + if (!this._window.XULBrowserWindow.watch) { + Object.defineProperty(this._window.XULBrowserWindow, "watch", { + enumerable: false, + configurable: true, + writable: false, + value: function (prop, handler) { + var oldval = this[prop], + newval = oldval, + getter = function () { + return newval; + }, + setter = function (val) { + oldval = newval; + return newval = handler.call(this, prop, oldval, val); + } + ; + + try { + if (delete this[prop]) { // can't watch constants + Object.defineProperty(this, prop, { + get: getter, + set: setter, + enumerable: true, + configurable: true + }); + } + } catch(e) { + // This fails fatally on non-configurable props, so just + // ignore errors if it does. + } + } + }); + } + + // object.unwatch + if (!this._window.XULBrowserWindow.unwatch) { + Object.defineProperty(this._window.XULBrowserWindow, "unwatch", { + enumerable: false, + configurable: true, + writable: false, + value: function (prop) { + var val = this[prop]; + delete this[prop]; // remove accessors + this[prop] = val; + } + }); + } + let XULBWPropHandler = function(prop, oldval, newval) { CU.reportError("Attempt to modify XULBrowserWindow." + prop); return oldval; @@ -139,21 +190,6 @@ S4EStatusService.prototype = this._window.XULBrowserWindow[prop] = this[prop].bind(this); this._window.XULBrowserWindow.watch(prop, XULBWPropHandler); }, this); - - let XULBWHandler = function(prop, oldval, newval) { - if(!newval) - { - return newval; - } - CU.reportError("XULBrowserWindow changed. Updating S4E bindings."); - this._window.setTimeout(function(self) - { - self.buildBinding(); - }, 0, this); - return newval; - }; - - this._window.watch("XULBrowserWindow", XULBWHandler); }, destroy: function() -- cgit v1.2.3 From 145527207538b6ee1018cb77e6705912c29f8a9f Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sat, 2 Nov 2019 22:39:03 +0100 Subject: Issue #146 - Part 1: Draw each table's background on their own display list items. This patch does the following things: 1. Creates nsDisplayTableBorderCollapse that draws all collapse border of tables. 2. Stops the use of nsDisplayTableBorderBackground. 3. Lets column and column group frames generate display items. 4. When traversing the table, also traverses the column and column group frames. 5. For each type of table frame (col group, col, row group, row and cell), draws their own background. --- layout/base/nsDisplayItemTypesList.h | 1 + layout/base/nsDisplayList.cpp | 9 +- layout/base/nsDisplayList.h | 3 +- layout/tables/nsTableCellFrame.cpp | 103 ++++++------- layout/tables/nsTableColFrame.cpp | 8 + layout/tables/nsTableColFrame.h | 5 +- layout/tables/nsTableColGroupFrame.cpp | 8 + layout/tables/nsTableColGroupFrame.h | 5 +- layout/tables/nsTableFrame.cpp | 268 ++++++++++++++++++--------------- layout/tables/nsTableFrame.h | 1 - layout/tables/nsTableRowFrame.cpp | 16 +- layout/tables/nsTableRowGroupFrame.cpp | 13 +- layout/tables/nsTableWrapperFrame.cpp | 6 +- 13 files changed, 223 insertions(+), 223 deletions(-) diff --git a/layout/base/nsDisplayItemTypesList.h b/layout/base/nsDisplayItemTypesList.h index 9865395a7..d24f6f38f 100644 --- a/layout/base/nsDisplayItemTypesList.h +++ b/layout/base/nsDisplayItemTypesList.h @@ -59,6 +59,7 @@ DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_SELECTION) DECLARE_DISPLAY_ITEM_TYPE(TABLE_ROW_BACKGROUND) DECLARE_DISPLAY_ITEM_TYPE(TABLE_ROW_GROUP_BACKGROUND) DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_BACKGROUND) +DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_COLLAPSE) DECLARE_DISPLAY_ITEM_TYPE(TEXT) DECLARE_DISPLAY_ITEM_TYPE(TEXT_OVERFLOW) DECLARE_DISPLAY_ITEM_TYPE_FLAGS(TRANSFORM,TYPE_RENDERS_NO_IMAGES) diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index e22230b41..744153831 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -2631,11 +2631,16 @@ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuil const nsRect& aBackgroundRect, nsDisplayList* aList, bool aAllowWillPaintBorderOptimization, - nsStyleContext* aStyleContext) + nsStyleContext* aStyleContext, + const nsRect& aBackgroundOriginRect) { nsStyleContext* bgSC = aStyleContext; const nsStyleBackground* bg = nullptr; nsRect bgRect = aBackgroundRect + aBuilder->ToReferenceFrame(aFrame); + nsRect bgOriginRect = bgRect; + if (!aBackgroundOriginRect.IsEmpty()) { + bgOriginRect = aBackgroundOriginRect + aBuilder->ToReferenceFrame(aFrame); + } nsPresContext* presContext = aFrame->PresContext(); bool isThemed = aFrame->IsThemed(); if (!isThemed) { @@ -2744,7 +2749,7 @@ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuil nsDisplayList thisItemList; nsDisplayBackgroundImage* bgItem = - new (aBuilder) nsDisplayBackgroundImage(aBuilder, aFrame, i, bgRect, bg); + new (aBuilder) nsDisplayBackgroundImage(aBuilder, aFrame, i, bgOriginRect, bg); if (bgItem->ShouldFixToViewport(aBuilder)) { thisItemList.AppendNewToTop( diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index c81d34fac..9431e2cc0 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -2737,7 +2737,8 @@ public: const nsRect& aBackgroundRect, nsDisplayList* aList, bool aAllowWillPaintBorderOptimization = true, - nsStyleContext* aStyleContext = nullptr); + nsStyleContext* aStyleContext = nullptr, + const nsRect& aBackgroundOriginRect = nsRect()); virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager, diff --git a/layout/tables/nsTableCellFrame.cpp b/layout/tables/nsTableCellFrame.cpp index dea82ea59..bac8d387f 100644 --- a/layout/tables/nsTableCellFrame.cpp +++ b/layout/tables/nsTableCellFrame.cpp @@ -484,70 +484,51 @@ nsTableCellFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { DO_GLOBAL_REFLOW_COUNT_DSP("nsTableCellFrame"); - if (IsVisibleInSelection(aBuilder)) { - nsTableFrame* tableFrame = GetTableFrame(); - int32_t emptyCellStyle = GetContentEmpty() && !tableFrame->IsBorderCollapse() ? - StyleTableBorder()->mEmptyCells - : NS_STYLE_TABLE_EMPTY_CELLS_SHOW; - // take account of 'empty-cells' - if (StyleVisibility()->IsVisible() && - (NS_STYLE_TABLE_EMPTY_CELLS_HIDE != emptyCellStyle)) { - // display outset box-shadows if we need to. - bool hasBoxShadow = !!StyleEffects()->mBoxShadow; - if (hasBoxShadow) { - aLists.BorderBackground()->AppendNewToTop( - new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, this)); - } - - // display background if we need to. - if (aBuilder->IsForEventDelivery() || - !StyleBackground()->IsTransparent() || StyleDisplay()->mAppearance) { - if (!tableFrame->IsBorderCollapse()) { - nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, - this, - GetRectRelativeToSelf(), - aLists.BorderBackground()); - } else if (aBuilder->IsAtRootOfPseudoStackingContext() || - aBuilder->IsForEventDelivery()) { - // The cell background was not painted by the nsTablePainter, - // so we need to do it. We have special background processing here - // so we need to duplicate some code from nsFrame::DisplayBorderBackgroundOutline - nsDisplayTableItem* item = - new (aBuilder) nsDisplayTableCellBackground(aBuilder, this); - aLists.BorderBackground()->AppendNewToTop(item); - item->UpdateForFrameBackground(this); - } else { - // The nsTablePainter will paint our background. Make sure it - // knows if we're background-attachment:fixed. - nsDisplayTableItem* currentItem = aBuilder->GetCurrentTableItem(); - if (currentItem) { - currentItem->UpdateForFrameBackground(this); - } - } - } - - // display inset box-shadows if we need to. - if (hasBoxShadow) { - aLists.BorderBackground()->AppendNewToTop( - new (aBuilder) nsDisplayBoxShadowInner(aBuilder, this)); - } - - // display borders if we need to - ProcessBorders(tableFrame, aBuilder, aLists); - - // and display the selection border if we need to - if (IsSelected()) { - aLists.BorderBackground()->AppendNewToTop(new (aBuilder) - nsDisplayGeneric(aBuilder, this, ::PaintTableCellSelection, - "TableCellSelection", - nsDisplayItem::TYPE_TABLE_CELL_SELECTION)); - } + nsTableFrame* tableFrame = GetTableFrame(); + int32_t emptyCellStyle = GetContentEmpty() && !tableFrame->IsBorderCollapse() ? + StyleTableBorder()->mEmptyCells + : NS_STYLE_TABLE_EMPTY_CELLS_SHOW; + // take account of 'empty-cells' + if (StyleVisibility()->IsVisible() && + (NS_STYLE_TABLE_EMPTY_CELLS_HIDE != emptyCellStyle)) { + // display outset box-shadows if we need to. + bool hasBoxShadow = !!StyleEffects()->mBoxShadow; + if (hasBoxShadow) { + aLists.BorderBackground()->AppendNewToTop( + new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, this)); + } + + // display background if we need to. + if (aBuilder->IsForEventDelivery() || + !StyleBackground()->IsTransparent() || + StyleDisplay()->mAppearance) { + nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, + this, + GetRectRelativeToSelf(), + aLists.BorderBackground()); + } + + // display inset box-shadows if we need to. + if (hasBoxShadow) { + aLists.BorderBackground()->AppendNewToTop( + new (aBuilder) nsDisplayBoxShadowInner(aBuilder, this)); + } + + // display borders if we need to + ProcessBorders(tableFrame, aBuilder, aLists); + + // and display the selection border if we need to + if (IsSelected()) { + aLists.BorderBackground()->AppendNewToTop(new (aBuilder) + nsDisplayGeneric(aBuilder, this, ::PaintTableCellSelection, + "TableCellSelection", + nsDisplayItem::TYPE_TABLE_CELL_SELECTION)); } - - // the 'empty-cells' property has no effect on 'outline' - DisplayOutline(aBuilder, aLists); } + // the 'empty-cells' property has no effect on 'outline' + DisplayOutline(aBuilder, aLists); + // Push a null 'current table item' so that descendant tables can't // accidentally mess with our table nsAutoPushCurrentTableItem pushTableItem; diff --git a/layout/tables/nsTableColFrame.cpp b/layout/tables/nsTableColFrame.cpp index 8f449c3d9..54b03522b 100644 --- a/layout/tables/nsTableColFrame.cpp +++ b/layout/tables/nsTableColFrame.cpp @@ -108,6 +108,14 @@ nsTableColFrame::Reflow(nsPresContext* aPresContext, NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); } +void +nsTableColFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists); +} + int32_t nsTableColFrame::GetSpan() { return StyleTable()->mSpan; diff --git a/layout/tables/nsTableColFrame.h b/layout/tables/nsTableColFrame.h index e95fe76b1..fb989061f 100644 --- a/layout/tables/nsTableColFrame.h +++ b/layout/tables/nsTableColFrame.h @@ -59,12 +59,9 @@ public: const ReflowInput& aReflowInput, nsReflowStatus& aStatus) override; - /** - * Table columns never paint anything, nor receive events. - */ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, - const nsDisplayListSet& aLists) override {} + const nsDisplayListSet& aLists) override; /** * Get the "type" of the frame diff --git a/layout/tables/nsTableColGroupFrame.cpp b/layout/tables/nsTableColGroupFrame.cpp index ff8879a0b..6ee7f0b24 100644 --- a/layout/tables/nsTableColGroupFrame.cpp +++ b/layout/tables/nsTableColGroupFrame.cpp @@ -383,6 +383,14 @@ nsTableColGroupFrame::Reflow(nsPresContext* aPresContext, NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); } +void +nsTableColGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists); +} + nsTableColFrame * nsTableColGroupFrame::GetFirstColumn() { return GetNextColumn(nullptr); diff --git a/layout/tables/nsTableColGroupFrame.h b/layout/tables/nsTableColGroupFrame.h index 2a25fdc44..b3dfb94e7 100644 --- a/layout/tables/nsTableColGroupFrame.h +++ b/layout/tables/nsTableColGroupFrame.h @@ -43,12 +43,9 @@ public: return static_cast(parent); } - /** - * ColGroups never paint anything, nor receive events. - */ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, - const nsDisplayListSet& aLists) override {} + const nsDisplayListSet& aLists) override; /** A colgroup can be caused by three things: * 1) An element with table-column-group display diff --git a/layout/tables/nsTableFrame.cpp b/layout/tables/nsTableFrame.cpp index 272a77406..b5cd3b487 100644 --- a/layout/tables/nsTableFrame.cpp +++ b/layout/tables/nsTableFrame.cpp @@ -1131,6 +1131,42 @@ nsDisplayTableItem::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); } +// A display item that draws all collapsed borders for a table. +class nsDisplayTableBorderCollapse : public nsDisplayTableItem { +public: + nsDisplayTableBorderCollapse(nsDisplayListBuilder* aBuilder, + nsTableFrame* aFrame) + : nsDisplayTableItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayTableBorderCollapse); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayTableBorderCollapse() { + MOZ_COUNT_DTOR(nsDisplayTableBorderCollapse); + } +#endif + + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + NS_DISPLAY_DECL_NAME("TableBorderCollapse", TYPE_TABLE_BORDER_COLLAPSE) +}; + +void +nsDisplayTableBorderCollapse::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + nsPoint pt = ToReferenceFrame(); + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + + gfxPoint devPixelOffset = + nsLayoutUtils::PointToGfxPoint(pt, mFrame->PresContext()->AppUnitsPerDevPixel()); + + AutoRestoreTransform autoRestoreTransform(drawTarget); + drawTarget->SetTransform( + drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset))); + + static_cast(mFrame)->PaintBCBorders(*drawTarget, mVisibleRect - pt); +} + class nsDisplayTableBorderBackground : public nsDisplayTableItem { public: nsDisplayTableBorderBackground(nsDisplayListBuilder* aBuilder, @@ -1150,20 +1186,6 @@ public: NS_DISPLAY_DECL_NAME("TableBorderBackground", TYPE_TABLE_BORDER_BACKGROUND) }; -#ifdef DEBUG -static bool -IsFrameAllowedInTable(nsIAtom* aType) -{ - return IS_TABLE_CELL(aType) || - nsGkAtoms::tableRowFrame == aType || - nsGkAtoms::tableRowGroupFrame == aType || - nsGkAtoms::scrollFrame == aType || - nsGkAtoms::tableFrame == aType || - nsGkAtoms::tableColFrame == aType || - nsGkAtoms::tableColGroupFrame == aType; -} -#endif - void nsDisplayTableBorderBackground::Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) @@ -1175,25 +1197,6 @@ nsDisplayTableBorderBackground::Paint(nsDisplayListBuilder* aBuilder, nsDisplayTableItemGeometry::UpdateDrawResult(this, result); } -static int32_t -GetTablePartRank(nsDisplayItem* aItem) -{ - nsIAtom* type = aItem->Frame()->GetType(); - if (type == nsGkAtoms::tableFrame) - return 0; - if (type == nsGkAtoms::tableRowGroupFrame) - return 1; - if (type == nsGkAtoms::tableRowFrame) - return 2; - return 3; -} - -static bool CompareByTablePartRank(nsDisplayItem* aItem1, nsDisplayItem* aItem2, - void* aClosure) -{ - return GetTablePartRank(aItem1) <= GetTablePartRank(aItem2); -} - /* static */ void nsTableFrame::GenericTraversal(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, const nsRect& aDirtyRect, const nsDisplayListSet& aLists) @@ -1206,31 +1209,73 @@ nsTableFrame::GenericTraversal(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, // stacking context, in which case the child won't use its passed-in // BorderBackground list anyway. It does affect cell borders though; this // lets us get cell borders into the nsTableFrame's BorderBackground list. + for (nsIFrame* kid : aFrame->GetChildList(kColGroupList)) { + aFrame->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); + } + for (nsIFrame* kid : aFrame->PrincipalChildList()) { aFrame->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); } } +static void +PaintRowBackground(nsTableRowFrame* aRow, + nsIFrame* aFrame, + nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists, + const nsPoint& aOffset = nsPoint()) +{ + // Compute background rect by iterating all cell frame. + for (nsTableCellFrame* cell = aRow->GetFirstCell(); cell; cell = cell->GetNextCell()) { + auto cellRect = cell->GetRectRelativeToSelf() + cell->GetNormalPosition() + aOffset; + nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, cellRect, + aLists.BorderBackground(), + true, nullptr, + aFrame->GetRectRelativeToSelf()); + } +} + +static void +PaintRowGroupBackground(nsTableRowGroupFrame* aRowGroup, + nsIFrame* aFrame, + nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) +{ + for (nsTableRowFrame* row = aRowGroup->GetFirstRow(); row; row = row->GetNextRow()) { + PaintRowBackground(row, aFrame, aBuilder, aLists, row->GetNormalPosition()); + } +} + +static void +PaintRowGroupBackgroundByColIdx(nsTableRowGroupFrame* aRowGroup, + nsIFrame* aFrame, + nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists, + const nsTArray& aColIdx, + const nsPoint& aOffset) +{ + for (nsTableRowFrame* row = aRowGroup->GetFirstRow(); row; row = row->GetNextRow()) { + for (nsTableCellFrame* cell = row->GetFirstCell(); cell; cell = cell->GetNextCell()) { + int32_t curColIdx; + cell->GetColIndex(curColIdx); + if (aColIdx.Contains(curColIdx)) { + auto cellRect = cell->GetRectRelativeToSelf() + cell->GetNormalPosition() + row->GetNormalPosition() + aOffset; + nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, cellRect, + aLists.BorderBackground(), + true, nullptr, + aFrame->GetRectRelativeToSelf()); + } + } + } +} + /* static */ void nsTableFrame::DisplayGenericTablePart(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, const nsRect& aDirtyRect, const nsDisplayListSet& aLists, - nsDisplayTableItem* aDisplayItem, DisplayGenericTablePartTraversal aTraversal) { - nsDisplayList eventsBorderBackground; - // If we need to sort the event backgrounds, then we'll put descendants' - // display items into their own set of lists. - bool sortEventBackgrounds = aDisplayItem && aBuilder->IsForEventDelivery(); - nsDisplayListCollection separatedCollection; - const nsDisplayListSet* lists = sortEventBackgrounds ? &separatedCollection : &aLists; - - nsAutoPushCurrentTableItem pushTableItem; - if (aDisplayItem) { - pushTableItem.Push(aBuilder, aDisplayItem); - } - if (aFrame->IsVisibleForPainting(aBuilder)) { nsDisplayTableItem* currentItem = aBuilder->GetCurrentTableItem(); // currentItem may be null, when none of the table parts have a @@ -1242,72 +1287,79 @@ nsTableFrame::DisplayGenericTablePart(nsDisplayListBuilder* aBuilder, // Paint the outset box-shadows for the table frames bool hasBoxShadow = aFrame->StyleEffects()->mBoxShadow != nullptr; if (hasBoxShadow) { - lists->BorderBackground()->AppendNewToTop( + aLists.BorderBackground()->AppendNewToTop( new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, aFrame)); } - // Create dedicated background display items per-frame when we're - // handling events. - // XXX how to handle collapsed borders? - if (aBuilder->IsForEventDelivery()) { + if (aFrame->GetType() == nsGkAtoms::tableRowGroupFrame) { + nsTableRowGroupFrame* rowGroup = static_cast(aFrame); + PaintRowGroupBackground(rowGroup, aFrame, aBuilder, aLists); + } else if (aFrame->GetType() == nsGkAtoms::tableRowFrame) { + nsTableRowFrame* row = static_cast(aFrame); + PaintRowBackground(row, aFrame, aBuilder, aLists); + } else if (aFrame->GetType() == nsGkAtoms::tableColGroupFrame) { + // Compute background rect by iterating all cell frame. + nsTableColGroupFrame* colGroup = static_cast(aFrame); + // Collecting column index. + AutoTArray colIdx; + for (nsTableColFrame* col = colGroup->GetFirstColumn(); col; col = col->GetNextCol()) { + colIdx.AppendElement(col->GetColIndex()); + } + + nsTableFrame* table = colGroup->GetTableFrame(); + RowGroupArray rowGroups; + table->OrderRowGroups(rowGroups); + for (nsTableRowGroupFrame* rowGroup : rowGroups) { + auto offset = rowGroup->GetNormalPosition() - colGroup->GetNormalPosition(); + PaintRowGroupBackgroundByColIdx(rowGroup, aFrame, aBuilder, aLists, colIdx, offset); + } + } else if (aFrame->GetType() == nsGkAtoms::tableColFrame) { + // Compute background rect by iterating all cell frame. + nsTableColFrame* col = static_cast(aFrame); + AutoTArray colIdx; + colIdx.AppendElement(col->GetColIndex()); + + nsTableFrame* table = col->GetTableFrame(); + RowGroupArray rowGroups; + table->OrderRowGroups(rowGroups); + for (nsTableRowGroupFrame* rowGroup : rowGroups) { + auto offset = rowGroup->GetNormalPosition() - + col->GetNormalPosition() - + col->GetTableColGroupFrame()->GetNormalPosition(); + PaintRowGroupBackgroundByColIdx(rowGroup, aFrame, aBuilder, aLists, colIdx, offset); + } + } else { nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, aFrame->GetRectRelativeToSelf(), - lists->BorderBackground()); + aLists.BorderBackground()); } // Paint the inset box-shadows for the table frames if (hasBoxShadow) { - lists->BorderBackground()->AppendNewToTop( + aLists.BorderBackground()->AppendNewToTop( new (aBuilder) nsDisplayBoxShadowInner(aBuilder, aFrame)); } } - aTraversal(aBuilder, aFrame, aDirtyRect, *lists); + aTraversal(aBuilder, aFrame, aDirtyRect, aLists); - if (sortEventBackgrounds) { - // Ensure that the table frame event background goes before the - // table rowgroups event backgrounds, before the table row event backgrounds, - // before everything else (cells and their blocks) - separatedCollection.BorderBackground()->Sort(CompareByTablePartRank, nullptr); - separatedCollection.MoveTo(aLists); + if (aFrame->IsVisibleForPainting(aBuilder)) { + if (aFrame->GetType() == nsGkAtoms::tableFrame) { + nsTableFrame* table = static_cast(aFrame); + // In the collapsed border model, overlay all collapsed borders. + if (table->IsBorderCollapse()) { + aLists.BorderBackground()->AppendNewToTop( + new (aBuilder) nsDisplayTableBorderCollapse(aBuilder, table)); + } else { + aLists.BorderBackground()->AppendNewToTop( + new (aBuilder) nsDisplayBorder(aBuilder, table)); + } + } } aFrame->DisplayOutline(aBuilder, aLists); } -static bool -AnyTablePartHasBorderOrBackground(nsIFrame* aStart, nsIFrame* aEnd) -{ - for (nsIFrame* f = aStart; f != aEnd; f = f->GetNextSibling()) { - NS_ASSERTION(IsFrameAllowedInTable(f->GetType()), "unexpected frame type"); - - if (FrameHasBorderOrBackground(f)) - return true; - - nsTableCellFrame *cellFrame = do_QueryFrame(f); - if (cellFrame) - continue; - - if (AnyTablePartHasBorderOrBackground(f->PrincipalChildList().FirstChild(), nullptr)) - return true; - } - - return false; -} - -static void -UpdateItemForColGroupBackgrounds(nsDisplayTableItem* item, - const nsFrameList& aFrames) { - for (nsFrameList::Enumerator e(aFrames); !e.AtEnd(); e.Next()) { - nsTableColGroupFrame* cg = static_cast(e.get()); - item->UpdateForFrameBackground(cg); - for (nsTableColFrame* colFrame = cg->GetFirstColumn(); colFrame; - colFrame = colFrame->GetNextCol()) { - item->UpdateForFrameBackground(colFrame); - } - } -} - // table paint code is concerned primarily with borders and bg color // SEC: TODO: adjust the rect for captions void @@ -1317,35 +1369,7 @@ nsTableFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, { DO_GLOBAL_REFLOW_COUNT_DSP_COLOR("nsTableFrame", NS_RGB(255,128,255)); - nsDisplayTableItem* item = nullptr; - if (IsVisibleInSelection(aBuilder)) { - nsMargin deflate = GetDeflationForBackground(PresContext()); - if (StyleVisibility()->IsVisible()) { - // If 'deflate' is (0,0,0,0) then we can paint the table background - // in its own display item, so do that to take advantage of - // opacity and visibility optimizations - if (deflate == nsMargin(0, 0, 0, 0)) { - DisplayBackgroundUnconditional(aBuilder, aLists, false); - } - } - - // This background is created if any of the table parts are visible, - // or if we're doing event handling (since DisplayGenericTablePart - // needs the item for the |sortEventBackgrounds|-dependent code). - // Specific visibility decisions are delegated to the table background - // painter, which handles borders and backgrounds for the table. - if (aBuilder->IsForEventDelivery() || - AnyTablePartHasBorderOrBackground(this, GetNextSibling()) || - AnyTablePartHasBorderOrBackground(mColGroups.FirstChild(), nullptr)) { - item = new (aBuilder) nsDisplayTableBorderBackground(aBuilder, this, - deflate != nsMargin(0, 0, 0, 0)); - aLists.BorderBackground()->AppendNewToTop(item); - } - } - DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists, item); - if (item) { - UpdateItemForColGroupBackgrounds(item, mColGroups); - } + DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists); } nsMargin diff --git a/layout/tables/nsTableFrame.h b/layout/tables/nsTableFrame.h index c7b92d387..d4b6d88a7 100644 --- a/layout/tables/nsTableFrame.h +++ b/layout/tables/nsTableFrame.h @@ -250,7 +250,6 @@ public: nsFrame* aFrame, const nsRect& aDirtyRect, const nsDisplayListSet& aLists, - nsDisplayTableItem* aDisplayItem, DisplayGenericTablePartTraversal aTraversal = GenericTraversal); // Return the closest sibling of aPriorChildFrame (including aPriroChildFrame) diff --git a/layout/tables/nsTableRowFrame.cpp b/layout/tables/nsTableRowFrame.cpp index 1b6051ef2..dc053c991 100644 --- a/layout/tables/nsTableRowFrame.cpp +++ b/layout/tables/nsTableRowFrame.cpp @@ -605,21 +605,7 @@ nsTableRowFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists) { - nsDisplayTableItem* item = nullptr; - if (IsVisibleInSelection(aBuilder)) { - bool isRoot = aBuilder->IsAtRootOfPseudoStackingContext(); - if (isRoot) { - // This background is created regardless of whether this frame is - // visible or not. Visibility decisions are delegated to the - // table background painter. - // We would use nsDisplayGeneric for this rare case except that we - // need the background to be larger than the row frame in some - // cases. - item = new (aBuilder) nsDisplayTableRowBackground(aBuilder, this); - aLists.BorderBackground()->AppendNewToTop(item); - } - } - nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists, item); + nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists); } nsIFrame::LogicalSides diff --git a/layout/tables/nsTableRowGroupFrame.cpp b/layout/tables/nsTableRowGroupFrame.cpp index 8f014b204..943bdf50b 100644 --- a/layout/tables/nsTableRowGroupFrame.cpp +++ b/layout/tables/nsTableRowGroupFrame.cpp @@ -249,19 +249,8 @@ nsTableRowGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists) { - nsDisplayTableItem* item = nullptr; - if (IsVisibleInSelection(aBuilder)) { - bool isRoot = aBuilder->IsAtRootOfPseudoStackingContext(); - if (isRoot) { - // This background is created regardless of whether this frame is - // visible or not. Visibility decisions are delegated to the - // table background painter. - item = new (aBuilder) nsDisplayTableRowGroupBackground(aBuilder, this); - aLists.BorderBackground()->AppendNewToTop(item); - } - } nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect, - aLists, item, DisplayRows); + aLists, DisplayRows); } nsIFrame::LogicalSides diff --git a/layout/tables/nsTableWrapperFrame.cpp b/layout/tables/nsTableWrapperFrame.cpp index f0b6d1512..da71375d5 100644 --- a/layout/tables/nsTableWrapperFrame.cpp +++ b/layout/tables/nsTableWrapperFrame.cpp @@ -190,7 +190,11 @@ nsTableWrapperFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, // Now we have to sort everything by content order, since the caption // may be somewhere inside the table - set.SortAllByContentOrder(GetContent()); + set.BlockBorderBackgrounds()->SortByContentOrder(GetContent()); + set.Floats()->SortByContentOrder(GetContent()); + set.Content()->SortByContentOrder(GetContent()); + set.PositionedDescendants()->SortByContentOrder(GetContent()); + set.Outlines()->SortByContentOrder(GetContent()); set.MoveTo(aLists); } -- cgit v1.2.3 From 0de40040f006c493feeff7d688c169b904b8a22e Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sat, 2 Nov 2019 23:07:40 +0100 Subject: Issue #146 - Part 2: Remove custom table painting component. Since we're now putting table borders and backgrounds properly in the display lists, we no longer need this custom component to do this work for us. --- layout/base/nsDisplayItemTypesList.h | 3 - layout/tables/moz.build | 1 - layout/tables/nsTableCellFrame.cpp | 14 - layout/tables/nsTableCellFrame.h | 5 - layout/tables/nsTableFrame.cpp | 84 ---- layout/tables/nsTableFrame.h | 10 - layout/tables/nsTablePainter.cpp | 696 --------------------------------- layout/tables/nsTablePainter.h | 268 ------------- layout/tables/nsTableRowFrame.cpp | 39 -- layout/tables/nsTableRowFrame.h | 1 - layout/tables/nsTableRowGroupFrame.cpp | 40 -- layout/tables/nsTableRowGroupFrame.h | 1 - 12 files changed, 1162 deletions(-) delete mode 100644 layout/tables/nsTablePainter.cpp delete mode 100644 layout/tables/nsTablePainter.h diff --git a/layout/base/nsDisplayItemTypesList.h b/layout/base/nsDisplayItemTypesList.h index d24f6f38f..cf809817f 100644 --- a/layout/base/nsDisplayItemTypesList.h +++ b/layout/base/nsDisplayItemTypesList.h @@ -56,9 +56,6 @@ DECLARE_DISPLAY_ITEM_TYPE(SVG_PATH_GEOMETRY) DECLARE_DISPLAY_ITEM_TYPE(SVG_TEXT) DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_BACKGROUND) DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_SELECTION) -DECLARE_DISPLAY_ITEM_TYPE(TABLE_ROW_BACKGROUND) -DECLARE_DISPLAY_ITEM_TYPE(TABLE_ROW_GROUP_BACKGROUND) -DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_BACKGROUND) DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_COLLAPSE) DECLARE_DISPLAY_ITEM_TYPE(TEXT) DECLARE_DISPLAY_ITEM_TYPE(TEXT_OVERFLOW) diff --git a/layout/tables/moz.build b/layout/tables/moz.build index b77776320..e28e21ee0 100644 --- a/layout/tables/moz.build +++ b/layout/tables/moz.build @@ -21,7 +21,6 @@ UNIFIED_SOURCES += [ 'nsTableColFrame.cpp', 'nsTableColGroupFrame.cpp', 'nsTableFrame.cpp', - 'nsTablePainter.cpp', 'nsTableRowFrame.cpp', 'nsTableRowGroupFrame.cpp', 'nsTableWrapperFrame.cpp', diff --git a/layout/tables/nsTableCellFrame.cpp b/layout/tables/nsTableCellFrame.cpp index bac8d387f..ec9458f76 100644 --- a/layout/tables/nsTableCellFrame.cpp +++ b/layout/tables/nsTableCellFrame.cpp @@ -12,7 +12,6 @@ #include "nsTableColFrame.h" #include "nsTableRowFrame.h" #include "nsTableRowGroupFrame.h" -#include "nsTablePainter.h" #include "nsStyleContext.h" #include "nsStyleConsts.h" #include "nsPresContext.h" @@ -380,19 +379,6 @@ nsTableCellFrame::PaintBackground(nsRenderingContext& aRenderingContext, return nsCSSRendering::PaintBackground(params); } -// Called by nsTablePainter -DrawResult -nsTableCellFrame::PaintCellBackground(nsRenderingContext& aRenderingContext, - const nsRect& aDirtyRect, nsPoint aPt, - uint32_t aFlags) -{ - if (!StyleVisibility()->IsVisible()) { - return DrawResult::SUCCESS; - } - - return PaintBackground(aRenderingContext, aDirtyRect, aPt, aFlags); -} - nsresult nsTableCellFrame::ProcessBorders(nsTableFrame* aFrame, nsDisplayListBuilder* aBuilder, diff --git a/layout/tables/nsTableCellFrame.h b/layout/tables/nsTableCellFrame.h index 240809850..5f87c5f6d 100644 --- a/layout/tables/nsTableCellFrame.h +++ b/layout/tables/nsTableCellFrame.h @@ -107,11 +107,6 @@ public: const nsRect& aDirtyRect, const nsDisplayListSet& aLists) override; - DrawResult PaintCellBackground(nsRenderingContext& aRenderingContext, - const nsRect& aDirtyRect, nsPoint aPt, - uint32_t aFlags); - - virtual nsresult ProcessBorders(nsTableFrame* aFrame, nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists); diff --git a/layout/tables/nsTableFrame.cpp b/layout/tables/nsTableFrame.cpp index b5cd3b487..06a05292f 100644 --- a/layout/tables/nsTableFrame.cpp +++ b/layout/tables/nsTableFrame.cpp @@ -23,7 +23,6 @@ #include "nsTableRowFrame.h" #include "nsTableRowGroupFrame.h" #include "nsTableWrapperFrame.h" -#include "nsTablePainter.h" #include "BasicTableLayoutStrategy.h" #include "FixedTableLayoutStrategy.h" @@ -1167,36 +1166,6 @@ nsDisplayTableBorderCollapse::Paint(nsDisplayListBuilder* aBuilder, static_cast(mFrame)->PaintBCBorders(*drawTarget, mVisibleRect - pt); } -class nsDisplayTableBorderBackground : public nsDisplayTableItem { -public: - nsDisplayTableBorderBackground(nsDisplayListBuilder* aBuilder, - nsTableFrame* aFrame, - bool aDrawsBackground) : - nsDisplayTableItem(aBuilder, aFrame, aDrawsBackground) { - MOZ_COUNT_CTOR(nsDisplayTableBorderBackground); - } -#ifdef NS_BUILD_REFCNT_LOGGING - virtual ~nsDisplayTableBorderBackground() { - MOZ_COUNT_DTOR(nsDisplayTableBorderBackground); - } -#endif - - virtual void Paint(nsDisplayListBuilder* aBuilder, - nsRenderingContext* aCtx) override; - NS_DISPLAY_DECL_NAME("TableBorderBackground", TYPE_TABLE_BORDER_BACKGROUND) -}; - -void -nsDisplayTableBorderBackground::Paint(nsDisplayListBuilder* aBuilder, - nsRenderingContext* aCtx) -{ - DrawResult result = static_cast(mFrame)-> - PaintTableBorderBackground(aBuilder, *aCtx, mVisibleRect, - ToReferenceFrame()); - - nsDisplayTableItemGeometry::UpdateDrawResult(this, result); -} - /* static */ void nsTableFrame::GenericTraversal(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, const nsRect& aDirtyRect, const nsDisplayListSet& aLists) @@ -1383,59 +1352,6 @@ nsTableFrame::GetDeflationForBackground(nsPresContext* aPresContext) const return GetOuterBCBorder(wm).GetPhysicalMargin(wm); } -// XXX We don't put the borders and backgrounds in tree order like we should. -// That requires some major surgery which we aren't going to do right now. -DrawResult -nsTableFrame::PaintTableBorderBackground(nsDisplayListBuilder* aBuilder, - nsRenderingContext& aRenderingContext, - const nsRect& aDirtyRect, - nsPoint aPt) -{ - nsPresContext* presContext = PresContext(); - - uint32_t bgFlags = aBuilder->GetBackgroundPaintFlags(); - PaintBorderFlags borderFlags = aBuilder->ShouldSyncDecodeImages() - ? PaintBorderFlags::SYNC_DECODE_IMAGES - : PaintBorderFlags(); - - TableBackgroundPainter painter(this, TableBackgroundPainter::eOrigin_Table, - presContext, aRenderingContext, - aDirtyRect, aPt, bgFlags); - nsMargin deflate = GetDeflationForBackground(presContext); - // If 'deflate' is (0,0,0,0) then we'll paint the table background - // in a separate display item, so don't do it here. - DrawResult result = - painter.PaintTable(this, deflate, deflate != nsMargin(0, 0, 0, 0)); - - if (StyleVisibility()->IsVisible()) { - if (!IsBorderCollapse()) { - Sides skipSides = GetSkipSides(); - nsRect rect(aPt, mRect.Size()); - - result &= - nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, - aDirtyRect, rect, mStyleContext, - borderFlags, skipSides); - } else { - DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); - - gfxPoint devPixelOffset = - nsLayoutUtils::PointToGfxPoint(aPt, - PresContext()->AppUnitsPerDevPixel()); - - // XXX we should probably get rid of this translation at some stage - // But that would mean modifying PaintBCBorders, ugh - AutoRestoreTransform autoRestoreTransform(drawTarget); - drawTarget->SetTransform( - drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset))); - - PaintBCBorders(*drawTarget, aDirtyRect - aPt); - } - } - - return result; -} - nsIFrame::LogicalSides nsTableFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const { diff --git a/layout/tables/nsTableFrame.h b/layout/tables/nsTableFrame.h index d4b6d88a7..a6b786402 100644 --- a/layout/tables/nsTableFrame.h +++ b/layout/tables/nsTableFrame.h @@ -271,16 +271,6 @@ public: const nsRect& aDirtyRect, const nsDisplayListSet& aLists) override; - /** - * Paint the background of the table and its parts (column groups, - * columns, row groups, rows, and cells), and the table border, and all - * internal borders if border-collapse is on. - */ - DrawResult PaintTableBorderBackground(nsDisplayListBuilder* aBuilder, - nsRenderingContext& aRenderingContext, - const nsRect& aDirtyRect, - nsPoint aPt); - /** Get the outer half (i.e., the part outside the height and width of * the table) of the largest segment (?) of border-collapsed border on * the table on each side, or 0 for non border-collapsed tables. diff --git a/layout/tables/nsTablePainter.cpp b/layout/tables/nsTablePainter.cpp deleted file mode 100644 index bfe2a7d42..000000000 --- a/layout/tables/nsTablePainter.cpp +++ /dev/null @@ -1,696 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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 "nsTableFrame.h" -#include "nsTableRowGroupFrame.h" -#include "nsTableRowFrame.h" -#include "nsTableColGroupFrame.h" -#include "nsTableColFrame.h" -#include "nsTableCellFrame.h" -#include "nsTablePainter.h" -#include "nsCSSRendering.h" -#include "nsDisplayList.h" -#include "mozilla/WritingModes.h" - -/* ~*~ Table Background Painting ~*~ - - Mozilla's Table Background painting follows CSS2.1:17.5.1 - That section does not, however, describe the effect of - borders on background image positioning. What we do is: - - - in separate borders, the borders are passed in so that - their width figures in image positioning, even for rows/cols, which - don't have visible borders. This is done to allow authors - to position row backgrounds by, for example, aligning the - top left corner with the top left padding corner of the - top left table cell in the row in cases where all cells - have consistent border widths. If we didn't honor these - invisible borders, there would be no way to align - backgrounds with the padding edges, and designs would be - lost underneath the border. - - - in collapsing borders, because the borders collapse, we - use the -continuous border- width to synthesize a border - style and pass that in instead of using the element's - assigned style directly. - - The continuous border on a given edge of an element is - the collapse of all borders guaranteed to be continuous - along that edge. Cell borders are ignored (because, for - example, setting a thick border on the leftmost cell - should not shift the row background over; this way a - striped background set on
will line up across rows - even if the cells are assigned arbitrary border widths. - - For example, the continuous border on the top edge of a - row group is the collapse of any row group, row, and - table borders involved. (The first row group's top would - be [table-top + row group top + first row top]. It's bottom - would be [row group bottom + last row bottom + next row - top + next row group top].) - The top edge of a column group likewise includes the - table top, row group top, and first row top borders. However, - it *also* includes its own top border, since that is guaranteed - to be continuous. It does not include column borders because - those are not guaranteed to be continuous: there may be two - columns with different borders in a single column group. - - An alternative would be to define the continuous border as - [table? + row group + row] for horizontal - [table? + col group + col] for vertical - This makes it easier to line up backgrounds across elements - despite varying border widths, but it does not give much - flexibility in aligning /to/ those border widths. -*/ - - -/* ~*~ TableBackgroundPainter ~*~ - - The TableBackgroundPainter is created and destroyed in one painting call. - Its principal function is PaintTable, which paints all table element - backgrounds. The initial code in that method sets up an array of column - data that caches the background styles and the border sizes for the - columns and colgroups in TableBackgroundData structs in mCols. Data for - BC borders are calculated and stashed in a synthesized border style struct - in the data struct since collapsed borders aren't the same width as style- - assigned borders. The data struct optimizes by only doing this if there's - an image background; otherwise we don't care. //XXX should also check background-origin - The class then loops through the row groups, rows, and cells. At the cell - level, it paints the backgrounds, one over the other, inside the cell rect. - - The exception to this pattern is when a table element creates a (pseudo) - stacking context. Elements with stacking contexts (e.g., 'opacity' applied) - are passed through, which means their data (and their - descendants' data) are not cached. The full loop is still executed, however, - so that underlying layers can get painted at the cell level. - - The TableBackgroundPainter is then destroyed. - - Elements with stacking contexts set up their own painter to finish the - painting process, since they were skipped. They call the appropriate - sub-part of the loop (e.g. PaintRow) which will paint the frame and - descendants. - - XXX views are going - */ - -using namespace mozilla; -using namespace mozilla::image; - -TableBackgroundPainter::TableBackgroundData::TableBackgroundData() - : mFrame(nullptr) - , mVisible(false) - , mUsesSynthBorder(false) -{ -} - -TableBackgroundPainter::TableBackgroundData::TableBackgroundData(nsIFrame* aFrame) - : mFrame(aFrame) - , mRect(aFrame->GetRect()) - , mVisible(mFrame->IsVisibleForPainting()) - , mUsesSynthBorder(false) -{ -} - -inline bool -TableBackgroundPainter::TableBackgroundData::ShouldSetBCBorder() const -{ - /* we only need accurate border data when positioning background images*/ - if (!mVisible) { - return false; - } - - const nsStyleImageLayers& layers = mFrame->StyleBackground()->mImage; - NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, layers) { - if (!layers.mLayers[i].mImage.IsEmpty()) - return true; - } - return false; -} - -void -TableBackgroundPainter::TableBackgroundData::SetBCBorder(const nsMargin& aBorder) -{ - mUsesSynthBorder = true; - mSynthBorderWidths = aBorder; -} - -nsStyleBorder -TableBackgroundPainter::TableBackgroundData::StyleBorder(const nsStyleBorder& aZeroBorder) const -{ - MOZ_ASSERT(mVisible, "Don't call StyleBorder on an invisible TableBackgroundData"); - - if (mUsesSynthBorder) { - nsStyleBorder result = aZeroBorder; - NS_FOR_CSS_SIDES(side) { - result.SetBorderWidth(side, mSynthBorderWidths.Side(side)); - } - return result; - } - - MOZ_ASSERT(mFrame); - - return *mFrame->StyleBorder(); -} - -TableBackgroundPainter::TableBackgroundPainter(nsTableFrame* aTableFrame, - Origin aOrigin, - nsPresContext* aPresContext, - nsRenderingContext& aRenderingContext, - const nsRect& aDirtyRect, - const nsPoint& aRenderPt, - uint32_t aBGPaintFlags) - : mPresContext(aPresContext), - mRenderingContext(aRenderingContext), - mRenderPt(aRenderPt), - mDirtyRect(aDirtyRect), - mOrigin(aOrigin), - mZeroBorder(aPresContext), - mBGPaintFlags(aBGPaintFlags) -{ - MOZ_COUNT_CTOR(TableBackgroundPainter); - - NS_FOR_CSS_SIDES(side) { - mZeroBorder.SetBorderStyle(side, NS_STYLE_BORDER_STYLE_SOLID); - mZeroBorder.SetBorderWidth(side, 0); - } - - mIsBorderCollapse = aTableFrame->IsBorderCollapse(); -#ifdef DEBUG - mCompatMode = mPresContext->CompatibilityMode(); -#endif - mNumCols = aTableFrame->GetColCount(); -} - -TableBackgroundPainter::~TableBackgroundPainter() -{ - MOZ_COUNT_DTOR(TableBackgroundPainter); -} - -DrawResult -TableBackgroundPainter::PaintTableFrame(nsTableFrame* aTableFrame, - nsTableRowGroupFrame* aFirstRowGroup, - nsTableRowGroupFrame* aLastRowGroup, - const nsMargin& aDeflate) -{ - MOZ_ASSERT(aTableFrame, "null frame"); - TableBackgroundData tableData(aTableFrame); - tableData.mRect.MoveTo(0,0); //using table's coords - tableData.mRect.Deflate(aDeflate); - WritingMode wm = aTableFrame->GetWritingMode(); - if (mIsBorderCollapse && tableData.ShouldSetBCBorder()) { - if (aFirstRowGroup && aLastRowGroup && mNumCols > 0) { - //only handle non-degenerate tables; we need a more robust BC model - //to make degenerate tables' borders reasonable to deal with - LogicalMargin border(wm); - LogicalMargin tempBorder(wm); - nsTableColFrame* colFrame = aTableFrame->GetColFrame(mNumCols - 1); - if (colFrame) { - colFrame->GetContinuousBCBorderWidth(wm, tempBorder); - } - border.IEnd(wm) = tempBorder.IEnd(wm); - - aLastRowGroup->GetContinuousBCBorderWidth(wm, tempBorder); - border.BEnd(wm) = tempBorder.BEnd(wm); - - nsTableRowFrame* rowFrame = aFirstRowGroup->GetFirstRow(); - if (rowFrame) { - rowFrame->GetContinuousBCBorderWidth(wm, tempBorder); - border.BStart(wm) = tempBorder.BStart(wm); - } - - border.IStart(wm) = aTableFrame->GetContinuousIStartBCBorderWidth(); - - tableData.SetBCBorder(border.GetPhysicalMargin(wm)); - } - } - - DrawResult result = DrawResult::SUCCESS; - - if (tableData.IsVisible()) { - nsCSSRendering::PaintBGParams params = - nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, - mRenderingContext, - mDirtyRect, - tableData.mRect + mRenderPt, - tableData.mFrame, - mBGPaintFlags); - - result &= - nsCSSRendering::PaintBackgroundWithSC(params, - tableData.mFrame->StyleContext(), - tableData.StyleBorder(mZeroBorder)); - } - - return result; -} - -void -TableBackgroundPainter::TranslateContext(nscoord aDX, - nscoord aDY) -{ - mRenderPt += nsPoint(aDX, aDY); - for (auto& col : mCols) { - col.mCol.mRect.MoveBy(-aDX, -aDY); - } - for (auto& colGroup : mColGroups) { - colGroup.mRect.MoveBy(-aDX, -aDY); - } -} - -TableBackgroundPainter::ColData::ColData(nsIFrame* aFrame, TableBackgroundData& aColGroupBGData) - : mCol(aFrame) - , mColGroup(aColGroupBGData) -{ -} - -DrawResult -TableBackgroundPainter::PaintTable(nsTableFrame* aTableFrame, - const nsMargin& aDeflate, - bool aPaintTableBackground) -{ - NS_PRECONDITION(aTableFrame, "null table frame"); - - nsTableFrame::RowGroupArray rowGroups; - aTableFrame->OrderRowGroups(rowGroups); - WritingMode wm = aTableFrame->GetWritingMode(); - - DrawResult result = DrawResult::SUCCESS; - - if (rowGroups.Length() < 1) { //degenerate case - if (aPaintTableBackground) { - result &= PaintTableFrame(aTableFrame, nullptr, nullptr, nsMargin(0,0,0,0)); - } - /* No cells; nothing else to paint */ - return result; - } - - if (aPaintTableBackground) { - result &= - PaintTableFrame(aTableFrame, rowGroups[0], rowGroups[rowGroups.Length() - 1], - aDeflate); - } - - /*Set up column background/border data*/ - if (mNumCols > 0) { - nsFrameList& colGroupList = aTableFrame->GetColGroups(); - NS_ASSERTION(colGroupList.FirstChild(), "table should have at least one colgroup"); - - // Collect all col group frames first so that we know how many there are. - nsTArray colGroupFrames; - for (nsTableColGroupFrame* cgFrame = static_cast(colGroupList.FirstChild()); - cgFrame; cgFrame = static_cast(cgFrame->GetNextSibling())) { - - if (cgFrame->GetColCount() < 1) { - //No columns, no cells, so no need for data - continue; - } - colGroupFrames.AppendElement(cgFrame); - } - - // Ensure that mColGroups won't reallocate during the loop below, because - // we grab references to its contents and need those to stay valid until - // mColGroups is destroyed as part of TablePainter destruction. - mColGroups.SetCapacity(colGroupFrames.Length()); - - LogicalMargin border(wm); - /* BC iStart borders aren't stored on cols, but the previous column's - iEnd border is the next one's iStart border.*/ - //Start with table's iStart border. - nscoord lastIStartBorder = aTableFrame->GetContinuousIStartBCBorderWidth(); - - for (nsTableColGroupFrame* cgFrame : colGroupFrames) { - /*Create data struct for column group*/ - TableBackgroundData& cgData = *mColGroups.AppendElement(TableBackgroundData(cgFrame)); - if (mIsBorderCollapse && cgData.ShouldSetBCBorder()) { - border.IStart(wm) = lastIStartBorder; - cgFrame->GetContinuousBCBorderWidth(wm, border); - cgData.SetBCBorder(border.GetPhysicalMargin(wm)); - } - - /*Loop over columns in this colgroup*/ - for (nsTableColFrame* col = cgFrame->GetFirstColumn(); col; - col = static_cast(col->GetNextSibling())) { - MOZ_ASSERT(size_t(col->GetColIndex()) == mCols.Length()); - // Store a reference to the colGroup in the ColData element. - ColData& colData = *mCols.AppendElement(ColData(col, cgData)); - //Bring column mRect into table's coord system - colData.mCol.mRect.MoveBy(cgData.mRect.x, cgData.mRect.y); - if (mIsBorderCollapse) { - border.IStart(wm) = lastIStartBorder; - lastIStartBorder = col->GetContinuousBCBorderWidth(wm, border); - if (colData.mCol.ShouldSetBCBorder()) { - colData.mCol.SetBCBorder(border.GetPhysicalMargin(wm)); - } - } - } - } - } - - for (uint32_t i = 0; i < rowGroups.Length(); i++) { - nsTableRowGroupFrame* rg = rowGroups[i]; - TableBackgroundData rowGroupBGData(rg); - // Need to compute the right rect via GetOffsetTo, since the row - // group may not be a child of the table. - rowGroupBGData.mRect.MoveTo(rg->GetOffsetTo(aTableFrame)); - - // We have to draw backgrounds not only within the overflow region of this - // row group, but also possibly (in the case of column / column group - // backgrounds) at its pre-relative-positioning location. - nsRect rgVisualOverflow = rg->GetVisualOverflowRectRelativeToSelf(); - nsRect rgOverflowRect = rgVisualOverflow + rg->GetPosition(); - nsRect rgNormalRect = rgVisualOverflow + rg->GetNormalPosition(); - - if (rgOverflowRect.Union(rgNormalRect).Intersects(mDirtyRect - mRenderPt)) { - result &= - PaintRowGroup(rg, rowGroupBGData, rg->IsPseudoStackingContextFromStyle()); - } - } - - return result; -} - -DrawResult -TableBackgroundPainter::PaintRowGroup(nsTableRowGroupFrame* aFrame) -{ - return PaintRowGroup(aFrame, TableBackgroundData(aFrame), false); -} - -DrawResult -TableBackgroundPainter::PaintRowGroup(nsTableRowGroupFrame* aFrame, - TableBackgroundData aRowGroupBGData, - bool aPassThrough) -{ - MOZ_ASSERT(aFrame, "null frame"); - - nsTableRowFrame* firstRow = aFrame->GetFirstRow(); - WritingMode wm = aFrame->GetWritingMode(); - - /* Load row group data */ - if (aPassThrough) { - aRowGroupBGData.MakeInvisible(); - } else { - if (mIsBorderCollapse && aRowGroupBGData.ShouldSetBCBorder()) { - LogicalMargin border(wm); - if (firstRow) { - //pick up first row's bstart border (= rg bstart border) - firstRow->GetContinuousBCBorderWidth(wm, border); - /* (row group doesn't store its bstart border) */ - } - //overwrite sides+bottom borders with rg's own - aFrame->GetContinuousBCBorderWidth(wm, border); - aRowGroupBGData.SetBCBorder(border.GetPhysicalMargin(wm)); - } - aPassThrough = !aRowGroupBGData.IsVisible(); - } - - /* translate everything into row group coord system*/ - if (eOrigin_TableRowGroup != mOrigin) { - TranslateContext(aRowGroupBGData.mRect.x, aRowGroupBGData.mRect.y); - } - nsRect rgRect = aRowGroupBGData.mRect; - aRowGroupBGData.mRect.MoveTo(0, 0); - - /* Find the right row to start with */ - - // Note that mDirtyRect - mRenderPt is guaranteed to be in the row - // group's coordinate system here, so passing its .y to - // GetFirstRowContaining is ok. - nscoord overflowAbove; - nsIFrame* cursor = aFrame->GetFirstRowContaining(mDirtyRect.y - mRenderPt.y, &overflowAbove); - - // Sadly, it seems like there may be non-row frames in there... or something? - // There are certainly null-checks in GetFirstRow() and GetNextRow(). :( - while (cursor && cursor->GetType() != nsGkAtoms::tableRowFrame) { - cursor = cursor->GetNextSibling(); - } - - // It's OK if cursor is null here. - nsTableRowFrame* row = static_cast(cursor); - if (!row) { - // No useful cursor; just start at the top. Don't bother to set up a - // cursor; if we've gotten this far then we've already built the display - // list for the rowgroup, so not having a cursor means that there's some - // good reason we don't have a cursor and we shouldn't create one here. - row = firstRow; - } - - DrawResult result = DrawResult::SUCCESS; - - /* Finally paint */ - for (; row; row = row->GetNextRow()) { - TableBackgroundData rowBackgroundData(row); - - // Be sure to consider our positions both pre- and post-relative - // positioning, since we potentially need to paint at both places. - nscoord rowY = std::min(rowBackgroundData.mRect.y, row->GetNormalPosition().y); - - // Intersect wouldn't handle rowspans. - if (cursor && - (mDirtyRect.YMost() - mRenderPt.y) <= (rowY - overflowAbove)) { - // All done; cells originating in later rows can't intersect mDirtyRect. - break; - } - - result &= - PaintRow(row, aRowGroupBGData, rowBackgroundData, - aPassThrough || row->IsPseudoStackingContextFromStyle()); - } - - /* translate back into table coord system */ - if (eOrigin_TableRowGroup != mOrigin) { - TranslateContext(-rgRect.x, -rgRect.y); - } - - return result; -} - -DrawResult -TableBackgroundPainter::PaintRow(nsTableRowFrame* aFrame) -{ - return PaintRow(aFrame, TableBackgroundData(), TableBackgroundData(aFrame), false); -} - -DrawResult -TableBackgroundPainter::PaintRow(nsTableRowFrame* aFrame, - const TableBackgroundData& aRowGroupBGData, - TableBackgroundData aRowBGData, - bool aPassThrough) -{ - MOZ_ASSERT(aFrame, "null frame"); - - /* Load row data */ - WritingMode wm = aFrame->GetWritingMode(); - if (aPassThrough) { - aRowBGData.MakeInvisible(); - } else { - if (mIsBorderCollapse && aRowBGData.ShouldSetBCBorder()) { - LogicalMargin border(wm); - nsTableRowFrame* nextRow = aFrame->GetNextRow(); - if (nextRow) { //outer bStart after us is inner bEnd for us - border.BEnd(wm) = nextRow->GetOuterBStartContBCBorderWidth(); - } - else { //acquire rg's bEnd border - nsTableRowGroupFrame* rowGroup = static_cast(aFrame->GetParent()); - rowGroup->GetContinuousBCBorderWidth(wm, border); - } - //get the rest of the borders; will overwrite all but bEnd - aFrame->GetContinuousBCBorderWidth(wm, border); - - aRowBGData.SetBCBorder(border.GetPhysicalMargin(wm)); - } - aPassThrough = !aRowBGData.IsVisible(); - } - - /* Translate */ - if (eOrigin_TableRow == mOrigin) { - /* If we originate from the row, then make the row the origin. */ - aRowBGData.mRect.MoveTo(0, 0); - } - //else: Use row group's coord system -> no translation necessary - - DrawResult result = DrawResult::SUCCESS; - - for (nsTableCellFrame* cell = aFrame->GetFirstCell(); cell; cell = cell->GetNextCell()) { - nsRect cellBGRect, rowBGRect, rowGroupBGRect, colBGRect; - ComputeCellBackgrounds(cell, aRowGroupBGData, aRowBGData, - cellBGRect, rowBGRect, - rowGroupBGRect, colBGRect); - - // Find the union of all the cell background layers. - nsRect combinedRect(cellBGRect); - combinedRect.UnionRect(combinedRect, rowBGRect); - combinedRect.UnionRect(combinedRect, rowGroupBGRect); - combinedRect.UnionRect(combinedRect, colBGRect); - - if (combinedRect.Intersects(mDirtyRect)) { - bool passCell = aPassThrough || cell->IsPseudoStackingContextFromStyle(); - result &= - PaintCell(cell, aRowGroupBGData, aRowBGData, cellBGRect, rowBGRect, - rowGroupBGRect, colBGRect, passCell); - } - } - - return result; -} - -DrawResult -TableBackgroundPainter::PaintCell(nsTableCellFrame* aCell, - const TableBackgroundData& aRowGroupBGData, - const TableBackgroundData& aRowBGData, - nsRect& aCellBGRect, - nsRect& aRowBGRect, - nsRect& aRowGroupBGRect, - nsRect& aColBGRect, - bool aPassSelf) -{ - MOZ_ASSERT(aCell, "null frame"); - - const nsStyleTableBorder* cellTableStyle; - cellTableStyle = aCell->StyleTableBorder(); - if (NS_STYLE_TABLE_EMPTY_CELLS_SHOW != cellTableStyle->mEmptyCells && - aCell->GetContentEmpty() && !mIsBorderCollapse) { - return DrawResult::SUCCESS; - } - - int32_t colIndex; - aCell->GetColIndex(colIndex); - // We're checking mNumCols instead of mCols.Length() here because mCols can - // be empty even if mNumCols > 0. - NS_ASSERTION(size_t(colIndex) < mNumCols, "out-of-bounds column index"); - if (size_t(colIndex) >= mNumCols) { - return DrawResult::SUCCESS; - } - - // If callers call PaintRowGroup or PaintRow directly, we haven't processed - // our columns. Ignore column / col group backgrounds in that case. - bool haveColumns = !mCols.IsEmpty(); - - DrawResult result = DrawResult::SUCCESS; - - //Paint column group background - if (haveColumns && mCols[colIndex].mColGroup.IsVisible()) { - nsCSSRendering::PaintBGParams params = - nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, mRenderingContext, - mDirtyRect, - mCols[colIndex].mColGroup.mRect + mRenderPt, - mCols[colIndex].mColGroup.mFrame, - mBGPaintFlags); - params.bgClipRect = &aColBGRect; - result &= - nsCSSRendering::PaintBackgroundWithSC(params, - mCols[colIndex].mColGroup.mFrame->StyleContext(), - mCols[colIndex].mColGroup.StyleBorder(mZeroBorder)); - } - - //Paint column background - if (haveColumns && mCols[colIndex].mCol.IsVisible()) { - nsCSSRendering::PaintBGParams params = - nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, mRenderingContext, - mDirtyRect, - mCols[colIndex].mCol.mRect + mRenderPt, - mCols[colIndex].mCol.mFrame, - mBGPaintFlags); - params.bgClipRect = &aColBGRect; - result &= - nsCSSRendering::PaintBackgroundWithSC(params, - mCols[colIndex].mCol.mFrame->StyleContext(), - mCols[colIndex].mCol.StyleBorder(mZeroBorder)); - } - - //Paint row group background - if (aRowGroupBGData.IsVisible()) { - nsCSSRendering::PaintBGParams params = - nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, mRenderingContext, - mDirtyRect, - aRowGroupBGData.mRect + mRenderPt, - aRowGroupBGData.mFrame, mBGPaintFlags); - params.bgClipRect = &aRowGroupBGRect; - result &= - nsCSSRendering::PaintBackgroundWithSC(params, - aRowGroupBGData.mFrame->StyleContext(), - aRowGroupBGData.StyleBorder(mZeroBorder)); - } - - //Paint row background - if (aRowBGData.IsVisible()) { - nsCSSRendering::PaintBGParams params = - nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, mRenderingContext, - mDirtyRect, - aRowBGData.mRect + mRenderPt, - aRowBGData.mFrame, mBGPaintFlags); - params.bgClipRect = &aRowBGRect; - result &= - nsCSSRendering::PaintBackgroundWithSC(params, - aRowBGData.mFrame->StyleContext(), - aRowBGData.StyleBorder(mZeroBorder)); - } - - //Paint cell background in border-collapse unless we're just passing - if (mIsBorderCollapse && !aPassSelf) { - result &= - aCell->PaintCellBackground(mRenderingContext, mDirtyRect, - aCellBGRect.TopLeft(), mBGPaintFlags); - } - - return result; -} - -void -TableBackgroundPainter::ComputeCellBackgrounds(nsTableCellFrame* aCell, - const TableBackgroundData& aRowGroupBGData, - const TableBackgroundData& aRowBGData, - nsRect& aCellBGRect, - nsRect& aRowBGRect, - nsRect& aRowGroupBGRect, - nsRect& aColBGRect) -{ - // We need to compute table background layer rects for this cell space, - // adjusted for possible relative positioning. This behavior is not specified - // at the time of this writing, but the approach below should be web - // compatible. - // - // Our goal is that relative positioning of a table part should leave - // backgrounds *under* that part unchanged. ("Under" being defined by CSS 2.1 - // Section 17.5.1.) If a cell is positioned, we do not expect the row - // background to move. On the other hand, the backgrounds of layers *above* - // the positioned part are taken along for the ride -- for example, - // positioning a row group will also cause the row background to be drawn in - // the new location, unless it has further positioning applied. - // - // Each table part layer has its position stored in the coordinate space of - // the layer below (which is to say, its geometric parent), and the stored - // position is the post-relative-positioning one. The position of each - // background layer rect is thus determined by peeling off successive table - // part layers, removing the contribution of each layer's positioning one by - // one. Every rect we generate will be the same size, the size of the cell - // space. - - // We cannot rely on the row group background data to be available, since some - // callers enter through PaintRow. - nsIFrame* rowGroupFrame = - aRowGroupBGData.mFrame ? aRowGroupBGData.mFrame : aRowBGData.mFrame->GetParent(); - - // The cell background goes at the cell's position, translated to use the same - // coordinate system as aRowBGData. - aCellBGRect = aCell->GetRect() + aRowBGData.mRect.TopLeft() + mRenderPt; - - // The row background goes at the normal position of the cell, which is to say - // the position without relative positioning applied. - aRowBGRect = aCellBGRect + (aCell->GetNormalPosition() - aCell->GetPosition()); - - // The row group background goes at the position we'd find the cell if neither - // the cell's relative positioning nor the row's were applied. - aRowGroupBGRect = aRowBGRect + - (aRowBGData.mFrame->GetNormalPosition() - aRowBGData.mFrame->GetPosition()); - - // The column and column group backgrounds (they're always at the same - // location, since relative positioning doesn't apply to columns or column - // groups) are drawn at the position we'd find the cell if none of the cell's, - // row's, or row group's relative positioning were applied. - aColBGRect = aRowGroupBGRect + - (rowGroupFrame->GetNormalPosition() - rowGroupFrame->GetPosition()); - -} diff --git a/layout/tables/nsTablePainter.h b/layout/tables/nsTablePainter.h deleted file mode 100644 index dfba42156..000000000 --- a/layout/tables/nsTablePainter.h +++ /dev/null @@ -1,268 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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 nsTablePainter_h__ -#define nsTablePainter_h__ - -#include "imgIContainer.h" - -#include "celldata.h" - -// flags for Paint, PaintChild, PaintChildren are currently only used by tables. -//Table-based paint call; not a direct call as with views -#define NS_PAINT_FLAG_TABLE_BG_PAINT 0x00000001 -//Cells should paint their backgrounds only, no children -#define NS_PAINT_FLAG_TABLE_CELL_BG_PASS 0x00000002 - -class nsIFrame; -class nsTableFrame; -class nsTableRowGroupFrame; -class nsTableRowFrame; -class nsTableCellFrame; - -class TableBackgroundPainter -{ - /* - * Helper class for painting table backgrounds - * - */ - - typedef mozilla::image::DrawResult DrawResult; - - public: - - enum Origin { eOrigin_Table, eOrigin_TableRowGroup, eOrigin_TableRow }; - - /** Public constructor - * @param aTableFrame - the table's table frame - * @param aOrigin - what type of table frame is creating this instance - * @param aPresContext - the presentation context - * @param aRenderingContext - the rendering context - * @param aDirtyRect - the area that needs to be painted, - * relative to aRenderingContext - * @param aPt - offset of the table frame relative to - * aRenderingContext - * @param aBGPaintFlags - Flags of the nsCSSRendering::PAINTBG_* variety - */ - TableBackgroundPainter(nsTableFrame* aTableFrame, - Origin aOrigin, - nsPresContext* aPresContext, - nsRenderingContext& aRenderingContext, - const nsRect& aDirtyRect, - const nsPoint& aPt, - uint32_t aBGPaintFlags); - - /** Destructor */ - ~TableBackgroundPainter(); - - /* ~*~ The Border Collapse Painting Issue ~*~ - - In border-collapse, the *table* paints the cells' borders, - so we need to make sure the backgrounds get painted first - (underneath) by doing a cell-background-only painting pass. - */ - - /* ~*~ Using nsTablePainter Background Painting ~*~ - - A call to PaintTable will normally paint all of the table's - elements (except for the table background, if aPaintTableBackground - is false). - Elements with views however, will be skipped and must create their - own painter to call the appropriate paint function in their ::Paint - method (e.g. painter.PaintRow in nsTableRow::Paint) - */ - - /** Paint background for the table frame (if requested) and its children - * down through cells. - * (Cells themselves will only be painted in border collapse) - * Table must do a flagged TABLE_BG_PAINT ::Paint call on its - * children afterwards - * @param aTableFrame - the table frame - * @param aDeflate - deflation needed to bring table's mRect - * to the outer grid lines in border-collapse - * @param aPaintTableBackground - if true, the table background - * is included, otherwise it isn't - * @returns DrawResult::SUCCESS if all painting was successful. If some - * painting failed or an improved result could be achieved by sync - * decoding images, returns another value. - */ - DrawResult PaintTable(nsTableFrame* aTableFrame, const nsMargin& aDeflate, - bool aPaintTableBackground); - - /** Paint background for the row group and its children down through cells - * (Cells themselves will only be painted in border collapse) - * Standards mode only - * Table Row Group must do a flagged TABLE_BG_PAINT ::Paint call on its - * children afterwards - * @param aFrame - the table row group frame - * @returns DrawResult::SUCCESS if all painting was successful. If some - * painting failed or an improved result could be achieved by sync - * decoding images, returns another value. - */ - DrawResult PaintRowGroup(nsTableRowGroupFrame* aFrame); - - /** Paint background for the row and its children down through cells - * (Cells themselves will only be painted in border collapse) - * Standards mode only - * Table Row must do a flagged TABLE_BG_PAINT ::Paint call on its - * children afterwards - * @param aFrame - the table row frame - * @returns DrawResult::SUCCESS if all painting was successful. If some - * painting failed or an improved result could be achieved by sync - * decoding images, returns another value. - */ - DrawResult PaintRow(nsTableRowFrame* aFrame); - - private: - struct TableBackgroundData; - - /** Paint table frame's background - * @param aTableFrame - the table frame - * @param aFirstRowGroup - the first (in layout order) row group - * may be null - * @param aLastRowGroup - the last (in layout order) row group - * may be null - * @param aDeflate - adjustment to frame's rect (used for quirks BC) - * may be null - */ - DrawResult PaintTableFrame(nsTableFrame* aTableFrame, - nsTableRowGroupFrame* aFirstRowGroup, - nsTableRowGroupFrame* aLastRowGroup, - const nsMargin& aDeflate); - - /* aPassThrough params indicate whether to paint the element or to just - * pass through and paint underlying layers only. - * aRowGroupBGData is not a const reference because the function modifies - * its copy. Same for aRowBGData in PaintRow. - * See Public versions for function descriptions - */ - DrawResult PaintRowGroup(nsTableRowGroupFrame* aFrame, - TableBackgroundData aRowGroupBGData, - bool aPassThrough); - - DrawResult PaintRow(nsTableRowFrame* aFrame, - const TableBackgroundData& aRowGroupBGData, - TableBackgroundData aRowBGData, - bool aPassThrough); - - /** Paint table background layers for this cell space - * Also paints cell's own background in border-collapse mode - * @param aCell - the cell - * @param aRowGroupBGData - background drawing info for the row group - * @param aRowBGData - background drawing info for the row - * @param aCellBGRect - background rect for the cell - * @param aRowBGRect - background rect for the row - * @param aRowGroupBGRect - background rect for the row group - * @param aColBGRect - background rect for the column and column group - * @param aPassSelf - pass this cell; i.e. paint only underlying layers - */ - DrawResult PaintCell(nsTableCellFrame* aCell, - const TableBackgroundData& aRowGroupBGData, - const TableBackgroundData& aRowBGData, - nsRect& aCellBGRect, - nsRect& aRowBGRect, - nsRect& aRowGroupBGRect, - nsRect& aColBGRect, - bool aPassSelf); - - /** Compute table background layer positions for this cell space - * @param aCell - the cell - * @param aRowGroupBGData - background drawing info for the row group - * @param aRowBGData - background drawing info for the row - * @param aCellBGRectOut - outparam: background rect for the cell - * @param aRowBGRectOut - outparam: background rect for the row - * @param aRowGroupBGRectOut - outparam: background rect for the row group - * @param aColBGRectOut - outparam: background rect for the column - and column group - */ - void ComputeCellBackgrounds(nsTableCellFrame* aCell, - const TableBackgroundData& aRowGroupBGData, - const TableBackgroundData& aRowBGData, - nsRect& aCellBGRect, - nsRect& aRowBGRect, - nsRect& aRowGroupBGRect, - nsRect& aColBGRect); - - /** Translate mRenderingContext, mDirtyRect, and mCols' column and - * colgroup coords - * @param aDX - origin's x-coord change - * @param aDY - origin's y-coord change - */ - void TranslateContext(nscoord aDX, - nscoord aDY); - - struct TableBackgroundData { - public: - /** - * Construct an empty TableBackgroundData instance, which is invisible. - */ - TableBackgroundData(); - - /** - * Construct a TableBackgroundData instance for a frame. Visibility will - * be derived from the frame and can be overridden using MakeInvisible(). - */ - explicit TableBackgroundData(nsIFrame* aFrame); - - /** Destructor */ - ~TableBackgroundData() {} - - /** Data is valid & frame is visible */ - bool IsVisible() const { return mVisible; } - - /** Override visibility of the frame, force it to be invisible */ - void MakeInvisible() { mVisible = false; } - - /** True if need to set border-collapse border; must call SetFull beforehand */ - bool ShouldSetBCBorder() const; - - /** Set border-collapse border with aBorderWidth as widths */ - void SetBCBorder(const nsMargin& aBorderWidth); - - /** - * @param aZeroBorder An nsStyleBorder instance that has been initialized - * for the right nsPresContext, with all border widths - * set to zero and border styles set to solid. - * @return The nsStyleBorder that should be used for rendering - * this background. - */ - nsStyleBorder StyleBorder(const nsStyleBorder& aZeroBorder) const; - - nsIFrame* const mFrame; - - /** mRect is the rect of mFrame in the current coordinate system */ - nsRect mRect; - - private: - nsMargin mSynthBorderWidths; - bool mVisible; - bool mUsesSynthBorder; - }; - - struct ColData { - ColData(nsIFrame* aFrame, TableBackgroundData& aColGroupBGData); - TableBackgroundData mCol; - TableBackgroundData& mColGroup; // reference to col's parent colgroup's data, owned by TablePainter in mColGroups - }; - - nsPresContext* mPresContext; - nsRenderingContext& mRenderingContext; - nsPoint mRenderPt; - nsRect mDirtyRect; -#ifdef DEBUG - nsCompatibility mCompatMode; -#endif - bool mIsBorderCollapse; - Origin mOrigin; //user's table frame type - - nsTArray mColGroups; - nsTArray mCols; - size_t mNumCols; - - nsStyleBorder mZeroBorder; //cached zero-width border - uint32_t mBGPaintFlags; -}; - -#endif diff --git a/layout/tables/nsTableRowFrame.cpp b/layout/tables/nsTableRowFrame.cpp index dc053c991..05b9deee9 100644 --- a/layout/tables/nsTableRowFrame.cpp +++ b/layout/tables/nsTableRowFrame.cpp @@ -561,45 +561,6 @@ nsTableRowFrame::CalcBSize(const ReflowInput& aReflowInput) return GetInitialBSize(); } -/** - * We need a custom display item for table row backgrounds. This is only used - * when the table row is the root of a stacking context (e.g., has 'opacity'). - * Table row backgrounds can extend beyond the row frame bounds, when - * the row contains row-spanning cells. - */ -class nsDisplayTableRowBackground : public nsDisplayTableItem { -public: - nsDisplayTableRowBackground(nsDisplayListBuilder* aBuilder, - nsTableRowFrame* aFrame) : - nsDisplayTableItem(aBuilder, aFrame) { - MOZ_COUNT_CTOR(nsDisplayTableRowBackground); - } -#ifdef NS_BUILD_REFCNT_LOGGING - virtual ~nsDisplayTableRowBackground() { - MOZ_COUNT_DTOR(nsDisplayTableRowBackground); - } -#endif - - virtual void Paint(nsDisplayListBuilder* aBuilder, - nsRenderingContext* aCtx) override; - NS_DISPLAY_DECL_NAME("TableRowBackground", TYPE_TABLE_ROW_BACKGROUND) -}; - -void -nsDisplayTableRowBackground::Paint(nsDisplayListBuilder* aBuilder, - nsRenderingContext* aCtx) -{ - auto rowFrame = static_cast(mFrame); - TableBackgroundPainter painter(rowFrame->GetTableFrame(), - TableBackgroundPainter::eOrigin_TableRow, - mFrame->PresContext(), *aCtx, - mVisibleRect, ToReferenceFrame(), - aBuilder->GetBackgroundPaintFlags()); - - DrawResult result = painter.PaintRow(rowFrame); - nsDisplayTableItemGeometry::UpdateDrawResult(this, result); -} - void nsTableRowFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, diff --git a/layout/tables/nsTableRowFrame.h b/layout/tables/nsTableRowFrame.h index a6aba81e7..9e15d851f 100644 --- a/layout/tables/nsTableRowFrame.h +++ b/layout/tables/nsTableRowFrame.h @@ -8,7 +8,6 @@ #include "mozilla/Attributes.h" #include "nscore.h" #include "nsContainerFrame.h" -#include "nsTablePainter.h" #include "nsTableRowGroupFrame.h" #include "mozilla/WritingModes.h" diff --git a/layout/tables/nsTableRowGroupFrame.cpp b/layout/tables/nsTableRowGroupFrame.cpp index 943bdf50b..3e4f60f62 100644 --- a/layout/tables/nsTableRowGroupFrame.cpp +++ b/layout/tables/nsTableRowGroupFrame.cpp @@ -154,46 +154,6 @@ nsTableRowGroupFrame::InitRepeatedFrame(nsTableRowGroupFrame* aHeaderFooterFrame return NS_OK; } -/** - * We need a custom display item for table row backgrounds. This is only used - * when the table row is the root of a stacking context (e.g., has 'opacity'). - * Table row backgrounds can extend beyond the row frame bounds, when - * the row contains row-spanning cells. - */ -class nsDisplayTableRowGroupBackground : public nsDisplayTableItem { -public: - nsDisplayTableRowGroupBackground(nsDisplayListBuilder* aBuilder, - nsTableRowGroupFrame* aFrame) : - nsDisplayTableItem(aBuilder, aFrame) { - MOZ_COUNT_CTOR(nsDisplayTableRowGroupBackground); - } -#ifdef NS_BUILD_REFCNT_LOGGING - virtual ~nsDisplayTableRowGroupBackground() { - MOZ_COUNT_DTOR(nsDisplayTableRowGroupBackground); - } -#endif - - virtual void Paint(nsDisplayListBuilder* aBuilder, - nsRenderingContext* aCtx) override; - - NS_DISPLAY_DECL_NAME("TableRowGroupBackground", TYPE_TABLE_ROW_GROUP_BACKGROUND) -}; - -void -nsDisplayTableRowGroupBackground::Paint(nsDisplayListBuilder* aBuilder, - nsRenderingContext* aCtx) -{ - auto rgFrame = static_cast(mFrame); - TableBackgroundPainter painter(rgFrame->GetTableFrame(), - TableBackgroundPainter::eOrigin_TableRowGroup, - mFrame->PresContext(), *aCtx, - mVisibleRect, ToReferenceFrame(), - aBuilder->GetBackgroundPaintFlags()); - - DrawResult result = painter.PaintRowGroup(rgFrame); - nsDisplayTableItemGeometry::UpdateDrawResult(this, result); -} - // Handle the child-traversal part of DisplayGenericTablePart static void DisplayRows(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, diff --git a/layout/tables/nsTableRowGroupFrame.h b/layout/tables/nsTableRowGroupFrame.h index 7abdd4b74..721d91046 100644 --- a/layout/tables/nsTableRowGroupFrame.h +++ b/layout/tables/nsTableRowGroupFrame.h @@ -10,7 +10,6 @@ #include "nsContainerFrame.h" #include "nsIAtom.h" #include "nsILineIterator.h" -#include "nsTablePainter.h" #include "nsTArray.h" #include "nsTableFrame.h" #include "mozilla/WritingModes.h" -- cgit v1.2.3 From 873f2cf8cc30125d6c573210f328bc71b95cdc5b Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Sat, 2 Nov 2019 20:31:13 -0400 Subject: Issue #1265 - Expose sndio as a build option for any supporting system --- build/moz.configure/old.configure | 1 + config/system-headers | 2 ++ media/libcubeb/src/moz.build | 2 +- media/libcubeb/tests/moz.build | 2 +- old-configure.in | 17 +++++++++++++++++ toolkit/library/moz.build | 2 +- 6 files changed, 23 insertions(+), 3 deletions(-) diff --git a/build/moz.configure/old.configure b/build/moz.configure/old.configure index 326b2327e..552019a13 100644 --- a/build/moz.configure/old.configure +++ b/build/moz.configure/old.configure @@ -215,6 +215,7 @@ def old_configure_options(*options): '--enable-pref-extensions', '--enable-private-build', '--enable-pulseaudio', + '--enable-sndio', '--enable-raw', '--enable-readline', '--enable-reflow-perf', diff --git a/config/system-headers b/config/system-headers index b4f901792..b10324f0f 100644 --- a/config/system-headers +++ b/config/system-headers @@ -910,7 +910,9 @@ signal.h SimpleGameSound.h SIOUX.h size_t.h +#ifdef MOZ_SNDIO sndio.h +#endif someincludefile.h Sound.h soundcard.h diff --git a/media/libcubeb/src/moz.build b/media/libcubeb/src/moz.build index b6d07126a..772aa6d39 100644 --- a/media/libcubeb/src/moz.build +++ b/media/libcubeb/src/moz.build @@ -39,7 +39,7 @@ if CONFIG['MOZ_JACK']: ] DEFINES['USE_JACK'] = True -if CONFIG['OS_ARCH'] == 'OpenBSD': +if CONFIG['MOZ_SNDIO']: SOURCES += [ 'cubeb_sndio.c', ] diff --git a/media/libcubeb/tests/moz.build b/media/libcubeb/tests/moz.build index 1b17c7b1c..ca63a4d8f 100644 --- a/media/libcubeb/tests/moz.build +++ b/media/libcubeb/tests/moz.build @@ -68,7 +68,7 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'uikit': '-framework CoreFoundation', '-framework AudioToolbox', ] -elif CONFIG['OS_TARGET'] == 'OpenBSD': +elif CONFIG['MOZ_SNDIO']: OS_LIBS += [ 'sndio', ] diff --git a/old-configure.in b/old-configure.in index dae43d6f1..6638ea688 100644 --- a/old-configure.in +++ b/old-configure.in @@ -3145,6 +3145,23 @@ fi AC_SUBST(MOZ_PULSEAUDIO) +dnl ======================================================== +dnl = Enable sndio +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(sndio, +[ --enable-sndio Enable sndio support], + MOZ_SNDIO=1, + MOZ_SNDIO= ) + +if test -n "$MOZ_SNDIO"; then + AC_DEFINE(MOZ_SNDIO) + MOZ_CHECK_HEADER([sndio.h], [], + AC_MSG_ERROR( + [--enable-sndio specified but it requires sndio development headers])) + AC_DEFINE(MOZ_SNDIO) +fi +AC_SUBST(MOZ_SNDIO) + dnl ======================================================== dnl NegotiateAuth dnl ======================================================== diff --git a/toolkit/library/moz.build b/toolkit/library/moz.build index 293c2feaf..4d870c73c 100644 --- a/toolkit/library/moz.build +++ b/toolkit/library/moz.build @@ -241,7 +241,7 @@ OS_LIBS += CONFIG['ICONV_LIBS'] if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'uikit'): OS_LIBS += CONFIG['TK_LIBS'] -if CONFIG['OS_ARCH'] == 'OpenBSD': +if CONFIG['MOZ_SNDIO']: OS_LIBS += [ 'sndio', ] -- cgit v1.2.3 From d162ecbaffe845c9707da5d2f6cab11f343ef00e Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Sun, 27 Oct 2019 19:40:52 -0400 Subject: Issue #1267 - Part 1: Update libcubeb to a1200c34. --- media/libcubeb/AUTHORS | 1 + media/libcubeb/bug1292803_pulse_assert.patch | 46 - media/libcubeb/bug1302231_emergency_bailout.patch | 140 - media/libcubeb/disable-assert.patch | 23 + media/libcubeb/disable-iaudioclient3.patch | 64 + media/libcubeb/fix-crashes.patch | 71 - media/libcubeb/include/cubeb.h | 255 +- media/libcubeb/osx-linearize-operations.patch | 968 ------- media/libcubeb/prevent-double-free.patch | 46 - media/libcubeb/src/android/cubeb-output-latency.h | 76 + media/libcubeb/src/android/cubeb_media_library.h | 62 + media/libcubeb/src/android/sles_definitions.h | 39 +- media/libcubeb/src/cubeb-internal.h | 17 +- media/libcubeb/src/cubeb-jni-instances.h | 34 + media/libcubeb/src/cubeb-jni.cpp | 68 + media/libcubeb/src/cubeb-jni.h | 10 + media/libcubeb/src/cubeb.c | 233 +- media/libcubeb/src/cubeb_alsa.c | 560 +++- media/libcubeb/src/cubeb_array_queue.h | 97 + media/libcubeb/src/cubeb_assert.h | 17 + media/libcubeb/src/cubeb_audiotrack.c | 15 +- media/libcubeb/src/cubeb_audiounit.cpp | 3156 +++++++++++++-------- media/libcubeb/src/cubeb_jack.cpp | 160 +- media/libcubeb/src/cubeb_log.cpp | 144 + media/libcubeb/src/cubeb_log.h | 18 +- media/libcubeb/src/cubeb_mixer.cpp | 663 +++++ media/libcubeb/src/cubeb_mixer.h | 37 + media/libcubeb/src/cubeb_opensl.c | 1566 +++++++--- media/libcubeb/src/cubeb_pulse.c | 488 +++- media/libcubeb/src/cubeb_resampler.cpp | 122 +- media/libcubeb/src/cubeb_resampler.h | 15 +- media/libcubeb/src/cubeb_resampler_internal.h | 123 +- media/libcubeb/src/cubeb_ringbuffer.h | 495 ++++ media/libcubeb/src/cubeb_sndio.c | 416 ++- media/libcubeb/src/cubeb_strings.c | 155 + media/libcubeb/src/cubeb_strings.h | 44 + media/libcubeb/src/cubeb_sun.c | 1006 ++++--- media/libcubeb/src/cubeb_utils.cpp | 23 + media/libcubeb/src/cubeb_utils.h | 148 +- media/libcubeb/src/cubeb_utils_unix.h | 4 +- media/libcubeb/src/cubeb_utils_win.h | 4 +- media/libcubeb/src/cubeb_wasapi.cpp | 2276 +++++++++------ media/libcubeb/src/cubeb_winmm.c | 160 +- media/libcubeb/src/moz.build | 8 +- media/libcubeb/unresampled-frames.patch | 36 - media/libcubeb/update.sh | 73 +- media/libcubeb/uplift-part-of-f07ee6d-esr52.patch | 167 -- media/libcubeb/uplift-patch-7a4c711.patch | 69 - media/libcubeb/uplift-system-listener-patch.patch | 402 --- media/libcubeb/uplift-wasapi-part-to-beta.patch | 118 - 50 files changed, 9348 insertions(+), 5590 deletions(-) delete mode 100644 media/libcubeb/bug1292803_pulse_assert.patch delete mode 100644 media/libcubeb/bug1302231_emergency_bailout.patch create mode 100644 media/libcubeb/disable-assert.patch create mode 100644 media/libcubeb/disable-iaudioclient3.patch delete mode 100644 media/libcubeb/fix-crashes.patch delete mode 100644 media/libcubeb/osx-linearize-operations.patch delete mode 100644 media/libcubeb/prevent-double-free.patch create mode 100644 media/libcubeb/src/android/cubeb-output-latency.h create mode 100644 media/libcubeb/src/android/cubeb_media_library.h create mode 100644 media/libcubeb/src/cubeb-jni-instances.h create mode 100644 media/libcubeb/src/cubeb-jni.cpp create mode 100644 media/libcubeb/src/cubeb-jni.h create mode 100644 media/libcubeb/src/cubeb_array_queue.h create mode 100644 media/libcubeb/src/cubeb_assert.h create mode 100644 media/libcubeb/src/cubeb_log.cpp create mode 100644 media/libcubeb/src/cubeb_mixer.cpp create mode 100644 media/libcubeb/src/cubeb_mixer.h create mode 100644 media/libcubeb/src/cubeb_ringbuffer.h create mode 100644 media/libcubeb/src/cubeb_strings.c create mode 100644 media/libcubeb/src/cubeb_strings.h create mode 100644 media/libcubeb/src/cubeb_utils.cpp delete mode 100644 media/libcubeb/unresampled-frames.patch delete mode 100644 media/libcubeb/uplift-part-of-f07ee6d-esr52.patch delete mode 100644 media/libcubeb/uplift-patch-7a4c711.patch delete mode 100644 media/libcubeb/uplift-system-listener-patch.patch delete mode 100644 media/libcubeb/uplift-wasapi-part-to-beta.patch diff --git a/media/libcubeb/AUTHORS b/media/libcubeb/AUTHORS index 0fde65baa..f0f959522 100644 --- a/media/libcubeb/AUTHORS +++ b/media/libcubeb/AUTHORS @@ -13,3 +13,4 @@ Landry Breuil Jacek Caban Paul Hancock Ted Mielczarek +Chun-Min Chang diff --git a/media/libcubeb/bug1292803_pulse_assert.patch b/media/libcubeb/bug1292803_pulse_assert.patch deleted file mode 100644 index 8dee88777..000000000 --- a/media/libcubeb/bug1292803_pulse_assert.patch +++ /dev/null @@ -1,46 +0,0 @@ -commit 2c7617f5ca20b764c605e19af490889c761e65e2 -Author: Matthew Gregan -Date: Thu Nov 10 19:07:07 2016 +1300 - - pulse: Bail early from pulse_defer_event_cb when shutting down. - - When starting a stream, trigger_user_callback may be called from - stream_write_callback and immediately enter a drain situation, creating - a drain timer and setting shutdown to true. If pulse_defer_event_cb - then runs without checking for shutdown, it can overwrite the current - timer with a new timer, resulting in a leaked timer and a null pointer - assertion. - -diff --git a/src/cubeb_pulse.c b/src/cubeb_pulse.c -index 5b61bda..86f2ba3 100644 ---- a/src/cubeb_pulse.c -+++ b/src/cubeb_pulse.c -@@ -181,9 +181,9 @@ static void - stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u) - { - (void)a; -- (void)e; - (void)tv; - cubeb_stream * stm = u; -+ assert(stm->drain_timer == e); - stream_state_change_callback(stm, CUBEB_STATE_DRAINED); - /* there's no pa_rttime_free, so use this instead. */ - a->time_free(stm->drain_timer); -@@ -267,6 +267,7 @@ trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cub - assert(r == 0 || r == -PA_ERR_NODATA); - /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */ - /* arbitrary safety margin: double the current latency. */ -+ assert(!stm->drain_timer); - stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm); - stm->shutdown = 1; - return; -@@ -851,6 +852,9 @@ pulse_defer_event_cb(pa_mainloop_api * a, void * userdata) - { - (void)a; - cubeb_stream * stm = userdata; -+ if (stm->shutdown) { -+ return; -+ } - size_t writable_size = WRAP(pa_stream_writable_size)(stm->output_stream); - trigger_user_callback(stm->output_stream, NULL, writable_size, stm); - } diff --git a/media/libcubeb/bug1302231_emergency_bailout.patch b/media/libcubeb/bug1302231_emergency_bailout.patch deleted file mode 100644 index 82152c23f..000000000 --- a/media/libcubeb/bug1302231_emergency_bailout.patch +++ /dev/null @@ -1,140 +0,0 @@ -From 37ce70d4400a2ab6b59ee432b41d4ffcc9d136ff Mon Sep 17 00:00:00 2001 -From: Paul Adenot -Date: Thu, 10 Nov 2016 21:45:14 +0100 -Subject: [PATCH] Bail out safely from the rendering loop if we could not join - the rendering thread in time (#187) - -Bail out safely from the rendering loop if we could not join the rendering thread in time. ---- - src/cubeb_wasapi.cpp | 41 ++++++++++++++++++++++++++++++++++++----- - 1 file changed, 36 insertions(+), 5 deletions(-) - -diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp -index 9e689b9..519d5ca 100644 ---- a/src/cubeb_wasapi.cpp -+++ b/src/cubeb_wasapi.cpp -@@ -22,6 +22,7 @@ - #include - #include - #include -+#include - - #include "cubeb/cubeb.h" - #include "cubeb-internal.h" -@@ -220,9 +221,11 @@ struct cubeb_stream - float volume; - /* True if the stream is draining. */ - bool draining; -+ /* True when we've destroyed the stream. This pointer is leaked on stream -+ * destruction if we could not join the thread. */ -+ std::atomic*> emergency_bailout; - }; - -- - class wasapi_endpoint_notification_client : public IMMNotificationClient - { - public: -@@ -781,6 +784,7 @@ static unsigned int __stdcall - wasapi_stream_render_loop(LPVOID stream) - { - cubeb_stream * stm = static_cast(stream); -+ std::atomic * emergency_bailout = stm->emergency_bailout; - - bool is_playing = true; - HANDLE wait_array[4] = { -@@ -820,6 +824,10 @@ wasapi_stream_render_loop(LPVOID stream) - wait_array, - FALSE, - 1000); -+ if (*emergency_bailout) { -+ delete emergency_bailout; -+ return 0; -+ } - if (waitResult != WAIT_TIMEOUT) { - timeout_count = 0; - } -@@ -1134,12 +1142,13 @@ int wasapi_init(cubeb ** context, char const * context_name) - } - - namespace { --void stop_and_join_render_thread(cubeb_stream * stm) -+bool stop_and_join_render_thread(cubeb_stream * stm) - { -+ bool rv = true; - LOG("Stop and join render thread."); - if (!stm->thread) { - LOG("No thread present."); -- return; -+ return true; - } - - BOOL ok = SetEvent(stm->shutdown_event); -@@ -1153,11 +1162,15 @@ void stop_and_join_render_thread(cubeb_stream * stm) - if (r == WAIT_TIMEOUT) { - /* Something weird happened, leak the thread and continue the shutdown - * process. */ -+ *(stm->emergency_bailout) = true; - LOG("Destroy WaitForSingleObject on thread timed out," - " leaking the thread: %d", GetLastError()); -+ rv = false; - } - if (r == WAIT_FAILED) { -+ *(stm->emergency_bailout) = true; - LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError()); -+ rv = false; - } - - LOG("Closing thread."); -@@ -1167,6 +1180,8 @@ void stop_and_join_render_thread(cubeb_stream * stm) - - CloseHandle(stm->shutdown_event); - stm->shutdown_event = 0; -+ -+ return rv; - } - - void wasapi_destroy(cubeb * context) -@@ -1775,7 +1790,16 @@ void wasapi_stream_destroy(cubeb_stream * stm) - { - XASSERT(stm); - -- stop_and_join_render_thread(stm); -+ // Only free stm->emergency_bailout if we could not join the thread. -+ // If we could not join the thread, stm->emergency_bailout is true -+ // and is still alive until the thread wakes up and exits cleanly. -+ if (stop_and_join_render_thread(stm)) { -+ delete stm->emergency_bailout.load(); -+ stm->emergency_bailout = nullptr; -+ } else { -+ // If we're leaking, it must be that this is true. -+ assert(*(stm->emergency_bailout)); -+ } - - unregister_notification_client(stm); - -@@ -1844,6 +1868,8 @@ int wasapi_stream_start(cubeb_stream * stm) - - auto_lock lock(stm->stream_reset_lock); - -+ stm->emergency_bailout = new std::atomic(false); -+ - if (stm->output_client) { - int rv = stream_start_one_side(stm, OUTPUT); - if (rv != CUBEB_OK) { -@@ -1903,7 +1929,12 @@ int wasapi_stream_stop(cubeb_stream * stm) - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - } - -- stop_and_join_render_thread(stm); -+ if (stop_and_join_render_thread(stm)) { -+ if (stm->emergency_bailout.load()) { -+ delete stm->emergency_bailout.load(); -+ stm->emergency_bailout = nullptr; -+ } -+ } - - return CUBEB_OK; - } --- -2.7.4 - diff --git a/media/libcubeb/disable-assert.patch b/media/libcubeb/disable-assert.patch new file mode 100644 index 000000000..6fae3a766 --- /dev/null +++ b/media/libcubeb/disable-assert.patch @@ -0,0 +1,23 @@ +diff --git a/media/libcubeb/src/cubeb_resampler.cpp b/media/libcubeb/src/cubeb_resampler.cpp +--- a/media/libcubeb/src/cubeb_resampler.cpp ++++ b/media/libcubeb/src/cubeb_resampler.cpp +@@ -50,18 +50,17 @@ passthrough_resampler::passthrough_re + + template + long passthrough_resampler::fill(void * input_buffer, long * input_frames_count, + void * output_buffer, long output_frames) + { + if (input_buffer) { + assert(input_frames_count); + } +- assert((input_buffer && output_buffer && +- *input_frames_count + static_cast(samples_to_frames(internal_input_buffer.length())) >= output_frames) || ++ assert((input_buffer && output_buffer) || + (output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) || + (input_buffer && !output_buffer && output_frames == 0)); + + if (input_buffer) { + if (!output_buffer) { + output_frames = *input_frames_count; + } + internal_input_buffer.push(static_cast(input_buffer), diff --git a/media/libcubeb/disable-iaudioclient3.patch b/media/libcubeb/disable-iaudioclient3.patch new file mode 100644 index 000000000..658806b26 --- /dev/null +++ b/media/libcubeb/disable-iaudioclient3.patch @@ -0,0 +1,64 @@ +diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp +--- a/media/libcubeb/src/cubeb_wasapi.cpp ++++ b/media/libcubeb/src/cubeb_wasapi.cpp +@@ -1916,24 +1916,24 @@ int setup_wasapi_stream_one_side(cubeb_s + LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME, hr); + } + return CUBEB_ERROR; + } + } + + /* Get a client. We will get all other interfaces we need from + * this pointer. */ +- hr = device->Activate(__uuidof(IAudioClient3), +- CLSCTX_INPROC_SERVER, +- NULL, audio_client.receive_vpp()); +- if (hr == E_NOINTERFACE) { ++ // hr = device->Activate(__uuidof(IAudioClient3), ++ // CLSCTX_INPROC_SERVER, ++ // NULL, audio_client.receive_vpp()); ++ // if (hr == E_NOINTERFACE) { + hr = device->Activate(__uuidof(IAudioClient), + CLSCTX_INPROC_SERVER, + NULL, audio_client.receive_vpp()); +- } ++ //} + + if (FAILED(hr)) { + LOG("Could not activate the device to get an audio" + " client for %s: error: %lx\n", DIRECTION_NAME, hr); + // A particular device can't be activated because it has been + // unplugged, try fall back to the default audio device. + if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED) { + LOG("Trying again with the default %s audio device.", DIRECTION_NAME); +@@ -1989,26 +1989,26 @@ int setup_wasapi_stream_one_side(cubeb_s + // Check if a loopback device should be requested. Note that event callbacks + // do not work with loopback devices, so only request these if not looping. + if (is_loopback) { + flags |= AUDCLNT_STREAMFLAGS_LOOPBACK; + } else { + flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; + } + +- if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) { +- LOG("Initialized with IAudioClient3"); +- } else { ++ // if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) { ++ // LOG("Initialized with IAudioClient3"); ++ // } else { + hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, + flags, + frames_to_hns(stm, stm->latency), + 0, + mix_format.get(), + NULL); +- } ++ // } + if (FAILED(hr)) { + LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr); + return CUBEB_ERROR; + } + + hr = audio_client->GetBufferSize(buffer_frame_count); + if (FAILED(hr)) { + LOG("Could not get the buffer size from the client" diff --git a/media/libcubeb/fix-crashes.patch b/media/libcubeb/fix-crashes.patch deleted file mode 100644 index b23501fcf..000000000 --- a/media/libcubeb/fix-crashes.patch +++ /dev/null @@ -1,71 +0,0 @@ -This patch fixes three different crashes, one crash per chunk in this patch, -in the same order. -- Bug 1342389 -- Bug 1345147 -- Bug 1347453 - -diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp ---- a/media/libcubeb/src/cubeb_wasapi.cpp -+++ b/media/libcubeb/src/cubeb_wasapi.cpp -@@ -878,16 +878,23 @@ wasapi_stream_render_loop(LPVOID stream) - - /* WaitForMultipleObjects timeout can trigger in cases where we don't want to - treat it as a timeout, such as across a system sleep/wake cycle. Trigger - the timeout error handling only when the timeout_limit is reached, which is - reset on each successful loop. */ - unsigned timeout_count = 0; - const unsigned timeout_limit = 5; - while (is_playing) { -+ // We want to check the emergency bailout variable before a -+ // and after the WaitForMultipleObject, because the handles WaitForMultipleObjects -+ // is going to wait on might have been closed already. -+ if (*emergency_bailout) { -+ delete emergency_bailout; -+ return 0; -+ } - DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array), - wait_array, - FALSE, - 1000); - if (*emergency_bailout) { - delete emergency_bailout; - return 0; - } -@@ -1199,16 +1206,22 @@ bool stop_and_join_render_thread(cubeb_s - { - bool rv = true; - LOG("Stop and join render thread."); - if (!stm->thread) { - LOG("No thread present."); - return true; - } - -+ // If we've already leaked the thread, just return, -+ // there is not much we can do. -+ if (!stm->emergency_bailout.load()) { -+ return false; -+ } -+ - BOOL ok = SetEvent(stm->shutdown_event); - if (!ok) { - LOG("Destroy SetEvent failed: %lx", GetLastError()); - } - - /* Wait five seconds for the rendering thread to return. It's supposed to - * check its event loop very often, five seconds is rather conservative. */ - DWORD r = WaitForSingleObject(stm->thread, 5000); -diff --git a/media/libcubeb/update.sh b/media/libcubeb/update.sh ---- a/media/libcubeb/update.sh -+++ b/media/libcubeb/update.sh -@@ -66,8 +66,11 @@ fi - echo "Applying a patch on top of $version" - patch -p1 < ./wasapi-drift-fix-passthrough-resampler.patch - - echo "Applying a patch on top of $version" - patch -p1 < ./audiounit-drift-fix.patch - - echo "Applying a patch on top of $version" - patch -p1 < ./uplift-wasapi-fixes-aurora.patch -+ -+echo "Applying a patch on top of $version" -+patch -p3 < ./fix-crashes.patch diff --git a/media/libcubeb/include/cubeb.h b/media/libcubeb/include/cubeb.h index 449b39c55..e6cf8dd87 100644 --- a/media/libcubeb/include/cubeb.h +++ b/media/libcubeb/include/cubeb.h @@ -8,6 +8,7 @@ #define CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382 #include +#include #include "cubeb_export.h" #if defined(__cplusplus) @@ -32,11 +33,11 @@ extern "C" { cubeb * app_ctx; cubeb_init(&app_ctx, "Example Application"); int rv; - int rate; - int latency_frames; + uint32_t rate; + uint32_t latency_frames; uint64_t ts; - rv = cubeb_get_min_latency(app_ctx, output_params, &latency_frames); + rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames); if (rv != CUBEB_OK) { fprintf(stderr, "Could not get minimum latency"); return rv; @@ -52,16 +53,20 @@ extern "C" { output_params.format = CUBEB_SAMPLE_FLOAT32NE; output_params.rate = rate; output_params.channels = 2; + output_params.layout = CUBEB_LAYOUT_UNDEFINED; + output_params.prefs = CUBEB_STREAM_PREF_NONE; cubeb_stream_params input_params; - output_params.format = CUBEB_SAMPLE_FLOAT32NE; - output_params.rate = rate; - output_params.channels = 1; + input_params.format = CUBEB_SAMPLE_FLOAT32NE; + input_params.rate = rate; + input_params.channels = 1; + input_params.layout = CUBEB_LAYOUT_UNDEFINED; + input_params.prefs = CUBEB_STREAM_PREF_NONE; cubeb_stream * stm; rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1", - NULL, input_params, - NULL, output_params, + NULL, &input_params, + NULL, &output_params, latency_frames, data_cb, state_cb, NULL); @@ -99,7 +104,7 @@ extern "C" { for (i = 0; i < nframes; ++i) { for (c = 0; c < 2; ++c) { - buf[i][c] = in[i]; + out[i][c] = in[i]; } } return nframes; @@ -143,31 +148,9 @@ typedef enum { #endif } cubeb_sample_format; -#if defined(__ANDROID__) -/** - * This maps to the underlying stream types on supported platforms, e.g. - * Android. - */ -typedef enum { - CUBEB_STREAM_TYPE_VOICE_CALL = 0, - CUBEB_STREAM_TYPE_SYSTEM = 1, - CUBEB_STREAM_TYPE_RING = 2, - CUBEB_STREAM_TYPE_MUSIC = 3, - CUBEB_STREAM_TYPE_ALARM = 4, - CUBEB_STREAM_TYPE_NOTIFICATION = 5, - CUBEB_STREAM_TYPE_BLUETOOTH_SCO = 6, - CUBEB_STREAM_TYPE_SYSTEM_ENFORCED = 7, - CUBEB_STREAM_TYPE_DTMF = 8, - CUBEB_STREAM_TYPE_TTS = 9, - CUBEB_STREAM_TYPE_FM = 10, - - CUBEB_STREAM_TYPE_MAX -} cubeb_stream_type; -#endif - /** An opaque handle used to refer a particular input or output device * across calls. */ -typedef void * cubeb_devid; +typedef void const * cubeb_devid; /** Level (verbosity) of logging for a particular cubeb context. */ typedef enum { @@ -176,15 +159,93 @@ typedef enum { CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance implications. */ } cubeb_log_level; +typedef enum { + CHANNEL_UNKNOWN = 0, + CHANNEL_FRONT_LEFT = 1 << 0, + CHANNEL_FRONT_RIGHT = 1 << 1, + CHANNEL_FRONT_CENTER = 1 << 2, + CHANNEL_LOW_FREQUENCY = 1 << 3, + CHANNEL_BACK_LEFT = 1 << 4, + CHANNEL_BACK_RIGHT = 1 << 5, + CHANNEL_FRONT_LEFT_OF_CENTER = 1 << 6, + CHANNEL_FRONT_RIGHT_OF_CENTER = 1 << 7, + CHANNEL_BACK_CENTER = 1 << 8, + CHANNEL_SIDE_LEFT = 1 << 9, + CHANNEL_SIDE_RIGHT = 1 << 10, + CHANNEL_TOP_CENTER = 1 << 11, + CHANNEL_TOP_FRONT_LEFT = 1 << 12, + CHANNEL_TOP_FRONT_CENTER = 1 << 13, + CHANNEL_TOP_FRONT_RIGHT = 1 << 14, + CHANNEL_TOP_BACK_LEFT = 1 << 15, + CHANNEL_TOP_BACK_CENTER = 1 << 16, + CHANNEL_TOP_BACK_RIGHT = 1 << 17 +} cubeb_channel; + +typedef uint32_t cubeb_channel_layout; +// Some common layout definitions. +enum { + CUBEB_LAYOUT_UNDEFINED = 0, // Indicate the speaker's layout is undefined. + CUBEB_LAYOUT_MONO = CHANNEL_FRONT_CENTER, + CUBEB_LAYOUT_MONO_LFE = CUBEB_LAYOUT_MONO | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT, + CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_3F = + CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_FRONT_CENTER, + CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_2F1 = + CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_BACK_CENTER, + CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER, + CUBEB_LAYOUT_3F1_LFE = CUBEB_LAYOUT_3F1 | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_2F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT, + CUBEB_LAYOUT_2F2_LFE = CUBEB_LAYOUT_2F2 | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_QUAD = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT, + CUBEB_LAYOUT_QUAD_LFE = CUBEB_LAYOUT_QUAD | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_3F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | CHANNEL_SIDE_LEFT | + CHANNEL_SIDE_RIGHT, + CUBEB_LAYOUT_3F2_LFE = CUBEB_LAYOUT_3F2 | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_3F2_BACK = CUBEB_LAYOUT_QUAD | CHANNEL_FRONT_CENTER, + CUBEB_LAYOUT_3F2_LFE_BACK = CUBEB_LAYOUT_3F2_BACK | CHANNEL_LOW_FREQUENCY, + CUBEB_LAYOUT_3F3R_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY | + CHANNEL_BACK_CENTER | CHANNEL_SIDE_LEFT | + CHANNEL_SIDE_RIGHT, + CUBEB_LAYOUT_3F4_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY | + CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT | + CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT, +}; + +/** Miscellaneous stream preferences. */ +typedef enum { + CUBEB_STREAM_PREF_NONE = 0x00, /**< No stream preferences are requested. */ + CUBEB_STREAM_PREF_LOOPBACK = 0x01, /**< Request a loopback stream. Should be + specified on the input params and an + output device to loopback from should + be passed in place of an input device. */ + CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02, /**< Disable switching + default device on OS + changes. */ + CUBEB_STREAM_PREF_VOICE = 0x04 /**< This stream is going to transport voice data. + Depending on the backend and platform, this can + change the audio input or output devices + selected, as well as the quality of the stream, + for example to accomodate bluetooth SCO modes on + bluetooth devices. */ +} cubeb_stream_prefs; + /** Stream format initialization parameters. */ typedef struct { - cubeb_sample_format format; /**< Requested sample format. One of - #cubeb_sample_format. */ - unsigned int rate; /**< Requested sample rate. Valid range is [1000, 192000]. */ - unsigned int channels; /**< Requested channel count. Valid range is [1, 8]. */ -#if defined(__ANDROID__) - cubeb_stream_type stream_type; /**< Used to map Android audio stream types */ -#endif + cubeb_sample_format format; /**< Requested sample format. One of + #cubeb_sample_format. */ + uint32_t rate; /**< Requested sample rate. Valid range is [1000, 192000]. */ + uint32_t channels; /**< Requested channel count. Valid range is [1, 8]. */ + cubeb_channel_layout layout; /**< Requested channel layout. This must be consistent with the provided channels. CUBEB_LAYOUT_UNDEFINED if unknown */ + cubeb_stream_prefs prefs; /**< Requested preferences. */ } cubeb_stream_params; /** Audio device description */ @@ -271,15 +332,16 @@ typedef enum { } cubeb_device_pref; /** This structure holds the characteristics - * of an input or output audio device. It can be obtained using - * `cubeb_enumerate_devices`, and must be destroyed using - * `cubeb_device_info_destroy`. */ + * of an input or output audio device. It is obtained using + * `cubeb_enumerate_devices`, which returns these structures via + * `cubeb_device_collection` and must be destroyed via + * `cubeb_device_collection_destroy`. */ typedef struct { cubeb_devid devid; /**< Device identifier handle. */ - char * device_id; /**< Device identifier which might be presented in a UI. */ - char * friendly_name; /**< Friendly device name which might be presented in a UI. */ - char * group_id; /**< Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */ - char * vendor_name; /**< Optional vendor name, may be NULL. */ + char const * device_id; /**< Device identifier which might be presented in a UI. */ + char const * friendly_name; /**< Friendly device name which might be presented in a UI. */ + char const * group_id; /**< Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */ + char const * vendor_name; /**< Optional vendor name, may be NULL. */ cubeb_device_type type; /**< Type of device (Input/Output). */ cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */ @@ -287,19 +349,21 @@ typedef struct { cubeb_device_fmt format; /**< Sample format supported. */ cubeb_device_fmt default_format; /**< The default sample format for this device. */ - unsigned int max_channels; /**< Channels. */ - unsigned int default_rate; /**< Default/Preferred sample rate. */ - unsigned int max_rate; /**< Maximum sample rate supported. */ - unsigned int min_rate; /**< Minimum sample rate supported. */ + uint32_t max_channels; /**< Channels. */ + uint32_t default_rate; /**< Default/Preferred sample rate. */ + uint32_t max_rate; /**< Maximum sample rate supported. */ + uint32_t min_rate; /**< Minimum sample rate supported. */ - unsigned int latency_lo; /**< Lowest possible latency in frames. */ - unsigned int latency_hi; /**< Higest possible latency in frames. */ + uint32_t latency_lo; /**< Lowest possible latency in frames. */ + uint32_t latency_hi; /**< Higest possible latency in frames. */ } cubeb_device_info; -/** Device collection. */ +/** Device collection. + * Returned by `cubeb_enumerate_devices` and destroyed by + * `cubeb_device_collection_destroy`. */ typedef struct { - uint32_t count; /**< Device count in collection. */ - cubeb_device_info * device[1]; /**< Array of pointers to device info. */ + cubeb_device_info * device; /**< Array of pointers to device info. */ + size_t count; /**< Device count in collection. */ } cubeb_device_collection; /** User supplied data callback. @@ -316,13 +380,17 @@ typedef struct { @param output_buffer A pointer to a buffer to be filled with audio samples, or nullptr if this is an input-only stream. @param nframes The number of frames of the two buffer. - @retval Number of frames written to the output buffer. If this number is - less than nframes, then the stream will start to drain. + @retval If the stream has output, this is the number of frames written to + the output buffer. In this case, if this number is less than + nframes then the stream will start to drain. If the stream is + input only, then returning nframes indicates data has been read. + In this case, a value less than nframes will result in the stream + being stopped. @retval CUBEB_ERROR on error, in which case the data callback will stop and the stream will enter a shutdown state. */ typedef long (* cubeb_data_callback)(cubeb_stream * stream, void * user_ptr, - const void * input_buffer, + void const * input_buffer, void * output_buffer, long nframes); @@ -342,23 +410,33 @@ typedef void (* cubeb_device_changed_callback)(void * user_ptr); /** * User supplied callback called when the underlying device collection changed. * @param context A pointer to the cubeb context. - * @param user_ptr The pointer passed to cubeb_stream_init. */ + * @param user_ptr The pointer passed to cubeb_register_device_collection_changed. */ typedef void (* cubeb_device_collection_changed_callback)(cubeb * context, void * user_ptr); /** User supplied callback called when a message needs logging. */ -typedef void (* cubeb_log_callback)(const char * fmt, ...); +typedef void (* cubeb_log_callback)(char const * fmt, ...); /** Initialize an application context. This will perform any library or application scoped initialization. + + Note: On Windows platforms, COM must be initialized in MTA mode on + any thread that will call the cubeb API. + @param context A out param where an opaque pointer to the application context will be returned. @param context_name A name for the context. Depending on the platform this can appear in different locations. + @param backend_name The name of the cubeb backend user desires to select. + Accepted values self-documented in cubeb.c: init_oneshot + If NULL, a default ordering is used for backend choice. + A valid choice overrides all other possible backends, + so long as the backend was included at compile time. @retval CUBEB_OK in case of success. @retval CUBEB_ERROR in case of error, for example because the host has no audio hardware. */ -CUBEB_EXPORT int cubeb_init(cubeb ** context, char const * context_name); +CUBEB_EXPORT int cubeb_init(cubeb ** context, char const * context_name, + char const * backend_name); /** Get a read-only string identifying this context's current backend. @param context A pointer to the cubeb context. @@ -376,7 +454,7 @@ CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_cha /** Get the minimal latency value, in frames, that is guaranteed to work when creating a stream for the specified sample rate. This is platform, - hardware and backend dependant. + hardware and backend dependent. @param context A pointer to the cubeb context. @param params On some backends, the minimum achievable latency depends on the characteristics of the stream. @@ -386,11 +464,11 @@ CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_cha @retval CUBEB_ERROR_INVALID_PARAMETER @retval CUBEB_ERROR_NOT_SUPPORTED */ CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context, - cubeb_stream_params params, + cubeb_stream_params * params, uint32_t * latency_frames); /** Get the preferred sample rate for this backend: this is hardware and - platform dependant, and can avoid resampling, and/or trigger fastpaths. + platform dependent, and can avoid resampling, and/or trigger fastpaths. @param context A pointer to the cubeb context. @param rate The samplerate (in Hz) the current configuration prefers. @retval CUBEB_OK @@ -434,7 +512,7 @@ CUBEB_EXPORT int cubeb_stream_init(cubeb * context, cubeb_stream_params * input_stream_params, cubeb_devid output_device, cubeb_stream_params * output_stream_params, - unsigned int latency_frames, + uint32_t latency_frames, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr); @@ -456,6 +534,14 @@ CUBEB_EXPORT int cubeb_stream_start(cubeb_stream * stream); @retval CUBEB_ERROR */ CUBEB_EXPORT int cubeb_stream_stop(cubeb_stream * stream); +/** Reset stream to the default device. + @param stream + @retval CUBEB_OK + @retval CUBEB_ERROR_INVALID_PARAMETER + @retval CUBEB_ERROR_NOT_SUPPORTED + @retval CUBEB_ERROR */ +CUBEB_EXPORT int cubeb_stream_reset_default_device(cubeb_stream * stream); + /** Get the current stream playback position. @param stream @param position Playback position in frames. @@ -482,20 +568,6 @@ CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * late @retval CUBEB_ERROR_NOT_SUPPORTED */ CUBEB_EXPORT int cubeb_stream_set_volume(cubeb_stream * stream, float volume); -/** If the stream is stereo, set the left/right panning. If the stream is mono, - this has no effect. - @param stream the stream for which to change the panning - @param panning a number from -1.0 to 1.0. -1.0 means that the stream is - fully mixed in the left channel, 1.0 means the stream is fully - mixed in the right channel. 0.0 is equal power in the right and - left channel (default). - @retval CUBEB_OK - @retval CUBEB_ERROR_INVALID_PARAMETER if stream is null or if panning is - outside the [-1.0, 1.0] range. - @retval CUBEB_ERROR_NOT_SUPPORTED - @retval CUBEB_ERROR stream is not mono nor stereo */ -CUBEB_EXPORT int cubeb_stream_set_panning(cubeb_stream * stream, float panning); - /** Get the current output device for this stream. @param stm the stream for which to query the current output device @param device a pointer in which the current output device will be stored. @@ -526,6 +598,11 @@ CUBEB_EXPORT int cubeb_stream_device_destroy(cubeb_stream * stream, CUBEB_EXPORT int cubeb_stream_register_device_changed_callback(cubeb_stream * stream, cubeb_device_changed_callback device_changed_callback); +/** Return the user data pointer registered with the stream with cubeb_stream_init. + @param stream the stream for which to retrieve user data pointer. + @retval user data pointer */ +CUBEB_EXPORT void * cubeb_stream_user_ptr(cubeb_stream * stream); + /** Returns enumerated devices. @param context @param devtype device type to include @@ -535,26 +612,26 @@ CUBEB_EXPORT int cubeb_stream_register_device_changed_callback(cubeb_stream * st @retval CUBEB_ERROR_NOT_SUPPORTED */ CUBEB_EXPORT int cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype, - cubeb_device_collection ** collection); + cubeb_device_collection * collection); /** Destroy a cubeb_device_collection, and its `cubeb_device_info`. + @param context @param collection collection to destroy @retval CUBEB_OK @retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer */ -CUBEB_EXPORT int cubeb_device_collection_destroy(cubeb_device_collection * collection); - -/** Destroy a cubeb_device_info structure. - @param info pointer to device info structure - @retval CUBEB_OK - @retval CUBEB_ERROR_INVALID_PARAMETER if info is an invalid pointer */ -CUBEB_EXPORT int cubeb_device_info_destroy(cubeb_device_info * info); +CUBEB_EXPORT int cubeb_device_collection_destroy(cubeb * context, + cubeb_device_collection * collection); /** Registers a callback which is called when the system detects a new device or a device is removed. @param context - @param devtype device type to include + @param devtype device type to include. Different callbacks and user pointers + can be registered for each devtype. The hybrid devtype + `CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT` is also valid + and will register the provided callback and user pointer in both sides. @param callback a function called whenever the system device list changes. - Passing NULL allow to unregister a function + Passing NULL allow to unregister a function. You have to unregister + first before you register a new callback. @param user_ptr pointer to user specified data which will be present in subsequent callbacks. @retval CUBEB_ERROR_NOT_SUPPORTED */ diff --git a/media/libcubeb/osx-linearize-operations.patch b/media/libcubeb/osx-linearize-operations.patch deleted file mode 100644 index 9f4f31bca..000000000 --- a/media/libcubeb/osx-linearize-operations.patch +++ /dev/null @@ -1,968 +0,0 @@ -From: Paul Adenot -Subject: Linearize operations on AudioUnits to sidestep a deadlock. - ---- - -diff --git a/src/cubeb_audiounit.cpp b/src/cubeb_audiounit.cpp ---- a/src/cubeb_audiounit.cpp -+++ b/src/cubeb_audiounit.cpp -@@ -53,40 +53,45 @@ typedef UInt32 AudioFormatFlags; - - #define AU_OUT_BUS 0 - #define AU_IN_BUS 1 - - #define PRINT_ERROR_CODE(str, r) do { \ - LOG("System call failed: %s (rv: %d)", str, r); \ - } while(0) - -+const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb"; -+ - /* Testing empirically, some headsets report a minimal latency that is very - * low, but this does not work in practice. Lie and say the minimum is 256 - * frames. */ - const uint32_t SAFE_MIN_LATENCY_FRAMES = 256; - const uint32_t SAFE_MAX_LATENCY_FRAMES = 512; - - void audiounit_stream_stop_internal(cubeb_stream * stm); - void audiounit_stream_start_internal(cubeb_stream * stm); --static void close_audiounit_stream(cubeb_stream * stm); --static int setup_audiounit_stream(cubeb_stream * stm); -+static void audiounit_close_stream(cubeb_stream *stm); -+static int audiounit_setup_stream(cubeb_stream *stm); - - extern cubeb_ops const audiounit_ops; - - struct cubeb { - cubeb_ops const * ops; - owned_critical_section mutex; - std::atomic active_streams; -+ uint32_t global_latency_frames = 0; - int limit_streams; - cubeb_device_collection_changed_callback collection_changed_callback; - void * collection_changed_user_ptr; - /* Differentiate input from output devices. */ - cubeb_device_type collection_changed_devtype; - uint32_t devtype_device_count; - AudioObjectID * devtype_device_array; -+ // The queue is asynchronously deallocated once all references to it are released -+ dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL); - }; - - class auto_array_wrapper - { - public: - explicit auto_array_wrapper(auto_array * ar) - : float_ar(ar) - , short_ar(nullptr) -@@ -205,16 +210,17 @@ struct cubeb_stream { - cubeb_resampler * resampler; - /* This is the number of output callback we got in a row. This is usually one, - * but can be two when the input and output rate are different, and more when - * a device has been plugged or unplugged, as there can be some time before - * the device is ready. */ - std::atomic output_callback_in_a_row; - /* This is true if a device change callback is currently running. */ - std::atomic switching_device; -+ std::atomic buffer_size_change_state{ false }; - }; - - bool has_input(cubeb_stream * stm) - { - return stm->input_stream_params.rate != 0; - } - - bool has_output(cubeb_stream * stm) -@@ -256,16 +262,24 @@ audiotimestamp_to_latency(AudioTimeStamp - - uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime); - uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); - - return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL; - } - - static void -+audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames) -+{ -+ stm->mutex.assert_current_thread_owns(); -+ assert(stm->context->active_streams == 1); -+ stm->context->global_latency_frames = latency_frames; -+} -+ -+static void - audiounit_make_silent(AudioBuffer * ioData) - { - assert(ioData); - assert(ioData->mData); - memset(ioData->mData, 0, ioData->mDataByteSize); - } - - static OSStatus -@@ -576,29 +590,54 @@ audiounit_get_input_device_id(AudioDevic - device_id); - if (r != noErr) { - return CUBEB_ERROR; - } - - return CUBEB_OK; - } - -+static int -+audiounit_reinit_stream(cubeb_stream * stm, bool is_started) -+{ -+ if (is_started) { -+ audiounit_stream_stop_internal(stm); -+ } -+ -+ { -+ auto_lock lock(stm->mutex); -+ -+ audiounit_close_stream(stm); -+ -+ if (audiounit_setup_stream(stm) != CUBEB_OK) { -+ LOG("(%p) Stream reinit failed.", stm); -+ return CUBEB_ERROR; -+ } -+ -+ // Reset input frames to force new stream pre-buffer -+ // silence if needed, check `is_extra_input_needed()` -+ stm->frames_read = 0; -+ -+ // If the stream was running, start it again. -+ if (is_started) { -+ audiounit_stream_start_internal(stm); -+ } -+ } -+ return CUBEB_OK; -+} -+ - static OSStatus - audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count, - const AudioObjectPropertyAddress * addresses, - void * user) - { - cubeb_stream * stm = (cubeb_stream*) user; -- int rv; -- bool was_running = false; -- - stm->switching_device = true; -- - // Note if the stream was running or not -- was_running = !stm->shutdown; -+ bool was_running = !stm->shutdown; - - LOG("(%p) Audio device changed, %d events.", stm, address_count); - for (UInt32 i = 0; i < address_count; i++) { - switch(addresses[i].mSelector) { - case kAudioHardwarePropertyDefaultOutputDevice: { - LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i); - // Allow restart to choose the new default - stm->output_device = nullptr; -@@ -639,38 +678,25 @@ audiounit_property_listener_callback(Aud - if (stm->device_changed_callback) { - stm->device_changed_callback(stm->user_ptr); - } - break; - } - } - } - -- // This means the callback won't be called again. -- audiounit_stream_stop_internal(stm); -- -- { -- auto_lock lock(stm->mutex); -- close_audiounit_stream(stm); -- rv = setup_audiounit_stream(stm); -- if (rv != CUBEB_OK) { -- LOG("(%p) Could not reopen a stream after switching.", stm); -+ // Use a new thread, through the queue, to avoid deadlock when calling -+ // Get/SetProperties method from inside notify callback -+ dispatch_async(stm->context->serial_queue, ^() { -+ if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); -- return noErr; -+ LOG("(%p) Could not reopen the stream after switching.", stm); - } -- -- stm->frames_read = 0; -- -- // If the stream was running, start it again. -- if (was_running) { -- audiounit_stream_start_internal(stm); -- } -- } -- -- stm->switching_device = false; -+ stm->switching_device = false; -+ }); - - return noErr; - } - - OSStatus - audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector, - AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener) - { -@@ -1155,18 +1181,17 @@ audiounit_init_input_linear_buffer(cubeb - - static void - audiounit_destroy_input_linear_buffer(cubeb_stream * stream) - { - delete stream->input_linear_buffer; - } - - static uint32_t --audiounit_clamp_latency(cubeb_stream * stm, -- uint32_t latency_frames) -+audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) - { - // For the 1st stream set anything within safe min-max - assert(stm->context->active_streams > 0); - if (stm->context->active_streams == 1) { - return std::max(std::min(latency_frames, SAFE_MAX_LATENCY_FRAMES), - SAFE_MIN_LATENCY_FRAMES); - } - -@@ -1219,26 +1244,374 @@ audiounit_clamp_latency(cubeb_stream * s - } else { - upper_latency_limit = SAFE_MAX_LATENCY_FRAMES; - } - - return std::max(std::min(latency_frames, upper_latency_limit), - SAFE_MIN_LATENCY_FRAMES); - } - -+/* -+ * Change buffer size is prone to deadlock thus we change it -+ * following the steps: -+ * - register a listener for the buffer size property -+ * - change the property -+ * - wait until the listener is executed -+ * - property has changed, remove the listener -+ * */ -+static void -+buffer_size_changed_callback(void * inClientData, -+ AudioUnit inUnit, -+ AudioUnitPropertyID inPropertyID, -+ AudioUnitScope inScope, -+ AudioUnitElement inElement) -+{ -+ cubeb_stream * stm = (cubeb_stream *)inClientData; -+ -+ AudioUnit au = inUnit; -+ AudioUnitScope au_scope = kAudioUnitScope_Input; -+ AudioUnitElement au_element = inElement; -+ const char * au_type = "output"; -+ -+ if (au == stm->input_unit) { -+ au_scope = kAudioUnitScope_Output; -+ au_type = "input"; -+ } -+ -+ switch (inPropertyID) { -+ -+ case kAudioDevicePropertyBufferFrameSize: { -+ if (inScope != au_scope) { -+ break; -+ } -+ UInt32 new_buffer_size; -+ UInt32 outSize = sizeof(UInt32); -+ OSStatus r = AudioUnitGetProperty(au, -+ kAudioDevicePropertyBufferFrameSize, -+ au_scope, -+ au_element, -+ &new_buffer_size, -+ &outSize); -+ if (r != noErr) { -+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current buffer size", stm); -+ } else { -+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size = %d for scope %d", stm, -+ au_type, new_buffer_size, inScope); -+ } -+ stm->buffer_size_change_state = true; -+ break; -+ } -+ } -+} -+ -+enum set_buffer_size_side { -+ INPUT, -+ OUTPUT, -+}; -+ - static int --setup_audiounit_stream(cubeb_stream * stm) -+audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side) -+{ -+ AudioUnit au = stm->output_unit; -+ AudioUnitScope au_scope = kAudioUnitScope_Input; -+ AudioUnitElement au_element = AU_OUT_BUS; -+ const char * au_type = "output"; -+ -+ if (set_side == INPUT) { -+ au = stm->input_unit; -+ au_scope = kAudioUnitScope_Output; -+ au_element = AU_IN_BUS; -+ au_type = "input"; -+ } -+ -+ uint32_t buffer_frames = 0; -+ UInt32 size = sizeof(buffer_frames); -+ int r = AudioUnitGetProperty(au, -+ kAudioDevicePropertyBufferFrameSize, -+ au_scope, -+ au_element, -+ &buffer_frames, -+ &size); -+ if (r != noErr) { -+ if (set_side == INPUT) { -+ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r); -+ } else { -+ PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r); -+ } -+ return CUBEB_ERROR; -+ } -+ -+ if (new_size_frames == buffer_frames) { -+ LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames); -+ return CUBEB_OK; -+ } -+ -+ r = AudioUnitAddPropertyListener(au, -+ kAudioDevicePropertyBufferFrameSize, -+ buffer_size_changed_callback, -+ stm); -+ if (r != noErr) { -+ if (set_side == INPUT) { -+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); -+ } else { -+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); -+ } -+ return CUBEB_ERROR; -+ } -+ -+ stm->buffer_size_change_state = false; -+ -+ r = AudioUnitSetProperty(au, -+ kAudioDevicePropertyBufferFrameSize, -+ au_scope, -+ au_element, -+ &new_size_frames, -+ sizeof(new_size_frames)); -+ if (r != noErr) { -+ if (set_side == INPUT) { -+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r); -+ } else { -+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r); -+ } -+ -+ r = AudioUnitRemovePropertyListenerWithUserData(au, -+ kAudioDevicePropertyBufferFrameSize, -+ buffer_size_changed_callback, -+ stm); -+ if (r != noErr) { -+ if (set_side == INPUT) { -+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); -+ } else { -+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); -+ } -+ } -+ -+ return CUBEB_ERROR; -+ } -+ -+ int count = 0; -+ while (!stm->buffer_size_change_state && count++ < 30) { -+ struct timespec req, rem; -+ req.tv_sec = 0; -+ req.tv_nsec = 100000000L; // 0.1 sec -+ if (nanosleep(&req , &rem) < 0 ) { -+ LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time %ld nano secs \n", stm, rem.tv_nsec); -+ } -+ LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count); -+ } -+ -+ r = AudioUnitRemovePropertyListenerWithUserData(au, -+ kAudioDevicePropertyBufferFrameSize, -+ buffer_size_changed_callback, -+ stm); -+ if (r != noErr) { -+ return CUBEB_ERROR; -+ if (set_side == INPUT) { -+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); -+ } else { -+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); -+ } -+ } -+ -+ if (!stm->buffer_size_change_state && count >= 30) { -+ LOG("(%p) Error, did not get buffer size change callback ...", stm); -+ return CUBEB_ERROR; -+ } -+ -+ LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames); -+ return CUBEB_OK; -+} -+ -+static int -+audiounit_configure_input(cubeb_stream * stm) -+{ -+ int r = 0; -+ UInt32 size; -+ AURenderCallbackStruct aurcbs_in; -+ -+ LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.", -+ stm, stm->input_stream_params.rate, stm->input_stream_params.channels, -+ stm->input_stream_params.format, stm->latency_frames); -+ -+ /* Get input device sample rate. */ -+ AudioStreamBasicDescription input_hw_desc; -+ size = sizeof(AudioStreamBasicDescription); -+ r = AudioUnitGetProperty(stm->input_unit, -+ kAudioUnitProperty_StreamFormat, -+ kAudioUnitScope_Input, -+ AU_IN_BUS, -+ &input_hw_desc, -+ &size); -+ if (r != noErr) { -+ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r); -+ return CUBEB_ERROR; -+ } -+ stm->input_hw_rate = input_hw_desc.mSampleRate; -+ LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate); -+ -+ /* Set format description according to the input params. */ -+ r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params); -+ if (r != CUBEB_OK) { -+ LOG("(%p) Setting format description for input failed.", stm); -+ return r; -+ } -+ -+ // Use latency to set buffer size -+ stm->input_buffer_frames = stm->latency_frames; -+ r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT); -+ if (r != CUBEB_OK) { -+ LOG("(%p) Error in change input buffer size.", stm); -+ return CUBEB_ERROR; -+ } -+ -+ AudioStreamBasicDescription src_desc = stm->input_desc; -+ /* Input AudioUnit must be configured with device's sample rate. -+ we will resample inside input callback. */ -+ src_desc.mSampleRate = stm->input_hw_rate; -+ -+ r = AudioUnitSetProperty(stm->input_unit, -+ kAudioUnitProperty_StreamFormat, -+ kAudioUnitScope_Output, -+ AU_IN_BUS, -+ &src_desc, -+ sizeof(AudioStreamBasicDescription)); -+ if (r != noErr) { -+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r); -+ return CUBEB_ERROR; -+ } -+ -+ /* Frames per buffer in the input callback. */ -+ r = AudioUnitSetProperty(stm->input_unit, -+ kAudioUnitProperty_MaximumFramesPerSlice, -+ kAudioUnitScope_Global, -+ AU_IN_BUS, -+ &stm->input_buffer_frames, -+ sizeof(UInt32)); -+ if (r != noErr) { -+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r); -+ return CUBEB_ERROR; -+ } -+ -+ // Input only capacity -+ unsigned int array_capacity = 1; -+ if (has_output(stm)) { -+ // Full-duplex increase capacity -+ array_capacity = 8; -+ } -+ if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) { -+ return CUBEB_ERROR; -+ } -+ -+ assert(stm->input_unit != NULL); -+ aurcbs_in.inputProc = audiounit_input_callback; -+ aurcbs_in.inputProcRefCon = stm; -+ -+ r = AudioUnitSetProperty(stm->input_unit, -+ kAudioOutputUnitProperty_SetInputCallback, -+ kAudioUnitScope_Global, -+ AU_OUT_BUS, -+ &aurcbs_in, -+ sizeof(aurcbs_in)); -+ if (r != noErr) { -+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r); -+ return CUBEB_ERROR; -+ } -+ LOG("(%p) Input audiounit init successfully.", stm); -+ -+ return CUBEB_OK; -+} -+ -+static int -+audiounit_configure_output(cubeb_stream * stm) -+{ -+ int r; -+ AURenderCallbackStruct aurcbs_out; -+ UInt32 size; -+ -+ -+ LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.", -+ stm, stm->output_stream_params.rate, stm->output_stream_params.channels, -+ stm->output_stream_params.format, stm->latency_frames); -+ -+ r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params); -+ if (r != CUBEB_OK) { -+ LOG("(%p) Could not initialize the audio stream description.", stm); -+ return r; -+ } -+ -+ /* Get output device sample rate. */ -+ AudioStreamBasicDescription output_hw_desc; -+ size = sizeof(AudioStreamBasicDescription); -+ memset(&output_hw_desc, 0, size); -+ r = AudioUnitGetProperty(stm->output_unit, -+ kAudioUnitProperty_StreamFormat, -+ kAudioUnitScope_Output, -+ AU_OUT_BUS, -+ &output_hw_desc, -+ &size); -+ if (r != noErr) { -+ PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r); -+ return CUBEB_ERROR; -+ } -+ stm->output_hw_rate = output_hw_desc.mSampleRate; -+ LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate); -+ -+ r = AudioUnitSetProperty(stm->output_unit, -+ kAudioUnitProperty_StreamFormat, -+ kAudioUnitScope_Input, -+ AU_OUT_BUS, -+ &stm->output_desc, -+ sizeof(AudioStreamBasicDescription)); -+ if (r != noErr) { -+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r); -+ return CUBEB_ERROR; -+ } -+ -+ r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT); -+ if (r != CUBEB_OK) { -+ LOG("(%p) Error in change output buffer size.", stm); -+ return CUBEB_ERROR; -+ } -+ -+ /* Frames per buffer in the input callback. */ -+ r = AudioUnitSetProperty(stm->output_unit, -+ kAudioUnitProperty_MaximumFramesPerSlice, -+ kAudioUnitScope_Global, -+ AU_OUT_BUS, -+ &stm->latency_frames, -+ sizeof(UInt32)); -+ if (r != noErr) { -+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r); -+ return CUBEB_ERROR; -+ } -+ -+ assert(stm->output_unit != NULL); -+ aurcbs_out.inputProc = audiounit_output_callback; -+ aurcbs_out.inputProcRefCon = stm; -+ r = AudioUnitSetProperty(stm->output_unit, -+ kAudioUnitProperty_SetRenderCallback, -+ kAudioUnitScope_Global, -+ AU_OUT_BUS, -+ &aurcbs_out, -+ sizeof(aurcbs_out)); -+ if (r != noErr) { -+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r); -+ return CUBEB_ERROR; -+ } -+ -+ LOG("(%p) Output audiounit init successfully.", stm); -+ return CUBEB_OK; -+} -+ -+static int -+audiounit_setup_stream(cubeb_stream * stm) - { - stm->mutex.assert_current_thread_owns(); - -- int r; -- AURenderCallbackStruct aurcbs_in; -- AURenderCallbackStruct aurcbs_out; -- UInt32 size; -- -+ int r = 0; - if (has_input(stm)) { - r = audiounit_create_unit(&stm->input_unit, true, - &stm->input_stream_params, - stm->input_device); - if (r != CUBEB_OK) { - LOG("(%p) AudioUnit creation for input failed.", stm); - return r; - } -@@ -1249,180 +1622,46 @@ setup_audiounit_stream(cubeb_stream * st - &stm->output_stream_params, - stm->output_device); - if (r != CUBEB_OK) { - LOG("(%p) AudioUnit creation for output failed.", stm); - return r; - } - } - -+ /* Latency cannot change if another stream is operating in parallel. In this case -+ * latecy is set to the other stream value. */ -+ if (stm->context->active_streams > 1) { -+ LOG("(%p) More than one active stream, use global latency.", stm); -+ stm->latency_frames = stm->context->global_latency_frames; -+ } else { -+ /* Silently clamp the latency down to the platform default, because we -+ * synthetize the clock from the callbacks, and we want the clock to update -+ * often. */ -+ stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames); -+ assert(stm->latency_frames); // Ungly error check -+ audiounit_set_global_latency(stm, stm->latency_frames); -+ } -+ - /* Setup Input Stream! */ - if (has_input(stm)) { -- LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.", -- stm, stm->input_stream_params.rate, stm->input_stream_params.channels, -- stm->input_stream_params.format, stm->latency_frames); -- /* Get input device sample rate. */ -- AudioStreamBasicDescription input_hw_desc; -- size = sizeof(AudioStreamBasicDescription); -- r = AudioUnitGetProperty(stm->input_unit, -- kAudioUnitProperty_StreamFormat, -- kAudioUnitScope_Input, -- AU_IN_BUS, -- &input_hw_desc, -- &size); -- if (r != noErr) { -- PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r); -- return CUBEB_ERROR; -- } -- stm->input_hw_rate = input_hw_desc.mSampleRate; -- LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate); -- -- /* Set format description according to the input params. */ -- r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params); -+ r = audiounit_configure_input(stm); - if (r != CUBEB_OK) { -- LOG("(%p) Setting format description for input failed.", stm); -+ LOG("(%p) Configure audiounit input failed.", stm); - return r; - } -- -- // Use latency to set buffer size -- stm->input_buffer_frames = stm->latency_frames; -- LOG("(%p) Input buffer frame count %u.", stm, unsigned(stm->input_buffer_frames)); -- r = AudioUnitSetProperty(stm->input_unit, -- kAudioDevicePropertyBufferFrameSize, -- kAudioUnitScope_Output, -- AU_IN_BUS, -- &stm->input_buffer_frames, -- sizeof(UInt32)); -- if (r != noErr) { -- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r); -- return CUBEB_ERROR; -- } -- -- AudioStreamBasicDescription src_desc = stm->input_desc; -- /* Input AudioUnit must be configured with device's sample rate. -- we will resample inside input callback. */ -- src_desc.mSampleRate = stm->input_hw_rate; -- -- r = AudioUnitSetProperty(stm->input_unit, -- kAudioUnitProperty_StreamFormat, -- kAudioUnitScope_Output, -- AU_IN_BUS, -- &src_desc, -- sizeof(AudioStreamBasicDescription)); -- if (r != noErr) { -- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r); -- return CUBEB_ERROR; -- } -- -- /* Frames per buffer in the input callback. */ -- r = AudioUnitSetProperty(stm->input_unit, -- kAudioUnitProperty_MaximumFramesPerSlice, -- kAudioUnitScope_Output, -- AU_IN_BUS, -- &stm->input_buffer_frames, -- sizeof(UInt32)); -- if (r != noErr) { -- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r); -- return CUBEB_ERROR; -- } -- -- // Input only capacity -- unsigned int array_capacity = 1; -- if (has_output(stm)) { -- // Full-duplex increase capacity -- array_capacity = 8; -- } -- if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) { -- return CUBEB_ERROR; -- } -- -- assert(stm->input_unit != NULL); -- aurcbs_in.inputProc = audiounit_input_callback; -- aurcbs_in.inputProcRefCon = stm; -- -- r = AudioUnitSetProperty(stm->input_unit, -- kAudioOutputUnitProperty_SetInputCallback, -- kAudioUnitScope_Global, -- AU_OUT_BUS, -- &aurcbs_in, -- sizeof(aurcbs_in)); -- if (r != noErr) { -- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r); -- return CUBEB_ERROR; -- } -- LOG("(%p) Input audiounit init successfully.", stm); - } - - /* Setup Output Stream! */ - if (has_output(stm)) { -- LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.", -- stm, stm->output_stream_params.rate, stm->output_stream_params.channels, -- stm->output_stream_params.format, stm->latency_frames); -- r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params); -+ r = audiounit_configure_output(stm); - if (r != CUBEB_OK) { -- LOG("(%p) Could not initialize the audio stream description.", stm); -+ LOG("(%p) Configure audiounit output failed.", stm); - return r; - } -- -- /* Get output device sample rate. */ -- AudioStreamBasicDescription output_hw_desc; -- size = sizeof(AudioStreamBasicDescription); -- memset(&output_hw_desc, 0, size); -- r = AudioUnitGetProperty(stm->output_unit, -- kAudioUnitProperty_StreamFormat, -- kAudioUnitScope_Output, -- AU_OUT_BUS, -- &output_hw_desc, -- &size); -- if (r != noErr) { -- PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r); -- return CUBEB_ERROR; -- } -- stm->output_hw_rate = output_hw_desc.mSampleRate; -- LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate); -- -- r = AudioUnitSetProperty(stm->output_unit, -- kAudioUnitProperty_StreamFormat, -- kAudioUnitScope_Input, -- AU_OUT_BUS, -- &stm->output_desc, -- sizeof(AudioStreamBasicDescription)); -- if (r != noErr) { -- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r); -- return CUBEB_ERROR; -- } -- -- // Use latency to calculate buffer size -- uint32_t output_buffer_frames = stm->latency_frames; -- LOG("(%p) Output buffer frame count %u.", stm, output_buffer_frames); -- r = AudioUnitSetProperty(stm->output_unit, -- kAudioDevicePropertyBufferFrameSize, -- kAudioUnitScope_Input, -- AU_OUT_BUS, -- &output_buffer_frames, -- sizeof(output_buffer_frames)); -- if (r != noErr) { -- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r); -- return CUBEB_ERROR; -- } -- -- assert(stm->output_unit != NULL); -- aurcbs_out.inputProc = audiounit_output_callback; -- aurcbs_out.inputProcRefCon = stm; -- r = AudioUnitSetProperty(stm->output_unit, -- kAudioUnitProperty_SetRenderCallback, -- kAudioUnitScope_Global, -- AU_OUT_BUS, -- &aurcbs_out, -- sizeof(aurcbs_out)); -- if (r != noErr) { -- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r); -- return CUBEB_ERROR; -- } -- LOG("(%p) Output audiounit init successfully.", stm); - } - - // Setting the latency doesn't work well for USB headsets (eg. plantronics). - // Keep the default latency for now. - #if 0 - buffer_size = latency; - - /* Get the range of latency this particular device can work with, and clamp -@@ -1535,63 +1774,60 @@ audiounit_stream_init(cubeb * context, - void * user_ptr) - { - cubeb_stream * stm; - int r; - - assert(context); - *stream = NULL; - -+ assert(latency_frames > 0); - if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) { - LOG("Reached the stream limit of %d", CUBEB_STREAM_MAX); - return CUBEB_ERROR; - } -- context->active_streams += 1; - - stm = (cubeb_stream *) calloc(1, sizeof(cubeb_stream)); - assert(stm); - // Placement new to call the ctors of cubeb_stream members. - new (stm) cubeb_stream(); - - /* These could be different in the future if we have both - * full-duplex stream and different devices for input vs output. */ - stm->context = context; - stm->data_callback = data_callback; - stm->state_callback = state_callback; - stm->user_ptr = user_ptr; -+ stm->latency_frames = latency_frames; - stm->device_changed_callback = NULL; - if (input_stream_params) { - stm->input_stream_params = *input_stream_params; - stm->input_device = input_device; - stm->is_default_input = input_device == nullptr || - (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) == - reinterpret_cast(input_device)); - } - if (output_stream_params) { - stm->output_stream_params = *output_stream_params; - stm->output_device = output_device; - } - - /* Init data members where necessary */ - stm->hw_latency_frames = UINT64_MAX; - -- /* Silently clamp the latency down to the platform default, because we -- * synthetize the clock from the callbacks, and we want the clock to update -- * often. */ -- stm->latency_frames = audiounit_clamp_latency(stm, latency_frames); -- assert(latency_frames > 0); -- - stm->switching_device = false; - -+ auto_lock context_lock(context->mutex); - { - // It's not critical to lock here, because no other thread has been started - // yet, but it allows to assert that the lock has been taken in -- // `setup_audiounit_stream`. -+ // `audiounit_setup_stream`. -+ context->active_streams += 1; - auto_lock lock(stm->mutex); -- r = setup_audiounit_stream(stm); -+ r = audiounit_setup_stream(stm); - } - - if (r != CUBEB_OK) { - LOG("(%p) Could not setup the audiounit stream.", stm); - audiounit_stream_destroy(stm); - return r; - } - -@@ -1602,17 +1838,17 @@ audiounit_stream_init(cubeb * context, - } - - *stream = stm; - LOG("Cubeb stream (%p) init successful.", stm); - return CUBEB_OK; - } - - static void --close_audiounit_stream(cubeb_stream * stm) -+audiounit_close_stream(cubeb_stream *stm) - { - stm->mutex.assert_current_thread_owns(); - if (stm->input_unit) { - AudioUnitUninitialize(stm->input_unit); - AudioComponentInstanceDispose(stm->input_unit); - } - - audiounit_destroy_input_linear_buffer(stm); -@@ -1625,33 +1861,36 @@ close_audiounit_stream(cubeb_stream * st - cubeb_resampler_destroy(stm->resampler); - } - - static void - audiounit_stream_destroy(cubeb_stream * stm) - { - stm->shutdown = true; - -+ auto_lock context_locl(stm->context->mutex); - audiounit_stream_stop_internal(stm); - - { - auto_lock lock(stm->mutex); -- close_audiounit_stream(stm); -+ audiounit_close_stream(stm); - } - - #if !TARGET_OS_IPHONE - int r = audiounit_uninstall_device_changed_callback(stm); - if (r != CUBEB_OK) { - LOG("(%p) Could not uninstall the device changed callback", stm); - } - #endif - - assert(stm->context->active_streams >= 1); - stm->context->active_streams -= 1; - -+ LOG("Cubeb stream (%p) destroyed successful.", stm); -+ - stm->~cubeb_stream(); - free(stm); - } - - void - audiounit_stream_start_internal(cubeb_stream * stm) - { - OSStatus r; -@@ -1666,16 +1905,17 @@ audiounit_stream_start_internal(cubeb_st - } - - static int - audiounit_stream_start(cubeb_stream * stm) - { - stm->shutdown = false; - stm->draining = false; - -+ auto_lock context_locl(stm->context->mutex); - audiounit_stream_start_internal(stm); - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); - - LOG("Cubeb stream (%p) started successfully.", stm); - return CUBEB_OK; - } - -@@ -1693,16 +1933,17 @@ audiounit_stream_stop_internal(cubeb_str - } - } - - static int - audiounit_stream_stop(cubeb_stream * stm) - { - stm->shutdown = true; - -+ auto_lock context_locl(stm->context->mutex); - audiounit_stream_stop_internal(stm); - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - - LOG("Cubeb stream (%p) stopped successfully.", stm); - return CUBEB_OK; - } - diff --git a/media/libcubeb/prevent-double-free.patch b/media/libcubeb/prevent-double-free.patch deleted file mode 100644 index aa5356d7f..000000000 --- a/media/libcubeb/prevent-double-free.patch +++ /dev/null @@ -1,46 +0,0 @@ -From f82f15635e09aac4f07d2ddac3d53c84b593d911 Mon Sep 17 00:00:00 2001 -From: Paul Adenot -Date: Mon, 16 Jan 2017 04:49:41 -0800 -Subject: [PATCH 1/1] Prevent double-free when doing an emergency bailout from - the rendering thread. - -This caused gecko bug 1326176. - -This was caused by the fact that we would null out `stm->thread` when in -fact it was still running, so we would delete `stm->emergency_bailout` -twice, because we would return true from `stop_and_join_thread`. ---- - src/cubeb_wasapi.cpp | 15 ++++++++++----- - 1 file changed, 10 insertions(+), 5 deletions(-) - -diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp -index 63c12ac..2920b5d 100644 ---- a/src/cubeb_wasapi.cpp -+++ b/src/cubeb_wasapi.cpp -@@ -1230,13 +1230,18 @@ bool stop_and_join_render_thread(cubeb_stream * stm) - rv = false; - } - -- LOG("Closing thread."); - -- CloseHandle(stm->thread); -- stm->thread = NULL; -+ // Only attempts to close and null out the thread and event if the -+ // WaitForSingleObject above succeeded, so that calling this function again -+ // attemps to clean up the thread and event each time. -+ if (rv) { -+ LOG("Closing thread."); -+ CloseHandle(stm->thread); -+ stm->thread = NULL; - -- CloseHandle(stm->shutdown_event); -- stm->shutdown_event = 0; -+ CloseHandle(stm->shutdown_event); -+ stm->shutdown_event = 0; -+ } - - return rv; - } --- -2.7.4 - diff --git a/media/libcubeb/src/android/cubeb-output-latency.h b/media/libcubeb/src/android/cubeb-output-latency.h new file mode 100644 index 000000000..a824fc1c2 --- /dev/null +++ b/media/libcubeb/src/android/cubeb-output-latency.h @@ -0,0 +1,76 @@ +#ifndef _CUBEB_OUTPUT_LATENCY_H_ +#define _CUBEB_OUTPUT_LATENCY_H_ + +#include +#include "cubeb_media_library.h" +#include "../cubeb-jni.h" + +struct output_latency_function { + media_lib * from_lib; + cubeb_jni * from_jni; + int version; +}; + +typedef struct output_latency_function output_latency_function; + +const int ANDROID_JELLY_BEAN_MR1_4_2 = 17; + +output_latency_function * +cubeb_output_latency_load_method(int version) +{ + output_latency_function * ol = NULL; + ol = calloc(1, sizeof(output_latency_function)); + + ol->version = version; + + if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){ + ol->from_jni = cubeb_jni_init(); + return ol; + } + + ol->from_lib = cubeb_load_media_library(); + return ol; +} + +bool +cubeb_output_latency_method_is_loaded(output_latency_function * ol) +{ + assert(ol && (ol->from_jni || ol->from_lib)); + if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){ + return !!ol->from_jni; + } + + return !!ol->from_lib; +} + +void +cubeb_output_latency_unload_method(output_latency_function * ol) +{ + if (!ol) { + return; + } + + if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_jni) { + cubeb_jni_destroy(ol->from_jni); + } + + if (ol->version <= ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_lib) { + cubeb_close_media_library(ol->from_lib); + } + + free(ol); +} + +uint32_t +cubeb_get_output_latency(output_latency_function * ol) +{ + assert(cubeb_output_latency_method_is_loaded(ol)); + + if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){ + return cubeb_get_output_latency_from_jni(ol->from_jni); + } + + return cubeb_get_output_latency_from_media_library(ol->from_lib); +} + +#endif // _CUBEB_OUTPUT_LATENCY_H_ diff --git a/media/libcubeb/src/android/cubeb_media_library.h b/media/libcubeb/src/android/cubeb_media_library.h new file mode 100644 index 000000000..ab21b779d --- /dev/null +++ b/media/libcubeb/src/android/cubeb_media_library.h @@ -0,0 +1,62 @@ +#ifndef _CUBEB_MEDIA_LIBRARY_H_ +#define _CUBEB_MEDIA_LIBRARY_H_ + +struct media_lib { + void * libmedia; + int32_t (* get_output_latency)(uint32_t * latency, int stream_type); +}; + +typedef struct media_lib media_lib; + +media_lib * +cubeb_load_media_library() +{ + media_lib ml = {0}; + ml.libmedia = dlopen("libmedia.so", RTLD_LAZY); + if (!ml.libmedia) { + return NULL; + } + + // Get the latency, in ms, from AudioFlinger. First, try the most recent signature. + // status_t AudioSystem::getOutputLatency(uint32_t* latency, audio_stream_type_t streamType) + ml.get_output_latency = + dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t"); + if (!ml.get_output_latency) { + // In case of failure, try the signature from legacy version. + // status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType) + ml.get_output_latency = + dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); + if (!ml.get_output_latency) { + return NULL; + } + } + + media_lib * rv = NULL; + rv = calloc(1, sizeof(media_lib)); + assert(rv); + *rv = ml; + return rv; +} + +void +cubeb_close_media_library(media_lib * ml) +{ + dlclose(ml->libmedia); + ml->libmedia = NULL; + ml->get_output_latency = NULL; + free(ml); +} + +uint32_t +cubeb_get_output_latency_from_media_library(media_lib * ml) +{ + uint32_t latency = 0; + const int audio_stream_type_music = 3; + int32_t r = ml->get_output_latency(&latency, audio_stream_type_music); + if (r) { + return 0; + } + return latency; +} + +#endif // _CUBEB_MEDIA_LIBRARY_H_ diff --git a/media/libcubeb/src/android/sles_definitions.h b/media/libcubeb/src/android/sles_definitions.h index 1b1ace567..06d2e8d49 100644 --- a/media/libcubeb/src/android/sles_definitions.h +++ b/media/libcubeb/src/android/sles_definitions.h @@ -43,10 +43,9 @@ #define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32) 0x00000003) /** uses the main microphone tuned for audio communications */ #define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32) 0x00000004) +/** uses the main microphone unprocessed */ +#define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32) 0x00000005) -/** Audio recording get session ID (read only) */ -/** Audio recording get session ID key */ -#define SL_ANDROID_KEY_RECORDING_SESSION_ID ((const SLchar*) "androidRecordingSessionId") /*---------------------------------------------------------------------------*/ /* Android AudioPlayer configuration */ @@ -69,9 +68,35 @@ #define SL_ANDROID_STREAM_ALARM ((SLint32) 0x00000004) /* same as android.media.AudioManager.STREAM_NOTIFICATION */ #define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005) -/* same as android.media.AudioManager.STREAM_BLUETOOTH_SCO */ -#define SL_ANDROID_STREAM_BLUETOOTH_SCO ((SLint32) 0x00000006) -/* same as android.media.AudioManager.STREAM_SYSTEM_ENFORCED */ -#define SL_ANDROID_STREAM_SYSTEM_ENFORCED ((SLint32) 0x00000007) + + +/*---------------------------------------------------------------------------*/ +/* Android AudioPlayer and AudioRecorder configuration */ +/*---------------------------------------------------------------------------*/ + +/** Audio Performance mode. + * Performance mode tells the framework how to configure the audio path + * for a player or recorder according to application performance and + * functional requirements. + * It affects the output or input latency based on acceptable tradeoffs on + * battery drain and use of pre or post processing effects. + * Performance mode should be set before realizing the object and should be + * read after realizing the object to check if the requested mode could be + * granted or not. + */ +/** Audio Performance mode key */ +#define SL_ANDROID_KEY_PERFORMANCE_MODE ((const SLchar*) "androidPerformanceMode") + +/** Audio performance values */ +/* No specific performance requirement. Allows HW and SW pre/post processing. */ +#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32) 0x00000000) +/* Priority given to latency. No HW or software pre/post processing. + * This is the default if no performance mode is specified. */ +#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32) 0x00000001) +/* Priority given to latency while still allowing HW pre and post processing. */ +#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32) 0x00000002) +/* Priority given to power saving if latency is not a concern. + * Allows HW and SW pre/post processing. */ +#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32) 0x00000003) #endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */ diff --git a/media/libcubeb/src/cubeb-internal.h b/media/libcubeb/src/cubeb-internal.h index dfcc186c5..312a9ea3a 100644 --- a/media/libcubeb/src/cubeb-internal.h +++ b/media/libcubeb/src/cubeb-internal.h @@ -9,6 +9,7 @@ #include "cubeb/cubeb.h" #include "cubeb_log.h" +#include "cubeb_assert.h" #include #include @@ -28,9 +29,6 @@ extern "C" { #endif -/* Crash the caller. */ -void cubeb_crash() CLANG_ANALYZER_NORETURN; - #if defined(__cplusplus) } #endif @@ -44,7 +42,9 @@ struct cubeb_ops { uint32_t * latency_ms); int (* get_preferred_sample_rate)(cubeb * context, uint32_t * rate); int (* enumerate_devices)(cubeb * context, cubeb_device_type type, - cubeb_device_collection ** collection); + cubeb_device_collection * collection); + int (* device_collection_destroy)(cubeb * context, + cubeb_device_collection * collection); void (* destroy)(cubeb * context); int (* stream_init)(cubeb * context, cubeb_stream ** stream, @@ -60,10 +60,10 @@ struct cubeb_ops { void (* stream_destroy)(cubeb_stream * stream); int (* stream_start)(cubeb_stream * stream); int (* stream_stop)(cubeb_stream * stream); + int (* stream_reset_default_device)(cubeb_stream * stream); int (* stream_get_position)(cubeb_stream * stream, uint64_t * position); int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency); int (* stream_set_volume)(cubeb_stream * stream, float volumes); - int (* stream_set_panning)(cubeb_stream * stream, float panning); int (* stream_get_current_device)(cubeb_stream * stream, cubeb_device ** const device); int (* stream_device_destroy)(cubeb_stream * stream, @@ -76,11 +76,4 @@ struct cubeb_ops { void * user_ptr); }; -#define XASSERT(expr) do { \ - if (!(expr)) { \ - fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \ - cubeb_crash(); \ - } \ - } while (0) - #endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */ diff --git a/media/libcubeb/src/cubeb-jni-instances.h b/media/libcubeb/src/cubeb-jni-instances.h new file mode 100644 index 000000000..19c5c29da --- /dev/null +++ b/media/libcubeb/src/cubeb-jni-instances.h @@ -0,0 +1,34 @@ +#ifndef _CUBEB_JNI_INSTANCES_H_ +#define _CUBEB_JNI_INSTANCES_H_ + +#include "GeneratedJNIWrappers.h" +#include "mozilla/jni/Utils.h" + +/* + * The methods in this file offer a way to pass in the required + * JNI instances in the cubeb library. By default they return NULL. + * In this case part of the cubeb API that depends on JNI + * will return CUBEB_ERROR_NOT_SUPPORTED. Currently only one + * method depends on that: + * + * cubeb_stream_get_position() + * + * Users that want to use that cubeb API method must "override" + * the methods bellow to return a valid instance of JavaVM + * and application's Context object. + * */ + +JNIEnv * +cubeb_get_jni_env_for_thread() +{ + return mozilla::jni::GetEnvForThread(); +} + +jobject +cubeb_jni_get_context_instance() +{ + auto context = mozilla::java::GeckoAppShell::GetApplicationContext(); + return context.Forget(); +} + +#endif //_CUBEB_JNI_INSTANCES_H_ diff --git a/media/libcubeb/src/cubeb-jni.cpp b/media/libcubeb/src/cubeb-jni.cpp new file mode 100644 index 000000000..a5066967a --- /dev/null +++ b/media/libcubeb/src/cubeb-jni.cpp @@ -0,0 +1,68 @@ +#include "jni.h" +#include +#include "cubeb-jni-instances.h" + +#define AUDIO_STREAM_TYPE_MUSIC 3 + +struct cubeb_jni { + jobject s_audio_manager_obj = nullptr; + jclass s_audio_manager_class = nullptr; + jmethodID s_get_output_latency_id = nullptr; +}; + +extern "C" +cubeb_jni * +cubeb_jni_init() +{ + jobject ctx_obj = cubeb_jni_get_context_instance(); + JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); + if (!jni_env || !ctx_obj) { + return nullptr; + } + + cubeb_jni * cubeb_jni_ptr = new cubeb_jni; + assert(cubeb_jni_ptr); + + // Find the audio manager object and make it global to call it from another method + jclass context_class = jni_env->FindClass("android/content/Context"); + jfieldID audio_service_field = jni_env->GetStaticFieldID(context_class, "AUDIO_SERVICE", "Ljava/lang/String;"); + jstring jstr = (jstring)jni_env->GetStaticObjectField(context_class, audio_service_field); + jmethodID get_system_service_id = jni_env->GetMethodID(context_class, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); + jobject audio_manager_obj = jni_env->CallObjectMethod(ctx_obj, get_system_service_id, jstr); + cubeb_jni_ptr->s_audio_manager_obj = reinterpret_cast(jni_env->NewGlobalRef(audio_manager_obj)); + + // Make the audio manager class a global reference in order to preserve method id + jclass audio_manager_class = jni_env->FindClass("android/media/AudioManager"); + cubeb_jni_ptr->s_audio_manager_class = reinterpret_cast(jni_env->NewGlobalRef(audio_manager_class)); + cubeb_jni_ptr->s_get_output_latency_id = jni_env->GetMethodID (audio_manager_class, "getOutputLatency", "(I)I"); + + jni_env->DeleteLocalRef(ctx_obj); + jni_env->DeleteLocalRef(context_class); + jni_env->DeleteLocalRef(jstr); + jni_env->DeleteLocalRef(audio_manager_obj); + jni_env->DeleteLocalRef(audio_manager_class); + + return cubeb_jni_ptr; +} + +extern "C" +int cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr) +{ + assert(cubeb_jni_ptr); + JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); + return jni_env->CallIntMethod(cubeb_jni_ptr->s_audio_manager_obj, cubeb_jni_ptr->s_get_output_latency_id, AUDIO_STREAM_TYPE_MUSIC); //param: AudioManager.STREAM_MUSIC +} + +extern "C" +void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr) +{ + assert(cubeb_jni_ptr); + + JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); + assert(jni_env); + + jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_obj); + jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_class); + + delete cubeb_jni_ptr; +} diff --git a/media/libcubeb/src/cubeb-jni.h b/media/libcubeb/src/cubeb-jni.h new file mode 100644 index 000000000..8c7ddb6ac --- /dev/null +++ b/media/libcubeb/src/cubeb-jni.h @@ -0,0 +1,10 @@ +#ifndef _CUBEB_JNI_H_ +#define _CUBEB_JNI_H_ + +typedef struct cubeb_jni cubeb_jni; + +cubeb_jni * cubeb_jni_init(); +int cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr); +void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr); + +#endif // _CUBEB_JNI_H_ diff --git a/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c index a239319a4..3ad39dee0 100644 --- a/media/libcubeb/src/cubeb.c +++ b/media/libcubeb/src/cubeb.c @@ -8,20 +8,23 @@ #include #include #include +#include #include "cubeb/cubeb.h" #include "cubeb-internal.h" #define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0]))) -cubeb_log_level g_log_level; -cubeb_log_callback g_log_callback; - struct cubeb { struct cubeb_ops * ops; }; struct cubeb_stream { + /* + * Note: All implementations of cubeb_stream must keep the following + * layout. + */ struct cubeb * context; + void * user_ptr; }; #if defined(USE_PULSE) @@ -45,6 +48,9 @@ int wasapi_init(cubeb ** context, char const * context_name); #if defined(USE_SNDIO) int sndio_init(cubeb ** context, char const * context_name); #endif +#if defined(USE_SUN) +int sun_init(cubeb ** context, char const * context_name); +#endif #if defined(USE_OPENSL) int opensl_init(cubeb ** context, char const * context_name); #endif @@ -54,10 +60,6 @@ int audiotrack_init(cubeb ** context, char const * context_name); #if defined(USE_KAI) int kai_init(cubeb ** context, char const * context_name); #endif -#if defined(USE_SUN) -int sunaudio_init(cubeb ** context, char const * context_name); -#endif - static int validate_stream_params(cubeb_stream_params * input_stream_params, @@ -66,7 +68,7 @@ validate_stream_params(cubeb_stream_params * input_stream_params, XASSERT(input_stream_params || output_stream_params); if (output_stream_params) { if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 || - output_stream_params->channels < 1 || output_stream_params->channels > 8) { + output_stream_params->channels < 1 || output_stream_params->channels > UINT8_MAX) { return CUBEB_ERROR_INVALID_FORMAT; } } @@ -99,8 +101,6 @@ validate_stream_params(cubeb_stream_params * input_stream_params, return CUBEB_ERROR_INVALID_FORMAT; } - - static int validate_latency(int latency) { @@ -111,15 +111,75 @@ validate_latency(int latency) } int -cubeb_init(cubeb ** context, char const * context_name) +cubeb_init(cubeb ** context, char const * context_name, char const * backend_name) { - int (* init[])(cubeb **, char const *) = { + int (* init_oneshot)(cubeb **, char const *) = NULL; + + if (backend_name != NULL) { + if (!strcmp(backend_name, "pulse")) { +#if defined(USE_PULSE) + init_oneshot = pulse_init; +#endif + } else if (!strcmp(backend_name, "jack")) { #if defined(USE_JACK) - jack_init, + init_oneshot = jack_init; +#endif + } else if (!strcmp(backend_name, "alsa")) { +#if defined(USE_ALSA) + init_oneshot = alsa_init; +#endif + } else if (!strcmp(backend_name, "audiounit")) { +#if defined(USE_AUDIOUNIT) + init_oneshot = audiounit_init; +#endif + } else if (!strcmp(backend_name, "wasapi")) { +#if defined(USE_WASAPI) + init_oneshot = wasapi_init; +#endif + } else if (!strcmp(backend_name, "winmm")) { +#if defined(USE_WINMM) + init_oneshot = winmm_init; +#endif + } else if (!strcmp(backend_name, "sndio")) { +#if defined(USE_SNDIO) + init_oneshot = sndio_init; +#endif + } else if (!strcmp(backend_name, "sun")) { +#if defined(USE_SUN) + init_oneshot = sun_init; #endif + } else if (!strcmp(backend_name, "opensl")) { +#if defined(USE_OPENSL) + init_oneshot = opensl_init; +#endif + } else if (!strcmp(backend_name, "audiotrack")) { +#if defined(USE_AUDIOTRACK) + init_oneshot = audiotrack_init; +#endif + } else if (!strcmp(backend_name, "kai")) { +#if defined(USE_KAI) + init_oneshot = kai_init; +#endif + } else { + /* Already set */ + } + } + + int (* default_init[])(cubeb **, char const *) = { + /* + * init_oneshot must be at the top to allow user + * to override all other choices + */ + init_oneshot, #if defined(USE_PULSE) pulse_init, #endif +#if defined(USE_JACK) + jack_init, +#endif +#if defined(USE_SNDIO) + sndio_init, +#endif #if defined(USE_ALSA) alsa_init, #endif @@ -132,8 +192,8 @@ cubeb_init(cubeb ** context, char const * context_name) #if defined(USE_WINMM) winmm_init, #endif -#if defined(USE_SNDIO) - sndio_init, +#if defined(USE_SUN) + sun_init, #endif #if defined(USE_OPENSL) opensl_init, @@ -143,9 +203,6 @@ cubeb_init(cubeb ** context, char const * context_name) #endif #if defined(USE_KAI) kai_init, -#endif -#if defined(USE_SUN) - sunaudio_init, #endif }; int i; @@ -154,10 +211,10 @@ cubeb_init(cubeb ** context, char const * context_name) return CUBEB_ERROR_INVALID_PARAMETER; } - for (i = 0; i < NELEMS(init); ++i) { - if (init[i](context, context_name) == CUBEB_OK) { - /* Assert that the minimal API is implemented. */ #define OK(fn) assert((* context)->ops->fn) + for (i = 0; i < NELEMS(default_init); ++i) { + if (default_init[i] && default_init[i](context, context_name) == CUBEB_OK) { + /* Assert that the minimal API is implemented. */ OK(get_backend_id); OK(destroy); OK(stream_init); @@ -168,7 +225,6 @@ cubeb_init(cubeb ** context, char const * context_name) return CUBEB_OK; } } - return CUBEB_ERROR; } @@ -197,9 +253,9 @@ cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels) } int -cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms) +cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params, uint32_t * latency_ms) { - if (!context || !latency_ms) { + if (!context || !params || !latency_ms) { return CUBEB_ERROR_INVALID_PARAMETER; } @@ -207,7 +263,7 @@ cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * la return CUBEB_ERROR_NOT_SUPPORTED; } - return context->ops->get_min_latency(context, params, latency_ms); + return context->ops->get_min_latency(context, *params, latency_ms); } int @@ -247,7 +303,7 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n { int r; - if (!context || !stream) { + if (!context || !stream || !data_callback || !state_callback) { return CUBEB_ERROR_INVALID_PARAMETER; } @@ -256,15 +312,24 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n return r; } - return context->ops->stream_init(context, stream, stream_name, - input_device, - input_stream_params, - output_device, - output_stream_params, - latency, - data_callback, - state_callback, - user_ptr); + r = context->ops->stream_init(context, stream, stream_name, + input_device, + input_stream_params, + output_device, + output_stream_params, + latency, + data_callback, + state_callback, + user_ptr); + + if (r == CUBEB_ERROR_INVALID_FORMAT) { + LOG("Invalid format, %p %p %d %d", + output_stream_params, input_stream_params, + output_stream_params && output_stream_params->format, + input_stream_params && input_stream_params->format); + } + + return r; } void @@ -297,6 +362,20 @@ cubeb_stream_stop(cubeb_stream * stream) return stream->context->ops->stream_stop(stream); } +int +cubeb_stream_reset_default_device(cubeb_stream * stream) +{ + if (!stream) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (!stream->context->ops->stream_reset_default_device) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return stream->context->ops->stream_reset_default_device(stream); +} + int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position) { @@ -335,19 +414,6 @@ cubeb_stream_set_volume(cubeb_stream * stream, float volume) return stream->context->ops->stream_set_volume(stream, volume); } -int cubeb_stream_set_panning(cubeb_stream * stream, float panning) -{ - if (!stream || panning < -1.0 || panning > 1.0) { - return CUBEB_ERROR_INVALID_PARAMETER; - } - - if (!stream->context->ops->stream_set_panning) { - return CUBEB_ERROR_NOT_SUPPORTED; - } - - return stream->context->ops->stream_set_panning(stream, panning); -} - int cubeb_stream_get_current_device(cubeb_stream * stream, cubeb_device ** const device) { @@ -390,6 +456,15 @@ int cubeb_stream_register_device_changed_callback(cubeb_stream * stream, return stream->context->ops->stream_register_device_changed_callback(stream, device_changed_callback); } +void * cubeb_stream_user_ptr(cubeb_stream * stream) +{ + if (!stream) { + return NULL; + } + + return stream->user_ptr; +} + static void log_device(cubeb_device_info * device_info) { @@ -479,7 +554,7 @@ void log_device(cubeb_device_info * device_info) int cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype, - cubeb_device_collection ** collection) + cubeb_device_collection * collection) { int rv; if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0) @@ -491,42 +566,36 @@ int cubeb_enumerate_devices(cubeb * context, rv = context->ops->enumerate_devices(context, devtype, collection); - if (g_log_callback) { - for (uint32_t i = 0; i < (*collection)->count; i++) { - log_device((*collection)->device[i]); + if (g_cubeb_log_callback) { + for (size_t i = 0; i < collection->count; i++) { + log_device(&collection->device[i]); } } return rv; } -int cubeb_device_collection_destroy(cubeb_device_collection * collection) +int cubeb_device_collection_destroy(cubeb * context, + cubeb_device_collection * collection) { - uint32_t i; + int r; - if (collection == NULL) + if (context == NULL || collection == NULL) return CUBEB_ERROR_INVALID_PARAMETER; - for (i = 0; i < collection->count; i++) - cubeb_device_info_destroy(collection->device[i]); + if (!context->ops->device_collection_destroy) + return CUBEB_ERROR_NOT_SUPPORTED; - free(collection); - return CUBEB_OK; -} + if (!collection->device) + return CUBEB_OK; -int cubeb_device_info_destroy(cubeb_device_info * info) -{ - if (info == NULL) { - return CUBEB_ERROR_INVALID_PARAMETER; + r = context->ops->device_collection_destroy(context, collection); + if (r == CUBEB_OK) { + collection->device = NULL; + collection->count = 0; } - free(info->device_id); - free(info->friendly_name); - free(info->group_id); - free(info->vendor_name); - - free(info); - return CUBEB_OK; + return r; } int cubeb_register_device_collection_changed(cubeb * context, @@ -555,20 +624,22 @@ int cubeb_set_log_callback(cubeb_log_level log_level, return CUBEB_ERROR_INVALID_PARAMETER; } - if (g_log_callback && log_callback) { + if (g_cubeb_log_callback && log_callback) { return CUBEB_ERROR_NOT_SUPPORTED; } - g_log_callback = log_callback; - g_log_level = log_level; + g_cubeb_log_callback = log_callback; + g_cubeb_log_level = log_level; - return CUBEB_OK; -} + // Logging a message here allows to initialize the asynchronous logger from a + // thread that is not the audio rendering thread, and especially to not + // initialize it the first time we find a verbose log, which is often in the + // audio rendering callback, that runs from the audio rendering thread, and + // that is high priority, and that we don't want to block. + if (log_level >= CUBEB_LOG_VERBOSE) { + ALOGV("Starting cubeb log"); + } -void -cubeb_crash() -{ - *((volatile int *) NULL) = 0; - abort(); + return CUBEB_OK; } diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c index 72a6acfb1..a564fbfc6 100644 --- a/media/libcubeb/src/cubeb_alsa.c +++ b/media/libcubeb/src/cubeb_alsa.c @@ -14,10 +14,58 @@ #include #include #include +#include #include #include "cubeb/cubeb.h" #include "cubeb-internal.h" +#ifdef DISABLE_LIBASOUND_DLOPEN +#define WRAP(x) x +#else +#define WRAP(x) cubeb_##x +#define LIBASOUND_API_VISIT(X) \ + X(snd_config) \ + X(snd_config_add) \ + X(snd_config_copy) \ + X(snd_config_delete) \ + X(snd_config_get_id) \ + X(snd_config_get_string) \ + X(snd_config_imake_integer) \ + X(snd_config_search) \ + X(snd_config_search_definition) \ + X(snd_lib_error_set_handler) \ + X(snd_pcm_avail_update) \ + X(snd_pcm_close) \ + X(snd_pcm_delay) \ + X(snd_pcm_drain) \ + X(snd_pcm_frames_to_bytes) \ + X(snd_pcm_get_params) \ + X(snd_pcm_hw_params_any) \ + X(snd_pcm_hw_params_get_channels_max) \ + X(snd_pcm_hw_params_get_rate) \ + X(snd_pcm_hw_params_set_rate_near) \ + X(snd_pcm_hw_params_sizeof) \ + X(snd_pcm_nonblock) \ + X(snd_pcm_open) \ + X(snd_pcm_open_lconf) \ + X(snd_pcm_pause) \ + X(snd_pcm_poll_descriptors) \ + X(snd_pcm_poll_descriptors_count) \ + X(snd_pcm_poll_descriptors_revents) \ + X(snd_pcm_readi) \ + X(snd_pcm_recover) \ + X(snd_pcm_set_params) \ + X(snd_pcm_start) \ + X(snd_pcm_state) \ + X(snd_pcm_writei) \ + +#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; +LIBASOUND_API_VISIT(MAKE_TYPEDEF); +#undef MAKE_TYPEDEF +/* snd_pcm_hw_params_alloca is actually a macro */ +#define snd_pcm_hw_params_sizeof cubeb_snd_pcm_hw_params_sizeof +#endif + #define CUBEB_STREAM_MAX 16 #define CUBEB_WATCHDOG_MS 10000 @@ -36,6 +84,7 @@ static struct cubeb_ops const alsa_ops; struct cubeb { struct cubeb_ops const * ops; + void * libasound; pthread_t thread; @@ -76,13 +125,15 @@ enum stream_state { }; struct cubeb_stream { + /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; + void * user_ptr; + /**/ pthread_mutex_t mutex; snd_pcm_t * pcm; cubeb_data_callback data_callback; cubeb_state_callback state_callback; - void * user_ptr; - snd_pcm_uframes_t write_position; + snd_pcm_uframes_t stream_position; snd_pcm_uframes_t last_position; snd_pcm_uframes_t buffer_size; cubeb_stream_params params; @@ -107,6 +158,12 @@ struct cubeb_stream { being logically active and playing. */ struct timeval last_activity; float volume; + + char * buffer; + snd_pcm_uframes_t bufframes; + snd_pcm_stream_t stream_type; + + struct cubeb_stream * other_stream; }; static int @@ -234,6 +291,14 @@ set_timeout(struct timeval * timeout, unsigned int ms) timeout->tv_usec += (ms % 1000) * 1000; } +static void +stream_buffer_decrement(cubeb_stream * stm, long count) +{ + char * bufremains = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, count); + memmove(stm->buffer, bufremains, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes - count)); + stm->bufframes -= count; +} + static void alsa_set_stream_state(cubeb_stream * stm, enum stream_state state) { @@ -249,97 +314,173 @@ alsa_set_stream_state(cubeb_stream * stm, enum stream_state state) } static enum stream_state -alsa_refill_stream(cubeb_stream * stm) +alsa_process_stream(cubeb_stream * stm) { + unsigned short revents; snd_pcm_sframes_t avail; - long got; - void * p; int draining; draining = 0; pthread_mutex_lock(&stm->mutex); - avail = snd_pcm_avail_update(stm->pcm); - if (avail < 0) { - snd_pcm_recover(stm->pcm, avail, 1); - avail = snd_pcm_avail_update(stm->pcm); - } + /* Call _poll_descriptors_revents() even if we don't use it + to let underlying plugins clear null events. Otherwise poll() + may wake up again and again, producing unnecessary CPU usage. */ + WRAP(snd_pcm_poll_descriptors_revents)(stm->pcm, stm->fds, stm->nfds, &revents); - /* Failed to recover from an xrun, this stream must be broken. */ - if (avail < 0) { + avail = WRAP(snd_pcm_avail_update)(stm->pcm); + + /* Got null event? Bail and wait for another wakeup. */ + if (avail == 0) { pthread_mutex_unlock(&stm->mutex); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return ERROR; + return RUNNING; } - /* This should never happen. */ + /* This could happen if we were suspended with SIGSTOP/Ctrl+Z for a long time. */ if ((unsigned int) avail > stm->buffer_size) { avail = stm->buffer_size; } - /* poll(2) claims this stream is active, so there should be some space - available to write. If avail is still zero here, the stream must be in - a funky state, bail and wait for another wakeup. */ - if (avail == 0) { + /* Capture: Read available frames */ + if (stm->stream_type == SND_PCM_STREAM_CAPTURE && avail > 0) { + snd_pcm_sframes_t got; + + if (avail + stm->bufframes > stm->buffer_size) { + /* Buffer overflow. Skip and overwrite with new data. */ + stm->bufframes = 0; + // TODO: should it be marked as DRAINING? + } + + got = WRAP(snd_pcm_readi)(stm->pcm, stm->buffer+stm->bufframes, avail); + + if (got < 0) { + avail = got; // the error handler below will recover us + } else { + stm->bufframes += got; + stm->stream_position += got; + + gettimeofday(&stm->last_activity, NULL); + } + } + + /* Capture: Pass read frames to callback function */ + if (stm->stream_type == SND_PCM_STREAM_CAPTURE && stm->bufframes > 0 && + (!stm->other_stream || stm->other_stream->bufframes < stm->other_stream->buffer_size)) { + snd_pcm_sframes_t wrote = stm->bufframes; + struct cubeb_stream * mainstm = stm->other_stream ? stm->other_stream : stm; + void * other_buffer = stm->other_stream ? stm->other_stream->buffer + stm->other_stream->bufframes : NULL; + + /* Correct write size to the other stream available space */ + if (stm->other_stream && wrote > (snd_pcm_sframes_t) (stm->other_stream->buffer_size - stm->other_stream->bufframes)) { + wrote = stm->other_stream->buffer_size - stm->other_stream->bufframes; + } + pthread_mutex_unlock(&stm->mutex); - return RUNNING; + wrote = stm->data_callback(mainstm, stm->user_ptr, stm->buffer, other_buffer, wrote); + pthread_mutex_lock(&stm->mutex); + + if (wrote < 0) { + avail = wrote; // the error handler below will recover us + } else { + stream_buffer_decrement(stm, wrote); + + if (stm->other_stream) { + stm->other_stream->bufframes += wrote; + } + } } - p = calloc(1, snd_pcm_frames_to_bytes(stm->pcm, avail)); - assert(p); + /* Playback: Don't have enough data? Let's ask for more. */ + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > (snd_pcm_sframes_t) stm->bufframes && + (!stm->other_stream || stm->other_stream->bufframes > 0)) { + long got = avail - stm->bufframes; + void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL; + char * buftail = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes); + + /* Correct read size to the other stream available frames */ + if (stm->other_stream && got > (snd_pcm_sframes_t) stm->other_stream->bufframes) { + got = stm->other_stream->bufframes; + } - pthread_mutex_unlock(&stm->mutex); - got = stm->data_callback(stm, stm->user_ptr, NULL, p, avail); - pthread_mutex_lock(&stm->mutex); - if (got < 0) { pthread_mutex_unlock(&stm->mutex); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - free(p); - return ERROR; + got = stm->data_callback(stm, stm->user_ptr, other_buffer, buftail, got); + pthread_mutex_lock(&stm->mutex); + + if (got < 0) { + avail = got; // the error handler below will recover us + } else { + stm->bufframes += got; + + if (stm->other_stream) { + stream_buffer_decrement(stm->other_stream, got); + } + } } - if (got > 0) { + + /* Playback: Still don't have enough data? Add some silence. */ + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > (snd_pcm_sframes_t) stm->bufframes) { + long drain_frames = avail - stm->bufframes; + double drain_time = (double) drain_frames / stm->params.rate; + + char * buftail = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes); + memset(buftail, 0, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, drain_frames)); + stm->bufframes = avail; + + /* Mark as draining, unless we're waiting for capture */ + if (!stm->other_stream || stm->other_stream->bufframes > 0) { + set_timeout(&stm->drain_timeout, drain_time * 1000); + + draining = 1; + } + } + + /* Playback: Have enough data and no errors. Let's write it out. */ + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > 0) { snd_pcm_sframes_t wrote; if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) { - float * b = (float *) p; - for (uint32_t i = 0; i < got * stm->params.channels; i++) { + float * b = (float *) stm->buffer; + for (uint32_t i = 0; i < avail * stm->params.channels; i++) { b[i] *= stm->volume; } } else { - short * b = (short *) p; - for (uint32_t i = 0; i < got * stm->params.channels; i++) { + short * b = (short *) stm->buffer; + for (uint32_t i = 0; i < avail * stm->params.channels; i++) { b[i] *= stm->volume; } } - wrote = snd_pcm_writei(stm->pcm, p, got); + + wrote = WRAP(snd_pcm_writei)(stm->pcm, stm->buffer, avail); if (wrote < 0) { - snd_pcm_recover(stm->pcm, wrote, 1); - wrote = snd_pcm_writei(stm->pcm, p, got); - } - if (wrote < 0 || wrote != got) { - /* Recovery failed, somehow. */ - pthread_mutex_unlock(&stm->mutex); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return ERROR; + avail = wrote; // the error handler below will recover us + } else { + stream_buffer_decrement(stm, wrote); + + stm->stream_position += wrote; + gettimeofday(&stm->last_activity, NULL); } - stm->write_position += wrote; - gettimeofday(&stm->last_activity, NULL); } - if (got != avail) { - long buffer_fill = stm->buffer_size - (avail - got); - double buffer_time = (double) buffer_fill / stm->params.rate; - /* Fill the remaining buffer with silence to guarantee one full period - has been written. */ - snd_pcm_writei(stm->pcm, (char *) p + got, avail - got); + /* Got some error? Let's try to recover the stream. */ + if (avail < 0) { + avail = WRAP(snd_pcm_recover)(stm->pcm, avail, 0); - set_timeout(&stm->drain_timeout, buffer_time * 1000); + /* Capture pcm must be started after initial setup/recover */ + if (avail >= 0 && + stm->stream_type == SND_PCM_STREAM_CAPTURE && + WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) { + avail = WRAP(snd_pcm_start)(stm->pcm); + } + } - draining = 1; + /* Failed to recover, this stream must be broken. */ + if (avail < 0) { + pthread_mutex_unlock(&stm->mutex); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + return ERROR; } - free(p); pthread_mutex_unlock(&stm->mutex); return draining ? DRAINING : RUNNING; } @@ -395,7 +536,7 @@ alsa_run(cubeb * ctx) if (stm && stm->state == RUNNING && stm->fds && any_revents(stm->fds, stm->nfds)) { alsa_set_stream_state(stm, PROCESSING); pthread_mutex_unlock(&ctx->mutex); - state = alsa_refill_stream(stm); + state = alsa_process_stream(stm); pthread_mutex_lock(&ctx->mutex); alsa_set_stream_state(stm, state); } @@ -445,26 +586,26 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm) slave_def = NULL; - r = snd_config_search(root_pcm, "slave", &slave_pcm); + r = WRAP(snd_config_search)(root_pcm, "slave", &slave_pcm); if (r < 0) { return NULL; } - r = snd_config_get_string(slave_pcm, &string); + r = WRAP(snd_config_get_string)(slave_pcm, &string); if (r >= 0) { - r = snd_config_search_definition(lconf, "pcm_slave", string, &slave_def); + r = WRAP(snd_config_search_definition)(lconf, "pcm_slave", string, &slave_def); if (r < 0) { return NULL; } } do { - r = snd_config_search(slave_def ? slave_def : slave_pcm, "pcm", &pcm); + r = WRAP(snd_config_search)(slave_def ? slave_def : slave_pcm, "pcm", &pcm); if (r < 0) { break; } - r = snd_config_get_string(slave_def ? slave_def : slave_pcm, &string); + r = WRAP(snd_config_get_string)(slave_def ? slave_def : slave_pcm, &string); if (r < 0) { break; } @@ -473,7 +614,7 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm) if (r < 0 || r > (int) sizeof(node_name)) { break; } - r = snd_config_search(lconf, node_name, &pcm); + r = WRAP(snd_config_search)(lconf, node_name, &pcm); if (r < 0) { break; } @@ -482,7 +623,7 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm) } while (0); if (slave_def) { - snd_config_delete(slave_def); + WRAP(snd_config_delete)(slave_def); } return NULL; @@ -505,22 +646,22 @@ init_local_config_with_workaround(char const * pcm_name) lconf = NULL; - if (snd_config == NULL) { + if (*WRAP(snd_config) == NULL) { return NULL; } - r = snd_config_copy(&lconf, snd_config); + r = WRAP(snd_config_copy)(&lconf, *WRAP(snd_config)); if (r < 0) { return NULL; } do { - r = snd_config_search_definition(lconf, "pcm", pcm_name, &pcm_node); + r = WRAP(snd_config_search_definition)(lconf, "pcm", pcm_name, &pcm_node); if (r < 0) { break; } - r = snd_config_get_id(pcm_node, &string); + r = WRAP(snd_config_get_id)(pcm_node, &string); if (r < 0) { break; } @@ -529,7 +670,7 @@ init_local_config_with_workaround(char const * pcm_name) if (r < 0 || r > (int) sizeof(node_name)) { break; } - r = snd_config_search(lconf, node_name, &pcm_node); + r = WRAP(snd_config_search)(lconf, node_name, &pcm_node); if (r < 0) { break; } @@ -540,12 +681,12 @@ init_local_config_with_workaround(char const * pcm_name) } /* Fetch the PCM node's type, and bail out if it's not the PulseAudio plugin. */ - r = snd_config_search(pcm_node, "type", &node); + r = WRAP(snd_config_search)(pcm_node, "type", &node); if (r < 0) { break; } - r = snd_config_get_string(node, &string); + r = WRAP(snd_config_get_string)(node, &string); if (r < 0) { break; } @@ -556,18 +697,18 @@ init_local_config_with_workaround(char const * pcm_name) /* Don't clobber an explicit existing handle_underrun value, set it only if it doesn't already exist. */ - r = snd_config_search(pcm_node, "handle_underrun", &node); + r = WRAP(snd_config_search)(pcm_node, "handle_underrun", &node); if (r != -ENOENT) { break; } /* Disable pcm_pulse's asynchronous underrun handling. */ - r = snd_config_imake_integer(&node, "handle_underrun", 0); + r = WRAP(snd_config_imake_integer)(&node, "handle_underrun", 0); if (r < 0) { break; } - r = snd_config_add(pcm_node, node); + r = WRAP(snd_config_add)(pcm_node, node); if (r < 0) { break; } @@ -575,21 +716,21 @@ init_local_config_with_workaround(char const * pcm_name) return lconf; } while (0); - snd_config_delete(lconf); + WRAP(snd_config_delete)(lconf); return NULL; } static int -alsa_locked_pcm_open(snd_pcm_t ** pcm, snd_pcm_stream_t stream, snd_config_t * local_config) +alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name, snd_pcm_stream_t stream, snd_config_t * local_config) { int r; pthread_mutex_lock(&cubeb_alsa_mutex); if (local_config) { - r = snd_pcm_open_lconf(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK, local_config); + r = WRAP(snd_pcm_open_lconf)(pcm, pcm_name, stream, SND_PCM_NONBLOCK, local_config); } else { - r = snd_pcm_open(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK); + r = WRAP(snd_pcm_open)(pcm, pcm_name, stream, SND_PCM_NONBLOCK); } pthread_mutex_unlock(&cubeb_alsa_mutex); @@ -602,7 +743,7 @@ alsa_locked_pcm_close(snd_pcm_t * pcm) int r; pthread_mutex_lock(&cubeb_alsa_mutex); - r = snd_pcm_close(pcm); + r = WRAP(snd_pcm_close)(pcm); pthread_mutex_unlock(&cubeb_alsa_mutex); return r; @@ -658,6 +799,7 @@ silent_error_handler(char const * file, int line, char const * function, alsa_init(cubeb ** context, char const * context_name) { (void)context_name; + void * libasound = NULL; cubeb * ctx; int r; int i; @@ -668,9 +810,30 @@ alsa_init(cubeb ** context, char const * context_name) assert(context); *context = NULL; +#ifndef DISABLE_LIBASOUND_DLOPEN + libasound = dlopen("libasound.so.2", RTLD_LAZY); + if (!libasound) { + libasound = dlopen("libasound.so", RTLD_LAZY); + if (!libasound) { + return CUBEB_ERROR; + } + } + +#define LOAD(x) { \ + cubeb_##x = dlsym(libasound, #x); \ + if (!cubeb_##x) { \ + dlclose(libasound); \ + return CUBEB_ERROR; \ + } \ + } + + LIBASOUND_API_VISIT(LOAD); +#undef LOAD +#endif + pthread_mutex_lock(&cubeb_alsa_mutex); if (!cubeb_alsa_error_handler_set) { - snd_lib_error_set_handler(silent_error_handler); + WRAP(snd_lib_error_set_handler)(silent_error_handler); cubeb_alsa_error_handler_set = 1; } pthread_mutex_unlock(&cubeb_alsa_mutex); @@ -679,6 +842,7 @@ alsa_init(cubeb ** context, char const * context_name) assert(ctx); ctx->ops = &alsa_ops; + ctx->libasound = libasound; r = pthread_mutex_init(&ctx->mutex, NULL); assert(r == 0); @@ -712,7 +876,7 @@ alsa_init(cubeb ** context, char const * context_name) /* Open a dummy PCM to force the configuration space to be evaluated so that init_local_config_with_workaround can find and modify the default node. */ - r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, NULL); + r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, NULL); if (r >= 0) { alsa_locked_pcm_close(dummy); } @@ -722,12 +886,12 @@ alsa_init(cubeb ** context, char const * context_name) pthread_mutex_unlock(&cubeb_alsa_mutex); if (ctx->local_config) { ctx->is_pa = 1; - r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, ctx->local_config); + r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, ctx->local_config); /* If we got a local_config, we found a PA PCM. If opening a PCM with that config fails with EINVAL, the PA PCM is too old for this workaround. */ if (r == -EINVAL) { pthread_mutex_lock(&cubeb_alsa_mutex); - snd_config_delete(ctx->local_config); + WRAP(snd_config_delete)(ctx->local_config); pthread_mutex_unlock(&cubeb_alsa_mutex); ctx->local_config = NULL; } else if (r >= 0) { @@ -769,24 +933,28 @@ alsa_destroy(cubeb * ctx) if (ctx->local_config) { pthread_mutex_lock(&cubeb_alsa_mutex); - snd_config_delete(ctx->local_config); + WRAP(snd_config_delete)(ctx->local_config); pthread_mutex_unlock(&cubeb_alsa_mutex); } + if (ctx->libasound) { + dlclose(ctx->libasound); + } + free(ctx); } static void alsa_stream_destroy(cubeb_stream * stm); static int -alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, - cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency_frames, - cubeb_data_callback data_callback, cubeb_state_callback state_callback, - void * user_ptr) +alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, + snd_pcm_stream_t stream_type, + cubeb_devid deviceid, + cubeb_stream_params * stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr) { (void)stream_name; cubeb_stream * stm; @@ -794,23 +962,17 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, snd_pcm_format_t format; snd_pcm_uframes_t period_size; int latency_us = 0; - + char const * pcm_name = deviceid ? (char const *) deviceid : CUBEB_ALSA_PCM_NAME; assert(ctx && stream); - if (input_stream_params) { - /* Capture support not yet implemented. */ - return CUBEB_ERROR_NOT_SUPPORTED; - } + *stream = NULL; - if (input_device || output_device) { - /* Device selection not yet implemented. */ - return CUBEB_ERROR_DEVICE_UNAVAILABLE; + if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { + return CUBEB_ERROR_NOT_SUPPORTED; } - *stream = NULL; - - switch (output_stream_params->format) { + switch (stream_params->format) { case CUBEB_SAMPLE_S16LE: format = SND_PCM_FORMAT_S16_LE; break; @@ -842,20 +1004,27 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; - stm->params = *output_stream_params; + stm->params = *stream_params; stm->state = INACTIVE; stm->volume = 1.0; + stm->buffer = NULL; + stm->bufframes = 0; + stm->stream_type = stream_type; + stm->other_stream = NULL; r = pthread_mutex_init(&stm->mutex, NULL); assert(r == 0); - r = alsa_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK, ctx->local_config); + r = pthread_cond_init(&stm->cond, NULL); + assert(r == 0); + + r = alsa_locked_pcm_open(&stm->pcm, pcm_name, stm->stream_type, ctx->local_config); if (r < 0) { alsa_stream_destroy(stm); return CUBEB_ERROR; } - r = snd_pcm_nonblock(stm->pcm, 1); + r = WRAP(snd_pcm_nonblock)(stm->pcm, 1); assert(r == 0); latency_us = latency_frames * 1e6 / stm->params.rate; @@ -868,7 +1037,7 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, latency_us = latency_us < min_latency ? min_latency: latency_us; } - r = snd_pcm_set_params(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED, + r = WRAP(snd_pcm_set_params)(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED, stm->params.channels, stm->params.rate, 1, latency_us); if (r < 0) { @@ -876,20 +1045,22 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, return CUBEB_ERROR_INVALID_FORMAT; } - r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &period_size); + r = WRAP(snd_pcm_get_params)(stm->pcm, &stm->buffer_size, &period_size); assert(r == 0); - stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm); + /* Double internal buffer size to have enough space when waiting for the other side of duplex connection */ + stm->buffer_size *= 2; + stm->buffer = calloc(1, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->buffer_size)); + assert(stm->buffer); + + stm->nfds = WRAP(snd_pcm_poll_descriptors_count)(stm->pcm); assert(stm->nfds > 0); stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd)); assert(stm->saved_fds); - r = snd_pcm_poll_descriptors(stm->pcm, stm->saved_fds, stm->nfds); + r = WRAP(snd_pcm_poll_descriptors)(stm->pcm, stm->saved_fds, stm->nfds); assert((nfds_t) r == stm->nfds); - r = pthread_cond_init(&stm->cond, NULL); - assert(r == 0); - if (alsa_register_stream(ctx, stm) != 0) { alsa_stream_destroy(stm); return CUBEB_ERROR; @@ -900,6 +1071,45 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, return CUBEB_OK; } +static int +alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, cubeb_state_callback state_callback, + void * user_ptr) +{ + int result = CUBEB_OK; + cubeb_stream * instm = NULL, * outstm = NULL; + + if (result == CUBEB_OK && input_stream_params) { + result = alsa_stream_init_single(ctx, &instm, stream_name, SND_PCM_STREAM_CAPTURE, + input_device, input_stream_params, latency_frames, + data_callback, state_callback, user_ptr); + } + + if (result == CUBEB_OK && output_stream_params) { + result = alsa_stream_init_single(ctx, &outstm, stream_name, SND_PCM_STREAM_PLAYBACK, + output_device, output_stream_params, latency_frames, + data_callback, state_callback, user_ptr); + } + + if (result == CUBEB_OK && input_stream_params && output_stream_params) { + instm->other_stream = outstm; + outstm->other_stream = instm; + } + + if (result != CUBEB_OK && instm) { + alsa_stream_destroy(instm); + } + + *stream = outstm ? outstm : instm; + + return result; +} + static void alsa_stream_destroy(cubeb_stream * stm) { @@ -912,10 +1122,15 @@ alsa_stream_destroy(cubeb_stream * stm) ctx = stm->context; + if (stm->other_stream) { + stm->other_stream->other_stream = NULL; // to stop infinite recursion + alsa_stream_destroy(stm->other_stream); + } + pthread_mutex_lock(&stm->mutex); if (stm->pcm) { if (stm->state == DRAINING) { - snd_pcm_drain(stm->pcm); + WRAP(snd_pcm_drain)(stm->pcm); } alsa_locked_pcm_close(stm->pcm); stm->pcm = NULL; @@ -934,6 +1149,8 @@ alsa_stream_destroy(cubeb_stream * stm) ctx->active_streams -= 1; pthread_mutex_unlock(&ctx->mutex); + free(stm->buffer); + free(stm); } @@ -957,12 +1174,14 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) return CUBEB_ERROR; } - r = snd_pcm_hw_params_any(stm->pcm, hw_params); + assert(stm); + + r = WRAP(snd_pcm_hw_params_any)(stm->pcm, hw_params); if (r < 0) { return CUBEB_ERROR; } - r = snd_pcm_hw_params_get_channels_max(hw_params, max_channels); + r = WRAP(snd_pcm_hw_params_get_channels_max)(hw_params, max_channels); if (r < 0) { return CUBEB_ERROR; } @@ -983,34 +1202,34 @@ alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) { /* get a pcm, disabling resampling, so we get a rate the * hardware/dmix/pulse/etc. supports. */ - r = snd_pcm_open(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE); + r = WRAP(snd_pcm_open)(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE); if (r < 0) { return CUBEB_ERROR; } - r = snd_pcm_hw_params_any(pcm, hw_params); + r = WRAP(snd_pcm_hw_params_any)(pcm, hw_params); if (r < 0) { - snd_pcm_close(pcm); + WRAP(snd_pcm_close)(pcm); return CUBEB_ERROR; } - r = snd_pcm_hw_params_get_rate(hw_params, rate, &dir); + r = WRAP(snd_pcm_hw_params_get_rate)(hw_params, rate, &dir); if (r >= 0) { /* There is a default rate: use it. */ - snd_pcm_close(pcm); + WRAP(snd_pcm_close)(pcm); return CUBEB_OK; } /* Use a common rate, alsa may adjust it based on hw/etc. capabilities. */ *rate = 44100; - r = snd_pcm_hw_params_set_rate_near(pcm, hw_params, rate, NULL); + r = WRAP(snd_pcm_hw_params_set_rate_near)(pcm, hw_params, rate, NULL); if (r < 0) { - snd_pcm_close(pcm); + WRAP(snd_pcm_close)(pcm); return CUBEB_ERROR; } - snd_pcm_close(pcm); + WRAP(snd_pcm_close)(pcm); return CUBEB_OK; } @@ -1034,8 +1253,19 @@ alsa_stream_start(cubeb_stream * stm) assert(stm); ctx = stm->context; + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) { + int r = alsa_stream_start(stm->other_stream); + if (r != CUBEB_OK) + return r; + } + pthread_mutex_lock(&stm->mutex); - snd_pcm_pause(stm->pcm, 0); + /* Capture pcm must be started after initial setup/recover */ + if (stm->stream_type == SND_PCM_STREAM_CAPTURE && + WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) { + WRAP(snd_pcm_start)(stm->pcm); + } + WRAP(snd_pcm_pause)(stm->pcm, 0); gettimeofday(&stm->last_activity, NULL); pthread_mutex_unlock(&stm->mutex); @@ -1059,6 +1289,12 @@ alsa_stream_stop(cubeb_stream * stm) assert(stm); ctx = stm->context; + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) { + int r = alsa_stream_stop(stm->other_stream); + if (r != CUBEB_OK) + return r; + } + pthread_mutex_lock(&ctx->mutex); while (stm->state == PROCESSING) { r = pthread_cond_wait(&stm->cond, &ctx->mutex); @@ -1069,7 +1305,7 @@ alsa_stream_stop(cubeb_stream * stm) pthread_mutex_unlock(&ctx->mutex); pthread_mutex_lock(&stm->mutex); - snd_pcm_pause(stm->pcm, 1); + WRAP(snd_pcm_pause)(stm->pcm, 1); pthread_mutex_unlock(&stm->mutex); return CUBEB_OK; @@ -1085,8 +1321,8 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position) pthread_mutex_lock(&stm->mutex); delay = -1; - if (snd_pcm_state(stm->pcm) != SND_PCM_STATE_RUNNING || - snd_pcm_delay(stm->pcm, &delay) != 0) { + if (WRAP(snd_pcm_state)(stm->pcm) != SND_PCM_STATE_RUNNING || + WRAP(snd_pcm_delay)(stm->pcm, &delay) != 0) { *position = stm->last_position; pthread_mutex_unlock(&stm->mutex); return CUBEB_OK; @@ -1095,8 +1331,8 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position) assert(delay >= 0); *position = 0; - if (stm->write_position >= (snd_pcm_uframes_t) delay) { - *position = stm->write_position - delay; + if (stm->stream_position >= (snd_pcm_uframes_t) delay) { + *position = stm->stream_position - delay; } stm->last_position = *position; @@ -1111,7 +1347,7 @@ alsa_stream_get_latency(cubeb_stream * stm, uint32_t * latency) snd_pcm_sframes_t delay; /* This function returns the delay in frames until a frame written using snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways. */ - if (snd_pcm_delay(stm->pcm, &delay)) { + if (WRAP(snd_pcm_delay)(stm->pcm, &delay)) { return CUBEB_ERROR; } @@ -1131,22 +1367,84 @@ alsa_stream_set_volume(cubeb_stream * stm, float volume) return CUBEB_OK; } +static int +alsa_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection * collection) +{ + cubeb_device_info* device = NULL; + + if (!context) + return CUBEB_ERROR; + + uint32_t rate, max_channels; + int r; + + r = alsa_get_preferred_sample_rate(context, &rate); + if (r != CUBEB_OK) { + return CUBEB_ERROR; + } + + r = alsa_get_max_channel_count(context, &max_channels); + if (r != CUBEB_OK) { + return CUBEB_ERROR; + } + + char const * a_name = "default"; + device = (cubeb_device_info *) calloc(1, sizeof(cubeb_device_info)); + assert(device); + if (!device) + return CUBEB_ERROR; + + device->device_id = a_name; + device->devid = (cubeb_devid) device->device_id; + device->friendly_name = a_name; + device->group_id = a_name; + device->vendor_name = a_name; + device->type = type; + device->state = CUBEB_DEVICE_STATE_ENABLED; + device->preferred = CUBEB_DEVICE_PREF_ALL; + device->format = CUBEB_DEVICE_FMT_S16NE; + device->default_format = CUBEB_DEVICE_FMT_S16NE; + device->max_channels = max_channels; + device->min_rate = rate; + device->max_rate = rate; + device->default_rate = rate; + device->latency_lo = 0; + device->latency_hi = 0; + + collection->device = device; + collection->count = 1; + + return CUBEB_OK; +} + +static int +alsa_device_collection_destroy(cubeb * context, + cubeb_device_collection * collection) +{ + assert(collection->count == 1); + (void) context; + free(collection->device); + return CUBEB_OK; +} + static struct cubeb_ops const alsa_ops = { .init = alsa_init, .get_backend_id = alsa_get_backend_id, .get_max_channel_count = alsa_get_max_channel_count, .get_min_latency = alsa_get_min_latency, .get_preferred_sample_rate = alsa_get_preferred_sample_rate, - .enumerate_devices = NULL, + .enumerate_devices = alsa_enumerate_devices, + .device_collection_destroy = alsa_device_collection_destroy, .destroy = alsa_destroy, .stream_init = alsa_stream_init, .stream_destroy = alsa_stream_destroy, .stream_start = alsa_stream_start, .stream_stop = alsa_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = alsa_stream_get_position, .stream_get_latency = alsa_stream_get_latency, .stream_set_volume = alsa_stream_set_volume, - .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, diff --git a/media/libcubeb/src/cubeb_array_queue.h b/media/libcubeb/src/cubeb_array_queue.h new file mode 100644 index 000000000..a8ea4cd17 --- /dev/null +++ b/media/libcubeb/src/cubeb_array_queue.h @@ -0,0 +1,97 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#ifndef CUBEB_ARRAY_QUEUE_H +#define CUBEB_ARRAY_QUEUE_H + +#include +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct +{ + void ** buf; + size_t num; + size_t writePos; + size_t readPos; + pthread_mutex_t mutex; +} array_queue; + +array_queue * array_queue_create(size_t num) +{ + assert(num != 0); + array_queue * new_queue = (array_queue*)calloc(1, sizeof(array_queue)); + new_queue->buf = (void **)calloc(1, sizeof(void *) * num); + new_queue->readPos = 0; + new_queue->writePos = 0; + new_queue->num = num; + + pthread_mutex_init(&new_queue->mutex, NULL); + + return new_queue; +} + +void array_queue_destroy(array_queue * aq) +{ + assert(aq); + + free(aq->buf); + pthread_mutex_destroy(&aq->mutex); + free(aq); +} + +int array_queue_push(array_queue * aq, void * item) +{ + assert(item); + + pthread_mutex_lock(&aq->mutex); + int ret = -1; + if(aq->buf[aq->writePos % aq->num] == NULL) + { + aq->buf[aq->writePos % aq->num] = item; + aq->writePos = (aq->writePos + 1) % aq->num; + ret = 0; + } + // else queue is full + pthread_mutex_unlock(&aq->mutex); + return ret; +} + +void* array_queue_pop(array_queue * aq) +{ + pthread_mutex_lock(&aq->mutex); + void * value = aq->buf[aq->readPos % aq->num]; + if(value) + { + aq->buf[aq->readPos % aq->num] = NULL; + aq->readPos = (aq->readPos + 1) % aq->num; + } + pthread_mutex_unlock(&aq->mutex); + return value; +} + +size_t array_queue_get_size(array_queue * aq) +{ + pthread_mutex_lock(&aq->mutex); + ssize_t r = aq->writePos - aq->readPos; + if (r < 0) { + r = aq->num + r; + assert(r >= 0); + } + pthread_mutex_unlock(&aq->mutex); + return (size_t)r; +} + +#if defined(__cplusplus) +} +#endif + +#endif //CUBE_ARRAY_QUEUE_H diff --git a/media/libcubeb/src/cubeb_assert.h b/media/libcubeb/src/cubeb_assert.h new file mode 100644 index 000000000..00d48d8ec --- /dev/null +++ b/media/libcubeb/src/cubeb_assert.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 CUBEB_ASSERT +#define CUBEB_ASSERT + +#include +#include +#include + +/* Forward fatal asserts to MOZ_RELEASE_ASSERT when built inside Gecko. */ +#define XASSERT(expr) MOZ_RELEASE_ASSERT(expr) + +#endif diff --git a/media/libcubeb/src/cubeb_audiotrack.c b/media/libcubeb/src/cubeb_audiotrack.c index fe2603405..22f1fe0bc 100644 --- a/media/libcubeb/src/cubeb_audiotrack.c +++ b/media/libcubeb/src/cubeb_audiotrack.c @@ -13,7 +13,7 @@ #include #include #include -#include "android/log.h" +#include #include "cubeb/cubeb.h" #include "cubeb-internal.h" @@ -75,12 +75,14 @@ struct cubeb { }; struct cubeb_stream { + /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; + void * user_ptr; + /**/ cubeb_stream_params params; cubeb_data_callback data_callback; cubeb_state_callback state_callback; void * instance; - void * user_ptr; /* Number of frames that have been passed to the AudioTrack callback */ long unsigned written; int draining; @@ -145,9 +147,9 @@ audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * status_t status; /* Recent Android have a getMinFrameCount method. */ if (!audiotrack_version_is_gingerbread(ctx)) { - status = ctx->klass.get_min_frame_count(min_frame_count, params->stream_type, params->rate); + status = ctx->klass.get_min_frame_count(min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate); } else { - status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, params->stream_type, params->rate); + status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate); } if (status != 0) { ALOG("error getting the min frame count"); @@ -325,7 +327,7 @@ audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_ channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS : AUDIO_CHANNEL_OUT_MONO_ICS; } - ctx->klass.ctor(stm->instance, stm->params.stream_type, stm->params.rate, + ctx->klass.ctor(stm->instance, AUDIO_STREAM_TYPE_MUSIC, stm->params.rate, AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0, audiotrack_refill, stm, 0, 0); @@ -422,15 +424,16 @@ static struct cubeb_ops const audiotrack_ops = { .get_min_latency = audiotrack_get_min_latency, .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate, .enumerate_devices = NULL, + .device_collection_destroy = NULL, .destroy = audiotrack_destroy, .stream_init = audiotrack_stream_init, .stream_destroy = audiotrack_stream_destroy, .stream_start = audiotrack_stream_start, .stream_stop = audiotrack_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = audiotrack_stream_get_position, .stream_get_latency = audiotrack_stream_get_latency, .stream_set_volume = audiotrack_stream_set_volume, - .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp index 9483c2795..e0c8fc696 100644 --- a/media/libcubeb/src/cubeb_audiounit.cpp +++ b/media/libcubeb/src/cubeb_audiounit.cpp @@ -22,200 +22,243 @@ #include #include "cubeb/cubeb.h" #include "cubeb-internal.h" -#include "cubeb_panner.h" +#include "cubeb_mixer.h" #if !TARGET_OS_IPHONE #include "cubeb_osx_run_loop.h" #endif #include "cubeb_resampler.h" #include "cubeb_ring_array.h" -#include "cubeb_utils.h" #include #include +#include +#include +#include +#include -#if !defined(kCFCoreFoundationVersionNumber10_7) -/* From CoreFoundation CFBase.h */ -#define kCFCoreFoundationVersionNumber10_7 635.00 -#endif - -#if !TARGET_OS_IPHONE && MAC_OS_X_VERSION_MIN_REQUIRED < 1060 -#define AudioComponent Component -#define AudioComponentDescription ComponentDescription -#define AudioComponentFindNext FindNextComponent -#define AudioComponentInstanceNew OpenAComponent -#define AudioComponentInstanceDispose CloseComponent -#endif +using namespace std; #if MAC_OS_X_VERSION_MIN_REQUIRED < 101000 -typedef UInt32 AudioFormatFlags; +typedef UInt32 AudioFormatFlags; #endif -#define CUBEB_STREAM_MAX 8 - #define AU_OUT_BUS 0 #define AU_IN_BUS 1 -#define PRINT_ERROR_CODE(str, r) do { \ - LOG("System call failed: %s (rv: %d)", str, r); \ -} while(0) - const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb"; +const char * PRIVATE_AGGREGATE_DEVICE_NAME = "CubebAggregateDevice"; + +#ifdef ALOGV +#undef ALOGV +#endif +#define ALOGV(msg, ...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{LOGV(msg, ##__VA_ARGS__);}) + +#ifdef ALOG +#undef ALOG +#endif +#define ALOG(msg, ...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{LOG(msg, ##__VA_ARGS__);}) /* Testing empirically, some headsets report a minimal latency that is very * low, but this does not work in practice. Lie and say the minimum is 256 * frames. */ -const uint32_t SAFE_MIN_LATENCY_FRAMES = 256; +const uint32_t SAFE_MIN_LATENCY_FRAMES = 128; const uint32_t SAFE_MAX_LATENCY_FRAMES = 512; +const AudioObjectPropertyAddress DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS = { + kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster +}; + +const AudioObjectPropertyAddress DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS = { + kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster +}; + +const AudioObjectPropertyAddress DEVICE_IS_ALIVE_PROPERTY_ADDRESS = { + kAudioDevicePropertyDeviceIsAlive, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster +}; + +const AudioObjectPropertyAddress DEVICES_PROPERTY_ADDRESS = { + kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster +}; + +const AudioObjectPropertyAddress INPUT_DATA_SOURCE_PROPERTY_ADDRESS = { + kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeInput, + kAudioObjectPropertyElementMaster +}; + +const AudioObjectPropertyAddress OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS = { + kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster +}; + +typedef uint32_t device_flags_value; + +enum device_flags { + DEV_UNKNOWN = 0x00, /* Unknown */ + DEV_INPUT = 0x01, /* Record device like mic */ + DEV_OUTPUT = 0x02, /* Playback device like speakers */ + DEV_SYSTEM_DEFAULT = 0x04, /* System default device */ + DEV_SELECTED_DEFAULT = 0x08, /* User selected to use the system default device */ +}; + void audiounit_stream_stop_internal(cubeb_stream * stm); -void audiounit_stream_start_internal(cubeb_stream * stm); +static int audiounit_stream_start_internal(cubeb_stream * stm); static void audiounit_close_stream(cubeb_stream *stm); static int audiounit_setup_stream(cubeb_stream *stm); +static vector +audiounit_get_devices_of_type(cubeb_device_type devtype); +static UInt32 audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectPropertyScope scope); + +#if !TARGET_OS_IPHONE +static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type); +static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm); +static int audiounit_uninstall_system_changed_callback(cubeb_stream * stm); +static void audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags); +#endif extern cubeb_ops const audiounit_ops; struct cubeb { - cubeb_ops const * ops; + cubeb_ops const * ops = &audiounit_ops; owned_critical_section mutex; - std::atomic active_streams; + int active_streams = 0; uint32_t global_latency_frames = 0; - int limit_streams; - cubeb_device_collection_changed_callback collection_changed_callback; - void * collection_changed_user_ptr; - /* Differentiate input from output devices. */ - cubeb_device_type collection_changed_devtype; - uint32_t devtype_device_count; - AudioObjectID * devtype_device_array; - // The queue is asynchronously deallocated once all references to it are released + cubeb_device_collection_changed_callback input_collection_changed_callback = nullptr; + void * input_collection_changed_user_ptr = nullptr; + cubeb_device_collection_changed_callback output_collection_changed_callback = nullptr; + void * output_collection_changed_user_ptr = nullptr; + // Store list of devices to detect changes + vector input_device_array; + vector output_device_array; + // The queue should be released when it’s no longer needed. dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL); + // Current used channel layout + atomic layout{ CUBEB_LAYOUT_UNDEFINED }; + uint32_t channels = 0; }; -class auto_array_wrapper +static unique_ptr +make_sized_audio_channel_layout(size_t sz) { -public: - explicit auto_array_wrapper(auto_array * ar) - : float_ar(ar) - , short_ar(nullptr) - {assert((float_ar && !short_ar) || (!float_ar && short_ar));} - - explicit auto_array_wrapper(auto_array * ar) - : float_ar(nullptr) - , short_ar(ar) - {assert((float_ar && !short_ar) || (!float_ar && short_ar));} - - ~auto_array_wrapper() { - auto_lock l(lock); - assert((float_ar && !short_ar) || (!float_ar && short_ar)); - delete float_ar; - delete short_ar; - } - - void push(void * elements, size_t length){ - assert((float_ar && !short_ar) || (!float_ar && short_ar)); - auto_lock l(lock); - if (float_ar) - return float_ar->push(static_cast(elements), length); - return short_ar->push(static_cast(elements), length); - } - - size_t length() { - assert((float_ar && !short_ar) || (!float_ar && short_ar)); - auto_lock l(lock); - if (float_ar) - return float_ar->length(); - return short_ar->length(); - } - - void push_silence(size_t length) { - assert((float_ar && !short_ar) || (!float_ar && short_ar)); - auto_lock l(lock); - if (float_ar) - return float_ar->push_silence(length); - return short_ar->push_silence(length); - } + assert(sz >= sizeof(AudioChannelLayout)); + AudioChannelLayout * acl = reinterpret_cast(calloc(1, sz)); + assert(acl); // Assert the allocation works. + return unique_ptr(acl, free); +} - bool pop(void * elements, size_t length) { - assert((float_ar && !short_ar) || (!float_ar && short_ar)); - auto_lock l(lock); - if (float_ar) - return float_ar->pop(static_cast(elements), length); - return short_ar->pop(static_cast(elements), length); - } +enum class io_side { + INPUT, + OUTPUT, +}; - void * data() { - assert((float_ar && !short_ar) || (!float_ar && short_ar)); - auto_lock l(lock); - if (float_ar) - return float_ar->data(); - return short_ar->data(); +static char const * +to_string(io_side side) +{ + switch (side) { + case io_side::INPUT: + return "input"; + case io_side::OUTPUT: + return "output"; } +} - void clear() { - assert((float_ar && !short_ar) || (!float_ar && short_ar)); - auto_lock l(lock); - if (float_ar) { - float_ar->clear(); - } else { - short_ar->clear(); - } - } +struct device_info { + AudioDeviceID id = kAudioObjectUnknown; + device_flags_value flags = DEV_UNKNOWN; +}; -private: - auto_array * float_ar; - auto_array * short_ar; - owned_critical_section lock; +struct property_listener { + AudioDeviceID device_id; + const AudioObjectPropertyAddress * property_address; + AudioObjectPropertyListenerProc callback; + cubeb_stream * stream; + + property_listener(AudioDeviceID id, + const AudioObjectPropertyAddress * address, + AudioObjectPropertyListenerProc proc, + cubeb_stream * stm) + : device_id(id) + , property_address(address) + , callback(proc) + , stream(stm) + {} }; struct cubeb_stream { + explicit cubeb_stream(cubeb * context); + + /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; - cubeb_data_callback data_callback; - cubeb_state_callback state_callback; - cubeb_device_changed_callback device_changed_callback; + void * user_ptr = nullptr; + /**/ + + cubeb_data_callback data_callback = nullptr; + cubeb_state_callback state_callback = nullptr; + cubeb_device_changed_callback device_changed_callback = nullptr; + owned_critical_section device_changed_callback_lock; /* Stream creation parameters */ - cubeb_stream_params input_stream_params; - cubeb_stream_params output_stream_params; - cubeb_devid input_device; - bool is_default_input; - cubeb_devid output_device; - /* User pointer of data_callback */ - void * user_ptr; + cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; + cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; + device_info input_device; + device_info output_device; /* Format descriptions */ AudioStreamBasicDescription input_desc; AudioStreamBasicDescription output_desc; /* I/O AudioUnits */ - AudioUnit input_unit; - AudioUnit output_unit; + AudioUnit input_unit = nullptr; + AudioUnit output_unit = nullptr; /* I/O device sample rate */ - Float64 input_hw_rate; - Float64 output_hw_rate; + Float64 input_hw_rate = 0; + Float64 output_hw_rate = 0; /* Expected I/O thread interleave, * calculated from I/O hw rate. */ - int expected_output_callbacks_in_a_row; + int expected_output_callbacks_in_a_row = 0; owned_critical_section mutex; - /* Hold the input samples in every - * input callback iteration */ - auto_array_wrapper * input_linear_buffer; - /* Frames on input buffer */ - std::atomic input_buffer_frames; + // Hold the input samples in every input callback iteration. + // Only accessed on input/output callback thread and during initial configure. + unique_ptr input_linear_buffer; /* Frame counters */ - uint64_t frames_played; - uint64_t frames_queued; - std::atomic frames_read; - std::atomic shutdown; - std::atomic draining; + atomic frames_played{ 0 }; + uint64_t frames_queued = 0; + // How many frames got read from the input since the stream started (includes + // padded silence) + atomic frames_read{ 0 }; + // How many frames got written to the output device since the stream started + atomic frames_written{ 0 }; + atomic shutdown{ true }; + atomic draining{ false }; + atomic reinit_pending { false }; + atomic destroy_pending{ false }; /* Latency requested by the user. */ - uint32_t latency_frames; - std::atomic current_latency_frames; - uint64_t hw_latency_frames; - std::atomic panning; - cubeb_resampler * resampler; - /* This is the number of output callback we got in a row. This is usually one, - * but can be two when the input and output rate are different, and more when - * a device has been plugged or unplugged, as there can be some time before - * the device is ready. */ - std::atomic output_callback_in_a_row; + uint32_t latency_frames = 0; + atomic current_latency_frames{ 0 }; + atomic total_output_latency_frames { 0 }; + unique_ptr resampler; /* This is true if a device change callback is currently running. */ - std::atomic switching_device; - std::atomic buffer_size_change_state{ false }; + atomic switching_device{ false }; + atomic buffer_size_change_state{ false }; + AudioDeviceID aggregate_device_id = kAudioObjectUnknown; // the aggregate device id + AudioObjectID plugin_id = kAudioObjectUnknown; // used to create aggregate device + /* Mixer interface */ + unique_ptr mixer; + /* Buffer where remixing/resampling will occur when upmixing is required */ + /* Only accessed from callback thread */ + unique_ptr temp_buffer; + size_t temp_buffer_size = 0; // size in bytes. + /* Listeners indicating what system events are monitored. */ + unique_ptr default_input_listener; + unique_ptr default_output_listener; + unique_ptr input_alive_listener; + unique_ptr input_source_listener; + unique_ptr output_source_listener; }; bool has_input(cubeb_stream * stm) @@ -228,14 +271,106 @@ bool has_output(cubeb_stream * stm) return stm->output_stream_params.rate != 0; } +cubeb_channel +channel_label_to_cubeb_channel(UInt32 label) +{ + switch (label) { + case kAudioChannelLabel_Left: + return CHANNEL_FRONT_LEFT; + case kAudioChannelLabel_Right: + return CHANNEL_FRONT_RIGHT; + case kAudioChannelLabel_Center: + return CHANNEL_FRONT_CENTER; + case kAudioChannelLabel_LFEScreen: + return CHANNEL_LOW_FREQUENCY; + case kAudioChannelLabel_LeftSurround: + return CHANNEL_BACK_LEFT; + case kAudioChannelLabel_RightSurround: + return CHANNEL_BACK_RIGHT; + case kAudioChannelLabel_LeftCenter: + return CHANNEL_FRONT_LEFT_OF_CENTER; + case kAudioChannelLabel_RightCenter: + return CHANNEL_FRONT_RIGHT_OF_CENTER; + case kAudioChannelLabel_CenterSurround: + return CHANNEL_BACK_CENTER; + case kAudioChannelLabel_LeftSurroundDirect: + return CHANNEL_SIDE_LEFT; + case kAudioChannelLabel_RightSurroundDirect: + return CHANNEL_SIDE_RIGHT; + case kAudioChannelLabel_TopCenterSurround: + return CHANNEL_TOP_CENTER; + case kAudioChannelLabel_VerticalHeightLeft: + return CHANNEL_TOP_FRONT_LEFT; + case kAudioChannelLabel_VerticalHeightCenter: + return CHANNEL_TOP_FRONT_CENTER; + case kAudioChannelLabel_VerticalHeightRight: + return CHANNEL_TOP_FRONT_RIGHT; + case kAudioChannelLabel_TopBackLeft: + return CHANNEL_TOP_BACK_LEFT; + case kAudioChannelLabel_TopBackCenter: + return CHANNEL_TOP_BACK_CENTER; + case kAudioChannelLabel_TopBackRight: + return CHANNEL_TOP_BACK_RIGHT; + default: + return CHANNEL_UNKNOWN; + } +} + +AudioChannelLabel +cubeb_channel_to_channel_label(cubeb_channel channel) +{ + switch (channel) { + case CHANNEL_FRONT_LEFT: + return kAudioChannelLabel_Left; + case CHANNEL_FRONT_RIGHT: + return kAudioChannelLabel_Right; + case CHANNEL_FRONT_CENTER: + return kAudioChannelLabel_Center; + case CHANNEL_LOW_FREQUENCY: + return kAudioChannelLabel_LFEScreen; + case CHANNEL_BACK_LEFT: + return kAudioChannelLabel_LeftSurround; + case CHANNEL_BACK_RIGHT: + return kAudioChannelLabel_RightSurround; + case CHANNEL_FRONT_LEFT_OF_CENTER: + return kAudioChannelLabel_LeftCenter; + case CHANNEL_FRONT_RIGHT_OF_CENTER: + return kAudioChannelLabel_RightCenter; + case CHANNEL_BACK_CENTER: + return kAudioChannelLabel_CenterSurround; + case CHANNEL_SIDE_LEFT: + return kAudioChannelLabel_LeftSurroundDirect; + case CHANNEL_SIDE_RIGHT: + return kAudioChannelLabel_RightSurroundDirect; + case CHANNEL_TOP_CENTER: + return kAudioChannelLabel_TopCenterSurround; + case CHANNEL_TOP_FRONT_LEFT: + return kAudioChannelLabel_VerticalHeightLeft; + case CHANNEL_TOP_FRONT_CENTER: + return kAudioChannelLabel_VerticalHeightCenter; + case CHANNEL_TOP_FRONT_RIGHT: + return kAudioChannelLabel_VerticalHeightRight; + case CHANNEL_TOP_BACK_LEFT: + return kAudioChannelLabel_TopBackLeft; + case CHANNEL_TOP_BACK_CENTER: + return kAudioChannelLabel_TopBackCenter; + case CHANNEL_TOP_BACK_RIGHT: + return kAudioChannelLabel_TopBackRight; + default: + return kAudioChannelLabel_Unknown; + } +} + #if TARGET_OS_IPHONE typedef UInt32 AudioDeviceID; typedef UInt32 AudioObjectID; #define AudioGetCurrentHostTime mach_absolute_time +#endif + uint64_t -AudioConvertHostTimeToNanos(uint64_t host_time) +ConvertHostTimeToNanos(uint64_t host_time) { static struct mach_timebase_info timebase_info; static bool initialized = false; @@ -251,27 +386,34 @@ AudioConvertHostTimeToNanos(uint64_t host_time) } return (uint64_t)answer; } -#endif -static int64_t -audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream) +static void +audiounit_increment_active_streams(cubeb * ctx) { - if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) { - return 0; - } + ctx->mutex.assert_current_thread_owns(); + ctx->active_streams += 1; +} - uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime); - uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); +static void +audiounit_decrement_active_streams(cubeb * ctx) +{ + ctx->mutex.assert_current_thread_owns(); + ctx->active_streams -= 1; +} - return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL; +static int +audiounit_active_streams(cubeb * ctx) +{ + ctx->mutex.assert_current_thread_owns(); + return ctx->active_streams; } static void -audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames) +audiounit_set_global_latency(cubeb * ctx, uint32_t latency_frames) { - stm->mutex.assert_current_thread_owns(); - assert(stm->context->active_streams == 1); - stm->context->global_latency_frames = latency_frames; + ctx->mutex.assert_current_thread_owns(); + assert(audiounit_active_streams(ctx) == 1); + ctx->global_latency_frames = latency_frames; } static void @@ -306,24 +448,38 @@ audiounit_render_input(cubeb_stream * stm, &input_buffer_list); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitRender", r); - return r; + LOG("AudioUnitRender rv=%d", r); + if (r != kAudioUnitErr_CannotDoInCurrentContext) { + return r; + } + if (stm->output_unit) { + // kAudioUnitErr_CannotDoInCurrentContext is returned when using a BT + // headset and the profile is changed from A2DP to HFP/HSP. The previous + // output device is no longer valid and must be reset. + audiounit_reinit_stream_async(stm, DEV_INPUT | DEV_OUTPUT); + } + // For now state that no error occurred and feed silence, stream will be + // resumed once reinit has completed. + ALOGV("(%p) input: reinit pending feeding silence instead", stm); + stm->input_linear_buffer->push_silence(input_frames * stm->input_desc.mChannelsPerFrame); + } else { + /* Copy input data in linear buffer. */ + stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData, + input_frames * stm->input_desc.mChannelsPerFrame); } - /* Copy input data in linear buffer. */ - stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData, - input_frames * stm->input_desc.mChannelsPerFrame); - - LOGV("(%p) input: buffers %d, size %d, channels %d, frames %d.", - stm, input_buffer_list.mNumberBuffers, - input_buffer_list.mBuffers[0].mDataByteSize, - input_buffer_list.mBuffers[0].mNumberChannels, - input_frames); - /* Advance input frame counter. */ assert(input_frames > 0); stm->frames_read += input_frames; + ALOGV("(%p) input: buffers %u, size %u, channels %u, rendered frames %d, total frames %lu.", + stm, + (unsigned int) input_buffer_list.mNumberBuffers, + (unsigned int) input_buffer_list.mBuffers[0].mDataByteSize, + (unsigned int) input_buffer_list.mBuffers[0].mNumberChannels, + (unsigned int) input_frames, + stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame); + return noErr; } @@ -336,26 +492,15 @@ audiounit_input_callback(void * user_ptr, AudioBufferList * /* bufs */) { cubeb_stream * stm = static_cast(user_ptr); - long outframes; assert(stm->input_unit != NULL); assert(AU_IN_BUS == bus); if (stm->shutdown) { - LOG("(%p) input shutdown", stm); + ALOG("(%p) input shutdown", stm); return noErr; } - // This happens when we're finally getting a new input callback after having - // switched device, we can clear the input buffer now, only keeping the data - // we just got. - if (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row) { - stm->input_linear_buffer->pop( - nullptr, - stm->input_linear_buffer->length() - - input_frames * stm->input_stream_params.channels); - } - OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames); if (r != noErr) { return r; @@ -363,7 +508,6 @@ audiounit_input_callback(void * user_ptr, // Full Duplex. We'll call data_callback in the AudioUnit output callback. if (stm->output_unit != NULL) { - stm->output_callback_in_a_row = 0; return noErr; } @@ -371,41 +515,59 @@ audiounit_input_callback(void * user_ptr, Resampler will deliver input buffer in the correct rate. */ assert(input_frames <= stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame); long total_input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame; - outframes = cubeb_resampler_fill(stm->resampler, - stm->input_linear_buffer->data(), - &total_input_frames, - NULL, - 0); - // Reset input buffer - stm->input_linear_buffer->clear(); - - if (outframes < 0 || outframes != input_frames) { - stm->shutdown = true; + long outframes = cubeb_resampler_fill(stm->resampler.get(), + stm->input_linear_buffer->data(), + &total_input_frames, + NULL, + 0); + if (outframes < total_input_frames) { + OSStatus r = AudioOutputUnitStop(stm->input_unit); + assert(r == 0); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); return noErr; } + // Reset input buffer + stm->input_linear_buffer->clear(); + return noErr; } -static bool -is_extra_input_needed(cubeb_stream * stm) +static void +audiounit_mix_output_buffer(cubeb_stream * stm, + size_t output_frames, + void * input_buffer, + size_t input_buffer_size, + void * output_buffer, + size_t output_buffer_size) +{ + assert(input_buffer_size >= + cubeb_sample_size(stm->output_stream_params.format) * + stm->output_stream_params.channels * output_frames); + assert(output_buffer_size >= stm->output_desc.mBytesPerFrame * output_frames); + + int r = cubeb_mixer_mix(stm->mixer.get(), + output_frames, + input_buffer, + input_buffer_size, + output_buffer, + output_buffer_size); + if (r != 0) { + LOG("Remix error = %d", r); + } +} + +// Return how many input frames (sampled at input_hw_rate) are needed to provide +// output_frames (sampled at output_stream_params.rate) +static int64_t +minimum_resampling_input_frames(cubeb_stream * stm, uint32_t output_frames) { - /* If the output callback came first and this is a duplex stream, we need to - * fill in some additional silence in the resampler. - * Otherwise, if we had more than expected callbacks in a row, or we're currently - * switching, we add some silence as well to compensate for the fact that - * we're lacking some input data. */ - - /* If resampling is taking place after every output callback - * the input buffer expected to be empty. Any frame left over - * from resampling is stored inside the resampler available to - * be used in next iteration as needed. - * BUT when noop_resampler is operating we have left over - * frames since it does not store anything internally. */ - return stm->frames_read == 0 || - (stm->input_linear_buffer->length() == 0 && - (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row || - stm->switching_device)); + if (stm->input_hw_rate == stm->output_stream_params.rate) { + // Fast path. + return output_frames; + } + return ceil(stm->input_hw_rate * output_frames / + stm->output_stream_params.rate); } static OSStatus @@ -421,23 +583,32 @@ audiounit_output_callback(void * user_ptr, cubeb_stream * stm = static_cast(user_ptr); - stm->output_callback_in_a_row++; + uint64_t now = ConvertHostTimeToNanos(mach_absolute_time()); + uint64_t audio_output_time = ConvertHostTimeToNanos(tstamp->mHostTime); + uint64_t output_latency_ns = audio_output_time - now; + + const int ns2s = 1e9; + // The total output latency is the timestamp difference + the stream latency + + // the hardware latency. + stm->total_output_latency_frames = output_latency_ns * stm->output_hw_rate / ns2s + stm->current_latency_frames; - LOGV("(%p) output: buffers %d, size %d, channels %d, frames %d.", - stm, outBufferList->mNumberBuffers, - outBufferList->mBuffers[0].mDataByteSize, - outBufferList->mBuffers[0].mNumberChannels, output_frames); + ALOGV("(%p) output: buffers %u, size %u, channels %u, frames %u, total input frames %lu.", + stm, + (unsigned int) outBufferList->mNumberBuffers, + (unsigned int) outBufferList->mBuffers[0].mDataByteSize, + (unsigned int) outBufferList->mBuffers[0].mNumberChannels, + (unsigned int) output_frames, + has_input(stm) ? stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame : 0); - long outframes = 0, input_frames = 0; + long input_frames = 0; void * output_buffer = NULL, * input_buffer = NULL; if (stm->shutdown) { - LOG("(%p) output shutdown.", stm); + ALOG("(%p) output shutdown.", stm); audiounit_make_silent(&outBufferList->mBuffers[0]); return noErr; } - stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm); if (stm->draining) { OSStatus r = AudioOutputUnitStop(stm->output_unit); assert(r == 0); @@ -449,61 +620,98 @@ audiounit_output_callback(void * user_ptr, audiounit_make_silent(&outBufferList->mBuffers[0]); return noErr; } + /* Get output buffer. */ - output_buffer = outBufferList->mBuffers[0].mData; + if (stm->mixer) { + // If remixing needs to occur, we can't directly work in our final + // destination buffer as data may be overwritten or too small to start with. + size_t size_needed = output_frames * stm->output_stream_params.channels * + cubeb_sample_size(stm->output_stream_params.format); + if (stm->temp_buffer_size < size_needed) { + stm->temp_buffer.reset(new uint8_t[size_needed]); + stm->temp_buffer_size = size_needed; + } + output_buffer = stm->temp_buffer.get(); + } else { + output_buffer = outBufferList->mBuffers[0].mData; + } + + stm->frames_written += output_frames; + /* If Full duplex get also input buffer */ if (stm->input_unit != NULL) { - if (is_extra_input_needed(stm)) { - uint32_t min_input_frames_required = ceilf(stm->input_hw_rate / stm->output_hw_rate * - stm->input_buffer_frames); - stm->input_linear_buffer->push_silence(min_input_frames_required * stm->input_desc.mChannelsPerFrame); - LOG("(%p) %s pushed %u frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," : - stm->switching_device ? "Device switching," : "Drop out,", min_input_frames_required); - } - // The input buffer + /* If the output callback came first and this is a duplex stream, we need to + * fill in some additional silence in the resampler. + * Otherwise, if we had more than expected callbacks in a row, or we're + * currently switching, we add some silence as well to compensate for the + * fact that we're lacking some input data. */ + uint32_t input_frames_needed = + minimum_resampling_input_frames(stm, stm->frames_written); + long missing_frames = input_frames_needed - stm->frames_read; + if (missing_frames > 0) { + stm->input_linear_buffer->push_silence(missing_frames * stm->input_desc.mChannelsPerFrame); + stm->frames_read = input_frames_needed; + + ALOG("(%p) %s pushed %ld frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," : + stm->switching_device ? "Device switching," : "Drop out,", missing_frames); + } input_buffer = stm->input_linear_buffer->data(); - // Number of input frames in the buffer + // Number of input frames in the buffer. It will change to actually used frames + // inside fill input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame; } /* Call user callback through resampler. */ - outframes = cubeb_resampler_fill(stm->resampler, - input_buffer, - input_buffer ? &input_frames : NULL, - output_buffer, - output_frames); + long outframes = cubeb_resampler_fill(stm->resampler.get(), + input_buffer, + input_buffer ? &input_frames : NULL, + output_buffer, + output_frames); if (input_buffer) { - stm->input_linear_buffer->pop(nullptr, input_frames * stm->input_desc.mChannelsPerFrame); + // Pop from the buffer the frames used by the the resampler. + stm->input_linear_buffer->pop(input_frames * stm->input_desc.mChannelsPerFrame); } - if (outframes < 0) { + if (outframes < 0 || outframes > output_frames) { stm->shutdown = true; + OSStatus r = AudioOutputUnitStop(stm->output_unit); + assert(r == 0); + if (stm->input_unit) { + r = AudioOutputUnitStop(stm->input_unit); + assert(r == 0); + } + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + audiounit_make_silent(&outBufferList->mBuffers[0]); return noErr; } - size_t outbpf = stm->output_desc.mBytesPerFrame; - stm->draining = outframes < output_frames; + stm->draining = (UInt32) outframes < output_frames; stm->frames_played = stm->frames_queued; stm->frames_queued += outframes; - AudioFormatFlags outaff = stm->output_desc.mFormatFlags; - float panning = (stm->output_desc.mChannelsPerFrame == 2) ? - stm->panning.load(std::memory_order_relaxed) : 0.0f; - /* Post process output samples. */ if (stm->draining) { /* Clear missing frames (silence) */ - memset((uint8_t*)output_buffer + outframes * outbpf, 0, (output_frames - outframes) * outbpf); + size_t channels = stm->output_stream_params.channels; + size_t missing_samples = (output_frames - outframes) * channels; + size_t size_sample = cubeb_sample_size(stm->output_stream_params.format); + /* number of bytes that have been filled with valid audio by the callback. */ + size_t audio_byte_count = outframes * channels * size_sample; + PodZero((uint8_t*)output_buffer + audio_byte_count, + missing_samples * size_sample); } - /* Pan stereo. */ - if (panning != 0.0f) { - if (outaff & kAudioFormatFlagIsFloat) { - cubeb_pan_stereo_buffer_float((float*)output_buffer, outframes, panning); - } else if (outaff & kAudioFormatFlagIsSignedInteger) { - cubeb_pan_stereo_buffer_int((short*)output_buffer, outframes, panning); - } + + /* Mixing */ + if (stm->mixer) { + audiounit_mix_output_buffer(stm, + output_frames, + output_buffer, + stm->temp_buffer_size, + outBufferList->mBuffers[0].mData, + outBufferList->mBuffers[0].mDataByteSize); } + return noErr; } @@ -511,25 +719,11 @@ extern "C" { int audiounit_init(cubeb ** context, char const * /* context_name */) { - cubeb * ctx; - - *context = NULL; - - ctx = (cubeb *)calloc(1, sizeof(cubeb)); - assert(ctx); - // Placement new to call the ctors of cubeb members. - new (ctx) cubeb(); - - ctx->ops = &audiounit_ops; - - ctx->active_streams = 0; - - ctx->limit_streams = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber10_7; #if !TARGET_OS_IPHONE cubeb_set_coreaudio_notification_runloop(); #endif - *context = ctx; + *context = new cubeb; return CUBEB_OK; } @@ -542,146 +736,233 @@ audiounit_get_backend_id(cubeb * /* ctx */) } #if !TARGET_OS_IPHONE + +static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); +static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); + static int -audiounit_get_output_device_id(AudioDeviceID * device_id) +audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side) { - UInt32 size; - OSStatus r; - AudioObjectPropertyAddress output_device_address = { - kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster - }; + assert(stm); - size = sizeof(*device_id); + device_info * info = nullptr; + cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN; - r = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &output_device_address, - 0, - NULL, - &size, - device_id); - if (r != noErr) { - PRINT_ERROR_CODE("output_device_id", r); - return CUBEB_ERROR; + if (side == io_side::INPUT) { + info = &stm->input_device; + type = CUBEB_DEVICE_TYPE_INPUT; + } else if (side == io_side::OUTPUT) { + info = &stm->output_device; + type = CUBEB_DEVICE_TYPE_OUTPUT; } + memset(info, 0, sizeof(device_info)); + info->id = id; - return CUBEB_OK; -} - -static int -audiounit_get_input_device_id(AudioDeviceID * device_id) -{ - UInt32 size; - OSStatus r; - AudioObjectPropertyAddress input_device_address = { - kAudioHardwarePropertyDefaultInputDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster - }; - - size = sizeof(*device_id); + if (side == io_side::INPUT) { + info->flags |= DEV_INPUT; + } else if (side == io_side::OUTPUT) { + info->flags |= DEV_OUTPUT; + } - r = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &input_device_address, - 0, - NULL, - &size, - device_id); - if (r != noErr) { + AudioDeviceID default_device_id = audiounit_get_default_device_id(type); + if (default_device_id == kAudioObjectUnknown) { return CUBEB_ERROR; } + if (id == kAudioObjectUnknown) { + info->id = default_device_id; + info->flags |= DEV_SELECTED_DEFAULT; + } + + if (info->id == default_device_id) { + info->flags |= DEV_SYSTEM_DEFAULT; + } + + assert(info->id); + assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) || + !(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT); return CUBEB_OK; } -static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); -static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); -static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm); static int -audiounit_reinit_stream(cubeb_stream * stm) +audiounit_reinit_stream(cubeb_stream * stm, device_flags_value flags) { auto_lock context_lock(stm->context->mutex); + assert((flags & DEV_INPUT && stm->input_unit) || + (flags & DEV_OUTPUT && stm->output_unit)); if (!stm->shutdown) { audiounit_stream_stop_internal(stm); } int r = audiounit_uninstall_device_changed_callback(stm); if (r != CUBEB_OK) { - LOG("(%p) Could not uninstall the device changed callback", stm); + LOG("(%p) Could not uninstall all device change listeners.", stm); } { auto_lock lock(stm->mutex); float volume = 0.0; - int vol_rv = audiounit_stream_get_volume(stm, &volume); + int vol_rv = CUBEB_ERROR; + if (stm->output_unit) { + vol_rv = audiounit_stream_get_volume(stm, &volume); + } audiounit_close_stream(stm); + /* Reinit occurs in one of the following case: + * - When the device is not alive any more + * - When the default system device change. + * - The bluetooth device changed from A2DP to/from HFP/HSP profile + * We first attempt to re-use the same device id, should that fail we will + * default to the (potentially new) default device. */ + AudioDeviceID input_device = flags & DEV_INPUT ? stm->input_device.id : kAudioObjectUnknown; + if (flags & DEV_INPUT) { + r = audiounit_set_device_info(stm, input_device, io_side::INPUT); + if (r != CUBEB_OK) { + LOG("(%p) Set input device info failed. This can happen when last media device is unplugged", stm); + return CUBEB_ERROR; + } + } + + /* Always use the default output on reinit. This is not correct in every + * case but it is sufficient for Firefox and prevent reinit from reporting + * failures. It will change soon when reinit mechanism will be updated. */ + r = audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::OUTPUT); + if (r != CUBEB_OK) { + LOG("(%p) Set output device info failed. This can happen when last media device is unplugged", stm); + return CUBEB_ERROR; + } + if (audiounit_setup_stream(stm) != CUBEB_OK) { LOG("(%p) Stream reinit failed.", stm); - return CUBEB_ERROR; + if (flags & DEV_INPUT && input_device != kAudioObjectUnknown) { + // Attempt to re-use the same device-id failed, so attempt again with + // default input device. + audiounit_close_stream(stm); + if (audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::INPUT) != CUBEB_OK || + audiounit_setup_stream(stm) != CUBEB_OK) { + LOG("(%p) Second stream reinit failed.", stm); + return CUBEB_ERROR; + } + } } if (vol_rv == CUBEB_OK) { audiounit_stream_set_volume(stm, volume); } - // Reset input frames to force new stream pre-buffer - // silence if needed, check `is_extra_input_needed()` - stm->frames_read = 0; - // If the stream was running, start it again. if (!stm->shutdown) { - audiounit_stream_start_internal(stm); + r = audiounit_stream_start_internal(stm); + if (r != CUBEB_OK) { + return CUBEB_ERROR; + } } } return CUBEB_OK; } +static void +audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags) +{ + if (std::atomic_exchange(&stm->reinit_pending, true)) { + // A reinit task is already pending, nothing more to do. + ALOG("(%p) re-init stream task already pending, cancelling request", stm); + return; + } + + // Use a new thread, through the queue, to avoid deadlock when calling + // Get/SetProperties method from inside notify callback + dispatch_async(stm->context->serial_queue, ^() { + if (stm->destroy_pending) { + ALOG("(%p) stream pending destroy, cancelling reinit task", stm); + return; + } + + if (audiounit_reinit_stream(stm, flags) != CUBEB_OK) { + if (audiounit_uninstall_system_changed_callback(stm) != CUBEB_OK) { + LOG("(%p) Could not uninstall system changed callback", stm); + } + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + LOG("(%p) Could not reopen the stream after switching.", stm); + } + stm->switching_device = false; + stm->reinit_pending = false; + }); +} + +static char const * +event_addr_to_string(AudioObjectPropertySelector selector) +{ + switch(selector) { + case kAudioHardwarePropertyDefaultOutputDevice: + return "kAudioHardwarePropertyDefaultOutputDevice"; + case kAudioHardwarePropertyDefaultInputDevice: + return "kAudioHardwarePropertyDefaultInputDevice"; + case kAudioDevicePropertyDeviceIsAlive: + return "kAudioDevicePropertyDeviceIsAlive"; + case kAudioDevicePropertyDataSource: + return "kAudioDevicePropertyDataSource"; + default: + return "Unknown"; + } +} + static OSStatus -audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count, +audiounit_property_listener_callback(AudioObjectID id, UInt32 address_count, const AudioObjectPropertyAddress * addresses, void * user) { cubeb_stream * stm = (cubeb_stream*) user; + if (stm->switching_device) { + LOG("Switching is already taking place. Skip Event %s for id=%d", event_addr_to_string(addresses[0].mSelector), id); + return noErr; + } stm->switching_device = true; - LOG("(%p) Audio device changed, %d events.", stm, address_count); + LOG("(%p) Audio device changed, %u events.", stm, (unsigned int) address_count); for (UInt32 i = 0; i < address_count; i++) { switch(addresses[i].mSelector) { case kAudioHardwarePropertyDefaultOutputDevice: { - LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i); - // Allow restart to choose the new default - stm->output_device = nullptr; + LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice for id=%d", (unsigned int) i, id); } break; case kAudioHardwarePropertyDefaultInputDevice: { - LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultInputDevice", i); - // Allow restart to choose the new default - stm->input_device = nullptr; + LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice for id=%d", (unsigned int) i, id); } break; case kAudioDevicePropertyDeviceIsAlive: { - LOG("Event[%d] - mSelector == kAudioDevicePropertyDeviceIsAlive", i); + LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive for id=%d", (unsigned int) i, id); // If this is the default input device ignore the event, // kAudioHardwarePropertyDefaultInputDevice will take care of the switch - if (stm->is_default_input) { + if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) { LOG("It's the default input device, ignore the event"); + stm->switching_device = false; return noErr; } - // Allow restart to choose the new default. Event register only for input. - stm->input_device = nullptr; } break; case kAudioDevicePropertyDataSource: { - LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i); - return noErr; + LOG("Event[%u] - mSelector == kAudioDevicePropertyDataSource for id=%d", (unsigned int) i, id); } + break; + default: + LOG("Event[%u] - mSelector == Unexpected Event id %d, return", (unsigned int) i, addresses[i].mSelector); + stm->switching_device = false; + return noErr; } } + // Allow restart to choose the new default + device_flags_value switch_side = DEV_UNKNOWN; + if (has_input(stm)) { + switch_side |= DEV_INPUT; + } + if (has_output(stm)) { + switch_side |= DEV_OUTPUT; + } + for (UInt32 i = 0; i < address_count; i++) { switch(addresses[i].mSelector) { case kAudioHardwarePropertyDefaultOutputDevice: @@ -689,7 +970,7 @@ audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_coun case kAudioDevicePropertyDeviceIsAlive: /* fall through */ case kAudioDevicePropertyDataSource: { - auto_lock lock(stm->mutex); + auto_lock dev_cb_lock(stm->device_changed_callback_lock); if (stm->device_changed_callback) { stm->device_changed_callback(stm->user_ptr); } @@ -698,99 +979,77 @@ audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_coun } } - // Use a new thread, through the queue, to avoid deadlock when calling - // Get/SetProperties method from inside notify callback - dispatch_async(stm->context->serial_queue, ^() { - if (audiounit_reinit_stream(stm) != CUBEB_OK) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - LOG("(%p) Could not reopen the stream after switching.", stm); - } - stm->switching_device = false; - }); + audiounit_reinit_stream_async(stm, switch_side); return noErr; } OSStatus -audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector, - AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener) +audiounit_add_listener(const property_listener * listener) { - AudioObjectPropertyAddress address = { - selector, - scope, - kAudioObjectPropertyElementMaster - }; - - return AudioObjectAddPropertyListener(id, &address, listener, stm); + assert(listener); + return AudioObjectAddPropertyListener(listener->device_id, + listener->property_address, + listener->callback, + listener->stream); } OSStatus -audiounit_remove_listener(cubeb_stream * stm, AudioDeviceID id, - AudioObjectPropertySelector selector, - AudioObjectPropertyScope scope, - AudioObjectPropertyListenerProc listener) -{ - AudioObjectPropertyAddress address = { - selector, - scope, - kAudioObjectPropertyElementMaster - }; - - return AudioObjectRemovePropertyListener(id, &address, listener, stm); +audiounit_remove_listener(const property_listener * listener) +{ + assert(listener); + return AudioObjectRemovePropertyListener(listener->device_id, + listener->property_address, + listener->callback, + listener->stream); } -static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type); - static int audiounit_install_device_changed_callback(cubeb_stream * stm) { - OSStatus r; + OSStatus rv; + int r = CUBEB_OK; if (stm->output_unit) { /* This event will notify us when the data source on the same device changes, * for example when the user plugs in a normal (non-usb) headset in the * headphone jack. */ - AudioDeviceID output_dev_id; - r = audiounit_get_output_device_id(&output_dev_id); - if (r != noErr) { - return CUBEB_ERROR; - } - - r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, - kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); - if (r != noErr) { - PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource", r); - return CUBEB_ERROR; + stm->output_source_listener.reset(new property_listener( + stm->output_device.id, &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS, + &audiounit_property_listener_callback, stm)); + rv = audiounit_add_listener(stm->output_source_listener.get()); + if (rv != noErr) { + stm->output_source_listener.reset(); + LOG("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->output_device.id); + r = CUBEB_ERROR; } } if (stm->input_unit) { /* This event will notify us when the data source on the input device changes. */ - AudioDeviceID input_dev_id; - r = audiounit_get_input_device_id(&input_dev_id); - if (r != noErr) { - return CUBEB_ERROR; - } - - r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, - kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); - if (r != noErr) { - PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource", r); - return CUBEB_ERROR; + stm->input_source_listener.reset(new property_listener( + stm->input_device.id, &INPUT_DATA_SOURCE_PROPERTY_ADDRESS, + &audiounit_property_listener_callback, stm)); + rv = audiounit_add_listener(stm->input_source_listener.get()); + if (rv != noErr) { + stm->input_source_listener.reset(); + LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->input_device.id); + r = CUBEB_ERROR; } /* Event to notify when the input is going away. */ - AudioDeviceID dev = stm->input_device ? reinterpret_cast(stm->input_device) : - audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); - r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive, - kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); - if (r != noErr) { - PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive", r); - return CUBEB_ERROR; + stm->input_alive_listener.reset(new property_listener( + stm->input_device.id, &DEVICE_IS_ALIVE_PROPERTY_ADDRESS, + &audiounit_property_listener_callback, stm)); + rv = audiounit_add_listener(stm->input_alive_listener.get()); + if (rv != noErr) { + stm->input_alive_listener.reset(); + LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d, device id =%d", rv, stm->input_device.id); + r = CUBEB_ERROR; } } - return CUBEB_OK; + return r; } static int @@ -803,9 +1062,12 @@ audiounit_install_system_changed_callback(cubeb_stream * stm) * for example when the user plugs in a USB headset and the system chooses it * automatically as the default, or when another device is chosen in the * dropdown list. */ - r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + stm->default_output_listener.reset(new property_listener( + kAudioObjectSystemObject, &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS, + &audiounit_property_listener_callback, stm)); + r = audiounit_add_listener(stm->default_output_listener.get()); if (r != noErr) { + stm->default_output_listener.reset(); LOG("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv=%d", r); return CUBEB_ERROR; } @@ -813,9 +1075,12 @@ audiounit_install_system_changed_callback(cubeb_stream * stm) if (stm->input_unit) { /* This event will notify us when the default input device changes. */ - r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, - kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + stm->default_input_listener.reset(new property_listener( + kAudioObjectSystemObject, &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS, + &audiounit_property_listener_callback, stm)); + r = audiounit_add_listener(stm->default_input_listener.get()); if (r != noErr) { + stm->default_input_listener.reset(); LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r); return CUBEB_ERROR; } @@ -827,36 +1092,38 @@ audiounit_install_system_changed_callback(cubeb_stream * stm) static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm) { - OSStatus r; - - if (stm->output_unit) { - AudioDeviceID output_dev_id; - r = audiounit_get_output_device_id(&output_dev_id); - if (r != noErr) { - return CUBEB_ERROR; - } + OSStatus rv; + // Failing to uninstall listeners is not a fatal error. + int r = CUBEB_OK; - r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, - kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); - if (r != noErr) { - return CUBEB_ERROR; + if (stm->output_source_listener) { + rv = audiounit_remove_listener(stm->output_source_listener.get()); + if (rv != noErr) { + LOG("AudioObjectRemovePropertyListener/output/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->output_device.id); + r = CUBEB_ERROR; } + stm->output_source_listener.reset(); } - if (stm->input_unit) { - AudioDeviceID input_dev_id; - r = audiounit_get_input_device_id(&input_dev_id); - if (r != noErr) { - return CUBEB_ERROR; + if (stm->input_source_listener) { + rv = audiounit_remove_listener(stm->input_source_listener.get()); + if (rv != noErr) { + LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->input_device.id); + r = CUBEB_ERROR; } + stm->input_source_listener.reset(); + } - r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, - kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); - if (r != noErr) { - return CUBEB_ERROR; + if (stm->input_alive_listener) { + rv = audiounit_remove_listener(stm->input_alive_listener.get()); + if (rv != noErr) { + LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d, device id=%d", rv, stm->input_device.id); + r = CUBEB_ERROR; } + stm->input_alive_listener.reset(); } - return CUBEB_OK; + + return r; } static int @@ -864,20 +1131,20 @@ audiounit_uninstall_system_changed_callback(cubeb_stream * stm) { OSStatus r; - if (stm->output_unit) { - r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + if (stm->default_output_listener) { + r = audiounit_remove_listener(stm->default_output_listener.get()); if (r != noErr) { return CUBEB_ERROR; } + stm->default_output_listener.reset(); } - if (stm->input_unit) { - r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, - kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + if (stm->default_input_listener) { + r = audiounit_remove_listener(stm->default_input_listener.get()); if (r != noErr) { return CUBEB_ERROR; } + stm->default_input_listener.reset(); } return CUBEB_OK; } @@ -895,7 +1162,8 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) kAudioObjectPropertyElementMaster }; - if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); + if (output_device_id == kAudioObjectUnknown) { LOG("Could not get default output device id."); return CUBEB_ERROR; } @@ -910,7 +1178,7 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) &size, latency_range); if (r != noErr) { - PRINT_ERROR_CODE("AudioObjectGetPropertyData/buffer size range", r); + LOG("AudioObjectGetPropertyData/buffer size range rv=%d", r); return CUBEB_ERROR; } @@ -921,20 +1189,19 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type) { - AudioObjectPropertyAddress adr = { 0, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - AudioDeviceID devid; - UInt32 size; - + const AudioObjectPropertyAddress * adr; if (type == CUBEB_DEVICE_TYPE_OUTPUT) { - adr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + adr = &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS; } else if (type == CUBEB_DEVICE_TYPE_INPUT) { - adr.mSelector = kAudioHardwarePropertyDefaultInputDevice; + adr = &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS; } else { return kAudioObjectUnknown; } - size = sizeof(AudioDeviceID); - if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devid) != noErr) { + AudioDeviceID devid; + UInt32 size = sizeof(AudioDeviceID); + if (AudioObjectGetPropertyData(kAudioObjectSystemObject, + adr, 0, NULL, &size, &devid) != noErr) { return kAudioObjectUnknown; } @@ -960,174 +1227,783 @@ audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) assert(ctx && max_channels); - if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); + if (output_device_id == kAudioObjectUnknown) { + return CUBEB_ERROR; + } + + size = sizeof(stream_format); + + r = AudioObjectGetPropertyData(output_device_id, + &stream_format_address, + 0, + NULL, + &size, + &stream_format); + if (r != noErr) { + LOG("AudioObjectPropertyAddress/StreamFormat rv=%d", r); + return CUBEB_ERROR; + } + + *max_channels = stream_format.mChannelsPerFrame; +#endif + return CUBEB_OK; +} + +static int +audiounit_get_min_latency(cubeb * /* ctx */, + cubeb_stream_params /* params */, + uint32_t * latency_frames) +{ +#if TARGET_OS_IPHONE + //TODO: [[AVAudioSession sharedInstance] inputLatency] + return CUBEB_ERROR_NOT_SUPPORTED; +#else + AudioValueRange latency_range; + if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) { + LOG("Could not get acceptable latency range."); + return CUBEB_ERROR; + } + + *latency_frames = max(latency_range.mMinimum, + SAFE_MIN_LATENCY_FRAMES); +#endif + + return CUBEB_OK; +} + +static int +audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate) +{ +#if TARGET_OS_IPHONE + //TODO + return CUBEB_ERROR_NOT_SUPPORTED; +#else + UInt32 size; + OSStatus r; + Float64 fsamplerate; + AudioDeviceID output_device_id; + AudioObjectPropertyAddress samplerate_address = { + kAudioDevicePropertyNominalSampleRate, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); + if (output_device_id == kAudioObjectUnknown) { + return CUBEB_ERROR; + } + + size = sizeof(fsamplerate); + r = AudioObjectGetPropertyData(output_device_id, + &samplerate_address, + 0, + NULL, + &size, + &fsamplerate); + + if (r != noErr) { + return CUBEB_ERROR; + } + + *rate = static_cast(fsamplerate); +#endif + return CUBEB_OK; +} + +static cubeb_channel_layout +audiounit_convert_channel_layout(AudioChannelLayout * layout) +{ + // When having one or two channel, force mono or stereo. Some devices (namely, + // Bose QC35, mark 1 and 2), expose a single channel mapped to the right for + // some reason. + if (layout->mNumberChannelDescriptions == 1) { + return CUBEB_LAYOUT_MONO; + } else if (layout->mNumberChannelDescriptions == 2) { + return CUBEB_LAYOUT_STEREO; + } + + if (layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions) { + // kAudioChannelLayoutTag_UseChannelBitmap + // kAudioChannelLayoutTag_Mono + // kAudioChannelLayoutTag_Stereo + // .... + LOG("Only handle UseChannelDescriptions for now.\n"); + return CUBEB_LAYOUT_UNDEFINED; + } + + cubeb_channel_layout cl = 0; + for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; ++i) { + cubeb_channel cc = channel_label_to_cubeb_channel( + layout->mChannelDescriptions[i].mChannelLabel); + if (cc == CHANNEL_UNKNOWN) { + return CUBEB_LAYOUT_UNDEFINED; + } + cl |= cc; + } + + return cl; +} + +static cubeb_channel_layout +audiounit_get_preferred_channel_layout(AudioUnit output_unit) +{ + OSStatus rv = noErr; + UInt32 size = 0; + rv = AudioUnitGetPropertyInfo(output_unit, + kAudioDevicePropertyPreferredChannelLayout, + kAudioUnitScope_Output, + AU_OUT_BUS, + &size, + nullptr); + if (rv != noErr) { + LOG("AudioUnitGetPropertyInfo/kAudioDevicePropertyPreferredChannelLayout rv=%d", rv); + return CUBEB_LAYOUT_UNDEFINED; + } + assert(size > 0); + + auto layout = make_sized_audio_channel_layout(size); + rv = AudioUnitGetProperty(output_unit, + kAudioDevicePropertyPreferredChannelLayout, + kAudioUnitScope_Output, + AU_OUT_BUS, + layout.get(), + &size); + if (rv != noErr) { + LOG("AudioUnitGetProperty/kAudioDevicePropertyPreferredChannelLayout rv=%d", rv); + return CUBEB_LAYOUT_UNDEFINED; + } + + return audiounit_convert_channel_layout(layout.get()); +} + +static cubeb_channel_layout +audiounit_get_current_channel_layout(AudioUnit output_unit) +{ + OSStatus rv = noErr; + UInt32 size = 0; + rv = AudioUnitGetPropertyInfo(output_unit, + kAudioUnitProperty_AudioChannelLayout, + kAudioUnitScope_Output, + AU_OUT_BUS, + &size, + nullptr); + if (rv != noErr) { + LOG("AudioUnitGetPropertyInfo/kAudioUnitProperty_AudioChannelLayout rv=%d", rv); + // This property isn't known before macOS 10.12, attempt another method. + return audiounit_get_preferred_channel_layout(output_unit); + } + assert(size > 0); + + auto layout = make_sized_audio_channel_layout(size); + rv = AudioUnitGetProperty(output_unit, + kAudioUnitProperty_AudioChannelLayout, + kAudioUnitScope_Output, + AU_OUT_BUS, + layout.get(), + &size); + if (rv != noErr) { + LOG("AudioUnitGetProperty/kAudioUnitProperty_AudioChannelLayout rv=%d", rv); + return CUBEB_LAYOUT_UNDEFINED; + } + + return audiounit_convert_channel_layout(layout.get()); +} + +static int audiounit_create_unit(AudioUnit * unit, device_info * device); + +static OSStatus audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype); + +static void +audiounit_destroy(cubeb * ctx) +{ + { + auto_lock lock(ctx->mutex); + + // Disabling this assert for bug 1083664 -- we seem to leak a stream + // assert(ctx->active_streams == 0); + if (audiounit_active_streams(ctx) > 0) { + LOG("(%p) API misuse, %d streams active when context destroyed!", ctx, audiounit_active_streams(ctx)); + } + + /* Unregister the callback if necessary. */ + if (ctx->input_collection_changed_callback) { + audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_INPUT); + } + if (ctx->output_collection_changed_callback) { + audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_OUTPUT); + } + } + + dispatch_release(ctx->serial_queue); + + delete ctx; +} + +static void audiounit_stream_destroy(cubeb_stream * stm); + +static int +audio_stream_desc_init(AudioStreamBasicDescription * ss, + const cubeb_stream_params * stream_params) +{ + switch (stream_params->format) { + case CUBEB_SAMPLE_S16LE: + ss->mBitsPerChannel = 16; + ss->mFormatFlags = kAudioFormatFlagIsSignedInteger; + break; + case CUBEB_SAMPLE_S16BE: + ss->mBitsPerChannel = 16; + ss->mFormatFlags = kAudioFormatFlagIsSignedInteger | + kAudioFormatFlagIsBigEndian; + break; + case CUBEB_SAMPLE_FLOAT32LE: + ss->mBitsPerChannel = 32; + ss->mFormatFlags = kAudioFormatFlagIsFloat; + break; + case CUBEB_SAMPLE_FLOAT32BE: + ss->mBitsPerChannel = 32; + ss->mFormatFlags = kAudioFormatFlagIsFloat | + kAudioFormatFlagIsBigEndian; + break; + default: + return CUBEB_ERROR_INVALID_FORMAT; + } + + ss->mFormatID = kAudioFormatLinearPCM; + ss->mFormatFlags |= kLinearPCMFormatFlagIsPacked; + ss->mSampleRate = stream_params->rate; + ss->mChannelsPerFrame = stream_params->channels; + + ss->mBytesPerFrame = (ss->mBitsPerChannel / 8) * ss->mChannelsPerFrame; + ss->mFramesPerPacket = 1; + ss->mBytesPerPacket = ss->mBytesPerFrame * ss->mFramesPerPacket; + + ss->mReserved = 0; + + return CUBEB_OK; +} + +void +audiounit_init_mixer(cubeb_stream * stm) +{ + // We can't rely on macOS' AudioUnit to properly downmix (or upmix) the audio + // data, it silently drop the channels so we need to remix the + // audio data by ourselves to keep all the information. + stm->mixer.reset(cubeb_mixer_create(stm->output_stream_params.format, + stm->output_stream_params.channels, + stm->output_stream_params.layout, + stm->context->channels, + stm->context->layout)); + assert(stm->mixer); +} + +static int +audiounit_set_channel_layout(AudioUnit unit, + io_side side, + cubeb_channel_layout layout) +{ + if (side != io_side::OUTPUT) { + return CUBEB_ERROR; + } + + if (layout == CUBEB_LAYOUT_UNDEFINED) { + // We leave everything as-is... + return CUBEB_OK; + } + + + OSStatus r; + uint32_t nb_channels = cubeb_channel_layout_nb_channels(layout); + + // We do not use CoreAudio standard layout for lack of documentation on what + // the actual channel orders are. So we set a custom layout. + size_t size = offsetof(AudioChannelLayout, mChannelDescriptions[nb_channels]); + auto au_layout = make_sized_audio_channel_layout(size); + au_layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions; + au_layout->mNumberChannelDescriptions = nb_channels; + + uint32_t channels = 0; + cubeb_channel_layout channelMap = layout; + for (uint32_t i = 0; channelMap != 0; ++i) { + XASSERT(channels < nb_channels); + uint32_t channel = (channelMap & 1) << i; + if (channel != 0) { + au_layout->mChannelDescriptions[channels].mChannelLabel = + cubeb_channel_to_channel_label(static_cast(channel)); + au_layout->mChannelDescriptions[channels].mChannelFlags = kAudioChannelFlags_AllOff; + channels++; + } + channelMap = channelMap >> 1; + } + + r = AudioUnitSetProperty(unit, + kAudioUnitProperty_AudioChannelLayout, + kAudioUnitScope_Input, + AU_OUT_BUS, + au_layout.get(), + size); + if (r != noErr) { + LOG("AudioUnitSetProperty/%s/kAudioUnitProperty_AudioChannelLayout rv=%d", to_string(side), r); + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +void +audiounit_layout_init(cubeb_stream * stm, io_side side) +{ + // We currently don't support the input layout setting. + if (side == io_side::INPUT) { + return; + } + + stm->context->layout = audiounit_get_current_channel_layout(stm->output_unit); + + audiounit_set_channel_layout(stm->output_unit, io_side::OUTPUT, stm->context->layout); +} + +static vector +audiounit_get_sub_devices(AudioDeviceID device_id) +{ + vector sub_devices; + AudioObjectPropertyAddress property_address = { kAudioAggregateDevicePropertyActiveSubDeviceList, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + UInt32 size = 0; + OSStatus rv = AudioObjectGetPropertyDataSize(device_id, + &property_address, + 0, + nullptr, + &size); + + if (rv != noErr) { + sub_devices.push_back(device_id); + return sub_devices; + } + + uint32_t count = static_cast(size / sizeof(AudioObjectID)); + sub_devices.resize(count); + rv = AudioObjectGetPropertyData(device_id, + &property_address, + 0, + nullptr, + &size, + sub_devices.data()); + if (rv != noErr) { + sub_devices.clear(); + sub_devices.push_back(device_id); + } else { + LOG("Found %u sub-devices", count); + } + return sub_devices; +} + +static int +audiounit_create_blank_aggregate_device(AudioObjectID * plugin_id, AudioDeviceID * aggregate_device_id) +{ + AudioObjectPropertyAddress address_plugin_bundle_id = { kAudioHardwarePropertyPlugInForBundleID, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + UInt32 size = 0; + OSStatus r = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, + &address_plugin_bundle_id, + 0, NULL, + &size); + if (r != noErr) { + LOG("AudioObjectGetPropertyDataSize/kAudioHardwarePropertyPlugInForBundleID, rv=%d", r); return CUBEB_ERROR; } - size = sizeof(stream_format); + AudioValueTranslation translation_value; + CFStringRef in_bundle_ref = CFSTR("com.apple.audio.CoreAudio"); + translation_value.mInputData = &in_bundle_ref; + translation_value.mInputDataSize = sizeof(in_bundle_ref); + translation_value.mOutputData = plugin_id; + translation_value.mOutputDataSize = sizeof(*plugin_id); - r = AudioObjectGetPropertyData(output_device_id, - &stream_format_address, + r = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &address_plugin_bundle_id, 0, - NULL, + nullptr, &size, - &stream_format); + &translation_value); if (r != noErr) { - PRINT_ERROR_CODE("AudioObjectPropertyAddress/StreamFormat", r); + LOG("AudioObjectGetPropertyData/kAudioHardwarePropertyPlugInForBundleID, rv=%d", r); return CUBEB_ERROR; } - *max_channels = stream_format.mChannelsPerFrame; -#endif + AudioObjectPropertyAddress create_aggregate_device_address = { kAudioPlugInCreateAggregateDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + r = AudioObjectGetPropertyDataSize(*plugin_id, + &create_aggregate_device_address, + 0, + nullptr, + &size); + if (r != noErr) { + LOG("AudioObjectGetPropertyDataSize/kAudioPlugInCreateAggregateDevice, rv=%d", r); + return CUBEB_ERROR; + } + + CFMutableDictionaryRef aggregate_device_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + struct timeval timestamp; + gettimeofday(×tamp, NULL); + long long int time_id = timestamp.tv_sec * 1000000LL + timestamp.tv_usec; + CFStringRef aggregate_device_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s_%llx"), PRIVATE_AGGREGATE_DEVICE_NAME, time_id); + CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceNameKey), aggregate_device_name); + CFRelease(aggregate_device_name); + + CFStringRef aggregate_device_UID = CFStringCreateWithFormat(NULL, NULL, CFSTR("org.mozilla.%s_%llx"), PRIVATE_AGGREGATE_DEVICE_NAME, time_id); + CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceUIDKey), aggregate_device_UID); + CFRelease(aggregate_device_UID); + + int private_value = 1; + CFNumberRef aggregate_device_private_key = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &private_value); + CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceIsPrivateKey), aggregate_device_private_key); + CFRelease(aggregate_device_private_key); + + int stacked_value = 0; + CFNumberRef aggregate_device_stacked_key = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &stacked_value); + CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceIsStackedKey), aggregate_device_stacked_key); + CFRelease(aggregate_device_stacked_key); + + r = AudioObjectGetPropertyData(*plugin_id, + &create_aggregate_device_address, + sizeof(aggregate_device_dict), + &aggregate_device_dict, + &size, + aggregate_device_id); + CFRelease(aggregate_device_dict); + if (r != noErr) { + LOG("AudioObjectGetPropertyData/kAudioPlugInCreateAggregateDevice, rv=%d", r); + return CUBEB_ERROR; + } + LOG("New aggregate device %u", *aggregate_device_id); + return CUBEB_OK; } -static int -audiounit_get_min_latency(cubeb * /* ctx */, - cubeb_stream_params /* params */, - uint32_t * latency_frames) +// The returned CFStringRef object needs to be released (via CFRelease) +// if it's not NULL, since the reference count of the returned CFStringRef +// object is increased. +static CFStringRef +get_device_name(AudioDeviceID id) { -#if TARGET_OS_IPHONE - //TODO: [[AVAudioSession sharedInstance] inputLatency] - return CUBEB_ERROR_NOT_SUPPORTED; -#else - AudioValueRange latency_range; - if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) { - LOG("Could not get acceptable latency range."); + UInt32 size = sizeof(CFStringRef); + CFStringRef UIname = nullptr; + AudioObjectPropertyAddress address_uuid = { kAudioDevicePropertyDeviceUID, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus err = AudioObjectGetPropertyData(id, &address_uuid, 0, nullptr, &size, &UIname); + return (err == noErr) ? UIname : NULL; +} + +static int +audiounit_set_aggregate_sub_device_list(AudioDeviceID aggregate_device_id, + AudioDeviceID input_device_id, + AudioDeviceID output_device_id) +{ + LOG("Add devices input %u and output %u into aggregate device %u", + input_device_id, output_device_id, aggregate_device_id); + const vector output_sub_devices = audiounit_get_sub_devices(output_device_id); + const vector input_sub_devices = audiounit_get_sub_devices(input_device_id); + + CFMutableArrayRef aggregate_sub_devices_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + /* The order of the items in the array is significant and is used to determine the order of the streams + of the AudioAggregateDevice. */ + for (UInt32 i = 0; i < output_sub_devices.size(); i++) { + CFStringRef ref = get_device_name(output_sub_devices[i]); + if (ref == NULL) { + CFRelease(aggregate_sub_devices_array); + return CUBEB_ERROR; + } + CFArrayAppendValue(aggregate_sub_devices_array, ref); + CFRelease(ref); + } + for (UInt32 i = 0; i < input_sub_devices.size(); i++) { + CFStringRef ref = get_device_name(input_sub_devices[i]); + if (ref == NULL) { + CFRelease(aggregate_sub_devices_array); + return CUBEB_ERROR; + } + CFArrayAppendValue(aggregate_sub_devices_array, ref); + CFRelease(ref); + } + + AudioObjectPropertyAddress aggregate_sub_device_list = { kAudioAggregateDevicePropertyFullSubDeviceList, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + UInt32 size = sizeof(CFMutableArrayRef); + OSStatus rv = AudioObjectSetPropertyData(aggregate_device_id, + &aggregate_sub_device_list, + 0, + nullptr, + size, + &aggregate_sub_devices_array); + CFRelease(aggregate_sub_devices_array); + if (rv != noErr) { + LOG("AudioObjectSetPropertyData/kAudioAggregateDevicePropertyFullSubDeviceList, rv=%d", rv); return CUBEB_ERROR; } - *latency_frames = std::max(latency_range.mMinimum, - SAFE_MIN_LATENCY_FRAMES); -#endif + return CUBEB_OK; +} + +static int +audiounit_set_master_aggregate_device(const AudioDeviceID aggregate_device_id) +{ + assert(aggregate_device_id != kAudioObjectUnknown); + AudioObjectPropertyAddress master_aggregate_sub_device = { kAudioAggregateDevicePropertyMasterSubDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + // Master become the 1st output sub device + AudioDeviceID output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); + const vector output_sub_devices = audiounit_get_sub_devices(output_device_id); + CFStringRef master_sub_device = get_device_name(output_sub_devices[0]); + + UInt32 size = sizeof(CFStringRef); + OSStatus rv = AudioObjectSetPropertyData(aggregate_device_id, + &master_aggregate_sub_device, + 0, + NULL, + size, + &master_sub_device); + if (master_sub_device) { + CFRelease(master_sub_device); + } + if (rv != noErr) { + LOG("AudioObjectSetPropertyData/kAudioAggregateDevicePropertyMasterSubDevice, rv=%d", rv); + return CUBEB_ERROR; + } return CUBEB_OK; } static int -audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate) +audiounit_activate_clock_drift_compensation(const AudioDeviceID aggregate_device_id) { -#if TARGET_OS_IPHONE - //TODO - return CUBEB_ERROR_NOT_SUPPORTED; -#else - UInt32 size; - OSStatus r; - Float64 fsamplerate; - AudioDeviceID output_device_id; - AudioObjectPropertyAddress samplerate_address = { - kAudioDevicePropertyNominalSampleRate, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster - }; + assert(aggregate_device_id != kAudioObjectUnknown); + AudioObjectPropertyAddress address_owned = { kAudioObjectPropertyOwnedObjects, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; - if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + UInt32 qualifier_data_size = sizeof(AudioObjectID); + AudioClassID class_id = kAudioSubDeviceClassID; + void * qualifier_data = &class_id; + UInt32 size = 0; + OSStatus rv = AudioObjectGetPropertyDataSize(aggregate_device_id, + &address_owned, + qualifier_data_size, + qualifier_data, + &size); + if (rv != noErr) { + LOG("AudioObjectGetPropertyDataSize/kAudioObjectPropertyOwnedObjects, rv=%d", rv); return CUBEB_ERROR; } - size = sizeof(fsamplerate); - r = AudioObjectGetPropertyData(output_device_id, - &samplerate_address, - 0, - NULL, - &size, - &fsamplerate); + UInt32 subdevices_num = 0; + subdevices_num = size / sizeof(AudioObjectID); + AudioObjectID sub_devices[subdevices_num]; + size = sizeof(sub_devices); - if (r != noErr) { + rv = AudioObjectGetPropertyData(aggregate_device_id, + &address_owned, + qualifier_data_size, + qualifier_data, + &size, + sub_devices); + if (rv != noErr) { + LOG("AudioObjectGetPropertyData/kAudioObjectPropertyOwnedObjects, rv=%d", rv); return CUBEB_ERROR; } - *rate = static_cast(fsamplerate); -#endif + AudioObjectPropertyAddress address_drift = { kAudioSubDevicePropertyDriftCompensation, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + // Start from the second device since the first is the master clock + for (UInt32 i = 1; i < subdevices_num; ++i) { + UInt32 drift_compensation_value = 1; + rv = AudioObjectSetPropertyData(sub_devices[i], + &address_drift, + 0, + nullptr, + sizeof(UInt32), + &drift_compensation_value); + if (rv != noErr) { + LOG("AudioObjectSetPropertyData/kAudioSubDevicePropertyDriftCompensation, rv=%d", rv); + return CUBEB_OK; + } + } return CUBEB_OK; } -static OSStatus audiounit_remove_device_listener(cubeb * context); +static int audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id); +static void audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope scope, + uint32_t * min, uint32_t * max, uint32_t * def); +static int +audiounit_create_device_from_hwdev(cubeb_device_info * dev_info, AudioObjectID devid, cubeb_device_type type); +static void audiounit_device_destroy(cubeb_device_info * device); static void -audiounit_destroy(cubeb * ctx) -{ - // Disabling this assert for bug 1083664 -- we seem to leak a stream - // assert(ctx->active_streams == 0); - - { - auto_lock lock(ctx->mutex); - /* Unregister the callback if necessary. */ - if(ctx->collection_changed_callback) { - audiounit_remove_device_listener(ctx); +audiounit_workaround_for_airpod(cubeb_stream * stm) +{ + cubeb_device_info input_device_info; + audiounit_create_device_from_hwdev(&input_device_info, stm->input_device.id, CUBEB_DEVICE_TYPE_INPUT); + + cubeb_device_info output_device_info; + audiounit_create_device_from_hwdev(&output_device_info, stm->output_device.id, CUBEB_DEVICE_TYPE_OUTPUT); + + std::string input_name_str(input_device_info.friendly_name); + std::string output_name_str(output_device_info.friendly_name); + + if(input_name_str.find("AirPods") != std::string::npos && + output_name_str.find("AirPods") != std::string::npos) { + uint32_t input_min_rate = 0; + uint32_t input_max_rate = 0; + uint32_t input_nominal_rate = 0; + audiounit_get_available_samplerate(stm->input_device.id, kAudioObjectPropertyScopeGlobal, + &input_min_rate, &input_max_rate, &input_nominal_rate); + LOG("(%p) Input device %u, name: %s, min: %u, max: %u, nominal rate: %u", stm, stm->input_device.id + , input_device_info.friendly_name, input_min_rate, input_max_rate, input_nominal_rate); + uint32_t output_min_rate = 0; + uint32_t output_max_rate = 0; + uint32_t output_nominal_rate = 0; + audiounit_get_available_samplerate(stm->output_device.id, kAudioObjectPropertyScopeGlobal, + &output_min_rate, &output_max_rate, &output_nominal_rate); + LOG("(%p) Output device %u, name: %s, min: %u, max: %u, nominal rate: %u", stm, stm->output_device.id + , output_device_info.friendly_name, output_min_rate, output_max_rate, output_nominal_rate); + + Float64 rate = input_nominal_rate; + AudioObjectPropertyAddress addr = {kAudioDevicePropertyNominalSampleRate, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster}; + + OSStatus rv = AudioObjectSetPropertyData(stm->aggregate_device_id, + &addr, + 0, + nullptr, + sizeof(Float64), + &rate); + if (rv != noErr) { + LOG("Non fatal error, AudioObjectSetPropertyData/kAudioDevicePropertyNominalSampleRate, rv=%d", rv); } } - - ctx->~cubeb(); - free(ctx); + audiounit_device_destroy(&input_device_info); + audiounit_device_destroy(&output_device_info); } -static void audiounit_stream_destroy(cubeb_stream * stm); - +/* + * Aggregate Device is a virtual audio interface which utilizes inputs and outputs + * of one or more physical audio interfaces. It is possible to use the clock of + * one of the devices as a master clock for all the combined devices and enable + * drift compensation for the devices that are not designated clock master. + * + * Creating a new aggregate device programmatically requires [0][1]: + * 1. Locate the base plug-in ("com.apple.audio.CoreAudio") + * 2. Create a dictionary that describes the aggregate device + * (don't add sub-devices in that step, prone to fail [0]) + * 3. Ask the base plug-in to create the aggregate device (blank) + * 4. Add the array of sub-devices. + * 5. Set the master device (1st output device in our case) + * 6. Enable drift compensation for the non-master devices + * + * [0] https://lists.apple.com/archives/coreaudio-api/2006/Apr/msg00092.html + * [1] https://lists.apple.com/archives/coreaudio-api/2005/Jul/msg00150.html + * [2] CoreAudio.framework/Headers/AudioHardware.h + * */ static int -audio_stream_desc_init(AudioStreamBasicDescription * ss, - const cubeb_stream_params * stream_params) +audiounit_create_aggregate_device(cubeb_stream * stm) { - switch (stream_params->format) { - case CUBEB_SAMPLE_S16LE: - ss->mBitsPerChannel = 16; - ss->mFormatFlags = kAudioFormatFlagIsSignedInteger; - break; - case CUBEB_SAMPLE_S16BE: - ss->mBitsPerChannel = 16; - ss->mFormatFlags = kAudioFormatFlagIsSignedInteger | - kAudioFormatFlagIsBigEndian; - break; - case CUBEB_SAMPLE_FLOAT32LE: - ss->mBitsPerChannel = 32; - ss->mFormatFlags = kAudioFormatFlagIsFloat; - break; - case CUBEB_SAMPLE_FLOAT32BE: - ss->mBitsPerChannel = 32; - ss->mFormatFlags = kAudioFormatFlagIsFloat | - kAudioFormatFlagIsBigEndian; - break; - default: - return CUBEB_ERROR_INVALID_FORMAT; + int r = audiounit_create_blank_aggregate_device(&stm->plugin_id, &stm->aggregate_device_id); + if (r != CUBEB_OK) { + LOG("(%p) Failed to create blank aggregate device", stm); + return CUBEB_ERROR; } - ss->mFormatID = kAudioFormatLinearPCM; - ss->mFormatFlags |= kLinearPCMFormatFlagIsPacked; - ss->mSampleRate = stream_params->rate; - ss->mChannelsPerFrame = stream_params->channels; + r = audiounit_set_aggregate_sub_device_list(stm->aggregate_device_id, stm->input_device.id, stm->output_device.id); + if (r != CUBEB_OK) { + LOG("(%p) Failed to set aggregate sub-device list", stm); + audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); + return CUBEB_ERROR; + } - ss->mBytesPerFrame = (ss->mBitsPerChannel / 8) * ss->mChannelsPerFrame; - ss->mFramesPerPacket = 1; - ss->mBytesPerPacket = ss->mBytesPerFrame * ss->mFramesPerPacket; + r = audiounit_set_master_aggregate_device(stm->aggregate_device_id); + if (r != CUBEB_OK) { + LOG("(%p) Failed to set master sub-device for aggregate device", stm); + audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); + return CUBEB_ERROR; + } - ss->mReserved = 0; + r = audiounit_activate_clock_drift_compensation(stm->aggregate_device_id); + if (r != CUBEB_OK) { + LOG("(%p) Failed to activate clock drift compensation for aggregate device", stm); + audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); + return CUBEB_ERROR; + } + + audiounit_workaround_for_airpod(stm); + + return CUBEB_OK; +} +static int +audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id) +{ + assert(aggregate_device_id && + *aggregate_device_id != kAudioDeviceUnknown && + plugin_id != kAudioObjectUnknown); + AudioObjectPropertyAddress destroy_aggregate_device_addr = { kAudioPlugInDestroyAggregateDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster}; + UInt32 size; + OSStatus rv = AudioObjectGetPropertyDataSize(plugin_id, + &destroy_aggregate_device_addr, + 0, + NULL, + &size); + if (rv != noErr) { + LOG("AudioObjectGetPropertyDataSize/kAudioPlugInDestroyAggregateDevice, rv=%d", rv); + return CUBEB_ERROR; + } + + rv = AudioObjectGetPropertyData(plugin_id, + &destroy_aggregate_device_addr, + 0, + NULL, + &size, + aggregate_device_id); + if (rv != noErr) { + LOG("AudioObjectGetPropertyData/kAudioPlugInDestroyAggregateDevice, rv=%d", rv); + return CUBEB_ERROR; + } + + LOG("Destroyed aggregate device %d", *aggregate_device_id); + *aggregate_device_id = kAudioObjectUnknown; return CUBEB_OK; } static int -audiounit_create_unit(AudioUnit * unit, - bool is_input, - const cubeb_stream_params * /* stream_params */, - cubeb_devid device) +audiounit_new_unit_instance(AudioUnit * unit, device_info * device) { AudioComponentDescription desc; AudioComponent comp; - UInt32 enable; - AudioDeviceID devid; OSStatus rv; desc.componentType = kAudioUnitType_Output; #if TARGET_OS_IPHONE - bool use_default_output = false; desc.componentSubType = kAudioUnitSubType_RemoteIO; #else // Use the DefaultOutputUnit for output when no device is specified // so we retain automatic output device switching when the default // changes. Once we have complete support for device notifications // and switching, we can use the AUHAL for everything. - bool use_default_output = device == NULL && !is_input; - if (use_default_output) { + if ((device->flags & DEV_SYSTEM_DEFAULT) && + (device->flags & DEV_OUTPUT)) { desc.componentSubType = kAudioUnitSubType_DefaultOutput; } else { desc.componentSubType = kAudioUnitSubType_HALOutput; @@ -1144,96 +2020,118 @@ audiounit_create_unit(AudioUnit * unit, rv = AudioComponentInstanceNew(comp, unit); if (rv != noErr) { - PRINT_ERROR_CODE("AudioComponentInstanceNew", rv); + LOG("AudioComponentInstanceNew rv=%d", rv); return CUBEB_ERROR; } + return CUBEB_OK; +} - if (!use_default_output) { - enable = 1; - rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO, - is_input ? kAudioUnitScope_Input : kAudioUnitScope_Output, - is_input ? AU_IN_BUS : AU_OUT_BUS, &enable, sizeof(UInt32)); - if (rv != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO", rv); - return CUBEB_ERROR; - } - - enable = 0; - rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO, - is_input ? kAudioUnitScope_Output : kAudioUnitScope_Input, - is_input ? AU_OUT_BUS : AU_IN_BUS, &enable, sizeof(UInt32)); - if (rv != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO", rv); - return CUBEB_ERROR; - } +enum enable_state { + DISABLE, + ENABLE, +}; - if (device == NULL) { - assert(is_input); - devid = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); - } else { - devid = reinterpret_cast(device); - } - rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, - is_input ? AU_IN_BUS : AU_OUT_BUS, - &devid, sizeof(AudioDeviceID)); - if (rv != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice", rv); - return CUBEB_ERROR; - } +static int +audiounit_enable_unit_scope(AudioUnit * unit, io_side side, enable_state state) +{ + OSStatus rv; + UInt32 enable = state; + rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO, + (side == io_side::INPUT) ? kAudioUnitScope_Input : kAudioUnitScope_Output, + (side == io_side::INPUT) ? AU_IN_BUS : AU_OUT_BUS, + &enable, + sizeof(UInt32)); + if (rv != noErr) { + LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO rv=%d", rv); + return CUBEB_ERROR; } - return CUBEB_OK; } static int -audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity) +audiounit_create_unit(AudioUnit * unit, device_info * device) { - if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) { - stream->input_linear_buffer = new auto_array_wrapper( - new auto_array(capacity * - stream->input_buffer_frames * - stream->input_desc.mChannelsPerFrame) ); - } else { - stream->input_linear_buffer = new auto_array_wrapper( - new auto_array(capacity * - stream->input_buffer_frames * - stream->input_desc.mChannelsPerFrame) ); + assert(*unit == nullptr); + assert(device); + + OSStatus rv; + int r; + + r = audiounit_new_unit_instance(unit, device); + if (r != CUBEB_OK) { + return r; } + assert(*unit); - if (!stream->input_linear_buffer) { - return CUBEB_ERROR; + if ((device->flags & DEV_SYSTEM_DEFAULT) && + (device->flags & DEV_OUTPUT)) { + return CUBEB_OK; } - assert(stream->input_linear_buffer->length() == 0); - // Pre-buffer silence if needed - if (capacity != 1) { - size_t silence_size = stream->input_buffer_frames * - stream->input_desc.mChannelsPerFrame; - stream->input_linear_buffer->push_silence(silence_size); + if (device->flags & DEV_INPUT) { + r = audiounit_enable_unit_scope(unit, io_side::INPUT, ENABLE); + if (r != CUBEB_OK) { + LOG("Failed to enable audiounit input scope"); + return r; + } + r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, DISABLE); + if (r != CUBEB_OK) { + LOG("Failed to disable audiounit output scope"); + return r; + } + } else if (device->flags & DEV_OUTPUT) { + r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, ENABLE); + if (r != CUBEB_OK) { + LOG("Failed to enable audiounit output scope"); + return r; + } + r = audiounit_enable_unit_scope(unit, io_side::INPUT, DISABLE); + if (r != CUBEB_OK) { + LOG("Failed to disable audiounit input scope"); + return r; + } + } else { + assert(false); + } - assert(stream->input_linear_buffer->length() == silence_size); + rv = AudioUnitSetProperty(*unit, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &device->id, sizeof(AudioDeviceID)); + if (rv != noErr) { + LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice rv=%d", rv); + return CUBEB_ERROR; } return CUBEB_OK; } -static void -audiounit_destroy_input_linear_buffer(cubeb_stream * stream) +static int +audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity) { - delete stream->input_linear_buffer; + uint32_t size = capacity * stream->latency_frames * stream->input_desc.mChannelsPerFrame; + if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) { + stream->input_linear_buffer.reset(new auto_array_wrapper_impl(size)); + } else { + stream->input_linear_buffer.reset(new auto_array_wrapper_impl(size)); + } + assert(stream->input_linear_buffer->length() == 0); + + return CUBEB_OK; } static uint32_t audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) { // For the 1st stream set anything within safe min-max - assert(stm->context->active_streams > 0); - if (stm->context->active_streams == 1) { - return std::max(std::min(latency_frames, SAFE_MAX_LATENCY_FRAMES), + assert(audiounit_active_streams(stm->context) > 0); + if (audiounit_active_streams(stm->context) == 1) { + return max(min(latency_frames, SAFE_MAX_LATENCY_FRAMES), SAFE_MIN_LATENCY_FRAMES); } + assert(stm->output_unit); // If more than one stream operates in parallel // allow only lower values of latency @@ -1248,11 +2146,11 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) &output_buffer_size, &size); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r); + LOG("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize rv=%d", r); return 0; } - output_buffer_size = std::max(std::min(output_buffer_size, SAFE_MAX_LATENCY_FRAMES), + output_buffer_size = max(min(output_buffer_size, SAFE_MAX_LATENCY_FRAMES), SAFE_MIN_LATENCY_FRAMES); } @@ -1265,18 +2163,18 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) &input_buffer_size, &size); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r); + LOG("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize rv=%d", r); return 0; } - input_buffer_size = std::max(std::min(input_buffer_size, SAFE_MAX_LATENCY_FRAMES), + input_buffer_size = max(min(input_buffer_size, SAFE_MAX_LATENCY_FRAMES), SAFE_MIN_LATENCY_FRAMES); } // Every following active streams can only set smaller latency UInt32 upper_latency_limit = 0; if (input_buffer_size != 0 && output_buffer_size != 0) { - upper_latency_limit = std::min(input_buffer_size, output_buffer_size); + upper_latency_limit = min(input_buffer_size, output_buffer_size); } else if (input_buffer_size != 0) { upper_latency_limit = input_buffer_size; } else if (output_buffer_size != 0) { @@ -1285,7 +2183,7 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) upper_latency_limit = SAFE_MAX_LATENCY_FRAMES; } - return std::max(std::min(latency_frames, upper_latency_limit), + return max(min(latency_frames, upper_latency_limit), SAFE_MIN_LATENCY_FRAMES); } @@ -1300,18 +2198,18 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) static void buffer_size_changed_callback(void * inClientData, AudioUnit inUnit, - AudioUnitPropertyID inPropertyID, - AudioUnitScope inScope, - AudioUnitElement inElement) + AudioUnitPropertyID inPropertyID, + AudioUnitScope inScope, + AudioUnitElement inElement) { cubeb_stream * stm = (cubeb_stream *)inClientData; AudioUnit au = inUnit; AudioUnitScope au_scope = kAudioUnitScope_Input; AudioUnitElement au_element = inElement; - const char * au_type = "output"; + char const * au_type = "output"; - if (au == stm->input_unit) { + if (AU_IN_BUS == inElement) { au_scope = kAudioUnitScope_Output; au_type = "input"; } @@ -1342,24 +2240,17 @@ buffer_size_changed_callback(void * inClientData, } } -enum set_buffer_size_side { - INPUT, - OUTPUT, -}; - static int -audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side) +audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, io_side side) { AudioUnit au = stm->output_unit; AudioUnitScope au_scope = kAudioUnitScope_Input; AudioUnitElement au_element = AU_OUT_BUS; - const char * au_type = "output"; - if (set_side == INPUT) { + if (side == io_side::INPUT) { au = stm->input_unit; au_scope = kAudioUnitScope_Output; au_element = AU_IN_BUS; - au_type = "input"; } uint32_t buffer_frames = 0; @@ -1371,16 +2262,12 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buff &buffer_frames, &size); if (r != noErr) { - if (set_side == INPUT) { - PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r); - } else { - PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r); - } + LOG("AudioUnitGetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r); return CUBEB_ERROR; } if (new_size_frames == buffer_frames) { - LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames); + LOG("(%p) No need to update %s buffer size already %u frames", stm, to_string(side), buffer_frames); return CUBEB_OK; } @@ -1389,11 +2276,7 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buff buffer_size_changed_callback, stm); if (r != noErr) { - if (set_side == INPUT) { - PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); - } else { - PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); - } + LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r); return CUBEB_ERROR; } @@ -1406,22 +2289,14 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buff &new_size_frames, sizeof(new_size_frames)); if (r != noErr) { - if (set_side == INPUT) { - PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r); - } else { - PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r); - } + LOG("AudioUnitSetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r); r = AudioUnitRemovePropertyListenerWithUserData(au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback, stm); if (r != noErr) { - if (set_side == INPUT) { - PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); - } else { - PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); - } + LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r); } return CUBEB_ERROR; @@ -1443,11 +2318,7 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buff buffer_size_changed_callback, stm); if (r != noErr) { - if (set_side == INPUT) { - PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); - } else { - PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); - } + LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r); return CUBEB_ERROR; } @@ -1456,13 +2327,15 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buff return CUBEB_ERROR; } - LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames); + LOG("(%p) %s buffer size changed to %u frames.", stm, to_string(side), new_size_frames); return CUBEB_OK; } static int audiounit_configure_input(cubeb_stream * stm) { + assert(stm && stm->input_unit); + int r = 0; UInt32 size; AURenderCallbackStruct aurcbs_in; @@ -1481,7 +2354,7 @@ audiounit_configure_input(cubeb_stream * stm) &input_hw_desc, &size); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r); + LOG("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r); return CUBEB_ERROR; } stm->input_hw_rate = input_hw_desc.mSampleRate; @@ -1495,8 +2368,7 @@ audiounit_configure_input(cubeb_stream * stm) } // Use latency to set buffer size - stm->input_buffer_frames = stm->latency_frames; - r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT); + r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::INPUT); if (r != CUBEB_OK) { LOG("(%p) Error in change input buffer size.", stm); return CUBEB_ERROR; @@ -1514,7 +2386,7 @@ audiounit_configure_input(cubeb_stream * stm) &src_desc, sizeof(AudioStreamBasicDescription)); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r); + LOG("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r); return CUBEB_ERROR; } @@ -1523,10 +2395,10 @@ audiounit_configure_input(cubeb_stream * stm) kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, AU_IN_BUS, - &stm->input_buffer_frames, + &stm->latency_frames, sizeof(UInt32)); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r); + LOG("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice rv=%d", r); return CUBEB_ERROR; } @@ -1540,7 +2412,6 @@ audiounit_configure_input(cubeb_stream * stm) return CUBEB_ERROR; } - assert(stm->input_unit != NULL); aurcbs_in.inputProc = audiounit_input_callback; aurcbs_in.inputProcRefCon = stm; @@ -1551,9 +2422,12 @@ audiounit_configure_input(cubeb_stream * stm) &aurcbs_in, sizeof(aurcbs_in)); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r); + LOG("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback rv=%d", r); return CUBEB_ERROR; } + + stm->frames_read = 0; + LOG("(%p) Input audiounit init successfully.", stm); return CUBEB_OK; @@ -1562,6 +2436,8 @@ audiounit_configure_input(cubeb_stream * stm) static int audiounit_configure_output(cubeb_stream * stm) { + assert(stm && stm->output_unit); + int r; AURenderCallbackStruct aurcbs_out; UInt32 size; @@ -1588,11 +2464,30 @@ audiounit_configure_output(cubeb_stream * stm) &output_hw_desc, &size); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r); + LOG("AudioUnitGetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r); return CUBEB_ERROR; } stm->output_hw_rate = output_hw_desc.mSampleRate; LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate); + stm->context->channels = output_hw_desc.mChannelsPerFrame; + + // Set the input layout to match the output device layout. + audiounit_layout_init(stm, io_side::OUTPUT); + if (stm->context->channels != stm->output_stream_params.channels || + stm->context->layout != stm->output_stream_params.layout) { + LOG("Incompatible channel layouts detected, setting up remixer"); + audiounit_init_mixer(stm); + // We will be remixing the data before it reaches the output device. + // We need to adjust the number of channels and other + // AudioStreamDescription details. + stm->output_desc.mChannelsPerFrame = stm->context->channels; + stm->output_desc.mBytesPerFrame = (stm->output_desc.mBitsPerChannel / 8) * + stm->output_desc.mChannelsPerFrame; + stm->output_desc.mBytesPerPacket = + stm->output_desc.mBytesPerFrame * stm->output_desc.mFramesPerPacket; + } else { + stm->mixer = nullptr; + } r = AudioUnitSetProperty(stm->output_unit, kAudioUnitProperty_StreamFormat, @@ -1601,11 +2496,11 @@ audiounit_configure_output(cubeb_stream * stm) &stm->output_desc, sizeof(AudioStreamBasicDescription)); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r); + LOG("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r); return CUBEB_ERROR; } - r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT); + r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::OUTPUT); if (r != CUBEB_OK) { LOG("(%p) Error in change output buffer size.", stm); return CUBEB_ERROR; @@ -1619,11 +2514,10 @@ audiounit_configure_output(cubeb_stream * stm) &stm->latency_frames, sizeof(UInt32)); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r); + LOG("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice rv=%d", r); return CUBEB_ERROR; } - assert(stm->output_unit != NULL); aurcbs_out.inputProc = audiounit_output_callback; aurcbs_out.inputProcRefCon = stm; r = AudioUnitSetProperty(stm->output_unit, @@ -1633,10 +2527,12 @@ audiounit_configure_output(cubeb_stream * stm) &aurcbs_out, sizeof(aurcbs_out)); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r); + LOG("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback rv=%d", r); return CUBEB_ERROR; } + stm->frames_written = 0; + LOG("(%p) Output audiounit init successfully.", stm); return CUBEB_OK; } @@ -1646,11 +2542,37 @@ audiounit_setup_stream(cubeb_stream * stm) { stm->mutex.assert_current_thread_owns(); + if ((stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) || + (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK)) { + LOG("(%p) Loopback not supported for audiounit.", stm); + return CUBEB_ERROR_NOT_SUPPORTED; + } + int r = 0; + + device_info in_dev_info = stm->input_device; + device_info out_dev_info = stm->output_device; + + if (has_input(stm) && has_output(stm) && + stm->input_device.id != stm->output_device.id) { + r = audiounit_create_aggregate_device(stm); + if (r != CUBEB_OK) { + stm->aggregate_device_id = kAudioObjectUnknown; + LOG("(%p) Create aggregate devices failed.", stm); + // !!!NOTE: It is not necessary to return here. If it does not + // return it will fallback to the old implementation. The intention + // is to investigate how often it fails. I plan to remove + // it after a couple of weeks. + return r; + } else { + in_dev_info.id = out_dev_info.id = stm->aggregate_device_id; + in_dev_info.flags = DEV_INPUT; + out_dev_info.flags = DEV_OUTPUT; + } + } + if (has_input(stm)) { - r = audiounit_create_unit(&stm->input_unit, true, - &stm->input_stream_params, - stm->input_device); + r = audiounit_create_unit(&stm->input_unit, &in_dev_info); if (r != CUBEB_OK) { LOG("(%p) AudioUnit creation for input failed.", stm); return r; @@ -1658,9 +2580,7 @@ audiounit_setup_stream(cubeb_stream * stm) } if (has_output(stm)) { - r = audiounit_create_unit(&stm->output_unit, false, - &stm->output_stream_params, - stm->output_device); + r = audiounit_create_unit(&stm->output_unit, &out_dev_info); if (r != CUBEB_OK) { LOG("(%p) AudioUnit creation for output failed.", stm); return r; @@ -1668,20 +2588,20 @@ audiounit_setup_stream(cubeb_stream * stm) } /* Latency cannot change if another stream is operating in parallel. In this case - * latecy is set to the other stream value. */ - if (stm->context->active_streams > 1) { + * latency is set to the other stream value. */ + if (audiounit_active_streams(stm->context) > 1) { LOG("(%p) More than one active stream, use global latency.", stm); stm->latency_frames = stm->context->global_latency_frames; } else { /* Silently clamp the latency down to the platform default, because we - * synthetize the clock from the callbacks, and we want the clock to update - * often. */ + * synthetize the clock from the callbacks, and we want the clock to update + * often. */ stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames); - assert(stm->latency_frames); // Ungly error check - audiounit_set_global_latency(stm, stm->latency_frames); + assert(stm->latency_frames); // Ugly error check + audiounit_set_global_latency(stm->context, stm->latency_frames); } - /* Setup Input Stream! */ + /* Configure I/O stream */ if (has_input(stm)) { r = audiounit_configure_input(stm); if (r != CUBEB_OK) { @@ -1690,7 +2610,6 @@ audiounit_setup_stream(cubeb_stream * stm) } } - /* Setup Output Stream! */ if (has_output(stm)) { r = audiounit_configure_output(stm); if (r != CUBEB_OK) { @@ -1762,13 +2681,13 @@ audiounit_setup_stream(cubeb_stream * stm) /* Create resampler. Output params are unchanged * because we do not need conversion on the output. */ - stm->resampler = cubeb_resampler_create(stm, - has_input(stm) ? &input_unconverted_params : NULL, - has_output(stm) ? &stm->output_stream_params : NULL, - target_sample_rate, - stm->data_callback, - stm->user_ptr, - CUBEB_RESAMPLER_QUALITY_DESKTOP); + stm->resampler.reset(cubeb_resampler_create(stm, + has_input(stm) ? &input_unconverted_params : NULL, + has_output(stm) ? &stm->output_stream_params : NULL, + target_sample_rate, + stm->data_callback, + stm->user_ptr, + CUBEB_RESAMPLER_QUALITY_DESKTOP)); if (!stm->resampler) { LOG("(%p) Could not create resampler.", stm); return CUBEB_ERROR; @@ -1777,7 +2696,7 @@ audiounit_setup_stream(cubeb_stream * stm) if (stm->input_unit != NULL) { r = AudioUnitInitialize(stm->input_unit); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitInitialize/input", r); + LOG("AudioUnitInitialize/input rv=%d", r); return CUBEB_ERROR; } } @@ -1785,9 +2704,17 @@ audiounit_setup_stream(cubeb_stream * stm) if (stm->output_unit != NULL) { r = AudioUnitInitialize(stm->output_unit); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitInitialize/output", r); + LOG("AudioUnitInitialize/output rv=%d", r); return CUBEB_ERROR; } + + stm->current_latency_frames = audiounit_get_device_presentation_latency(stm->output_device.id, kAudioDevicePropertyScopeOutput); + + Float64 unit_s; + UInt32 size = sizeof(unit_s); + if (AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &unit_s, &size) == noErr) { + stm->current_latency_frames += static_cast(unit_s * stm->output_desc.mSampleRate); + } } if (stm->input_unit && stm->output_unit) { @@ -1799,13 +2726,24 @@ audiounit_setup_stream(cubeb_stream * stm) r = audiounit_install_device_changed_callback(stm); if (r != CUBEB_OK) { - LOG("(%p) Could not install the device change callback.", stm); - return r; + LOG("(%p) Could not install all device change callback.", stm); } + return CUBEB_OK; } +cubeb_stream::cubeb_stream(cubeb * context) + : context(context) + , resampler(nullptr, cubeb_resampler_destroy) + , mixer(nullptr, cubeb_mixer_destroy) +{ + PodZero(&input_desc, 1); + PodZero(&output_desc, 1); +} + +static void audiounit_stream_destroy_internal(cubeb_stream * stm); + static int audiounit_stream_init(cubeb * context, cubeb_stream ** stream, @@ -1819,72 +2757,64 @@ audiounit_stream_init(cubeb * context, cubeb_state_callback state_callback, void * user_ptr) { - cubeb_stream * stm; - int r; - assert(context); + auto_lock context_lock(context->mutex); + audiounit_increment_active_streams(context); + unique_ptr stm(new cubeb_stream(context), + audiounit_stream_destroy_internal); + int r; *stream = NULL; - assert(latency_frames > 0); - if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) { - LOG("Reached the stream limit of %d", CUBEB_STREAM_MAX); - return CUBEB_ERROR; - } - - stm = (cubeb_stream *) calloc(1, sizeof(cubeb_stream)); - assert(stm); - // Placement new to call the ctors of cubeb_stream members. - new (stm) cubeb_stream(); /* These could be different in the future if we have both * full-duplex stream and different devices for input vs output. */ - stm->context = context; stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; stm->latency_frames = latency_frames; - stm->device_changed_callback = NULL; + + if ((input_device && !input_stream_params) || + (output_device && !output_stream_params)) { + return CUBEB_ERROR_INVALID_PARAMETER; + } if (input_stream_params) { stm->input_stream_params = *input_stream_params; - stm->input_device = input_device; - stm->is_default_input = input_device == nullptr || - (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) == - reinterpret_cast(input_device)); + r = audiounit_set_device_info(stm.get(), reinterpret_cast(input_device), io_side::INPUT); + if (r != CUBEB_OK) { + LOG("(%p) Fail to set device info for input.", stm.get()); + return r; + } } if (output_stream_params) { stm->output_stream_params = *output_stream_params; - stm->output_device = output_device; + r = audiounit_set_device_info(stm.get(), reinterpret_cast(output_device), io_side::OUTPUT); + if (r != CUBEB_OK) { + LOG("(%p) Fail to set device info for output.", stm.get()); + return r; + } } - /* Init data members where necessary */ - stm->hw_latency_frames = UINT64_MAX; - - stm->switching_device = false; - - auto_lock context_lock(context->mutex); { // It's not critical to lock here, because no other thread has been started // yet, but it allows to assert that the lock has been taken in // `audiounit_setup_stream`. - context->active_streams += 1; auto_lock lock(stm->mutex); - r = audiounit_setup_stream(stm); + r = audiounit_setup_stream(stm.get()); } if (r != CUBEB_OK) { - LOG("(%p) Could not setup the audiounit stream.", stm); - audiounit_stream_destroy(stm); + LOG("(%p) Could not setup the audiounit stream.", stm.get()); return r; } - r = audiounit_install_system_changed_callback(stm); + r = audiounit_install_system_changed_callback(stm.get()); if (r != CUBEB_OK) { - LOG("(%p) Could not install the device change callback.", stm); + LOG("(%p) Could not install the device change callback.", stm.get()); return r; } - *stream = stm; - LOG("Cubeb stream (%p) init successful.", stm); + *stream = stm.release(); + LOG("(%p) Cubeb stream init successful.", *stream); return CUBEB_OK; } @@ -1896,64 +2826,86 @@ audiounit_close_stream(cubeb_stream *stm) if (stm->input_unit) { AudioUnitUninitialize(stm->input_unit); AudioComponentInstanceDispose(stm->input_unit); + stm->input_unit = nullptr; } - audiounit_destroy_input_linear_buffer(stm); + stm->input_linear_buffer.reset(); if (stm->output_unit) { AudioUnitUninitialize(stm->output_unit); AudioComponentInstanceDispose(stm->output_unit); + stm->output_unit = nullptr; } - cubeb_resampler_destroy(stm->resampler); + stm->resampler.reset(); + stm->mixer.reset(); + + if (stm->aggregate_device_id != kAudioObjectUnknown) { + audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); + stm->aggregate_device_id = kAudioObjectUnknown; + } } static void -audiounit_stream_destroy(cubeb_stream * stm) +audiounit_stream_destroy_internal(cubeb_stream *stm) { - stm->shutdown = true; + stm->context->mutex.assert_current_thread_owns(); int r = audiounit_uninstall_system_changed_callback(stm); if (r != CUBEB_OK) { LOG("(%p) Could not uninstall the device changed callback", stm); } - r = audiounit_uninstall_device_changed_callback(stm); if (r != CUBEB_OK) { - LOG("(%p) Could not uninstall the device changed callback", stm); + LOG("(%p) Could not uninstall all device change listeners", stm); } - auto_lock context_lock(stm->context->mutex); - audiounit_stream_stop_internal(stm); + auto_lock lock(stm->mutex); + audiounit_close_stream(stm); + assert(audiounit_active_streams(stm->context) >= 1); + audiounit_decrement_active_streams(stm->context); +} + +static void +audiounit_stream_destroy(cubeb_stream * stm) +{ + if (!stm->shutdown.load()){ + auto_lock context_lock(stm->context->mutex); + audiounit_stream_stop_internal(stm); + stm->shutdown = true; + } + stm->destroy_pending = true; // Execute close in serial queue to avoid collision // with reinit when un/plug devices dispatch_sync(stm->context->serial_queue, ^() { - auto_lock lock(stm->mutex); - audiounit_close_stream(stm); + auto_lock context_lock(stm->context->mutex); + audiounit_stream_destroy_internal(stm); }); - assert(stm->context->active_streams >= 1); - stm->context->active_streams -= 1; - LOG("Cubeb stream (%p) destroyed successful.", stm); - - stm->~cubeb_stream(); - free(stm); + delete stm; } -void +static int audiounit_stream_start_internal(cubeb_stream * stm) { OSStatus r; if (stm->input_unit != NULL) { r = AudioOutputUnitStart(stm->input_unit); - assert(r == 0); + if (r != noErr) { + LOG("AudioOutputUnitStart (input) rv=%d", r); + return CUBEB_ERROR; + } } if (stm->output_unit != NULL) { r = AudioOutputUnitStart(stm->output_unit); - assert(r == 0); + if (r != noErr) { + LOG("AudioOutputUnitStart (output) rv=%d", r); + return CUBEB_ERROR; + } } + return CUBEB_OK; } static int @@ -1963,7 +2915,10 @@ audiounit_stream_start(cubeb_stream * stm) stm->shutdown = false; stm->draining = false; - audiounit_stream_start_internal(stm); + int r = audiounit_stream_start_internal(stm); + if (r != CUBEB_OK) { + return r; + } stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); @@ -2002,9 +2957,12 @@ audiounit_stream_stop(cubeb_stream * stm) static int audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position) { - auto_lock lock(stm->mutex); - - *position = stm->frames_played; + assert(stm); + if (stm->current_latency_frames > stm->frames_played) { + *position = 0; + } else { + *position = stm->frames_played - stm->current_latency_frames; + } return CUBEB_OK; } @@ -2015,74 +2973,7 @@ audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency) //TODO return CUBEB_ERROR_NOT_SUPPORTED; #else - auto_lock lock(stm->mutex); - if (stm->hw_latency_frames == UINT64_MAX) { - UInt32 size; - uint32_t device_latency_frames, device_safety_offset; - double unit_latency_sec; - AudioDeviceID output_device_id; - OSStatus r; - AudioObjectPropertyAddress latency_address = { - kAudioDevicePropertyLatency, - kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMaster - }; - AudioObjectPropertyAddress safety_offset_address = { - kAudioDevicePropertySafetyOffset, - kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMaster - }; - - r = audiounit_get_output_device_id(&output_device_id); - if (r != noErr) { - return CUBEB_ERROR; - } - - size = sizeof(unit_latency_sec); - r = AudioUnitGetProperty(stm->output_unit, - kAudioUnitProperty_Latency, - kAudioUnitScope_Global, - 0, - &unit_latency_sec, - &size); - if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitGetProperty/kAudioUnitProperty_Latency", r); - return CUBEB_ERROR; - } - - size = sizeof(device_latency_frames); - r = AudioObjectGetPropertyData(output_device_id, - &latency_address, - 0, - NULL, - &size, - &device_latency_frames); - if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitGetPropertyData/latency_frames", r); - return CUBEB_ERROR; - } - - size = sizeof(device_safety_offset); - r = AudioObjectGetPropertyData(output_device_id, - &safety_offset_address, - 0, - NULL, - &size, - &device_safety_offset); - if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitGetPropertyData/safety_offset", r); - return CUBEB_ERROR; - } - - /* This part is fixed and depend on the stream parameter and the hardware. */ - stm->hw_latency_frames = - static_cast(unit_latency_sec * stm->output_desc.mSampleRate) - + device_latency_frames - + device_safety_offset; - } - - *latency = stm->hw_latency_frames + stm->current_latency_frames; - + *latency = stm->total_output_latency_frames; return CUBEB_OK; #endif } @@ -2102,119 +2993,110 @@ audiounit_stream_get_volume(cubeb_stream * stm, float * volume) return CUBEB_OK; } -int audiounit_stream_set_volume(cubeb_stream * stm, float volume) +static int +audiounit_stream_set_volume(cubeb_stream * stm, float volume) { + assert(stm->output_unit); OSStatus r; - r = AudioUnitSetParameter(stm->output_unit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, volume, 0); if (r != noErr) { - PRINT_ERROR_CODE("AudioUnitSetParameter/kHALOutputParam_Volume", r); + LOG("AudioUnitSetParameter/kHALOutputParam_Volume rv=%d", r); return CUBEB_ERROR; } return CUBEB_OK; } -int audiounit_stream_set_panning(cubeb_stream * stm, float panning) +unique_ptr convert_uint32_into_string(UInt32 data) { - if (stm->output_desc.mChannelsPerFrame > 2) { - return CUBEB_ERROR_INVALID_PARAMETER; + // Simply create an empty string if no data. + size_t size = data == 0 ? 0 : 4; // 4 bytes for uint32. + auto str = unique_ptr { new char[size + 1] }; // + 1 for '\0'. + str[size] = '\0'; + if (size < 4) { + return str; } - stm->panning.store(panning, std::memory_order_relaxed); - return CUBEB_OK; + // Reverse 0xWXYZ into 0xZYXW. + str[0] = (char)(data >> 24); + str[1] = (char)(data >> 16); + str[2] = (char)(data >> 8); + str[3] = (char)(data); + return str; } -int audiounit_stream_get_current_device(cubeb_stream * stm, - cubeb_device ** const device) +int audiounit_get_default_device_datasource(cubeb_device_type type, + UInt32 * data) { -#if TARGET_OS_IPHONE - //TODO - return CUBEB_ERROR_NOT_SUPPORTED; -#else - OSStatus r; - UInt32 size; - UInt32 data; - char strdata[4]; - AudioDeviceID output_device_id; - AudioDeviceID input_device_id; - - AudioObjectPropertyAddress datasource_address = { - kAudioDevicePropertyDataSource, - kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMaster - }; - - AudioObjectPropertyAddress datasource_address_input = { - kAudioDevicePropertyDataSource, - kAudioDevicePropertyScopeInput, - kAudioObjectPropertyElementMaster - }; - - *device = NULL; - - if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { - return CUBEB_ERROR; - } - - *device = new cubeb_device; - if (!*device) { + AudioDeviceID id = audiounit_get_default_device_id(type); + if (id == kAudioObjectUnknown) { return CUBEB_ERROR; } - PodZero(*device, 1); - size = sizeof(UInt32); - /* This fails with some USB headset, so simply return an empty string. */ - r = AudioObjectGetPropertyData(output_device_id, - &datasource_address, - 0, NULL, &size, &data); + UInt32 size = sizeof(*data); + /* This fails with some USB headsets (e.g., Plantronic .Audio 628). */ + OSStatus r = AudioObjectGetPropertyData(id, + type == CUBEB_DEVICE_TYPE_INPUT ? + &INPUT_DATA_SOURCE_PROPERTY_ADDRESS : + &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS, + 0, NULL, &size, data); if (r != noErr) { - size = 0; - data = 0; - } - - (*device)->output_name = new char[size + 1]; - if (!(*device)->output_name) { - return CUBEB_ERROR; + *data = 0; } - // Turn the four chars packed into a uint32 into a string - strdata[0] = (char)(data >> 24); - strdata[1] = (char)(data >> 16); - strdata[2] = (char)(data >> 8); - strdata[3] = (char)(data); + return CUBEB_OK; +} - memcpy((*device)->output_name, strdata, size); - (*device)->output_name[size] = '\0'; +int audiounit_get_default_device_name(cubeb_stream * stm, + cubeb_device * const device, + cubeb_device_type type) +{ + assert(stm); + assert(device); - if (audiounit_get_input_device_id(&input_device_id) != CUBEB_OK) { - return CUBEB_ERROR; + UInt32 data; + int r = audiounit_get_default_device_datasource(type, &data); + if (r != CUBEB_OK) { + return r; } - - size = sizeof(UInt32); - r = AudioObjectGetPropertyData(input_device_id, &datasource_address_input, 0, NULL, &size, &data); - if (r != noErr) { - LOG("(%p) Error when getting device !", stm); - size = 0; - data = 0; + char ** name = type == CUBEB_DEVICE_TYPE_INPUT ? + &device->input_name : &device->output_name; + *name = convert_uint32_into_string(data).release(); + if (!strlen(*name)) { // empty string. + LOG("(%p) name of %s device is empty!", stm, + type == CUBEB_DEVICE_TYPE_INPUT ? "input" : "output"); } + return CUBEB_OK; +} + - (*device)->input_name = new char[size + 1]; - if (!(*device)->input_name) { +int audiounit_stream_get_current_device(cubeb_stream * stm, + cubeb_device ** const device) +{ +#if TARGET_OS_IPHONE + //TODO + return CUBEB_ERROR_NOT_SUPPORTED; +#else + *device = new cubeb_device; + if (!*device) { return CUBEB_ERROR; } + PodZero(*device, 1); - // Turn the four chars packed into a uint32 into a string - strdata[0] = (char)(data >> 24); - strdata[1] = (char)(data >> 16); - strdata[2] = (char)(data >> 8); - strdata[3] = (char)(data); + int r = audiounit_get_default_device_name(stm, *device, + CUBEB_DEVICE_TYPE_OUTPUT); + if (r != CUBEB_OK) { + return r; + } - memcpy((*device)->input_name, strdata, size); - (*device)->input_name[size] = '\0'; + r = audiounit_get_default_device_name(stm, *device, + CUBEB_DEVICE_TYPE_INPUT); + if (r != CUBEB_OK) { + return r; + } return CUBEB_OK; #endif @@ -2232,52 +3114,14 @@ int audiounit_stream_device_destroy(cubeb_stream * /* stream */, int audiounit_stream_register_device_changed_callback(cubeb_stream * stream, cubeb_device_changed_callback device_changed_callback) { + auto_lock dev_cb_lock(stream->device_changed_callback_lock); /* Note: second register without unregister first causes 'nope' error. * Current implementation requires unregister before register a new cb. */ - assert(!stream->device_changed_callback); - - auto_lock lock(stream->mutex); - + assert(!device_changed_callback || !stream->device_changed_callback); stream->device_changed_callback = device_changed_callback; - return CUBEB_OK; } -static OSStatus -audiounit_get_devices(AudioObjectID ** devices, uint32_t * count) -{ - OSStatus ret; - UInt32 size = 0; - AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - - ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size); - if (ret != noErr) { - return ret; - } - - *count = static_cast(size / sizeof(AudioObjectID)); - if (size >= sizeof(AudioObjectID)) { - if (*devices != NULL) { - delete [] (*devices); - } - *devices = new AudioObjectID[*count]; - PodZero(*devices, *count); - - ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, (void *)*devices); - if (ret != noErr) { - delete [] (*devices); - *devices = NULL; - } - } else { - *devices = NULL; - ret = -1; - } - - return ret; -} - static char * audiounit_strref_to_cstr_utf8(CFStringRef strref) { @@ -2288,11 +3132,12 @@ audiounit_strref_to_cstr_utf8(CFStringRef strref) } len = CFStringGetLength(strref); - size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8); - ret = static_cast(malloc(size)); + // Add 1 to size to allow for '\0' termination character. + size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1; + ret = new char[size]; if (!CFStringGetCString(strref, ret, size, kCFStringEncodingUTF8)) { - free(ret); + delete [] ret; ret = NULL; } @@ -2339,19 +3184,18 @@ audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope AudioValueRange range; if (AudioObjectHasProperty(devid, &adr) && AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr) { - uint32_t i, count = size / sizeof(AudioValueRange); - AudioValueRange * ranges = new AudioValueRange[count]; + uint32_t count = size / sizeof(AudioValueRange); + vector ranges(count); range.mMinimum = 9999999999.0; range.mMaximum = 0.0; - if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, ranges) == noErr) { - for (i = 0; i < count; i++) { + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, ranges.data()) == noErr) { + for (uint32_t i = 0; i < count; i++) { if (ranges[i].mMaximum > range.mMaximum) range.mMaximum = ranges[i].mMaximum; if (ranges[i].mMinimum < range.mMinimum) range.mMinimum = ranges[i].mMinimum; } } - delete [] ranges; *max = static_cast(range.mMaximum); *min = static_cast(range.mMinimum); } else { @@ -2364,7 +3208,7 @@ static UInt32 audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectPropertyScope scope) { AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster }; - UInt32 size, dev, stream = 0, offset; + UInt32 size, dev, stream = 0; AudioStreamID sid[1]; adr.mSelector = kAudioDevicePropertyLatency; @@ -2381,216 +3225,249 @@ audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectProper AudioObjectGetPropertyData(sid[0], &adr, 0, NULL, &size, &stream); } - adr.mSelector = kAudioDevicePropertySafetyOffset; - size = sizeof(UInt32); - if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &offset) != noErr) { - offset = 0; - } - - return dev + stream + offset; + return dev + stream; } -static cubeb_device_info * -audiounit_create_device_from_hwdev(AudioObjectID devid, cubeb_device_type type) +static int +audiounit_create_device_from_hwdev(cubeb_device_info * dev_info, AudioObjectID devid, cubeb_device_type type) { AudioObjectPropertyAddress adr = { 0, 0, kAudioObjectPropertyElementMaster }; - UInt32 size, ch, latency; - cubeb_device_info * ret; - CFStringRef str = NULL; - AudioValueRange range; + UInt32 size; if (type == CUBEB_DEVICE_TYPE_OUTPUT) { adr.mScope = kAudioDevicePropertyScopeOutput; } else if (type == CUBEB_DEVICE_TYPE_INPUT) { adr.mScope = kAudioDevicePropertyScopeInput; } else { - return NULL; + return CUBEB_ERROR; } - ch = audiounit_get_channel_count(devid, adr.mScope); + UInt32 ch = audiounit_get_channel_count(devid, adr.mScope); if (ch == 0) { - return NULL; + return CUBEB_ERROR; } - ret = new cubeb_device_info; - PodZero(ret, 1); + PodZero(dev_info, 1); + CFStringRef device_id_str = nullptr; size = sizeof(CFStringRef); adr.mSelector = kAudioDevicePropertyDeviceUID; - if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) { - ret->device_id = audiounit_strref_to_cstr_utf8(str); - ret->devid = (cubeb_devid)(size_t)devid; - ret->group_id = strdup(ret->device_id); - CFRelease(str); + OSStatus ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &device_id_str); + if ( ret == noErr && device_id_str != NULL) { + dev_info->device_id = audiounit_strref_to_cstr_utf8(device_id_str); + static_assert(sizeof(cubeb_devid) >= sizeof(decltype(devid)), "cubeb_devid can't represent devid"); + dev_info->devid = reinterpret_cast(devid); + dev_info->group_id = dev_info->device_id; + CFRelease(device_id_str); } - size = sizeof(CFStringRef); - adr.mSelector = kAudioObjectPropertyName; - if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) { - UInt32 ds; - size = sizeof(UInt32); - adr.mSelector = kAudioDevicePropertyDataSource; - if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds) == noErr) { - CFStringRef dsname; - AudioValueTranslation trl = { &ds, sizeof(ds), &dsname, sizeof(dsname) }; - adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString; - size = sizeof(AudioValueTranslation); - // If there is a datasource for this device, use it instead of the device - // name. - if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl) == noErr) { - CFRelease(str); - str = dsname; - } - } + CFStringRef friendly_name_str = nullptr; + UInt32 ds; + size = sizeof(UInt32); + adr.mSelector = kAudioDevicePropertyDataSource; + ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds); + if (ret == noErr) { + AudioValueTranslation trl = { &ds, sizeof(ds), &friendly_name_str, sizeof(CFStringRef) }; + adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString; + size = sizeof(AudioValueTranslation); + AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl); + } + + // If there is no datasource for this device, fall back to the + // device name. + if (!friendly_name_str) { + size = sizeof(CFStringRef); + adr.mSelector = kAudioObjectPropertyName; + AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &friendly_name_str); + } - ret->friendly_name = audiounit_strref_to_cstr_utf8(str); - CFRelease(str); + if (friendly_name_str) { + dev_info->friendly_name = audiounit_strref_to_cstr_utf8(friendly_name_str); + CFRelease(friendly_name_str); + } else { + // Couldn't get a datasource name nor a device name, return a + // valid string of length 0. + char * fallback_name = new char[1]; + fallback_name[0] = '\0'; + dev_info->friendly_name = fallback_name; } + CFStringRef vendor_name_str = nullptr; size = sizeof(CFStringRef); adr.mSelector = kAudioObjectPropertyManufacturer; - if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) { - ret->vendor_name = audiounit_strref_to_cstr_utf8(str); - CFRelease(str); + ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &vendor_name_str); + if (ret == noErr && vendor_name_str != NULL) { + dev_info->vendor_name = audiounit_strref_to_cstr_utf8(vendor_name_str); + CFRelease(vendor_name_str); } - ret->type = type; - ret->state = CUBEB_DEVICE_STATE_ENABLED; - ret->preferred = (devid == audiounit_get_default_device_id(type)) ? + dev_info->type = type; + dev_info->state = CUBEB_DEVICE_STATE_ENABLED; + dev_info->preferred = (devid == audiounit_get_default_device_id(type)) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; - ret->max_channels = ch; - ret->format = (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */ + dev_info->max_channels = ch; + dev_info->format = (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */ /* kAudioFormatFlagsAudioUnitCanonical is deprecated, prefer floating point */ - ret->default_format = CUBEB_DEVICE_FMT_F32NE; + dev_info->default_format = CUBEB_DEVICE_FMT_F32NE; audiounit_get_available_samplerate(devid, adr.mScope, - &ret->min_rate, &ret->max_rate, &ret->default_rate); + &dev_info->min_rate, &dev_info->max_rate, &dev_info->default_rate); - latency = audiounit_get_device_presentation_latency(devid, adr.mScope); + UInt32 latency = audiounit_get_device_presentation_latency(devid, adr.mScope); + AudioValueRange range; adr.mSelector = kAudioDevicePropertyBufferFrameSizeRange; size = sizeof(AudioValueRange); - if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range) == noErr) { - ret->latency_lo = latency + range.mMinimum; - ret->latency_hi = latency + range.mMaximum; + ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range); + if (ret == noErr) { + dev_info->latency_lo = latency + range.mMinimum; + dev_info->latency_hi = latency + range.mMaximum; } else { - ret->latency_lo = 10 * ret->default_rate / 1000; /* Default to 10ms */ - ret->latency_hi = 100 * ret->default_rate / 1000; /* Default to 100ms */ + dev_info->latency_lo = 10 * dev_info->default_rate / 1000; /* Default to 10ms */ + dev_info->latency_hi = 100 * dev_info->default_rate / 1000; /* Default to 100ms */ } - return ret; + return CUBEB_OK; +} + +bool +is_aggregate_device(cubeb_device_info * device_info) +{ + assert(device_info->friendly_name); + return !strncmp(device_info->friendly_name, PRIVATE_AGGREGATE_DEVICE_NAME, + strlen(PRIVATE_AGGREGATE_DEVICE_NAME)); } static int audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type, - cubeb_device_collection ** collection) + cubeb_device_collection * collection) { - AudioObjectID * hwdevs = NULL; - uint32_t i, hwdevcount = 0; - OSStatus err; + vector input_devs; + vector output_devs; - if ((err = audiounit_get_devices(&hwdevs, &hwdevcount)) != noErr) { - return CUBEB_ERROR; + // Count number of input and output devices. This is not + // necessarily the same as the count of raw devices supported by the + // system since, for example, with Soundflower installed, some + // devices may report as being both input *and* output and cubeb + // separates those into two different devices. + + if (type & CUBEB_DEVICE_TYPE_OUTPUT) { + output_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT); } - *collection = static_cast(malloc(sizeof(cubeb_device_collection) + - sizeof(cubeb_device_info*) * (hwdevcount > 0 ? hwdevcount - 1 : 0))); - (*collection)->count = 0; + if (type & CUBEB_DEVICE_TYPE_INPUT) { + input_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT); + } - if (hwdevcount > 0) { - cubeb_device_info * cur; + auto devices = new cubeb_device_info[output_devs.size() + input_devs.size()]; + collection->count = 0; - if (type & CUBEB_DEVICE_TYPE_OUTPUT) { - for (i = 0; i < hwdevcount; i++) { - if ((cur = audiounit_create_device_from_hwdev(hwdevs[i], CUBEB_DEVICE_TYPE_OUTPUT)) != NULL) - (*collection)->device[(*collection)->count++] = cur; + if (type & CUBEB_DEVICE_TYPE_OUTPUT) { + for (auto dev: output_devs) { + auto device = &devices[collection->count]; + auto err = audiounit_create_device_from_hwdev(device, dev, CUBEB_DEVICE_TYPE_OUTPUT); + if (err != CUBEB_OK || is_aggregate_device(device)) { + continue; } + collection->count += 1; } + } - if (type & CUBEB_DEVICE_TYPE_INPUT) { - for (i = 0; i < hwdevcount; i++) { - if ((cur = audiounit_create_device_from_hwdev(hwdevs[i], CUBEB_DEVICE_TYPE_INPUT)) != NULL) - (*collection)->device[(*collection)->count++] = cur; + if (type & CUBEB_DEVICE_TYPE_INPUT) { + for (auto dev: input_devs) { + auto device = &devices[collection->count]; + auto err = audiounit_create_device_from_hwdev(device, dev, CUBEB_DEVICE_TYPE_INPUT); + if (err != CUBEB_OK || is_aggregate_device(device)) { + continue; } + collection->count += 1; } } - delete [] hwdevs; + if (collection->count > 0) { + collection->device = devices; + } else { + delete [] devices; + collection->device = NULL; + } return CUBEB_OK; } -/* qsort compare method. */ -int compare_devid(const void * a, const void * b) +static void +audiounit_device_destroy(cubeb_device_info * device) { - return (*(AudioObjectID*)a - *(AudioObjectID*)b); + delete [] device->device_id; + delete [] device->friendly_name; + delete [] device->vendor_name; } -static uint32_t -audiounit_get_devices_of_type(cubeb_device_type devtype, AudioObjectID ** devid_array) +static int +audiounit_device_collection_destroy(cubeb * /* context */, + cubeb_device_collection * collection) { - assert(devid_array == NULL || *devid_array == NULL); + for (size_t i = 0; i < collection->count; i++) { + audiounit_device_destroy(&collection->device[i]); + } + delete [] collection->device; + + return CUBEB_OK; +} - AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; +static vector +audiounit_get_devices_of_type(cubeb_device_type devtype) +{ UInt32 size = 0; - OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size); + OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, + &DEVICES_PROPERTY_ADDRESS, 0, + NULL, &size); if (ret != noErr) { - return 0; + return vector(); } - /* Total number of input and output devices. */ - uint32_t count = (uint32_t)(size / sizeof(AudioObjectID)); - - AudioObjectID devices[count]; - ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devices); + vector devices(size / sizeof(AudioObjectID)); + ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &DEVICES_PROPERTY_ADDRESS, 0, NULL, &size, + devices.data()); if (ret != noErr) { - return 0; + return vector(); + } + + // Remove the aggregate device from the list of devices (if any). + for (auto it = devices.begin(); it != devices.end();) { + CFStringRef name = get_device_name(*it); + if (name && CFStringFind(name, CFSTR("CubebAggregateDevice"), 0).location != + kCFNotFound) { + it = devices.erase(it); + } else { + it++; + } + if (name) { + CFRelease(name); + } } + /* Expected sorted but did not find anything in the docs. */ - qsort(devices, count, sizeof(AudioObjectID), compare_devid); + sort(devices.begin(), devices.end(), [](AudioObjectID a, AudioObjectID b) { + return a < b; + }); if (devtype == (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) { - if (devid_array) { - *devid_array = new AudioObjectID[count]; - assert(*devid_array); - memcpy(*devid_array, &devices, count * sizeof(AudioObjectID)); - } - return count; + return devices; } AudioObjectPropertyScope scope = (devtype == CUBEB_DEVICE_TYPE_INPUT) ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; - uint32_t dev_count = 0; - AudioObjectID devices_in_scope[count]; - for(uint32_t i = 0; i < count; ++i) { + vector devices_in_scope; + for (uint32_t i = 0; i < devices.size(); ++i) { /* For device in the given scope channel must be > 0. */ if (audiounit_get_channel_count(devices[i], scope) > 0) { - devices_in_scope[dev_count] = devices[i]; - ++dev_count; + devices_in_scope.push_back(devices[i]); } } - if (devid_array && dev_count > 0) { - *devid_array = new AudioObjectID[dev_count]; - assert(*devid_array); - memcpy(*devid_array, &devices_in_scope, dev_count * sizeof(AudioObjectID)); - } - return dev_count; -} - -static uint32_t -audiounit_equal_arrays(AudioObjectID * left, AudioObjectID * right, uint32_t size) -{ - /* Expected sorted arrays. */ - for (uint32_t i = 0; i < size; ++i) { - if (left[i] != right[i]) { - return 0; - } - } - return 1; + return devices_in_scope; } static OSStatus @@ -2600,33 +3477,32 @@ audiounit_collection_changed_callback(AudioObjectID /* inObjectID */, void * inClientData) { cubeb * context = static_cast(inClientData); - auto_lock lock(context->mutex); - - if (context->collection_changed_callback == NULL) { - /* Listener removed while waiting in mutex, abort. */ - return noErr; - } - /* Differentiate input from output changes. */ - if (context->collection_changed_devtype == CUBEB_DEVICE_TYPE_INPUT || - context->collection_changed_devtype == CUBEB_DEVICE_TYPE_OUTPUT) { - AudioObjectID * devices = NULL; - uint32_t new_number_of_devices = audiounit_get_devices_of_type(context->collection_changed_devtype, &devices); - /* When count is the same examine the devid for the case of coalescing. */ - if (context->devtype_device_count == new_number_of_devices && - audiounit_equal_arrays(devices, context->devtype_device_array, new_number_of_devices)) { - /* Device changed for the other scope, ignore. */ - delete [] devices; - return noErr; + // This can be called from inside an AudioUnit function, dispatch to another queue. + dispatch_async(context->serial_queue, ^() { + auto_lock lock(context->mutex); + if (!context->input_collection_changed_callback && + !context->output_collection_changed_callback) { + /* Listener removed while waiting in mutex, abort. */ + return; } - /* Device on desired scope changed, reset counter and array. */ - context->devtype_device_count = new_number_of_devices; - /* Free the old array before replace. */ - delete [] context->devtype_device_array; - context->devtype_device_array = devices; - } - - context->collection_changed_callback(context, context->collection_changed_user_ptr); + if (context->input_collection_changed_callback) { + vector devices = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT); + /* Elements in the vector expected sorted. */ + if (context->input_device_array != devices) { + context->input_device_array = devices; + context->input_collection_changed_callback(context, context->input_collection_changed_user_ptr); + } + } + if (context->output_collection_changed_callback) { + vector devices = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT); + /* Elements in the vector expected sorted. */ + if (context->output_device_array != devices) { + context->output_device_array = devices; + context->output_collection_changed_callback(context, context->output_collection_changed_user_ptr); + } + } + }); return noErr; } @@ -2636,62 +3512,65 @@ audiounit_add_device_listener(cubeb * context, cubeb_device_collection_changed_callback collection_changed_callback, void * user_ptr) { + context->mutex.assert_current_thread_owns(); + assert(devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)); /* Note: second register without unregister first causes 'nope' error. * Current implementation requires unregister before register a new cb. */ - assert(context->collection_changed_callback == NULL); - - AudioObjectPropertyAddress devAddr; - devAddr.mSelector = kAudioHardwarePropertyDevices; - devAddr.mScope = kAudioObjectPropertyScopeGlobal; - devAddr.mElement = kAudioObjectPropertyElementMaster; - - OSStatus ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject, - &devAddr, - audiounit_collection_changed_callback, - context); - if (ret == noErr) { - /* Expected zero after unregister. */ - assert(context->devtype_device_count == 0); - assert(context->devtype_device_array == NULL); - /* Listener works for input and output. - * When requested one of them we need to differentiate. */ - if (devtype == CUBEB_DEVICE_TYPE_INPUT || - devtype == CUBEB_DEVICE_TYPE_OUTPUT) { - /* Used to differentiate input from output device changes. */ - context->devtype_device_count = audiounit_get_devices_of_type(devtype, &context->devtype_device_array); - } - context->collection_changed_devtype = devtype; - context->collection_changed_callback = collection_changed_callback; - context->collection_changed_user_ptr = user_ptr; + assert((devtype & CUBEB_DEVICE_TYPE_INPUT) && !context->input_collection_changed_callback || + (devtype & CUBEB_DEVICE_TYPE_OUTPUT) && !context->output_collection_changed_callback); + + if (!context->input_collection_changed_callback && + !context->output_collection_changed_callback) { + OSStatus ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject, + &DEVICES_PROPERTY_ADDRESS, + audiounit_collection_changed_callback, + context); + if (ret != noErr) { + return ret; + } } - return ret; + if (devtype & CUBEB_DEVICE_TYPE_INPUT) { + /* Expected empty after unregister. */ + assert(context->input_device_array.empty()); + context->input_device_array = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT); + context->input_collection_changed_callback = collection_changed_callback; + context->input_collection_changed_user_ptr = user_ptr; + } + if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { + /* Expected empty after unregister. */ + assert(context->output_device_array.empty()); + context->output_device_array = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT); + context->output_collection_changed_callback = collection_changed_callback; + context->output_collection_changed_user_ptr = user_ptr; + } + return noErr; } static OSStatus -audiounit_remove_device_listener(cubeb * context) +audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype) { - AudioObjectPropertyAddress devAddr; - devAddr.mSelector = kAudioHardwarePropertyDevices; - devAddr.mScope = kAudioObjectPropertyScopeGlobal; - devAddr.mElement = kAudioObjectPropertyElementMaster; + context->mutex.assert_current_thread_owns(); - /* Note: unregister a non registered cb is not a problem, not checking. */ - OSStatus ret = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, - &devAddr, - audiounit_collection_changed_callback, - context); - if (ret == noErr) { - /* Reset all values. */ - context->collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN; - context->collection_changed_callback = NULL; - context->collection_changed_user_ptr = NULL; - context->devtype_device_count = 0; - if (context->devtype_device_array) { - delete [] context->devtype_device_array; - context->devtype_device_array = NULL; - } + if (devtype & CUBEB_DEVICE_TYPE_INPUT) { + context->input_collection_changed_callback = nullptr; + context->input_collection_changed_user_ptr = nullptr; + context->input_device_array.clear(); } - return ret; + if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { + context->output_collection_changed_callback = nullptr; + context->output_collection_changed_user_ptr = nullptr; + context->output_device_array.clear(); + } + + if (context->input_collection_changed_callback || + context->output_collection_changed_callback) { + return noErr; + } + /* Note: unregister a non registered cb is not a problem, not checking. */ + return AudioObjectRemovePropertyListener(kAudioObjectSystemObject, + &DEVICES_PROPERTY_ADDRESS, + audiounit_collection_changed_callback, + context); } int audiounit_register_device_collection_changed(cubeb * context, @@ -2699,14 +3578,18 @@ int audiounit_register_device_collection_changed(cubeb * context, cubeb_device_collection_changed_callback collection_changed_callback, void * user_ptr) { + if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) { + return CUBEB_ERROR_INVALID_PARAMETER; + } OSStatus ret; auto_lock lock(context->mutex); if (collection_changed_callback) { - ret = audiounit_add_device_listener(context, devtype, + ret = audiounit_add_device_listener(context, + devtype, collection_changed_callback, user_ptr); } else { - ret = audiounit_remove_device_listener(context); + ret = audiounit_remove_device_listener(context, devtype); } return (ret == noErr) ? CUBEB_OK : CUBEB_ERROR; } @@ -2718,15 +3601,16 @@ cubeb_ops const audiounit_ops = { /*.get_min_latency =*/ audiounit_get_min_latency, /*.get_preferred_sample_rate =*/ audiounit_get_preferred_sample_rate, /*.enumerate_devices =*/ audiounit_enumerate_devices, + /*.device_collection_destroy =*/ audiounit_device_collection_destroy, /*.destroy =*/ audiounit_destroy, /*.stream_init =*/ audiounit_stream_init, /*.stream_destroy =*/ audiounit_stream_destroy, /*.stream_start =*/ audiounit_stream_start, /*.stream_stop =*/ audiounit_stream_stop, + /*.stream_reset_default_device =*/ nullptr, /*.stream_get_position =*/ audiounit_stream_get_position, /*.stream_get_latency =*/ audiounit_stream_get_latency, /*.stream_set_volume =*/ audiounit_stream_set_volume, - /*.stream_set_panning =*/ audiounit_stream_set_panning, /*.stream_get_current_device =*/ audiounit_stream_get_current_device, /*.stream_device_destroy =*/ audiounit_stream_device_destroy, /*.stream_register_device_changed_callback =*/ audiounit_stream_register_device_changed_callback, diff --git a/media/libcubeb/src/cubeb_jack.cpp b/media/libcubeb/src/cubeb_jack.cpp index 8f995da66..1ab876c3f 100644 --- a/media/libcubeb/src/cubeb_jack.cpp +++ b/media/libcubeb/src/cubeb_jack.cpp @@ -8,23 +8,20 @@ */ #define _DEFAULT_SOURCE #define _BSD_SOURCE +#ifndef __FreeBSD__ #define _POSIX_SOURCE -#include +#endif #include -#include #include -#include -#include #include #include -#include -#include #include #include #include #include "cubeb/cubeb.h" #include "cubeb-internal.h" #include "cubeb_resampler.h" +#include "cubeb_utils.h" #include #include @@ -98,7 +95,9 @@ static int cbjack_stream_device_destroy(cubeb_stream * stream, cubeb_device * device); static int cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device); static int cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection ** collection); + cubeb_device_collection * collection); +static int cbjack_device_collection_destroy(cubeb * context, + cubeb_device_collection * collection); static int cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, cubeb_devid input_device, cubeb_stream_params * input_stream_params, @@ -121,15 +120,16 @@ static struct cubeb_ops const cbjack_ops = { .get_min_latency = cbjack_get_min_latency, .get_preferred_sample_rate = cbjack_get_preferred_sample_rate, .enumerate_devices = cbjack_enumerate_devices, + .device_collection_destroy = cbjack_device_collection_destroy, .destroy = cbjack_destroy, .stream_init = cbjack_stream_init, .stream_destroy = cbjack_stream_destroy, .stream_start = cbjack_stream_start, .stream_stop = cbjack_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = cbjack_stream_get_position, .stream_get_latency = cbjack_get_latency, .stream_set_volume = cbjack_stream_set_volume, - .stream_set_panning = NULL, .stream_get_current_device = cbjack_stream_get_current_device, .stream_device_destroy = cbjack_stream_device_destroy, .stream_register_device_changed_callback = NULL, @@ -137,7 +137,10 @@ static struct cubeb_ops const cbjack_ops = { }; struct cubeb_stream { + /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; + void * user_ptr; + /**/ /**< Mutex for each stream */ pthread_mutex_t mutex; @@ -147,7 +150,6 @@ struct cubeb_stream { cubeb_data_callback data_callback; cubeb_state_callback state_callback; - void * user_ptr; cubeb_stream_params in_params; cubeb_stream_params out_params; @@ -183,7 +185,6 @@ struct cubeb { cubeb_stream streams[MAX_STREAMS]; unsigned int active_streams; - cubeb_device_info * devinfo[2]; cubeb_device_collection_changed_callback collection_changed_callback; bool active; @@ -210,6 +211,9 @@ load_jack_lib(cubeb * context) # endif #else context->libjack = dlopen("libjack.so.0", RTLD_LAZY); + if (!context->libjack) { + context->libjack = dlopen("libjack.so", RTLD_LAZY); + } #endif if (!context->libjack) { return CUBEB_ERROR; @@ -230,9 +234,10 @@ load_jack_lib(cubeb * context) return CUBEB_OK; } -static void +static int cbjack_connect_ports (cubeb_stream * stream) { + int r = CUBEB_ERROR; const char ** phys_in_ports = api_jack_get_ports (stream->context->jack_client, NULL, NULL, JackPortIsInput @@ -242,7 +247,7 @@ cbjack_connect_ports (cubeb_stream * stream) JackPortIsOutput | JackPortIsPhysical); - if (*phys_in_ports == NULL) { + if (phys_in_ports == NULL || *phys_in_ports == NULL) { goto skipplayback; } @@ -252,9 +257,10 @@ cbjack_connect_ports (cubeb_stream * stream) api_jack_connect (stream->context->jack_client, src_port, phys_in_ports[c]); } + r = CUBEB_OK; skipplayback: - if (*phys_out_ports == NULL) { + if (phys_out_ports == NULL || *phys_out_ports == NULL) { goto end; } // Connect inputs to capture @@ -263,9 +269,15 @@ skipplayback: api_jack_connect (stream->context->jack_client, phys_out_ports[c], src_port); } + r = CUBEB_OK; end: - api_jack_free(phys_out_ports); - api_jack_free(phys_in_ports); + if (phys_out_ports) { + api_jack_free(phys_out_ports); + } + if (phys_in_ports) { + api_jack_free(phys_in_ports); + } + return r; } static int @@ -426,7 +438,6 @@ cbjack_process(jack_nframes_t nframes, void * arg) return 0; } - static void cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, float ** bufs_out, jack_nframes_t nframes) { @@ -439,7 +450,6 @@ cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, fl long done_frames = 0; long input_frames_count = (in != NULL) ? nframes : 0; - done_frames = cubeb_resampler_fill(stream->resampler, inptr, &input_frames_count, @@ -736,6 +746,12 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_ if (input_device || output_device) return CUBEB_ERROR_NOT_SUPPORTED; + // Loopback is unsupported + if ((input_stream_params && (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK)) || + (output_stream_params && (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + *stream = NULL; // Find a free stream. @@ -867,7 +883,11 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_ } } - cbjack_connect_ports(stm); + if (cbjack_connect_ports(stm) != CUBEB_OK) { + pthread_mutex_unlock(&stm->mutex); + cbjack_stream_destroy(stm); + return CUBEB_ERROR; + } *stream = stm; @@ -940,7 +960,6 @@ cbjack_stream_set_volume(cubeb_stream * stm, float volume) return CUBEB_OK; } - static int cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device) { @@ -978,70 +997,77 @@ cbjack_stream_device_destroy(cubeb_stream * /*stream*/, return CUBEB_OK; } +#define JACK_DEFAULT_IN "JACK capture" +#define JACK_DEFAULT_OUT "JACK playback" + static int cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection ** collection) + cubeb_device_collection * collection) { if (!context) return CUBEB_ERROR; uint32_t rate; - uint8_t i = 0; - uint8_t j; cbjack_get_preferred_sample_rate(context, &rate); - const char * j_in = "JACK capture"; - const char * j_out = "JACK playback"; + + cubeb_device_info * devices = new cubeb_device_info[2]; + if (!devices) + return CUBEB_ERROR; + PodZero(devices, 2); + collection->count = 0; if (type & CUBEB_DEVICE_TYPE_OUTPUT) { - context->devinfo[i] = (cubeb_device_info *)malloc(sizeof(cubeb_device_info)); - context->devinfo[i]->device_id = strdup(j_out); - context->devinfo[i]->devid = context->devinfo[i]->device_id; - context->devinfo[i]->friendly_name = strdup(j_out); - context->devinfo[i]->group_id = strdup(j_out); - context->devinfo[i]->vendor_name = strdup(j_out); - context->devinfo[i]->type = CUBEB_DEVICE_TYPE_OUTPUT; - context->devinfo[i]->state = CUBEB_DEVICE_STATE_ENABLED; - context->devinfo[i]->preferred = CUBEB_DEVICE_PREF_ALL; - context->devinfo[i]->format = CUBEB_DEVICE_FMT_F32NE; - context->devinfo[i]->default_format = CUBEB_DEVICE_FMT_F32NE; - context->devinfo[i]->max_channels = MAX_CHANNELS; - context->devinfo[i]->min_rate = rate; - context->devinfo[i]->max_rate = rate; - context->devinfo[i]->default_rate = rate; - context->devinfo[i]->latency_lo = 0; - context->devinfo[i]->latency_hi = 0; - i++; + cubeb_device_info * cur = &devices[collection->count]; + cur->device_id = JACK_DEFAULT_OUT; + cur->devid = (cubeb_devid) cur->device_id; + cur->friendly_name = JACK_DEFAULT_OUT; + cur->group_id = JACK_DEFAULT_OUT; + cur->vendor_name = JACK_DEFAULT_OUT; + cur->type = CUBEB_DEVICE_TYPE_OUTPUT; + cur->state = CUBEB_DEVICE_STATE_ENABLED; + cur->preferred = CUBEB_DEVICE_PREF_ALL; + cur->format = CUBEB_DEVICE_FMT_F32NE; + cur->default_format = CUBEB_DEVICE_FMT_F32NE; + cur->max_channels = MAX_CHANNELS; + cur->min_rate = rate; + cur->max_rate = rate; + cur->default_rate = rate; + cur->latency_lo = 0; + cur->latency_hi = 0; + collection->count +=1 ; } if (type & CUBEB_DEVICE_TYPE_INPUT) { - context->devinfo[i] = (cubeb_device_info *)malloc(sizeof(cubeb_device_info)); - context->devinfo[i]->device_id = strdup(j_in); - context->devinfo[i]->devid = context->devinfo[i]->device_id; - context->devinfo[i]->friendly_name = strdup(j_in); - context->devinfo[i]->group_id = strdup(j_in); - context->devinfo[i]->vendor_name = strdup(j_in); - context->devinfo[i]->type = CUBEB_DEVICE_TYPE_INPUT; - context->devinfo[i]->state = CUBEB_DEVICE_STATE_ENABLED; - context->devinfo[i]->preferred = CUBEB_DEVICE_PREF_ALL; - context->devinfo[i]->format = CUBEB_DEVICE_FMT_F32NE; - context->devinfo[i]->default_format = CUBEB_DEVICE_FMT_F32NE; - context->devinfo[i]->max_channels = MAX_CHANNELS; - context->devinfo[i]->min_rate = rate; - context->devinfo[i]->max_rate = rate; - context->devinfo[i]->default_rate = rate; - context->devinfo[i]->latency_lo = 0; - context->devinfo[i]->latency_hi = 0; - i++; + cubeb_device_info * cur = &devices[collection->count]; + cur->device_id = JACK_DEFAULT_IN; + cur->devid = (cubeb_devid) cur->device_id; + cur->friendly_name = JACK_DEFAULT_IN; + cur->group_id = JACK_DEFAULT_IN; + cur->vendor_name = JACK_DEFAULT_IN; + cur->type = CUBEB_DEVICE_TYPE_INPUT; + cur->state = CUBEB_DEVICE_STATE_ENABLED; + cur->preferred = CUBEB_DEVICE_PREF_ALL; + cur->format = CUBEB_DEVICE_FMT_F32NE; + cur->default_format = CUBEB_DEVICE_FMT_F32NE; + cur->max_channels = MAX_CHANNELS; + cur->min_rate = rate; + cur->max_rate = rate; + cur->default_rate = rate; + cur->latency_lo = 0; + cur->latency_hi = 0; + collection->count += 1; } - *collection = (cubeb_device_collection *) - malloc(sizeof(cubeb_device_collection) + - i * sizeof(cubeb_device_info *)); + collection->device = devices; - (*collection)->count = i; + return CUBEB_OK; +} - for (j = 0; j < i; j++) { - (*collection)->device[j] = context->devinfo[j]; - } +static int +cbjack_device_collection_destroy(cubeb * /*ctx*/, + cubeb_device_collection * collection) +{ + XASSERT(collection); + delete [] collection->device; return CUBEB_OK; } diff --git a/media/libcubeb/src/cubeb_log.cpp b/media/libcubeb/src/cubeb_log.cpp new file mode 100644 index 000000000..54c7f4a15 --- /dev/null +++ b/media/libcubeb/src/cubeb_log.cpp @@ -0,0 +1,144 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#define NOMINMAX + +#include "cubeb_log.h" +#include "cubeb_ringbuffer.h" +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +cubeb_log_level g_cubeb_log_level; +cubeb_log_callback g_cubeb_log_callback; + +/** The maximum size of a log message, after having been formatted. */ +const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256; +/** The maximum number of log messages that can be queued before dropping + * messages. */ +const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40; +/** Number of milliseconds to wait before dequeuing log messages. */ +#define CUBEB_LOG_BATCH_PRINT_INTERVAL_MS 10 + +/** + * This wraps an inline buffer, that represents a log message, that must be + * null-terminated. + * This class should not use system calls or other potentially blocking code. + */ +class cubeb_log_message +{ +public: + cubeb_log_message() + { + *storage = '\0'; + } + cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE]) + { + size_t length = strlen(str); + /* paranoia against malformed message */ + assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE); + if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) { + return; + } + PodCopy(storage, str, length); + storage[length] = '\0'; + } + char const * get() { + return storage; + } +private: + char storage[CUBEB_LOG_MESSAGE_MAX_SIZE]; +}; + +/** Lock-free asynchronous logger, made so that logging from a + * real-time audio callback does not block the audio thread. */ +class cubeb_async_logger +{ +public: + /* This is thread-safe since C++11 */ + static cubeb_async_logger & get() { + static cubeb_async_logger instance; + return instance; + } + void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE]) + { + cubeb_log_message msg(str); + msg_queue.enqueue(msg); + } + void run() + { + std::thread([this]() { + while (true) { + cubeb_log_message msg; + while (msg_queue.dequeue(&msg, 1)) { + LOGV("%s", msg.get()); + } +#ifdef _WIN32 + Sleep(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS); +#else + timespec sleep_duration = sleep_for; + timespec remainder; + do { + if (nanosleep(&sleep_duration, &remainder) == 0 || + errno != EINTR) { + break; + } + sleep_duration = remainder; + } while (remainder.tv_sec || remainder.tv_nsec); +#endif + } + }).detach(); + } + // Tell the underlying queue the producer thread has changed, so it does not + // assert in debug. This should be called with the thread stopped. + void reset_producer_thread() + { + msg_queue.reset_thread_ids(); + } +private: +#ifndef _WIN32 + const struct timespec sleep_for = { + CUBEB_LOG_BATCH_PRINT_INTERVAL_MS/1000, + (CUBEB_LOG_BATCH_PRINT_INTERVAL_MS%1000)*1000*1000 + }; +#endif + cubeb_async_logger() + : msg_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH) + { + run(); + } + /** This is quite a big data structure, but is only instantiated if the + * asynchronous logger is used.*/ + lock_free_queue msg_queue; +}; + + +void cubeb_async_log(char const * fmt, ...) +{ + if (!g_cubeb_log_callback) { + return; + } + // This is going to copy a 256 bytes array around, which is fine. + // We don't want to allocate memory here, because this is made to + // be called from a real-time callback. + va_list args; + va_start(args, fmt); + char msg[CUBEB_LOG_MESSAGE_MAX_SIZE]; + vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args); + cubeb_async_logger::get().push(msg); + va_end(args); +} + +void cubeb_async_log_reset_threads() +{ + if (!g_cubeb_log_callback) { + return; + } + cubeb_async_logger::get().reset_producer_thread(); +} diff --git a/media/libcubeb/src/cubeb_log.h b/media/libcubeb/src/cubeb_log.h index bca98c96f..a79976bb3 100644 --- a/media/libcubeb/src/cubeb_log.h +++ b/media/libcubeb/src/cubeb_log.h @@ -8,6 +8,8 @@ #ifndef CUBEB_LOG #define CUBEB_LOG +#include "cubeb/cubeb.h" + #ifdef __cplusplus extern "C" { #endif @@ -18,8 +20,10 @@ extern "C" { #define PRINTF_FORMAT(fmt, args) #endif -extern cubeb_log_level g_log_level; -extern cubeb_log_callback g_log_callback PRINTF_FORMAT(1, 2); +extern cubeb_log_level g_cubeb_log_level; +extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2); +void cubeb_async_log(const char * fmt, ...); +void cubeb_async_log_reset_threads(); #ifdef __cplusplus } @@ -29,9 +33,15 @@ extern cubeb_log_callback g_log_callback PRINTF_FORMAT(1, 2); #define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) #define LOG_INTERNAL(level, fmt, ...) do { \ - if (g_log_callback && level <= g_log_level) { \ - g_log_callback("%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ + if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \ + g_cubeb_log_callback("%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ } \ } while(0) +/* Asynchronous verbose logging, to log in real-time callbacks. */ +#define ALOGV(fmt, ...) \ +do { \ + cubeb_async_log(fmt, ##__VA_ARGS__); \ +} while(0) + #endif // CUBEB_LOG diff --git a/media/libcubeb/src/cubeb_mixer.cpp b/media/libcubeb/src/cubeb_mixer.cpp new file mode 100644 index 000000000..2ab7f673a --- /dev/null +++ b/media/libcubeb/src/cubeb_mixer.cpp @@ -0,0 +1,663 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + * + * Adapted from code based on libswresample's rematrix.c + */ + +#define NOMINMAX + +#include +#include +#include +#include +#include +#include +#include +#include "cubeb-internal.h" +#include "cubeb_mixer.h" +#include "cubeb_utils.h" + +#ifndef FF_ARRAY_ELEMS +#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0])) +#endif + +#define CHANNELS_MAX 32 +#define FRONT_LEFT 0 +#define FRONT_RIGHT 1 +#define FRONT_CENTER 2 +#define LOW_FREQUENCY 3 +#define BACK_LEFT 4 +#define BACK_RIGHT 5 +#define FRONT_LEFT_OF_CENTER 6 +#define FRONT_RIGHT_OF_CENTER 7 +#define BACK_CENTER 8 +#define SIDE_LEFT 9 +#define SIDE_RIGHT 10 +#define TOP_CENTER 11 +#define TOP_FRONT_LEFT 12 +#define TOP_FRONT_CENTER 13 +#define TOP_FRONT_RIGHT 14 +#define TOP_BACK_LEFT 15 +#define TOP_BACK_CENTER 16 +#define TOP_BACK_RIGHT 17 +#define NUM_NAMED_CHANNELS 18 + +#ifndef M_SQRT1_2 +#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ +#endif +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#endif +#define SQRT3_2 1.22474487139158904909 /* sqrt(3/2) */ + +#define C30DB M_SQRT2 +#define C15DB 1.189207115 +#define C__0DB 1.0 +#define C_15DB 0.840896415 +#define C_30DB M_SQRT1_2 +#define C_45DB 0.594603558 +#define C_60DB 0.5 + +static cubeb_channel_layout +cubeb_channel_layout_check(cubeb_channel_layout l, uint32_t c) +{ + if (l == CUBEB_LAYOUT_UNDEFINED) { + switch (c) { + case 1: return CUBEB_LAYOUT_MONO; + case 2: return CUBEB_LAYOUT_STEREO; + } + } + return l; +} + +unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout x) +{ +#if __GNUC__ || __clang__ + return __builtin_popcount (x); +#else + x -= (x >> 1) & 0x55555555; + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0F0F0F0F; + x += x >> 8; + return (x + (x >> 16)) & 0x3F; +#endif +} + +struct MixerContext { + MixerContext(cubeb_sample_format f, + uint32_t in_channels, + cubeb_channel_layout in, + uint32_t out_channels, + cubeb_channel_layout out) + : _format(f) + , _in_ch_layout(cubeb_channel_layout_check(in, in_channels)) + , _out_ch_layout(cubeb_channel_layout_check(out, out_channels)) + , _in_ch_count(in_channels) + , _out_ch_count(out_channels) + { + if (in_channels != cubeb_channel_layout_nb_channels(in) || + out_channels != cubeb_channel_layout_nb_channels(out)) { + // Mismatch between channels and layout, aborting. + return; + } + _valid = init() >= 0; + } + + static bool even(cubeb_channel_layout layout) + { + if (!layout) { + return true; + } + if (layout & (layout - 1)) { + return true; + } + return false; + } + + // Ensure that the layout is sane (that is have symmetrical left/right + // channels), if not, layout will be treated as mono. + static cubeb_channel_layout clean_layout(cubeb_channel_layout layout) + { + if (layout && layout != CHANNEL_FRONT_LEFT && !(layout & (layout - 1))) { + LOG("Treating layout as mono"); + return CHANNEL_FRONT_CENTER; + } + + return layout; + } + + static bool sane_layout(cubeb_channel_layout layout) + { + if (!(layout & CUBEB_LAYOUT_3F)) { // at least 1 front speaker + return false; + } + if (!even(layout & (CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT))) { // no asymetric front + return false; + } + if (!even(layout & + (CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT))) { // no asymetric side + return false; + } + if (!even(layout & (CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT))) { + return false; + } + if (!even(layout & + (CHANNEL_FRONT_LEFT_OF_CENTER | CHANNEL_FRONT_RIGHT_OF_CENTER))) { + return false; + } + if (cubeb_channel_layout_nb_channels(layout) >= CHANNELS_MAX) { + return false; + } + return true; + } + + int auto_matrix(); + int init(); + + const cubeb_sample_format _format; + const cubeb_channel_layout _in_ch_layout; ///< input channel layout + const cubeb_channel_layout _out_ch_layout; ///< output channel layout + const uint32_t _in_ch_count; ///< input channel count + const uint32_t _out_ch_count; ///< output channel count + const float _surround_mix_level = C_30DB; ///< surround mixing level + const float _center_mix_level = C_30DB; ///< center mixing level + const float _lfe_mix_level = 1; ///< LFE mixing level + double _matrix[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< floating point rematrixing coefficients + float _matrix_flt[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< single precision floating point rematrixing coefficients + int32_t _matrix32[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< 17.15 fixed point rematrixing coefficients + uint8_t _matrix_ch[CHANNELS_MAX][CHANNELS_MAX+1] = {{ 0 }}; ///< Lists of input channels per output channel that have non zero rematrixing coefficients + bool _clipping = false; ///< Set to true if clipping detection is required + bool _valid = false; ///< Set to true if context is valid. +}; + +int MixerContext::auto_matrix() +{ + double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = { { 0 } }; + double maxcoef = 0; + float maxval; + + cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout); + cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout); + + if (!sane_layout(in_ch_layout)) { + // Channel Not Supported + LOG("Input Layout %x is not supported", _in_ch_layout); + return -1; + } + + if (!sane_layout(out_ch_layout)) { + LOG("Output Layout %x is not supported", _out_ch_layout); + return -1; + } + + for (uint32_t i = 0; i < FF_ARRAY_ELEMS(matrix); i++) { + if (in_ch_layout & out_ch_layout & (1U << i)) { + matrix[i][i] = 1.0; + } + } + + cubeb_channel_layout unaccounted = in_ch_layout & ~out_ch_layout; + + // Rematrixing is done via a matrix of coefficient that should be applied to + // all channels. Channels are treated as pair and must be symmetrical (if a + // left channel exists, the corresponding right should exist too) unless the + // output layout has similar layout. Channels are then mixed toward the front + // center or back center if they exist with a slight bias toward the front. + + if (unaccounted & CHANNEL_FRONT_CENTER) { + if ((out_ch_layout & CUBEB_LAYOUT_STEREO) == CUBEB_LAYOUT_STEREO) { + if (in_ch_layout & CUBEB_LAYOUT_STEREO) { + matrix[FRONT_LEFT][FRONT_CENTER] += _center_mix_level; + matrix[FRONT_RIGHT][FRONT_CENTER] += _center_mix_level; + } else { + matrix[FRONT_LEFT][FRONT_CENTER] += M_SQRT1_2; + matrix[FRONT_RIGHT][FRONT_CENTER] += M_SQRT1_2; + } + } + } + if (unaccounted & CUBEB_LAYOUT_STEREO) { + if (out_ch_layout & CHANNEL_FRONT_CENTER) { + matrix[FRONT_CENTER][FRONT_LEFT] += M_SQRT1_2; + matrix[FRONT_CENTER][FRONT_RIGHT] += M_SQRT1_2; + if (in_ch_layout & CHANNEL_FRONT_CENTER) + matrix[FRONT_CENTER][FRONT_CENTER] = _center_mix_level * M_SQRT2; + } + } + + if (unaccounted & CHANNEL_BACK_CENTER) { + if (out_ch_layout & CHANNEL_BACK_LEFT) { + matrix[BACK_LEFT][BACK_CENTER] += M_SQRT1_2; + matrix[BACK_RIGHT][BACK_CENTER] += M_SQRT1_2; + } else if (out_ch_layout & CHANNEL_SIDE_LEFT) { + matrix[SIDE_LEFT][BACK_CENTER] += M_SQRT1_2; + matrix[SIDE_RIGHT][BACK_CENTER] += M_SQRT1_2; + } else if (out_ch_layout & CHANNEL_FRONT_LEFT) { + matrix[FRONT_LEFT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2; + matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2; + } else if (out_ch_layout & CHANNEL_FRONT_CENTER) { + matrix[FRONT_CENTER][BACK_CENTER] += + _surround_mix_level * M_SQRT1_2; + } + } + if (unaccounted & CHANNEL_BACK_LEFT) { + if (out_ch_layout & CHANNEL_BACK_CENTER) { + matrix[BACK_CENTER][BACK_LEFT] += M_SQRT1_2; + matrix[BACK_CENTER][BACK_RIGHT] += M_SQRT1_2; + } else if (out_ch_layout & CHANNEL_SIDE_LEFT) { + if (in_ch_layout & CHANNEL_SIDE_LEFT) { + matrix[SIDE_LEFT][BACK_LEFT] += M_SQRT1_2; + matrix[SIDE_RIGHT][BACK_RIGHT] += M_SQRT1_2; + } else { + matrix[SIDE_LEFT][BACK_LEFT] += 1.0; + matrix[SIDE_RIGHT][BACK_RIGHT] += 1.0; + } + } else if (out_ch_layout & CHANNEL_FRONT_LEFT) { + matrix[FRONT_LEFT][BACK_LEFT] += _surround_mix_level; + matrix[FRONT_RIGHT][BACK_RIGHT] += _surround_mix_level; + } else if (out_ch_layout & CHANNEL_FRONT_CENTER) { + matrix[FRONT_CENTER][BACK_LEFT] += _surround_mix_level * M_SQRT1_2; + matrix[FRONT_CENTER][BACK_RIGHT] += _surround_mix_level * M_SQRT1_2; + } + } + + if (unaccounted & CHANNEL_SIDE_LEFT) { + if (out_ch_layout & CHANNEL_BACK_LEFT) { + /* if back channels do not exist in the input, just copy side + channels to back channels, otherwise mix side into back */ + if (in_ch_layout & CHANNEL_BACK_LEFT) { + matrix[BACK_LEFT][SIDE_LEFT] += M_SQRT1_2; + matrix[BACK_RIGHT][SIDE_RIGHT] += M_SQRT1_2; + } else { + matrix[BACK_LEFT][SIDE_LEFT] += 1.0; + matrix[BACK_RIGHT][SIDE_RIGHT] += 1.0; + } + } else if (out_ch_layout & CHANNEL_BACK_CENTER) { + matrix[BACK_CENTER][SIDE_LEFT] += M_SQRT1_2; + matrix[BACK_CENTER][SIDE_RIGHT] += M_SQRT1_2; + } else if (out_ch_layout & CHANNEL_FRONT_LEFT) { + matrix[FRONT_LEFT][SIDE_LEFT] += _surround_mix_level; + matrix[FRONT_RIGHT][SIDE_RIGHT] += _surround_mix_level; + } else if (out_ch_layout & CHANNEL_FRONT_CENTER) { + matrix[FRONT_CENTER][SIDE_LEFT] += _surround_mix_level * M_SQRT1_2; + matrix[FRONT_CENTER][SIDE_RIGHT] += _surround_mix_level * M_SQRT1_2; + } + } + + if (unaccounted & CHANNEL_FRONT_LEFT_OF_CENTER) { + if (out_ch_layout & CHANNEL_FRONT_LEFT) { + matrix[FRONT_LEFT][FRONT_LEFT_OF_CENTER] += 1.0; + matrix[FRONT_RIGHT][FRONT_RIGHT_OF_CENTER] += 1.0; + } else if (out_ch_layout & CHANNEL_FRONT_CENTER) { + matrix[FRONT_CENTER][FRONT_LEFT_OF_CENTER] += M_SQRT1_2; + matrix[FRONT_CENTER][FRONT_RIGHT_OF_CENTER] += M_SQRT1_2; + } + } + /* mix LFE into front left/right or center */ + if (unaccounted & CHANNEL_LOW_FREQUENCY) { + if (out_ch_layout & CHANNEL_FRONT_CENTER) { + matrix[FRONT_CENTER][LOW_FREQUENCY] += _lfe_mix_level; + } else if (out_ch_layout & CHANNEL_FRONT_LEFT) { + matrix[FRONT_LEFT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2; + matrix[FRONT_RIGHT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2; + } + } + + // Normalize the conversion matrix. + for (uint32_t out_i = 0, i = 0; i < CHANNELS_MAX; i++) { + double sum = 0; + int in_i = 0; + if ((out_ch_layout & (1U << i)) == 0) { + continue; + } + for (uint32_t j = 0; j < CHANNELS_MAX; j++) { + if ((in_ch_layout & (1U << j)) == 0) { + continue; + } + if (i < FF_ARRAY_ELEMS(matrix) && j < FF_ARRAY_ELEMS(matrix[0])) { + _matrix[out_i][in_i] = matrix[i][j]; + } else { + _matrix[out_i][in_i] = + i == j && (in_ch_layout & out_ch_layout & (1U << i)); + } + sum += fabs(_matrix[out_i][in_i]); + in_i++; + } + maxcoef = std::max(maxcoef, sum); + out_i++; + } + + if (_format == CUBEB_SAMPLE_S16NE) { + maxval = 1.0; + } else { + maxval = INT_MAX; + } + + // Normalize matrix if needed. + if (maxcoef > maxval) { + maxcoef /= maxval; + for (uint32_t i = 0; i < CHANNELS_MAX; i++) + for (uint32_t j = 0; j < CHANNELS_MAX; j++) { + _matrix[i][j] /= maxcoef; + } + } + + if (_format == CUBEB_SAMPLE_FLOAT32NE) { + for (uint32_t i = 0; i < FF_ARRAY_ELEMS(_matrix); i++) { + for (uint32_t j = 0; j < FF_ARRAY_ELEMS(_matrix[0]); j++) { + _matrix_flt[i][j] = _matrix[i][j]; + } + } + } + + return 0; +} + +int MixerContext::init() +{ + int r = auto_matrix(); + if (r) { + return r; + } + + // Determine if matrix operation would overflow + if (_format == CUBEB_SAMPLE_S16NE) { + int maxsum = 0; + for (uint32_t i = 0; i < _out_ch_count; i++) { + double rem = 0; + int sum = 0; + + for (uint32_t j = 0; j < _in_ch_count; j++) { + double target = _matrix[i][j] * 32768 + rem; + int value = lrintf(target); + rem += target - value; + sum += std::abs(value); + } + maxsum = std::max(maxsum, sum); + } + if (maxsum > 32768) { + _clipping = true; + } + } + + // FIXME quantize for integers + for (uint32_t i = 0; i < CHANNELS_MAX; i++) { + int ch_in = 0; + for (uint32_t j = 0; j < CHANNELS_MAX; j++) { + _matrix32[i][j] = lrintf(_matrix[i][j] * 32768); + if (_matrix[i][j]) { + _matrix_ch[i][++ch_in] = j; + } + } + _matrix_ch[i][0] = ch_in; + } + + return 0; +} + +template +void +sum2(TYPE_SAMPLE * out, + uint32_t stride_out, + const TYPE_SAMPLE * in1, + const TYPE_SAMPLE * in2, + uint32_t stride_in, + TYPE_COEFF coeff1, + TYPE_COEFF coeff2, + F&& operand, + uint32_t frames) +{ + static_assert( + std::is_same::type>::value, + "function must return the same type as used by matrix_coeff"); + for (uint32_t i = 0; i < frames; i++) { + *out = operand(coeff1 * *in1 + coeff2 * *in2); + out += stride_out; + in1 += stride_in; + in2 += stride_in; + } +} + +template +void +copy(TYPE_SAMPLE * out, + uint32_t stride_out, + const TYPE_SAMPLE * in, + uint32_t stride_in, + TYPE_COEFF coeff, + F&& operand, + uint32_t frames) +{ + static_assert( + std::is_same::type>::value, + "function must return the same type as used by matrix_coeff"); + for (uint32_t i = 0; i < frames; i++) { + *out = operand(coeff * *in); + out += stride_out; + in += stride_in; + } +} + +template +static int rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn, + const TYPE_COEFF (&matrix_coeff)[COLS][COLS], + F&& aF, uint32_t frames) +{ + static_assert( + std::is_same::type>::value, + "function must return the same type as used by matrix_coeff"); + + for (uint32_t out_i = 0; out_i < s->_out_ch_count; out_i++) { + TYPE* out = aOut + out_i; + switch (s->_matrix_ch[out_i][0]) { + case 0: + for (uint32_t i = 0; i < frames; i++) { + out[i * s->_out_ch_count] = 0; + } + break; + case 1: { + int in_i = s->_matrix_ch[out_i][1]; + copy(out, + s->_out_ch_count, + aIn + in_i, + s->_in_ch_count, + matrix_coeff[out_i][in_i], + aF, + frames); + } break; + case 2: + sum2(out, + s->_out_ch_count, + aIn + s->_matrix_ch[out_i][1], + aIn + s->_matrix_ch[out_i][2], + s->_in_ch_count, + matrix_coeff[out_i][s->_matrix_ch[out_i][1]], + matrix_coeff[out_i][s->_matrix_ch[out_i][2]], + aF, + frames); + break; + default: + for (uint32_t i = 0; i < frames; i++) { + TYPE_COEFF v = 0; + for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) { + uint32_t in_i = s->_matrix_ch[out_i][1 + j]; + v += + *(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i]; + } + out[i * s->_out_ch_count] = aF(v); + } + break; + } + } + return 0; +} + +struct cubeb_mixer +{ + cubeb_mixer(cubeb_sample_format format, + uint32_t in_channels, + cubeb_channel_layout in_layout, + uint32_t out_channels, + cubeb_channel_layout out_layout) + : _context(format, in_channels, in_layout, out_channels, out_layout) + { + } + + template + void copy_and_trunc(size_t frames, + const T * input_buffer, + T * output_buffer) const + { + if (_context._in_ch_count <= _context._out_ch_count) { + // Not enough channels to copy, fill the gaps with silence. + if (_context._in_ch_count == 1 && _context._out_ch_count >= 2) { + // Special case for upmixing mono input to stereo and more. We will + // duplicate the mono channel to the first two channels. On most system, + // the first two channels are for left and right. It is commonly + // expected that mono will on both left+right channels + for (uint32_t i = 0; i < frames; i++) { + output_buffer[0] = output_buffer[1] = *input_buffer; + PodZero(output_buffer + 2, _context._out_ch_count - 2); + output_buffer += _context._out_ch_count; + input_buffer++; + } + return; + } + for (uint32_t i = 0; i < frames; i++) { + PodCopy(output_buffer, input_buffer, _context._in_ch_count); + output_buffer += _context._in_ch_count; + input_buffer += _context._in_ch_count; + PodZero(output_buffer, _context._out_ch_count - _context._in_ch_count); + output_buffer += _context._out_ch_count - _context._in_ch_count; + } + } else { + for (uint32_t i = 0; i < frames; i++) { + PodCopy(output_buffer, input_buffer, _context._out_ch_count); + output_buffer += _context._out_ch_count; + input_buffer += _context._in_ch_count; + } + } + } + + int mix(size_t frames, + const void * input_buffer, + size_t input_buffer_size, + void * output_buffer, + size_t output_buffer_size) const + { + if (frames <= 0 || _context._out_ch_count == 0) { + return 0; + } + + // Check if output buffer is of sufficient size. + size_t size_read_needed = + frames * _context._in_ch_count * cubeb_sample_size(_context._format); + if (input_buffer_size < size_read_needed) { + // We don't have enough data to read! + return -1; + } + if (output_buffer_size * _context._in_ch_count < + size_read_needed * _context._out_ch_count) { + return -1; + } + + if (!valid()) { + // The channel layouts were invalid or unsupported, instead we will simply + // either drop the extra channels, or fill with silence the missing ones + if (_context._format == CUBEB_SAMPLE_FLOAT32NE) { + copy_and_trunc(frames, + static_cast(input_buffer), + static_cast(output_buffer)); + } else { + assert(_context._format == CUBEB_SAMPLE_S16NE); + copy_and_trunc(frames, + static_cast(input_buffer), + reinterpret_cast(output_buffer)); + } + return 0; + } + + switch (_context._format) + { + case CUBEB_SAMPLE_FLOAT32NE: { + auto f = [](float x) { return x; }; + return rematrix(&_context, + static_cast(output_buffer), + static_cast(input_buffer), + _context._matrix_flt, + f, + frames); + } + case CUBEB_SAMPLE_S16NE: + if (_context._clipping) { + auto f = [](int x) { + int y = (x + 16384) >> 15; + // clip the signed integer value into the -32768,32767 range. + if ((y + 0x8000U) & ~0xFFFF) { + return (y >> 31) ^ 0x7FFF; + } + return y; + }; + return rematrix(&_context, + static_cast(output_buffer), + static_cast(input_buffer), + _context._matrix32, + f, + frames); + } else { + auto f = [](int x) { return (x + 16384) >> 15; }; + return rematrix(&_context, + static_cast(output_buffer), + static_cast(input_buffer), + _context._matrix32, + f, + frames); + } + break; + default: + assert(false); + break; + } + + return -1; + } + + // Return false if any of the input or ouput layout were invalid. + bool valid() const { return _context._valid; } + + virtual ~cubeb_mixer(){}; + + MixerContext _context; +}; + +cubeb_mixer* cubeb_mixer_create(cubeb_sample_format format, + uint32_t in_channels, + cubeb_channel_layout in_layout, + uint32_t out_channels, + cubeb_channel_layout out_layout) +{ + return new cubeb_mixer( + format, in_channels, in_layout, out_channels, out_layout); +} + +void cubeb_mixer_destroy(cubeb_mixer * mixer) +{ + delete mixer; +} + +int cubeb_mixer_mix(cubeb_mixer * mixer, + size_t frames, + const void * input_buffer, + size_t input_buffer_size, + void * output_buffer, + size_t output_buffer_size) +{ + return mixer->mix( + frames, input_buffer, input_buffer_size, output_buffer, output_buffer_size); +} diff --git a/media/libcubeb/src/cubeb_mixer.h b/media/libcubeb/src/cubeb_mixer.h new file mode 100644 index 000000000..d43a237f9 --- /dev/null +++ b/media/libcubeb/src/cubeb_mixer.h @@ -0,0 +1,37 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#ifndef CUBEB_MIXER +#define CUBEB_MIXER + +#include "cubeb/cubeb.h" // for cubeb_channel_layout and cubeb_stream_params. + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct cubeb_mixer cubeb_mixer; +cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format, + uint32_t in_channels, + cubeb_channel_layout in_layout, + uint32_t out_channels, + cubeb_channel_layout out_layout); +void cubeb_mixer_destroy(cubeb_mixer * mixer); +int cubeb_mixer_mix(cubeb_mixer * mixer, + size_t frames, + const void * input_buffer, + size_t input_buffer_size, + void * output_buffer, + size_t output_buffer_size); + +unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout channel_layout); + +#if defined(__cplusplus) +} +#endif + +#endif // CUBEB_MIXER diff --git a/media/libcubeb/src/cubeb_opensl.c b/media/libcubeb/src/cubeb_opensl.c index dd5416228..96374ec07 100644 --- a/media/libcubeb/src/cubeb_opensl.c +++ b/media/libcubeb/src/cubeb_opensl.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -19,88 +20,268 @@ #include #include #include -#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args) -#define ANDROID_VERSION_GINGERBREAD_MR1 10 -#define ANDROID_VERSION_LOLLIPOP 21 -#define ANDROID_VERSION_MARSHMALLOW 23 #endif #include "cubeb/cubeb.h" #include "cubeb-internal.h" #include "cubeb_resampler.h" #include "cubeb-sles.h" +#include "cubeb_array_queue.h" +#include "android/cubeb-output-latency.h" + +#if defined(__ANDROID__) +#ifdef LOG +#undef LOG +#endif +//#define LOGGING_ENABLED +#ifdef LOGGING_ENABLED +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args) +#else +#define LOG(...) +#endif + +//#define TIMESTAMP_ENABLED +#ifdef TIMESTAMP_ENABLED +#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#define LOG_TS(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL ES: Timestamp(usec)" , ## args) +#define TIMESTAMP(msg) do { \ + struct timeval timestamp; \ + int ts_ret = gettimeofday(×tamp, NULL); \ + if (ts_ret == 0) { \ + LOG_TS("%lld: %s (%s %s:%d)", timestamp.tv_sec * 1000000LL + timestamp.tv_usec, msg, __FUNCTION__, FILENAME, __LINE__);\ + } else { \ + LOG_TS("Error: %s (%s %s:%d) - %s", msg, __FUNCTION__, FILENAME, __LINE__);\ + } \ +} while(0) +#else +#define TIMESTAMP(...) +#endif + +#define ANDROID_VERSION_GINGERBREAD_MR1 10 +#define ANDROID_VERSION_JELLY_BEAN 18 +#define ANDROID_VERSION_LOLLIPOP 21 +#define ANDROID_VERSION_MARSHMALLOW 23 +#define ANDROID_VERSION_N_MR1 25 +#endif + +#define DEFAULT_SAMPLE_RATE 48000 +#define DEFAULT_NUM_OF_FRAMES 480 +// If the latency requested is above this threshold, this stream is considered +// intended for playback (vs. real-time). Tell Android it should favor saving +// power over performance or latency. +// This is around 100ms at 44100 or 48000 +#define POWERSAVE_LATENCY_FRAMES_THRESHOLD 4000 static struct cubeb_ops const opensl_ops; struct cubeb { struct cubeb_ops const * ops; void * lib; - void * libmedia; - int32_t (* get_output_latency)(uint32_t * latency, int stream_type); SLInterfaceID SL_IID_BUFFERQUEUE; SLInterfaceID SL_IID_PLAY; #if defined(__ANDROID__) SLInterfaceID SL_IID_ANDROIDCONFIGURATION; + SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE; #endif SLInterfaceID SL_IID_VOLUME; + SLInterfaceID SL_IID_RECORD; SLObjectItf engObj; SLEngineItf eng; SLObjectItf outmixObj; + output_latency_function * p_output_latency_function; }; #define NELEMS(A) (sizeof(A) / sizeof A[0]) -#define NBUFS 4 -#define AUDIO_STREAM_TYPE_MUSIC 3 +#define NBUFS 2 struct cubeb_stream { + /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; + void * user_ptr; + /**/ pthread_mutex_t mutex; SLObjectItf playerObj; SLPlayItf play; SLBufferQueueItf bufq; SLVolumeItf volume; - uint8_t *queuebuf[NBUFS]; + void ** queuebuf; + uint32_t queuebuf_capacity; int queuebuf_idx; long queuebuf_len; long bytespersec; long framesize; + /* Total number of played frames. + * Synchronized by stream::mutex lock. */ long written; + /* Flag indicating draining. Synchronized + * by stream::mutex lock. */ int draining; - cubeb_stream_type stream_type; - + /* Flags to determine in/out.*/ + uint32_t input_enabled; + uint32_t output_enabled; + /* Recorder abstract object. */ + SLObjectItf recorderObj; + /* Recorder Itf for input capture. */ + SLRecordItf recorderItf; + /* Buffer queue for input capture. */ + SLAndroidSimpleBufferQueueItf recorderBufferQueueItf; + /* Store input buffers. */ + void ** input_buffer_array; + /* The capacity of the array. + * On capture only can be small (4). + * On full duplex is calculated to + * store 1 sec of data buffers. */ + uint32_t input_array_capacity; + /* Current filled index of input buffer array. + * It is initiated to -1 indicating buffering + * have not started yet. */ + int input_buffer_index; + /* Length of input buffer.*/ + uint32_t input_buffer_length; + /* Input frame size */ + uint32_t input_frame_size; + /* Device sampling rate. If user rate is not + * accepted an compatible rate is set. If it is + * accepted this is equal to params.rate. */ + uint32_t input_device_rate; + /* Exchange input buffers between input + * and full duplex threads. */ + array_queue * input_queue; + /* Silent input buffer used on full duplex. */ + void * input_silent_buffer; + /* Number of input frames from the start of the stream*/ + uint32_t input_total_frames; + /* Flag to stop the execution of user callback and + * close all working threads. Synchronized by + * stream::mutex lock. */ + uint32_t shutdown; + /* Store user callback. */ cubeb_data_callback data_callback; + /* Store state callback. */ cubeb_state_callback state_callback; - void * user_ptr; cubeb_resampler * resampler; - unsigned int inputrate; - unsigned int outputrate; - unsigned int latency; + unsigned int user_output_rate; + unsigned int output_configured_rate; + unsigned int buffer_size_frames; + // Audio output latency used in cubeb_stream_get_position(). + unsigned int output_latency_ms; int64_t lastPosition; int64_t lastPositionTimeStamp; int64_t lastCompensativePosition; + int voice; }; +/* Forward declaration. */ +static int opensl_stop_player(cubeb_stream * stm); +static int opensl_stop_recorder(cubeb_stream * stm); + +static int +opensl_get_draining(cubeb_stream * stm) +{ +#ifdef DEBUG + int r = pthread_mutex_trylock(&stm->mutex); + assert((r == EDEADLK || r == EBUSY) && "get_draining: mutex should be locked but it's not."); +#endif + return stm->draining; +} + +static void +opensl_set_draining(cubeb_stream * stm, int value) +{ +#ifdef DEBUG + int r = pthread_mutex_trylock(&stm->mutex); + LOG("set draining try r = %d", r); + assert((r == EDEADLK || r == EBUSY) && "set_draining: mutex should be locked but it's not."); +#endif + assert(value == 0 || value == 1); + stm->draining = value; +} + +static void +opensl_notify_drained(cubeb_stream * stm) +{ + assert(stm); + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int draining = opensl_get_draining(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + if (draining) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); + if (stm->play) { + LOG("stop player in play_callback"); + r = opensl_stop_player(stm); + assert(r == CUBEB_OK); + } + if (stm->recorderItf) { + r = opensl_stop_recorder(stm); + assert(r == CUBEB_OK); + } + } +} + +static uint32_t +opensl_get_shutdown(cubeb_stream * stm) +{ +#ifdef DEBUG + int r = pthread_mutex_trylock(&stm->mutex); + assert((r == EDEADLK || r == EBUSY) && "get_shutdown: mutex should be locked but it's not."); +#endif + return stm->shutdown; +} + +static void +opensl_set_shutdown(cubeb_stream * stm, uint32_t value) +{ +#ifdef DEBUG + int r = pthread_mutex_trylock(&stm->mutex); + LOG("set shutdown try r = %d", r); + assert((r == EDEADLK || r == EBUSY) && "set_shutdown: mutex should be locked but it's not."); +#endif + assert(value == 0 || value == 1); + stm->shutdown = value; +} + static void play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event) { cubeb_stream * stm = user_ptr; - int draining; assert(stm); switch (event) { - case SL_PLAYEVENT_HEADATMARKER: - pthread_mutex_lock(&stm->mutex); - draining = stm->draining; - pthread_mutex_unlock(&stm->mutex); - if (draining) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); - (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); - } + case SL_PLAYEVENT_HEADATMARKER: + opensl_notify_drained(stm); break; default: break; } } +static void +recorder_marker_callback (SLRecordItf caller, void * pContext, SLuint32 event) +{ + cubeb_stream * stm = pContext; + assert(stm); + + if (event == SL_RECORDEVENT_HEADATMARKER) { + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int draining = opensl_get_draining(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + if (draining) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); + if (stm->recorderItf) { + r = opensl_stop_recorder(stm); + assert(r == CUBEB_OK); + } + if (stm->play) { + r = opensl_stop_player(stm); + assert(r == CUBEB_OK); + } + } + } +} + static void bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) { @@ -108,90 +289,333 @@ bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) assert(stm); SLBufferQueueState state; SLresult res; + long written = 0; res = (*stm->bufq)->GetState(stm->bufq, &state); assert(res == SL_RESULT_SUCCESS); - if (state.count > 1) + if (state.count > 1) { return; + } - SLuint32 i; - for (i = state.count; i < NBUFS; i++) { - uint8_t *buf = stm->queuebuf[stm->queuebuf_idx]; - long written = 0; - pthread_mutex_lock(&stm->mutex); - int draining = stm->draining; - pthread_mutex_unlock(&stm->mutex); - - if (!draining) { - written = cubeb_resampler_fill(stm->resampler, - NULL, NULL, - buf, stm->queuebuf_len / stm->framesize); - if (written < 0 || written * stm->framesize > stm->queuebuf_len) { - (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); - return; - } + uint8_t *buf = stm->queuebuf[stm->queuebuf_idx]; + written = 0; + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int draining = opensl_get_draining(stm); + uint32_t shutdown = opensl_get_shutdown(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + if (!draining && !shutdown) { + written = cubeb_resampler_fill(stm->resampler, + NULL, NULL, + buf, stm->queuebuf_len / stm->framesize); + LOG("bufferqueue_callback: resampler fill returned %ld frames", written); + if (written < 0 || written * stm->framesize > stm->queuebuf_len) { + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + opensl_set_shutdown(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + opensl_stop_player(stm); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + return; } + } - // Keep sending silent data even in draining mode to prevent the audio - // back-end from being stopped automatically by OpenSL/ES. - memset(buf + written * stm->framesize, 0, stm->queuebuf_len - written * stm->framesize); - res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - stm->queuebuf_idx = (stm->queuebuf_idx + 1) % NBUFS; - if (written > 0) { - pthread_mutex_lock(&stm->mutex); - stm->written += written; - pthread_mutex_unlock(&stm->mutex); - } + // Keep sending silent data even in draining mode to prevent the audio + // back-end from being stopped automatically by OpenSL/ES. + assert(stm->queuebuf_len >= written * stm->framesize); + memset(buf + written * stm->framesize, 0, stm->queuebuf_len - written * stm->framesize); + res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len); + assert(res == SL_RESULT_SUCCESS); + stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity; + + if (written > 0) { + pthread_mutex_lock(&stm->mutex); + stm->written += written; + pthread_mutex_unlock(&stm->mutex); + } - if (!draining && written * stm->framesize < stm->queuebuf_len) { - pthread_mutex_lock(&stm->mutex); - int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; - stm->draining = 1; - pthread_mutex_unlock(&stm->mutex); + if (!draining && written * stm->framesize < stm->queuebuf_len) { + LOG("bufferqueue_callback draining"); + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; + opensl_set_draining(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (written_duration == 0) { + // since we didn't write any sample, it's not possible to reach the marker + // time and trigger the callback. We should initiative notify drained. + opensl_notify_drained(stm); + } else { // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf // to make sure all the data has been processed. (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); - return; } + return; } } -#if defined(__ANDROID__) -static SLuint32 -convert_stream_type_to_sl_stream(cubeb_stream_type stream_type) -{ - switch(stream_type) { - case CUBEB_STREAM_TYPE_SYSTEM: - return SL_ANDROID_STREAM_SYSTEM; - case CUBEB_STREAM_TYPE_MUSIC: - return SL_ANDROID_STREAM_MEDIA; - case CUBEB_STREAM_TYPE_NOTIFICATION: - return SL_ANDROID_STREAM_NOTIFICATION; - case CUBEB_STREAM_TYPE_ALARM: - return SL_ANDROID_STREAM_ALARM; - case CUBEB_STREAM_TYPE_VOICE_CALL: - return SL_ANDROID_STREAM_VOICE; - case CUBEB_STREAM_TYPE_RING: - return SL_ANDROID_STREAM_RING; - case CUBEB_STREAM_TYPE_SYSTEM_ENFORCED: - return SL_ANDROID_STREAM_SYSTEM_ENFORCED; - default: - return 0xFFFFFFFF; +static int +opensl_enqueue_recorder(cubeb_stream * stm, void ** last_filled_buffer) +{ + assert(stm); + + int current_index = stm->input_buffer_index; + void * last_buffer = NULL; + + if (current_index < 0) { + // This is the first enqueue + current_index = 0; + } else { + // The current index hold the last filled buffer get it before advance index. + last_buffer = stm->input_buffer_array[current_index]; + // Advance to get next available buffer + current_index = (current_index + 1) % stm->input_array_capacity; + } + // enqueue next empty buffer to be filled by the recorder + SLresult res = (*stm->recorderBufferQueueItf)->Enqueue(stm->recorderBufferQueueItf, + stm->input_buffer_array[current_index], + stm->input_buffer_length); + if (res != SL_RESULT_SUCCESS ) { + LOG("Enqueue recorder failed. Error code: %lu", res); + return CUBEB_ERROR; + } + // All good, update buffer and index. + stm->input_buffer_index = current_index; + if (last_filled_buffer) { + *last_filled_buffer = last_buffer; } + return CUBEB_OK; +} + +// input data callback +void recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context) +{ + assert(context); + cubeb_stream * stm = context; + assert(stm->recorderBufferQueueItf); + + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + uint32_t shutdown = opensl_get_shutdown(stm); + int draining = opensl_get_draining(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (shutdown || draining) { + // According to the OpenSL ES 1.1 Specification, 8.14 SLBufferQueueItf + // page 184, on transition to the SL_RECORDSTATE_STOPPED state, + // the application should continue to enqueue buffers onto the queue + // to retrieve the residual recorded data in the system. + r = opensl_enqueue_recorder(stm, NULL); + assert(r == CUBEB_OK); + return; + } + + // Enqueue next available buffer and get the last filled buffer. + void * input_buffer = NULL; + r = opensl_enqueue_recorder(stm, &input_buffer); + assert(r == CUBEB_OK); + assert(input_buffer); + // Fill resampler with last input + long input_frame_count = stm->input_buffer_length / stm->input_frame_size; + long got = cubeb_resampler_fill(stm->resampler, + input_buffer, + &input_frame_count, + NULL, + 0); + // Error case + if (got < 0 || got > input_frame_count) { + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + opensl_set_shutdown(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + r = opensl_stop_recorder(stm); + assert(r == CUBEB_OK); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + } + + // Advance total stream frames + stm->input_total_frames += got; + + if (got < input_frame_count) { + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + opensl_set_draining(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + int64_t duration = INT64_C(1000) * stm->input_total_frames / stm->input_device_rate; + (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)duration); + return; + } +} + +void recorder_fullduplex_callback(SLAndroidSimpleBufferQueueItf bq, void * context) +{ + assert(context); + cubeb_stream * stm = context; + assert(stm->recorderBufferQueueItf); + + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int draining = opensl_get_draining(stm); + uint32_t shutdown = opensl_get_shutdown(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (shutdown || draining) { + /* On draining and shutdown the recorder should have been stoped from + * the one set the flags. Accordint to the doc, on transition to + * the SL_RECORDSTATE_STOPPED state, the application should + * continue to enqueue buffers onto the queue to retrieve the residual + * recorded data in the system. */ + LOG("Input shutdown %d or drain %d", shutdown, draining); + int r = opensl_enqueue_recorder(stm, NULL); + assert(r == CUBEB_OK); + return; + } + + // Enqueue next available buffer and get the last filled buffer. + void * input_buffer = NULL; + r = opensl_enqueue_recorder(stm, &input_buffer); + assert(r == CUBEB_OK); + assert(input_buffer); + + assert(stm->input_queue); + r = array_queue_push(stm->input_queue, input_buffer); + if (r == -1) { + LOG("Input queue is full, drop input ..."); + return; + } + + LOG("Input pushed in the queue, input array %zu", + array_queue_get_size(stm->input_queue)); +} + +static void +player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr) +{ + TIMESTAMP("ENTER"); + cubeb_stream * stm = user_ptr; + assert(stm); + SLresult res; + + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int draining = opensl_get_draining(stm); + uint32_t shutdown = opensl_get_shutdown(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + // Get output + void * output_buffer = NULL; + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + output_buffer = stm->queuebuf[stm->queuebuf_idx]; + // Advance the output buffer queue index + stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity; + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (shutdown || draining) { + LOG("Shutdown/draining, send silent"); + // Set silent on buffer + memset(output_buffer, 0, stm->queuebuf_len); + + // Enqueue data in player buffer queue + res = (*stm->bufq)->Enqueue(stm->bufq, + output_buffer, + stm->queuebuf_len); + assert(res == SL_RESULT_SUCCESS); + return; + } + + // Get input. + void * input_buffer = array_queue_pop(stm->input_queue); + long input_frame_count = stm->input_buffer_length / stm->input_frame_size; + long frames_needed = stm->queuebuf_len / stm->framesize; + if (!input_buffer) { + LOG("Input hole set silent input buffer"); + input_buffer = stm->input_silent_buffer; + } + + long written = 0; + // Trigger user callback through resampler + written = cubeb_resampler_fill(stm->resampler, + input_buffer, + &input_frame_count, + output_buffer, + frames_needed); + + LOG("Fill: written %ld, frames_needed %ld, input array size %zu", + written, frames_needed, array_queue_get_size(stm->input_queue)); + + if (written < 0 || written > frames_needed) { + // Error case + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + opensl_set_shutdown(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + opensl_stop_player(stm); + opensl_stop_recorder(stm); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + memset(output_buffer, 0, stm->queuebuf_len); + + // Enqueue data in player buffer queue + res = (*stm->bufq)->Enqueue(stm->bufq, + output_buffer, + stm->queuebuf_len); + assert(res == SL_RESULT_SUCCESS); + return; + } + + // Advance total out written frames counter + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + stm->written += written; + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if ( written < frames_needed) { + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; + opensl_set_draining(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf + // to make sure all the data has been processed. + (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); + } + + // Keep sending silent data even in draining mode to prevent the audio + // back-end from being stopped automatically by OpenSL/ES. + memset((uint8_t *)output_buffer + written * stm->framesize, 0, + stm->queuebuf_len - written * stm->framesize); + + // Enqueue data in player buffer queue + res = (*stm->bufq)->Enqueue(stm->bufq, + output_buffer, + stm->queuebuf_len); + assert(res == SL_RESULT_SUCCESS); + TIMESTAMP("EXIT"); } -#endif static void opensl_destroy(cubeb * ctx); #if defined(__ANDROID__) - #if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) typedef int (system_property_get)(const char*, char*); static int -__system_property_get(const char* name, char* value) +wrap_system_property_get(const char* name, char* value) { void* libc = dlopen("libc.so", RTLD_LAZY); if (!libc) { @@ -216,14 +640,18 @@ get_android_version(void) memset(version_string, 0, PROP_VALUE_MAX); +#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) + int len = wrap_system_property_get("ro.build.version.sdk", version_string); +#else int len = __system_property_get("ro.build.version.sdk", version_string); +#endif if (len <= 0) { LOG("Failed to get Android version!\n"); return len; } int version = (int)strtol(version_string, NULL, 10); - LOG("%d", version); + LOG("Android version %d", version); return version; } #endif @@ -249,30 +677,11 @@ opensl_init(cubeb ** context, char const * context_name) ctx->ops = &opensl_ops; ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY); - ctx->libmedia = dlopen("libmedia.so", RTLD_LAZY); - if (!ctx->lib || !ctx->libmedia) { + if (!ctx->lib) { free(ctx); return CUBEB_ERROR; } - /* Get the latency, in ms, from AudioFlinger */ - /* status_t AudioSystem::getOutputLatency(uint32_t* latency, - * audio_stream_type_t streamType) */ - /* First, try the most recent signature. */ - ctx->get_output_latency = - dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t"); - if (!ctx->get_output_latency) { - /* in case of failure, try the legacy version. */ - /* status_t AudioSystem::getOutputLatency(uint32_t* latency, - * int streamType) */ - ctx->get_output_latency = - dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); - if (!ctx->get_output_latency) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - } - typedef SLresult (*slCreateEngine_t)(SLObjectItf *, SLuint32, const SLEngineOption *, @@ -287,16 +696,21 @@ opensl_init(cubeb ** context, char const * context_name) ctx->SL_IID_BUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE"); #if defined(__ANDROID__) ctx->SL_IID_ANDROIDCONFIGURATION = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION"); + ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); #endif ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY"); + ctx->SL_IID_RECORD = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_RECORD"); + if (!f_slCreateEngine || !SL_IID_ENGINE || !SL_IID_OUTPUTMIX || !ctx->SL_IID_BUFFERQUEUE || #if defined(__ANDROID__) !ctx->SL_IID_ANDROIDCONFIGURATION || + !ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE || #endif - !ctx->SL_IID_PLAY) { + !ctx->SL_IID_PLAY || + !ctx->SL_IID_RECORD) { opensl_destroy(ctx); return CUBEB_ERROR; } @@ -337,8 +751,14 @@ opensl_init(cubeb ** context, char const * context_name) return CUBEB_ERROR; } + ctx->p_output_latency_function = cubeb_output_latency_load_method(android_version); + if (!ctx->p_output_latency_function) { + LOG("Warning: output latency is not available, cubeb_stream_get_position() is not supported"); + } + *context = ctx; + LOG("Cubeb init (%p) success", ctx); return CUBEB_OK; } @@ -359,200 +779,257 @@ opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) return CUBEB_OK; } +static void +opensl_destroy(cubeb * ctx) +{ + if (ctx->outmixObj) + (*ctx->outmixObj)->Destroy(ctx->outmixObj); + if (ctx->engObj) + cubeb_destroy_sles_engine(&ctx->engObj); + dlclose(ctx->lib); + if (ctx->p_output_latency_function) + cubeb_output_latency_unload_method(ctx->p_output_latency_function); + free(ctx); +} + +static void opensl_stream_destroy(cubeb_stream * stm); + static int -opensl_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) -{ - /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html - * We don't want to deal with JNI here (and we don't have Java on b2g anyways), - * so we just dlopen the library and get the two symbols we need. */ - int r; - void * libmedia; - uint32_t (*get_primary_output_samplingrate)(); - uint32_t (*get_output_samplingrate)(int * samplingRate, int streamType); - - libmedia = dlopen("libmedia.so", RTLD_LAZY); - if (!libmedia) { - return CUBEB_ERROR; +opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params) +{ + assert(format); + assert(params); + + format->formatType = SL_DATAFORMAT_PCM; + format->numChannels = params->channels; + // samplesPerSec is in milliHertz + format->samplesPerSec = params->rate * 1000; + format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + format->channelMask = params->channels == 1 ? + SL_SPEAKER_FRONT_CENTER : + SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + + switch (params->format) { + case CUBEB_SAMPLE_S16LE: + format->endianness = SL_BYTEORDER_LITTLEENDIAN; + break; + case CUBEB_SAMPLE_S16BE: + format->endianness = SL_BYTEORDER_BIGENDIAN; + break; + default: + return CUBEB_ERROR_INVALID_FORMAT; + } + return CUBEB_OK; +} + +static int +opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params) +{ + assert(stm); + assert(params); + + SLDataLocator_AndroidSimpleBufferQueue lDataLocatorOut; + lDataLocatorOut.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; + lDataLocatorOut.numBuffers = NBUFS; + + SLDataFormat_PCM lDataFormat; + int r = opensl_set_format(&lDataFormat, params); + if (r != CUBEB_OK) { + return CUBEB_ERROR_INVALID_FORMAT; } - /* uint32_t AudioSystem::getPrimaryOutputSamplingRate(void) */ - get_primary_output_samplingrate = - dlsym(libmedia, "_ZN7android11AudioSystem28getPrimaryOutputSamplingRateEv"); - if (!get_primary_output_samplingrate) { - /* fallback to - * status_t AudioSystem::getOutputSamplingRate(int* samplingRate, int streamType) - * if we cannot find getPrimaryOutputSamplingRate. */ - get_output_samplingrate = - dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPj19audio_stream_type_t"); - if (!get_output_samplingrate) { - /* Another signature exists, with a int instead of an audio_stream_type_t */ - get_output_samplingrate = - dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPii"); - if (!get_output_samplingrate) { - dlclose(libmedia); - return CUBEB_ERROR; - } + /* For now set device rate to params rate. */ + stm->input_device_rate = params->rate; + + SLDataSink lDataSink; + lDataSink.pLocator = &lDataLocatorOut; + lDataSink.pFormat = &lDataFormat; + + SLDataLocator_IODevice lDataLocatorIn; + lDataLocatorIn.locatorType = SL_DATALOCATOR_IODEVICE; + lDataLocatorIn.deviceType = SL_IODEVICE_AUDIOINPUT; + lDataLocatorIn.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; + lDataLocatorIn.device = NULL; + + SLDataSource lDataSource; + lDataSource.pLocator = &lDataLocatorIn; + lDataSource.pFormat = NULL; + + const SLInterfaceID lSoundRecorderIIDs[] = { stm->context->SL_IID_RECORD, + stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + stm->context->SL_IID_ANDROIDCONFIGURATION }; + + const SLboolean lSoundRecorderReqs[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; + // create the audio recorder abstract object + SLresult res = (*stm->context->eng)->CreateAudioRecorder(stm->context->eng, + &stm->recorderObj, + &lDataSource, + &lDataSink, + NELEMS(lSoundRecorderIIDs), + lSoundRecorderIIDs, + lSoundRecorderReqs); + // Sample rate not supported. Try again with default sample rate! + if (res == SL_RESULT_CONTENT_UNSUPPORTED) { + if (stm->output_enabled && stm->output_configured_rate != 0) { + // Set the same with the player. Since there is no + // api for input device this is a safe choice. + stm->input_device_rate = stm->output_configured_rate; + } else { + // The output preferred rate is used for an input only scenario. + // The default rate expected to be supported from all android devices. + stm->input_device_rate = DEFAULT_SAMPLE_RATE; } - } + lDataFormat.samplesPerSec = stm->input_device_rate * 1000; + res = (*stm->context->eng)->CreateAudioRecorder(stm->context->eng, + &stm->recorderObj, + &lDataSource, + &lDataSink, + NELEMS(lSoundRecorderIIDs), + lSoundRecorderIIDs, + lSoundRecorderReqs); - if (get_primary_output_samplingrate) { - *rate = get_primary_output_samplingrate(); - } else { - /* We don't really know about the type, here, so we just pass music. */ - r = get_output_samplingrate((int *) rate, AUDIO_STREAM_TYPE_MUSIC); - if (r) { - dlclose(libmedia); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to create recorder. Error code: %lu", res); return CUBEB_ERROR; } } - dlclose(libmedia); - /* Depending on which method we called above, we can get a zero back, yet have - * a non-error return value, especially if the audio system is not - * ready/shutting down (i.e. when we can't get our hand on the AudioFlinger - * thread). */ - if (*rate == 0) { - return CUBEB_ERROR; - } + if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) { + SLAndroidConfigurationItf recorderConfig; + res = (*stm->recorderObj) + ->GetInterface(stm->recorderObj, + stm->context->SL_IID_ANDROIDCONFIGURATION, + &recorderConfig); - return CUBEB_OK; -} + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to get the android configuration interface for recorder. Error " + "code: %lu", + res); + return CUBEB_ERROR; + } -static int -opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) -{ - /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html - * We don't want to deal with JNI here (and we don't have Java on b2g anyways), - * so we just dlopen the library and get the two symbols we need. */ + // Voice recognition is the lowest latency, according to the docs. Camcorder + // uses a microphone that is in the same direction as the camera. + SLint32 streamType = stm->voice ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION + : SL_ANDROID_RECORDING_PRESET_CAMCORDER; + + res = (*recorderConfig) + ->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, + &streamType, sizeof(SLint32)); + + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to set the android configuration to VOICE for the recorder. " + "Error code: %lu", res); + return CUBEB_ERROR; + } + } + // realize the audio recorder + res = (*stm->recorderObj)->Realize(stm->recorderObj, SL_BOOLEAN_FALSE); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to realize recorder. Error code: %lu", res); + return CUBEB_ERROR; + } + // get the record interface + res = (*stm->recorderObj)->GetInterface(stm->recorderObj, + stm->context->SL_IID_RECORD, + &stm->recorderItf); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to get recorder interface. Error code: %lu", res); + return CUBEB_ERROR; + } - int r; - void * libmedia; - size_t (*get_primary_output_frame_count)(void); - int (*get_output_frame_count)(size_t * frameCount, int streamType); - uint32_t primary_sampling_rate; - size_t primary_buffer_size; + res = (*stm->recorderItf)->RegisterCallback(stm->recorderItf, recorder_marker_callback, stm); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to register recorder marker callback. Error code: %lu", res); + return CUBEB_ERROR; + } - r = opensl_get_preferred_sample_rate(ctx, &primary_sampling_rate); + (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0); - if (r) { + res = (*stm->recorderItf)->SetCallbackEventsMask(stm->recorderItf, (SLuint32)SL_RECORDEVENT_HEADATMARKER); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to set headatmarker event mask. Error code: %lu", res); + return CUBEB_ERROR; + } + // get the simple android buffer queue interface + res = (*stm->recorderObj)->GetInterface(stm->recorderObj, + stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &stm->recorderBufferQueueItf); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to get recorder (android) buffer queue interface. Error code: %lu", res); return CUBEB_ERROR; } - libmedia = dlopen("libmedia.so", RTLD_LAZY); - if (!libmedia) { + // register callback on record (input) buffer queue + slAndroidSimpleBufferQueueCallback rec_callback = recorder_callback; + if (stm->output_enabled) { + // Register full duplex callback instead. + rec_callback = recorder_fullduplex_callback; + } + res = (*stm->recorderBufferQueueItf)->RegisterCallback(stm->recorderBufferQueueItf, + rec_callback, + stm); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to register recorder buffer queue callback. Error code: %lu", res); return CUBEB_ERROR; } - /* JB variant */ - /* size_t AudioSystem::getPrimaryOutputFrameCount(void) */ - get_primary_output_frame_count = - dlsym(libmedia, "_ZN7android11AudioSystem26getPrimaryOutputFrameCountEv"); - if (!get_primary_output_frame_count) { - /* ICS variant */ - /* status_t AudioSystem::getOutputFrameCount(int* frameCount, int streamType) */ - get_output_frame_count = - dlsym(libmedia, "_ZN7android11AudioSystem19getOutputFrameCountEPii"); - if (!get_output_frame_count) { - dlclose(libmedia); - return CUBEB_ERROR; - } + // Calculate length of input buffer according to requested latency + stm->input_frame_size = params->channels * sizeof(int16_t); + stm->input_buffer_length = (stm->input_frame_size * stm->buffer_size_frames); + + // Calculate the capacity of input array + stm->input_array_capacity = NBUFS; + if (stm->output_enabled) { + // Full duplex, update capacity to hold 1 sec of data + stm->input_array_capacity = 1 * stm->input_device_rate / stm->input_buffer_length; + } + // Allocate input array + stm->input_buffer_array = (void**)calloc(1, sizeof(void*)*stm->input_array_capacity); + // Buffering has not started yet. + stm->input_buffer_index = -1; + // Prepare input buffers + for(uint32_t i = 0; i < stm->input_array_capacity; ++i) { + stm->input_buffer_array[i] = calloc(1, stm->input_buffer_length); } - if (get_primary_output_frame_count) { - primary_buffer_size = get_primary_output_frame_count(); - } else { - if (get_output_frame_count(&primary_buffer_size, params.stream_type) != 0) { - return CUBEB_ERROR; - } + // On full duplex allocate input queue and silent buffer + if (stm->output_enabled) { + stm->input_queue = array_queue_create(stm->input_array_capacity); + assert(stm->input_queue); + stm->input_silent_buffer = calloc(1, stm->input_buffer_length); + assert(stm->input_silent_buffer); } - /* To get a fast track in Android's mixer, we need to be at the native - * samplerate, which is device dependant. Some devices might be able to - * resample when playing a fast track, but it's pretty rare. */ - *latency_frames = NBUFS * primary_buffer_size; + // Enqueue buffer to start rolling once recorder started + r = opensl_enqueue_recorder(stm, NULL); + if (r != CUBEB_OK) { + return r; + } - dlclose(libmedia); + LOG("Cubeb stream init recorder success"); return CUBEB_OK; } -static void -opensl_destroy(cubeb * ctx) -{ - if (ctx->outmixObj) - (*ctx->outmixObj)->Destroy(ctx->outmixObj); - if (ctx->engObj) - cubeb_destroy_sles_engine(&ctx->engObj); - dlclose(ctx->lib); - dlclose(ctx->libmedia); - free(ctx); -} - -static void opensl_stream_destroy(cubeb_stream * stm); - static int -opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, - cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency_frames, - cubeb_data_callback data_callback, cubeb_state_callback state_callback, - void * user_ptr) -{ - cubeb_stream * stm; - - assert(ctx); - assert(!input_stream_params && "not supported"); - if (input_device || output_device) { - /* Device selection not yet implemented. */ - return CUBEB_ERROR_DEVICE_UNAVAILABLE; - } - - *stream = NULL; - - SLDataFormat_PCM format; - - format.formatType = SL_DATAFORMAT_PCM; - format.numChannels = output_stream_params->channels; - // samplesPerSec is in milliHertz - format.samplesPerSec = output_stream_params->rate * 1000; - format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - format.channelMask = output_stream_params->channels == 1 ? - SL_SPEAKER_FRONT_CENTER : - SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - - switch (output_stream_params->format) { - case CUBEB_SAMPLE_S16LE: - format.endianness = SL_BYTEORDER_LITTLEENDIAN; - break; - case CUBEB_SAMPLE_S16BE: - format.endianness = SL_BYTEORDER_BIGENDIAN; - break; - default: - return CUBEB_ERROR_INVALID_FORMAT; - } - - stm = calloc(1, sizeof(*stm)); +opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { assert(stm); + assert(params); - stm->context = ctx; - stm->data_callback = data_callback; - stm->state_callback = state_callback; - stm->user_ptr = user_ptr; - - stm->inputrate = output_stream_params->rate; - stm->latency = latency_frames; - stm->stream_type = output_stream_params->stream_type; - stm->framesize = output_stream_params->channels * sizeof(int16_t); + stm->user_output_rate = params->rate; + stm->framesize = params->channels * sizeof(int16_t); stm->lastPosition = -1; stm->lastPositionTimeStamp = 0; stm->lastCompensativePosition = -1; - int r = pthread_mutex_init(&stm->mutex, NULL); - assert(r == 0); + SLDataFormat_PCM format; + int r = opensl_set_format(&format, params); + if (r != CUBEB_OK) { + return CUBEB_ERROR_INVALID_FORMAT; + } SLDataLocator_BufferQueue loc_bufq; loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE; @@ -563,15 +1040,15 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name SLDataLocator_OutputMix loc_outmix; loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; - loc_outmix.outputMix = ctx->outmixObj; + loc_outmix.outputMix = stm->context->outmixObj; SLDataSink sink; sink.pLocator = &loc_outmix; sink.pFormat = NULL; #if defined(__ANDROID__) - const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, - ctx->SL_IID_VOLUME, - ctx->SL_IID_ANDROIDCONFIGURATION}; + const SLInterfaceID ids[] = {stm->context->SL_IID_BUFFERQUEUE, + stm->context->SL_IID_VOLUME, + stm->context->SL_IID_ANDROIDCONFIGURATION}; const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; #else const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME}; @@ -579,115 +1056,166 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name #endif assert(NELEMS(ids) == NELEMS(req)); - uint32_t preferred_sampling_rate = stm->inputrate; -#if defined(__ANDROID__) - if (get_android_version() >= ANDROID_VERSION_MARSHMALLOW) { - // Reset preferred samping rate to trigger fallback to native sampling rate. - preferred_sampling_rate = 0; - if (opensl_get_min_latency(ctx, *output_stream_params, &latency_frames) != CUBEB_OK) { - // Default to AudioFlinger's advertised fast track latency of 10ms. - latency_frames = 440; - } - stm->latency = latency_frames; - } -#endif - + uint32_t preferred_sampling_rate = stm->user_output_rate; SLresult res = SL_RESULT_CONTENT_UNSUPPORTED; if (preferred_sampling_rate) { - res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, &source, - &sink, NELEMS(ids), ids, req); + res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng, + &stm->playerObj, + &source, + &sink, + NELEMS(ids), + ids, + req); } // Sample rate not supported? Try again with primary sample rate! - if (res == SL_RESULT_CONTENT_UNSUPPORTED) { - if (opensl_get_preferred_sample_rate(ctx, &preferred_sampling_rate)) { - opensl_stream_destroy(stm); - return CUBEB_ERROR; - } - + if (res == SL_RESULT_CONTENT_UNSUPPORTED && + preferred_sampling_rate != DEFAULT_SAMPLE_RATE) { + preferred_sampling_rate = DEFAULT_SAMPLE_RATE; format.samplesPerSec = preferred_sampling_rate * 1000; - res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, - &source, &sink, NELEMS(ids), ids, req); + res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng, + &stm->playerObj, + &source, + &sink, + NELEMS(ids), + ids, + req); } if (res != SL_RESULT_SUCCESS) { - opensl_stream_destroy(stm); + LOG("Failed to create audio player. Error code: %lu", res); return CUBEB_ERROR; } - stm->outputrate = preferred_sampling_rate; - stm->bytespersec = stm->outputrate * stm->framesize; - stm->queuebuf_len = stm->framesize * latency_frames / NBUFS; - // round up to the next multiple of stm->framesize, if needed. - if (stm->queuebuf_len % stm->framesize) { - stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize); + stm->output_configured_rate = preferred_sampling_rate; + stm->bytespersec = stm->output_configured_rate * stm->framesize; + stm->queuebuf_len = stm->framesize * stm->buffer_size_frames; + + // Calculate the capacity of input array + stm->queuebuf_capacity = NBUFS; + if (stm->output_enabled) { + // Full duplex, update capacity to hold 1 sec of data + stm->queuebuf_capacity = 1 * stm->output_configured_rate / stm->queuebuf_len; + } + // Allocate input array + stm->queuebuf = (void**)calloc(1, sizeof(void*) * stm->queuebuf_capacity); + for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) { + stm->queuebuf[i] = calloc(1, stm->queuebuf_len); + assert(stm->queuebuf[i]); } - cubeb_stream_params params = *output_stream_params; - params.rate = preferred_sampling_rate; + SLAndroidConfigurationItf playerConfig = NULL; - stm->resampler = cubeb_resampler_create(stm, NULL, ¶ms, - output_stream_params->rate, - data_callback, - user_ptr, - CUBEB_RESAMPLER_QUALITY_DEFAULT); + if (get_android_version() >= ANDROID_VERSION_N_MR1) { + res = (*stm->playerObj) + ->GetInterface(stm->playerObj, + stm->context->SL_IID_ANDROIDCONFIGURATION, + &playerConfig); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to get Android configuration interface. Error code: %lu", res); + return CUBEB_ERROR; + } - if (!stm->resampler) { - opensl_stream_destroy(stm); - return CUBEB_ERROR; - } + SLint32 streamType = SL_ANDROID_STREAM_MEDIA; + if (stm->voice) { + streamType = SL_ANDROID_STREAM_VOICE; + } + res = (*playerConfig)->SetConfiguration(playerConfig, + SL_ANDROID_KEY_STREAM_TYPE, + &streamType, + sizeof(streamType)); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to set Android configuration to %d Error code: %lu", + streamType, res); + } - int i; - for (i = 0; i < NBUFS; i++) { - stm->queuebuf[i] = malloc(stm->queuebuf_len); - assert(stm->queuebuf[i]); - } + SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_LATENCY; + if (stm->buffer_size_frames > POWERSAVE_LATENCY_FRAMES_THRESHOLD) { + performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING; + } -#if defined(__ANDROID__) - SLuint32 stream_type = convert_stream_type_to_sl_stream(output_stream_params->stream_type); - if (stream_type != 0xFFFFFFFF) { - SLAndroidConfigurationItf playerConfig; - res = (*stm->playerObj)->GetInterface(stm->playerObj, - ctx->SL_IID_ANDROIDCONFIGURATION, &playerConfig); res = (*playerConfig)->SetConfiguration(playerConfig, - SL_ANDROID_KEY_STREAM_TYPE, &stream_type, sizeof(SLint32)); + SL_ANDROID_KEY_PERFORMANCE_MODE, + &performanceMode, + sizeof(performanceMode)); if (res != SL_RESULT_SUCCESS) { - opensl_stream_destroy(stm); - return CUBEB_ERROR; + LOG("Failed to set Android performance mode to %d Error code: %lu. This is" + " not fatal", performanceMode, res); } } -#endif res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE); if (res != SL_RESULT_SUCCESS) { - opensl_stream_destroy(stm); + LOG("Failed to realize player object. Error code: %lu", res); return CUBEB_ERROR; } - res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_PLAY, &stm->play); + // There are two ways of getting the audio output latency: + // - a configuration value, only available on some devices (notably devices + // running FireOS) + // - A Java method, that we call using JNI. + // + // The first method is prefered, if available, because it can account for more + // latency causes, and is more precise. + + // Latency has to be queried after the realization of the interface, when + // using SL_IID_ANDROIDCONFIGURATION. + SLuint32 audioLatency = 0; + SLuint32 paramSize = sizeof(SLuint32); + // The reported latency is in milliseconds. + if (playerConfig) { + res = (*playerConfig)->GetConfiguration(playerConfig, + (const SLchar *)"androidGetAudioLatency", + ¶mSize, + &audioLatency); + if (res == SL_RESULT_SUCCESS) { + LOG("Got playback latency using android configuration extension"); + stm->output_latency_ms = audioLatency; + } + } + // `playerConfig` is available, but the above failed, or `playerConfig` is not + // available. In both cases, we need to acquire the output latency by an other + // mean. + if ((playerConfig && res != SL_RESULT_SUCCESS) || + !playerConfig) { + if (cubeb_output_latency_method_is_loaded(stm->context->p_output_latency_function)) { + LOG("Got playback latency using JNI"); + stm->output_latency_ms = cubeb_get_output_latency(stm->context->p_output_latency_function); + } else { + LOG("No alternate latency querying method loaded, A/V sync will be off."); + stm->output_latency_ms = 0; + } + } + + LOG("Audio output latency: %dms", stm->output_latency_ms); + + res = (*stm->playerObj)->GetInterface(stm->playerObj, + stm->context->SL_IID_PLAY, + &stm->play); if (res != SL_RESULT_SUCCESS) { - opensl_stream_destroy(stm); + LOG("Failed to get play interface. Error code: %lu", res); return CUBEB_ERROR; } - res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE, + res = (*stm->playerObj)->GetInterface(stm->playerObj, + stm->context->SL_IID_BUFFERQUEUE, &stm->bufq); if (res != SL_RESULT_SUCCESS) { - opensl_stream_destroy(stm); + LOG("Failed to get bufferqueue interface. Error code: %lu", res); return CUBEB_ERROR; } - res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_VOLUME, + res = (*stm->playerObj)->GetInterface(stm->playerObj, + stm->context->SL_IID_VOLUME, &stm->volume); - if (res != SL_RESULT_SUCCESS) { - opensl_stream_destroy(stm); + LOG("Failed to get volume interface. Error code: %lu", res); return CUBEB_ERROR; } res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm); if (res != SL_RESULT_SUCCESS) { - opensl_stream_destroy(stm); + LOG("Failed to register play callback. Error code: %lu", res); return CUBEB_ERROR; } @@ -696,13 +1224,17 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name res = (*stm->play)->SetCallbackEventsMask(stm->play, (SLuint32)SL_PLAYEVENT_HEADATMARKER); if (res != SL_RESULT_SUCCESS) { - opensl_stream_destroy(stm); + LOG("Failed to set headatmarker event mask. Error code: %lu", res); return CUBEB_ERROR; } - res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm); + slBufferQueueCallback player_callback = bufferqueue_callback; + if (stm->input_enabled) { + player_callback = player_fullduplex_callback; + } + res = (*stm->bufq)->RegisterCallback(stm->bufq, player_callback, stm); if (res != SL_RESULT_SUCCESS) { - opensl_stream_destroy(stm); + LOG("Failed to register bufferqueue callback. Error code: %lu", res); return CUBEB_ERROR; } @@ -717,55 +1249,340 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name assert(res == SL_RESULT_SUCCESS); } - *stream = stm; + LOG("Cubeb stream init playback success"); return CUBEB_OK; } -static void -opensl_stream_destroy(cubeb_stream * stm) +static int +opensl_validate_stream_param(cubeb_stream_params * stream_params) { - if (stm->playerObj) - (*stm->playerObj)->Destroy(stm->playerObj); - int i; - for (i = 0; i < NBUFS; i++) { - free(stm->queuebuf[i]); + if ((stream_params && + (stream_params->channels < 1 || stream_params->channels > 32))) { + return CUBEB_ERROR_INVALID_FORMAT; } - pthread_mutex_destroy(&stm->mutex); + if ((stream_params && + (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) { + LOG("Loopback is not supported"); + return CUBEB_ERROR_NOT_SUPPORTED; + } + return CUBEB_OK; +} - cubeb_resampler_destroy(stm->resampler); +int has_pref_set(cubeb_stream_params* input_params, + cubeb_stream_params* output_params, + cubeb_stream_prefs pref) +{ + return (input_params && input_params->prefs & pref) || + (output_params && output_params->prefs & pref); +} - free(stm); +static int +opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, cubeb_state_callback state_callback, + void * user_ptr) +{ + cubeb_stream * stm; + + assert(ctx); + if (input_device || output_device) { + LOG("Device selection is not supported in Android. The default will be used"); + } + + *stream = NULL; + + int r = opensl_validate_stream_param(output_stream_params); + if(r != CUBEB_OK) { + LOG("Output stream params not valid"); + return r; + } + r = opensl_validate_stream_param(input_stream_params); + if(r != CUBEB_OK) { + LOG("Input stream params not valid"); + return r; + } + + stm = calloc(1, sizeof(*stm)); + assert(stm); + + stm->context = ctx; + stm->data_callback = data_callback; + stm->state_callback = state_callback; + stm->user_ptr = user_ptr; + stm->buffer_size_frames = latency_frames ? latency_frames : DEFAULT_NUM_OF_FRAMES; + stm->input_enabled = (input_stream_params) ? 1 : 0; + stm->output_enabled = (output_stream_params) ? 1 : 0; + stm->shutdown = 1; + stm->voice = has_pref_set(input_stream_params, output_stream_params, CUBEB_STREAM_PREF_VOICE); + + LOG("cubeb stream prefs: voice: %s", stm->voice ? "true" : "false"); + + +#ifdef DEBUG + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + r = pthread_mutex_init(&stm->mutex, &attr); +#else + r = pthread_mutex_init(&stm->mutex, NULL); +#endif + assert(r == 0); + + if (output_stream_params) { + LOG("Playback params: Rate %d, channels %d, format %d, latency in frames %d.", + output_stream_params->rate, output_stream_params->channels, + output_stream_params->format, stm->buffer_size_frames); + r = opensl_configure_playback(stm, output_stream_params); + if (r != CUBEB_OK) { + opensl_stream_destroy(stm); + return r; + } + } + + if (input_stream_params) { + LOG("Capture params: Rate %d, channels %d, format %d, latency in frames %d.", + input_stream_params->rate, input_stream_params->channels, + input_stream_params->format, stm->buffer_size_frames); + r = opensl_configure_capture(stm, input_stream_params); + if (r != CUBEB_OK) { + opensl_stream_destroy(stm); + return r; + } + } + + /* Configure resampler*/ + uint32_t target_sample_rate; + if (input_stream_params) { + target_sample_rate = input_stream_params->rate; + } else { + assert(output_stream_params); + target_sample_rate = output_stream_params->rate; + } + + // Use the actual configured rates for input + // and output. + cubeb_stream_params input_params; + if (input_stream_params) { + input_params = *input_stream_params; + input_params.rate = stm->input_device_rate; + } + cubeb_stream_params output_params; + if (output_stream_params) { + output_params = *output_stream_params; + output_params.rate = stm->output_configured_rate; + } + + stm->resampler = cubeb_resampler_create(stm, + input_stream_params ? &input_params : NULL, + output_stream_params ? &output_params : NULL, + target_sample_rate, + data_callback, + user_ptr, + CUBEB_RESAMPLER_QUALITY_DEFAULT); + if (!stm->resampler) { + LOG("Failed to create resampler"); + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + + *stream = stm; + LOG("Cubeb stream (%p) init success", stm); + return CUBEB_OK; +} + +static int +opensl_start_player(cubeb_stream * stm) +{ + assert(stm->playerObj); + SLuint32 playerState; + (*stm->playerObj)->GetState(stm->playerObj, &playerState); + if (playerState == SL_OBJECT_STATE_REALIZED) { + SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING); + if(res != SL_RESULT_SUCCESS) { + LOG("Failed to start player. Error code: %lu", res); + return CUBEB_ERROR; + } + } + return CUBEB_OK; +} + +static int +opensl_start_recorder(cubeb_stream * stm) +{ + assert(stm->recorderObj); + SLuint32 recorderState; + (*stm->recorderObj)->GetState(stm->recorderObj, &recorderState); + if (recorderState == SL_OBJECT_STATE_REALIZED) { + SLresult res = (*stm->recorderItf)->SetRecordState(stm->recorderItf, SL_RECORDSTATE_RECORDING); + if(res != SL_RESULT_SUCCESS) { + LOG("Failed to start recorder. Error code: %lu", res); + return CUBEB_ERROR; + } + } + return CUBEB_OK; } static int opensl_stream_start(cubeb_stream * stm) { - SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING); - if (res != SL_RESULT_SUCCESS) - return CUBEB_ERROR; + assert(stm); + + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + opensl_set_shutdown(stm, 0); + opensl_set_draining(stm, 0); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (stm->playerObj) { + r = opensl_start_player(stm); + if (r != CUBEB_OK) { + return r; + } + } + + if (stm->recorderObj) { + int r = opensl_start_recorder(stm); + if (r != CUBEB_OK) { + return r; + } + } + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); + LOG("Cubeb stream (%p) started", stm); return CUBEB_OK; } static int -opensl_stream_stop(cubeb_stream * stm) +opensl_stop_player(cubeb_stream * stm) { + assert(stm->playerObj); + assert(stm->shutdown || stm->draining); + SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); - if (res != SL_RESULT_SUCCESS) + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to stop player. Error code: %lu", res); + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +static int +opensl_stop_recorder(cubeb_stream * stm) +{ + assert(stm->recorderObj); + assert(stm->shutdown || stm->draining); + + SLresult res = (*stm->recorderItf)->SetRecordState(stm->recorderItf, SL_RECORDSTATE_PAUSED); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to stop recorder. Error code: %lu", res); return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +static int +opensl_stream_stop(cubeb_stream * stm) +{ + assert(stm); + + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + opensl_set_shutdown(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (stm->playerObj) { + r = opensl_stop_player(stm); + if (r != CUBEB_OK) { + return r; + } + } + + if (stm->recorderObj) { + int r = opensl_stop_recorder(stm); + if (r != CUBEB_OK) { + return r; + } + } + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + LOG("Cubeb stream (%p) stopped", stm); return CUBEB_OK; } +static int +opensl_destroy_recorder(cubeb_stream * stm) +{ + assert(stm); + assert(stm->recorderObj); + + if (stm->recorderBufferQueueItf) { + SLresult res = (*stm->recorderBufferQueueItf)->Clear(stm->recorderBufferQueueItf); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to clear recorder buffer queue. Error code: %lu", res); + return CUBEB_ERROR; + } + stm->recorderBufferQueueItf = NULL; + for (uint32_t i = 0; i < stm->input_array_capacity; ++i) { + free(stm->input_buffer_array[i]); + } + } + + (*stm->recorderObj)->Destroy(stm->recorderObj); + stm->recorderObj = NULL; + stm->recorderItf = NULL; + + if (stm->input_queue) { + array_queue_destroy(stm->input_queue); + } + free(stm->input_silent_buffer); + + return CUBEB_OK; +} + +static void +opensl_stream_destroy(cubeb_stream * stm) +{ + assert(stm->draining || stm->shutdown); + + if (stm->playerObj) { + (*stm->playerObj)->Destroy(stm->playerObj); + stm->playerObj = NULL; + stm->play = NULL; + stm->bufq = NULL; + for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) { + free(stm->queuebuf[i]); + } + } + + if (stm->recorderObj) { + int r = opensl_destroy_recorder(stm); + assert(r == CUBEB_OK); + } + + if (stm->resampler) { + cubeb_resampler_destroy(stm->resampler); + } + + pthread_mutex_destroy(&stm->mutex); + + LOG("Cubeb stream (%p) destroyed", stm); + free(stm); +} + static int opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) { SLmillisecond msec; - uint64_t samplerate; - SLresult res; - int r; - uint32_t mixer_latency; uint32_t compensation_msec = 0; + SLresult res; res = (*stm->play)->GetPosition(stm->play, &msec); if (res != SL_RESULT_SUCCESS) @@ -781,27 +1598,23 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) stm->lastPosition = msec; } - samplerate = stm->inputrate; - - r = stm->context->get_output_latency(&mixer_latency, stm->stream_type); - if (r) { - return CUBEB_ERROR; - } + uint64_t samplerate = stm->user_output_rate; + uint32_t output_latency = stm->output_latency_ms; pthread_mutex_lock(&stm->mutex); - int64_t maximum_position = stm->written * (int64_t)stm->inputrate / stm->outputrate; + int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate / stm->output_configured_rate; pthread_mutex_unlock(&stm->mutex); assert(maximum_position >= 0); - if (msec > mixer_latency) { + if (msec > output_latency) { int64_t unadjusted_position; if (stm->lastCompensativePosition > msec + compensation_msec) { // Over compensation, use lastCompensativePosition. unadjusted_position = - samplerate * (stm->lastCompensativePosition - mixer_latency) / 1000; + samplerate * (stm->lastCompensativePosition - output_latency) / 1000; } else { unadjusted_position = - samplerate * (msec - mixer_latency + compensation_msec) / 1000; + samplerate * (msec - output_latency + compensation_msec) / 1000; stm->lastCompensativePosition = msec + compensation_msec; } *position = unadjusted_position < maximum_position ? @@ -812,24 +1625,6 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) return CUBEB_OK; } -int -opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency) -{ - int r; - uint32_t mixer_latency; // The latency returned by AudioFlinger is in ms. - - /* audio_stream_type_t is an int, so this is okay. */ - r = stm->context->get_output_latency(&mixer_latency, stm->stream_type); - if (r) { - return CUBEB_ERROR; - } - - *latency = stm->latency * stm->inputrate / 1000 + // OpenSL latency - mixer_latency * stm->inputrate / 1000; // AudioFlinger latency - - return CUBEB_OK; -} - int opensl_stream_set_volume(cubeb_stream * stm, float volume) { @@ -865,18 +1660,19 @@ static struct cubeb_ops const opensl_ops = { .init = opensl_init, .get_backend_id = opensl_get_backend_id, .get_max_channel_count = opensl_get_max_channel_count, - .get_min_latency = opensl_get_min_latency, - .get_preferred_sample_rate = opensl_get_preferred_sample_rate, + .get_min_latency = NULL, + .get_preferred_sample_rate = NULL, .enumerate_devices = NULL, + .device_collection_destroy = NULL, .destroy = opensl_destroy, .stream_init = opensl_stream_init, .stream_destroy = opensl_stream_destroy, .stream_start = opensl_stream_start, .stream_stop = opensl_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = opensl_stream_get_position, - .stream_get_latency = opensl_stream_get_latency, + .stream_get_latency = NULL, .stream_set_volume = opensl_stream_set_volume, - .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, diff --git a/media/libcubeb/src/cubeb_pulse.c b/media/libcubeb/src/cubeb_pulse.c index 4f474452d..846ba426a 100644 --- a/media/libcubeb/src/cubeb_pulse.c +++ b/media/libcubeb/src/cubeb_pulse.c @@ -7,12 +7,14 @@ #undef NDEBUG #include #include -#include #include +#include +#include #include -#include "cubeb/cubeb.h" #include "cubeb-internal.h" -#include +#include "cubeb/cubeb.h" +#include "cubeb_mixer.h" +#include "cubeb_strings.h" #ifdef DISABLE_LIBPULSE_DLOPEN #define WRAP(x) x @@ -20,13 +22,14 @@ #define WRAP(x) cubeb_##x #define LIBPULSE_API_VISIT(X) \ X(pa_channel_map_can_balance) \ - X(pa_channel_map_init_auto) \ + X(pa_channel_map_init) \ X(pa_context_connect) \ X(pa_context_disconnect) \ X(pa_context_drain) \ X(pa_context_get_server_info) \ X(pa_context_get_sink_info_by_name) \ X(pa_context_get_sink_info_list) \ + X(pa_context_get_sink_input_info) \ X(pa_context_get_source_info_list) \ X(pa_context_get_state) \ X(pa_context_new) \ @@ -81,33 +84,50 @@ X(pa_context_set_subscribe_callback) \ X(pa_context_subscribe) \ X(pa_mainloop_api_once) \ + X(pa_get_library_version) \ + X(pa_channel_map_init_auto) \ #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; LIBPULSE_API_VISIT(MAKE_TYPEDEF); #undef MAKE_TYPEDEF #endif +#if PA_CHECK_VERSION(2, 0, 0) +static int has_pulse_v2 = 0; +#endif + static struct cubeb_ops const pulse_ops; +struct cubeb_default_sink_info { + pa_channel_map channel_map; + uint32_t sample_spec_rate; + pa_sink_flags_t flags; +}; + struct cubeb { struct cubeb_ops const * ops; void * libpulse; pa_threaded_mainloop * mainloop; pa_context * context; - pa_sink_info * default_sink_info; + struct cubeb_default_sink_info * default_sink_info; char * context_name; int error; - cubeb_device_collection_changed_callback collection_changed_callback; - void * collection_changed_user_ptr; + cubeb_device_collection_changed_callback output_collection_changed_callback; + void * output_collection_changed_user_ptr; + cubeb_device_collection_changed_callback input_collection_changed_callback; + void * input_collection_changed_user_ptr; + cubeb_strings * device_ids; }; struct cubeb_stream { + /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; + void * user_ptr; + /**/ pa_stream * output_stream; pa_stream * input_stream; cubeb_data_callback data_callback; cubeb_state_callback state_callback; - void * user_ptr; pa_time_event * drain_timer; pa_sample_spec output_sample_spec; pa_sample_spec input_sample_spec; @@ -124,6 +144,24 @@ enum cork_state { NOTIFY = 1 << 1 }; +static int +intern_device_id(cubeb * ctx, char const ** id) +{ + char const * interned; + + assert(ctx); + assert(id); + + interned = cubeb_strings_intern(ctx->device_ids, *id); + if (!interned) { + return CUBEB_ERROR; + } + + *id = interned; + + return CUBEB_OK; +} + static void sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u) { @@ -131,8 +169,10 @@ sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, voi cubeb * ctx = u; if (!eol) { free(ctx->default_sink_info); - ctx->default_sink_info = malloc(sizeof(pa_sink_info)); - memcpy(ctx->default_sink_info, info, sizeof(pa_sink_info)); + ctx->default_sink_info = malloc(sizeof(struct cubeb_default_sink_info)); + memcpy(&ctx->default_sink_info->channel_map, &info->channel_map, sizeof(pa_channel_map)); + ctx->default_sink_info->sample_spec_rate = info->sample_spec.rate; + ctx->default_sink_info->flags = info->flags; } WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0); } @@ -140,7 +180,11 @@ sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, voi static void server_info_callback(pa_context * context, const pa_server_info * info, void * u) { - WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u); + pa_operation * o; + o = WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u); + if (o) { + WRAP(pa_operation_unref)(o); + } } static void @@ -467,12 +511,81 @@ stream_update_timing_info(cubeb_stream * stm) return r; } +static pa_channel_position_t +cubeb_channel_to_pa_channel(cubeb_channel channel) +{ + switch (channel) { + case CHANNEL_FRONT_LEFT: + return PA_CHANNEL_POSITION_FRONT_LEFT; + case CHANNEL_FRONT_RIGHT: + return PA_CHANNEL_POSITION_FRONT_RIGHT; + case CHANNEL_FRONT_CENTER: + return PA_CHANNEL_POSITION_FRONT_CENTER; + case CHANNEL_LOW_FREQUENCY: + return PA_CHANNEL_POSITION_LFE; + case CHANNEL_BACK_LEFT: + return PA_CHANNEL_POSITION_REAR_LEFT; + case CHANNEL_BACK_RIGHT: + return PA_CHANNEL_POSITION_REAR_RIGHT; + case CHANNEL_FRONT_LEFT_OF_CENTER: + return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + case CHANNEL_FRONT_RIGHT_OF_CENTER: + return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + case CHANNEL_BACK_CENTER: + return PA_CHANNEL_POSITION_REAR_CENTER; + case CHANNEL_SIDE_LEFT: + return PA_CHANNEL_POSITION_SIDE_LEFT; + case CHANNEL_SIDE_RIGHT: + return PA_CHANNEL_POSITION_SIDE_RIGHT; + case CHANNEL_TOP_CENTER: + return PA_CHANNEL_POSITION_TOP_CENTER; + case CHANNEL_TOP_FRONT_LEFT: + return PA_CHANNEL_POSITION_TOP_FRONT_LEFT; + case CHANNEL_TOP_FRONT_CENTER: + return PA_CHANNEL_POSITION_TOP_FRONT_CENTER; + case CHANNEL_TOP_FRONT_RIGHT: + return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; + case CHANNEL_TOP_BACK_LEFT: + return PA_CHANNEL_POSITION_TOP_REAR_LEFT; + case CHANNEL_TOP_BACK_CENTER: + return PA_CHANNEL_POSITION_TOP_REAR_CENTER; + case CHANNEL_TOP_BACK_RIGHT: + return PA_CHANNEL_POSITION_TOP_REAR_RIGHT; + default: + return PA_CHANNEL_POSITION_INVALID; + } +} + +static void +layout_to_channel_map(cubeb_channel_layout layout, pa_channel_map * cm) +{ + assert(cm && layout != CUBEB_LAYOUT_UNDEFINED); + + WRAP(pa_channel_map_init)(cm); + + uint32_t channels = 0; + cubeb_channel_layout channelMap = layout; + for (uint32_t i = 0 ; channelMap != 0; ++i) { + uint32_t channel = (channelMap & 1) << i; + if (channel != 0) { + cm->map[channels] = cubeb_channel_to_pa_channel(channel); + channels++; + } + channelMap = channelMap >> 1; + } + unsigned int channels_from_layout = cubeb_channel_layout_nb_channels(layout); + assert(channels_from_layout <= UINT8_MAX); + cm->channels = (uint8_t) channels_from_layout; +} + static void pulse_context_destroy(cubeb * ctx); static void pulse_destroy(cubeb * ctx); static int pulse_context_init(cubeb * ctx) { + int r; + if (ctx->context) { assert(ctx->error == 1); pulse_context_destroy(ctx); @@ -486,9 +599,9 @@ pulse_context_init(cubeb * ctx) WRAP(pa_context_set_state_callback)(ctx->context, context_state_callback, ctx); WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); - WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL); + r = WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL); - if (wait_until_context_ready(ctx) != 0) { + if (r < 0 || wait_until_context_ready(ctx) != 0) { WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); pulse_context_destroy(ctx); ctx->context = NULL; @@ -502,18 +615,25 @@ pulse_context_init(cubeb * ctx) return 0; } +static int pulse_subscribe_notifications(cubeb * context, + pa_subscription_mask_t mask); + /*static*/ int pulse_init(cubeb ** context, char const * context_name) { void * libpulse = NULL; cubeb * ctx; + pa_operation * o; *context = NULL; #ifndef DISABLE_LIBPULSE_DLOPEN libpulse = dlopen("libpulse.so.0", RTLD_LAZY); if (!libpulse) { - return CUBEB_ERROR; + libpulse = dlopen("libpulse.so", RTLD_LAZY); + if (!libpulse) { + return CUBEB_ERROR; + } } #define LOAD(x) { \ @@ -528,11 +648,20 @@ pulse_init(cubeb ** context, char const * context_name) #undef LOAD #endif +#if PA_CHECK_VERSION(2, 0, 0) + const char* version = WRAP(pa_get_library_version)(); + has_pulse_v2 = strtol(version, NULL, 10) >= 2; +#endif + ctx = calloc(1, sizeof(*ctx)); assert(ctx); ctx->ops = &pulse_ops; ctx->libpulse = libpulse; + if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) { + pulse_destroy(ctx); + return CUBEB_ERROR; + } ctx->mainloop = WRAP(pa_threaded_mainloop_new)(); ctx->default_sink_info = NULL; @@ -545,10 +674,20 @@ pulse_init(cubeb ** context, char const * context_name) return CUBEB_ERROR; } + /* server_info_callback performs a second async query, which is + responsible for initializing default_sink_info and signalling the + mainloop to end the wait. */ WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); - WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx); + o = WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx); + if (o) { + operation_wait(ctx, NULL, o); + WRAP(pa_operation_unref)(o); + } WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); + /* Update `default_sink_info` when the default device changes. */ + pulse_subscribe_notifications(ctx, PA_SUBSCRIPTION_MASK_SERVER); + *context = ctx; return CUBEB_OK; @@ -567,11 +706,8 @@ pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) (void)ctx; assert(ctx && max_channels); - WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); - while (!ctx->default_sink_info) { - WRAP(pa_threaded_mainloop_wait)(ctx->mainloop); - } - WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); + if (!ctx->default_sink_info) + return CUBEB_ERROR; *max_channels = ctx->default_sink_info->channel_map.channels; @@ -584,13 +720,10 @@ pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) assert(ctx && rate); (void)ctx; - WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); - while (!ctx->default_sink_info) { - WRAP(pa_threaded_mainloop_wait)(ctx->mainloop); - } - WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); + if (!ctx->default_sink_info) + return CUBEB_ERROR; - *rate = ctx->default_sink_info->sample_spec.rate; + *rate = ctx->default_sink_info->sample_spec_rate; return CUBEB_OK; } @@ -625,9 +758,7 @@ pulse_context_destroy(cubeb * ctx) static void pulse_destroy(cubeb * ctx) { - if (ctx->context_name) { - free(ctx->context_name); - } + free(ctx->context_name); if (ctx->context) { pulse_context_destroy(ctx); } @@ -637,12 +768,14 @@ pulse_destroy(cubeb * ctx) WRAP(pa_threaded_mainloop_free)(ctx->mainloop); } + if (ctx->device_ids) { + cubeb_strings_destroy(ctx->device_ids); + } + if (ctx->libpulse) { dlclose(ctx->libpulse); } - if (ctx->default_sink_info) { - free(ctx->default_sink_info); - } + free(ctx->default_sink_info); free(ctx); } @@ -665,6 +798,25 @@ to_pulse_format(cubeb_sample_format format) } } +static cubeb_channel_layout +pulse_default_layout_for_channels(uint32_t ch) +{ + assert (ch > 0 && ch <= 8); + switch (ch) { + case 1: return CUBEB_LAYOUT_MONO; + case 2: return CUBEB_LAYOUT_STEREO; + case 3: return CUBEB_LAYOUT_3F; + case 4: return CUBEB_LAYOUT_QUAD; + case 5: return CUBEB_LAYOUT_3F2; + case 6: return CUBEB_LAYOUT_3F_LFE | + CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT; + case 7: return CUBEB_LAYOUT_3F3R_LFE; + case 8: return CUBEB_LAYOUT_3F4_LFE; + } + // Never get here! + return CUBEB_LAYOUT_UNDEFINED; +} + static int create_pa_stream(cubeb_stream * stm, pa_stream ** pa_stm, @@ -672,15 +824,39 @@ create_pa_stream(cubeb_stream * stm, char const * stream_name) { assert(stm && stream_params); + assert(&stm->input_stream == pa_stm || (&stm->output_stream == pa_stm && + (stream_params->layout == CUBEB_LAYOUT_UNDEFINED || + (stream_params->layout != CUBEB_LAYOUT_UNDEFINED && + cubeb_channel_layout_nb_channels(stream_params->layout) == stream_params->channels)))); + if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { + return CUBEB_ERROR_NOT_SUPPORTED; + } *pa_stm = NULL; pa_sample_spec ss; ss.format = to_pulse_format(stream_params->format); if (ss.format == PA_SAMPLE_INVALID) return CUBEB_ERROR_INVALID_FORMAT; ss.rate = stream_params->rate; - ss.channels = stream_params->channels; - - *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL); + if (stream_params->channels > UINT8_MAX) + return CUBEB_ERROR_INVALID_FORMAT; + ss.channels = (uint8_t) stream_params->channels; + + if (stream_params->layout == CUBEB_LAYOUT_UNDEFINED) { + pa_channel_map cm; + if (stream_params->channels <= 8 && + !WRAP(pa_channel_map_init_auto)(&cm, stream_params->channels, PA_CHANNEL_MAP_DEFAULT)) { + LOG("Layout undefined and PulseAudio's default layout has not been configured, guess one."); + layout_to_channel_map(pulse_default_layout_for_channels(stream_params->channels), &cm); + *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm); + } else { + LOG("Layout undefined, PulseAudio will use its default."); + *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL); + } + } else { + pa_channel_map cm; + layout_to_channel_map(stream_params->layout, &cm); + *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm); + } return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK; } @@ -753,7 +929,7 @@ pulse_stream_init(cubeb * context, battr = set_buffering_attribute(latency_frames, &stm->output_sample_spec); WRAP(pa_stream_connect_playback)(stm->output_stream, - output_device, + (char const *) output_device, &battr, PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY, @@ -776,7 +952,7 @@ pulse_stream_init(cubeb * context, battr = set_buffering_attribute(latency_frames, &stm->input_sample_spec); WRAP(pa_stream_connect_record)(stm->input_stream, - input_device, + (char const *) input_device, &battr, PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY); @@ -796,7 +972,7 @@ pulse_stream_init(cubeb * context, return CUBEB_ERROR; } - if (g_log_level) { + if (g_cubeb_log_level) { if (output_stream_params){ const pa_buffer_attr * output_att; output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream); @@ -813,6 +989,7 @@ pulse_stream_init(cubeb * context, } *stream = stm; + LOG("Cubeb stream (%p) init successful.", *stream); return CUBEB_OK; } @@ -844,6 +1021,7 @@ pulse_stream_destroy(cubeb_stream * stm) } WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + LOG("Cubeb stream (%p) destroyed successfully.", stm); free(stm); } @@ -875,6 +1053,7 @@ pulse_stream_start(cubeb_stream * stm) WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); } + LOG("Cubeb stream (%p) started successfully.", stm); return CUBEB_OK; } @@ -890,6 +1069,7 @@ pulse_stream_stop(cubeb_stream * stm) WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); stream_cork(stm, CORK | NOTIFY); + LOG("Cubeb stream (%p) stopped successfully.", stm); return CUBEB_OK; } @@ -962,6 +1142,7 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) pa_volume_t vol; pa_cvolume cvol; const pa_sample_spec * ss; + cubeb * ctx; if (!stm->output_stream) { return CUBEB_ERROR; @@ -969,13 +1150,11 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); - while (!stm->context->default_sink_info) { - WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop); - } - /* if the pulse daemon is configured to use flat volumes, * apply our own gain instead of changing the input volume on the sink. */ - if (stm->context->default_sink_info->flags & PA_SINK_FLAT_VOLUME) { + ctx = stm->context; + if (ctx->default_sink_info && + (ctx->default_sink_info->flags & PA_SINK_FLAT_VOLUME)) { stm->volume = volume; } else { ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream); @@ -985,46 +1164,40 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) index = WRAP(pa_stream_get_index)(stm->output_stream); - op = WRAP(pa_context_set_sink_input_volume)(stm->context->context, + op = WRAP(pa_context_set_sink_input_volume)(ctx->context, index, &cvol, volume_success, stm); if (op) { - operation_wait(stm->context, stm->output_stream, op); + operation_wait(ctx, stm->output_stream, op); WRAP(pa_operation_unref)(op); } } - WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); return CUBEB_OK; } -static int -pulse_stream_set_panning(cubeb_stream * stream, float panning) -{ - const pa_channel_map * map; - pa_cvolume vol; - - if (!stream->output_stream) { - return CUBEB_ERROR; - } - - map = WRAP(pa_stream_get_channel_map)(stream->output_stream); +struct sink_input_info_result { + pa_cvolume * cvol; + pa_threaded_mainloop * mainloop; +}; - if (!WRAP(pa_channel_map_can_balance)(map)) { - return CUBEB_ERROR; +static void +sink_input_info_cb(pa_context * c, pa_sink_input_info const * i, int eol, void * u) +{ + struct sink_input_info_result * r = u; + if (!eol) { + *r->cvol = i->volume; } - - WRAP(pa_cvolume_set_balance)(&vol, map, panning); - - return CUBEB_OK; + WRAP(pa_threaded_mainloop_signal)(r->mainloop, 0); } typedef struct { char * default_sink_name; char * default_source_name; - cubeb_device_info ** devinfo; + cubeb_device_info * devinfo; uint32_t max; uint32_t count; cubeb * context; @@ -1053,7 +1226,7 @@ pulse_ensure_dev_list_data_list_size (pulse_dev_list_data * list_data) if (list_data->count == list_data->max) { list_data->max += 8; list_data->devinfo = realloc(list_data->devinfo, - sizeof(cubeb_device_info *) * list_data->max); + sizeof(cubeb_device_info) * list_data->max); } } @@ -1062,33 +1235,47 @@ pulse_get_state_from_sink_port(pa_sink_port_info * info) { if (info != NULL) { #if PA_CHECK_VERSION(2, 0, 0) - if (info->available == PA_PORT_AVAILABLE_NO) + if (has_pulse_v2 && info->available == PA_PORT_AVAILABLE_NO) return CUBEB_DEVICE_STATE_UNPLUGGED; else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */ #endif return CUBEB_DEVICE_STATE_ENABLED; } - return CUBEB_DEVICE_STATE_DISABLED; + return CUBEB_DEVICE_STATE_ENABLED; } static void pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, - int eol, void * user_data) + int eol, void * user_data) { pulse_dev_list_data * list_data = user_data; cubeb_device_info * devinfo; - const char * prop; + char const * prop = NULL; + char const * device_id = NULL; (void)context; - if (eol || info == NULL) + if (eol) { + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); + return; + } + + if (info == NULL) return; - devinfo = calloc(1, sizeof(cubeb_device_info)); + device_id = info->name; + if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) { + assert(NULL); + return; + } + + pulse_ensure_dev_list_data_list_size(list_data); + devinfo = &list_data->devinfo[list_data->count]; + memset(devinfo, 0, sizeof(cubeb_device_info)); - devinfo->device_id = strdup(info->name); - devinfo->devid = devinfo->device_id; + devinfo->device_id = device_id; + devinfo->devid = (cubeb_devid) devinfo->device_id; devinfo->friendly_name = strdup(info->description); prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path"); if (prop) @@ -1099,7 +1286,8 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT; devinfo->state = pulse_get_state_from_sink_port(info->active_port); - devinfo->preferred = strcmp(info->name, list_data->default_sink_name) == 0; + devinfo->preferred = (strcmp(info->name, list_data->default_sink_name) == 0) ? + CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; devinfo->format = CUBEB_DEVICE_FMT_ALL; devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format); @@ -1111,10 +1299,7 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, devinfo->latency_lo = 0; devinfo->latency_hi = 0; - pulse_ensure_dev_list_data_list_size (list_data); - list_data->devinfo[list_data->count++] = devinfo; - - WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); + list_data->count += 1; } static cubeb_device_state @@ -1122,14 +1307,14 @@ pulse_get_state_from_source_port(pa_source_port_info * info) { if (info != NULL) { #if PA_CHECK_VERSION(2, 0, 0) - if (info->available == PA_PORT_AVAILABLE_NO) + if (has_pulse_v2 && info->available == PA_PORT_AVAILABLE_NO) return CUBEB_DEVICE_STATE_UNPLUGGED; else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */ #endif return CUBEB_DEVICE_STATE_ENABLED; } - return CUBEB_DEVICE_STATE_DISABLED; + return CUBEB_DEVICE_STATE_ENABLED; } static void @@ -1138,17 +1323,28 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info, { pulse_dev_list_data * list_data = user_data; cubeb_device_info * devinfo; - const char * prop; + char const * prop = NULL; + char const * device_id = NULL; (void)context; - if (eol) + if (eol) { + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); + return; + } + + device_id = info->name; + if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) { + assert(NULL); return; + } - devinfo = calloc(1, sizeof(cubeb_device_info)); + pulse_ensure_dev_list_data_list_size(list_data); + devinfo = &list_data->devinfo[list_data->count]; + memset(devinfo, 0, sizeof(cubeb_device_info)); - devinfo->device_id = strdup(info->name); - devinfo->devid = devinfo->device_id; + devinfo->device_id = device_id; + devinfo->devid = (cubeb_devid) devinfo->device_id; devinfo->friendly_name = strdup(info->description); prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path"); if (prop) @@ -1159,7 +1355,8 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info, devinfo->type = CUBEB_DEVICE_TYPE_INPUT; devinfo->state = pulse_get_state_from_source_port(info->active_port); - devinfo->preferred = strcmp(info->name, list_data->default_source_name) == 0; + devinfo->preferred = (strcmp(info->name, list_data->default_source_name) == 0) ? + CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; devinfo->format = CUBEB_DEVICE_FMT_ALL; devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format); @@ -1171,10 +1368,7 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info, devinfo->latency_lo = 0; devinfo->latency_hi = 0; - pulse_ensure_dev_list_data_list_size (list_data); - list_data->devinfo[list_data->count++] = devinfo; - - WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); + list_data->count += 1; } static void @@ -1186,19 +1380,20 @@ pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata) free(list_data->default_sink_name); free(list_data->default_source_name); - list_data->default_sink_name = strdup(i->default_sink_name); - list_data->default_source_name = strdup(i->default_source_name); + list_data->default_sink_name = + i->default_sink_name ? strdup(i->default_sink_name) : NULL; + list_data->default_source_name = + i->default_source_name ? strdup(i->default_source_name) : NULL; WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); } static int pulse_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection ** collection) + cubeb_device_collection * collection) { pulse_dev_list_data user_data = { NULL, NULL, NULL, 0, 0, context }; pa_operation * o; - uint32_t i; WRAP(pa_threaded_mainloop_lock)(context->mainloop); @@ -1229,15 +1424,26 @@ pulse_enumerate_devices(cubeb * context, cubeb_device_type type, WRAP(pa_threaded_mainloop_unlock)(context->mainloop); - *collection = malloc(sizeof(cubeb_device_collection) + - sizeof(cubeb_device_info *) * (user_data.count > 0 ? user_data.count - 1 : 0)); - (*collection)->count = user_data.count; - for (i = 0; i < user_data.count; i++) - (*collection)->device[i] = user_data.devinfo[i]; + collection->device = user_data.devinfo; + collection->count = user_data.count; free(user_data.default_sink_name); free(user_data.default_source_name); - free(user_data.devinfo); + return CUBEB_OK; +} + +static int +pulse_device_collection_destroy(cubeb * ctx, cubeb_device_collection * collection) +{ + size_t n; + + for (n = 0; n < collection->count; n++) { + free((void *) collection->device[n].friendly_name); + free((void *) collection->device[n].vendor_name); + free((void *) collection->device[n].group_id); + } + + free(collection->device); return CUBEB_OK; } @@ -1285,29 +1491,40 @@ pulse_subscribe_callback(pa_context * ctx, cubeb * context = userdata; switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SERVER: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) { + LOG("Server changed %d", index); + WRAP(pa_context_get_server_info)(context->context, server_info_callback, context); + } + break; case PA_SUBSCRIPTION_EVENT_SOURCE: case PA_SUBSCRIPTION_EVENT_SINK: - if (g_log_level) { + if (g_cubeb_log_level) { if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - LOG("Removing sink index %d", index); + LOG("Removing source index %d", index); } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - LOG("Adding sink index %d", index); + LOG("Adding source index %d", index); } if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - LOG("Removing source index %d", index); + LOG("Removing sink index %d", index); } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - LOG("Adding source index %d", index); + LOG("Adding sink index %d", index); } } if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE || (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - context->collection_changed_callback(context, context->collection_changed_user_ptr); + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) { + context->input_collection_changed_callback(context, context->input_collection_changed_user_ptr); + } + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { + context->output_collection_changed_callback(context, context->output_collection_changed_user_ptr); + } } break; } @@ -1323,34 +1540,15 @@ subscribe_success(pa_context *c, int success, void *userdata) } static int -pulse_register_device_collection_changed(cubeb * context, - cubeb_device_type devtype, - cubeb_device_collection_changed_callback collection_changed_callback, - void * user_ptr) -{ - context->collection_changed_callback = collection_changed_callback; - context->collection_changed_user_ptr = user_ptr; - +pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask) { WRAP(pa_threaded_mainloop_lock)(context->mainloop); - pa_subscription_mask_t mask; - if (context->collection_changed_callback == NULL) { - // Unregister subscription - WRAP(pa_context_set_subscribe_callback)(context->context, NULL, NULL); - mask = PA_SUBSCRIPTION_MASK_NULL; - } else { - WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context); - if (devtype == CUBEB_DEVICE_TYPE_INPUT) - mask = PA_SUBSCRIPTION_MASK_SOURCE; - else if (devtype == CUBEB_DEVICE_TYPE_OUTPUT) - mask = PA_SUBSCRIPTION_MASK_SINK; - else - mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE; - } + WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context); pa_operation * o; o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context); if (o == NULL) { + WRAP(pa_threaded_mainloop_unlock)(context->mainloop); LOG("Context subscribe failed"); return CUBEB_ERROR; } @@ -1362,6 +1560,37 @@ pulse_register_device_collection_changed(cubeb * context, return CUBEB_OK; } +static int +pulse_register_device_collection_changed(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback collection_changed_callback, + void * user_ptr) +{ + if (devtype & CUBEB_DEVICE_TYPE_INPUT) { + context->input_collection_changed_callback = collection_changed_callback; + context->input_collection_changed_user_ptr = user_ptr; + } + if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { + context->output_collection_changed_callback = collection_changed_callback; + context->output_collection_changed_user_ptr = user_ptr; + } + + pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_NULL; + if (context->input_collection_changed_callback) { + /* Input added or removed */ + mask |= PA_SUBSCRIPTION_MASK_SOURCE; + } + if (context->output_collection_changed_callback) { + /* Output added or removed */ + mask |= PA_SUBSCRIPTION_MASK_SINK; + } + /* Default device changed, this is always registered in order to update the + * `default_sink_info` when the default device changes. */ + mask |= PA_SUBSCRIPTION_MASK_SERVER; + + return pulse_subscribe_notifications(context, mask); +} + static struct cubeb_ops const pulse_ops = { .init = pulse_init, .get_backend_id = pulse_get_backend_id, @@ -1369,15 +1598,16 @@ static struct cubeb_ops const pulse_ops = { .get_min_latency = pulse_get_min_latency, .get_preferred_sample_rate = pulse_get_preferred_sample_rate, .enumerate_devices = pulse_enumerate_devices, + .device_collection_destroy = pulse_device_collection_destroy, .destroy = pulse_destroy, .stream_init = pulse_stream_init, .stream_destroy = pulse_stream_destroy, .stream_start = pulse_stream_start, .stream_stop = pulse_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = pulse_stream_get_position, .stream_get_latency = pulse_stream_get_latency, .stream_set_volume = pulse_stream_set_volume, - .stream_set_panning = pulse_stream_set_panning, .stream_get_current_device = pulse_stream_get_current_device, .stream_device_destroy = pulse_stream_device_destroy, .stream_register_device_changed_callback = NULL, diff --git a/media/libcubeb/src/cubeb_resampler.cpp b/media/libcubeb/src/cubeb_resampler.cpp index f6676946c..2bc889340 100644 --- a/media/libcubeb/src/cubeb_resampler.cpp +++ b/media/libcubeb/src/cubeb_resampler.cpp @@ -35,29 +35,54 @@ to_speex_quality(cubeb_resampler_quality q) } } -long noop_resampler::fill(void * input_buffer, long * input_frames_count, - void * output_buffer, long output_frames) +uint32_t min_buffered_audio_frame(uint32_t sample_rate) +{ + return sample_rate / 20; +} + +template +passthrough_resampler::passthrough_resampler(cubeb_stream * s, + cubeb_data_callback cb, + void * ptr, + uint32_t input_channels, + uint32_t sample_rate) + : processor(input_channels) + , stream(s) + , data_callback(cb) + , user_ptr(ptr) + , sample_rate(sample_rate) +{ +} + +template +long passthrough_resampler::fill(void * input_buffer, long * input_frames_count, + void * output_buffer, long output_frames) { if (input_buffer) { assert(input_frames_count); } - assert((input_buffer && output_buffer && - *input_frames_count >= output_frames) || - (!input_buffer && (!input_frames_count || *input_frames_count == 0)) || - (!output_buffer && output_frames == 0)); - - if (output_buffer == nullptr) { - assert(input_buffer); - output_frames = *input_frames_count; + assert((input_buffer && output_buffer) || + (output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) || + (input_buffer && !output_buffer && output_frames == 0)); + + if (input_buffer) { + if (!output_buffer) { + output_frames = *input_frames_count; + } + internal_input_buffer.push(static_cast(input_buffer), + frames_to_samples(*input_frames_count)); } - if (input_buffer && *input_frames_count != output_frames) { - assert(*input_frames_count > output_frames); + long rv = data_callback(stream, user_ptr, internal_input_buffer.data(), + output_buffer, output_frames); + + if (input_buffer) { + internal_input_buffer.pop(nullptr, frames_to_samples(output_frames)); *input_frames_count = output_frames; + drop_audio_if_needed(); } - return data_callback(stream, user_ptr, - input_buffer, output_buffer, output_frames); + return rv; } template @@ -120,27 +145,32 @@ cubeb_resampler_speex assert(!input_buffer && (!input_frames_count || *input_frames_count == 0) && output_buffer && output_frames_needed); - long got = 0; - T * out_unprocessed = nullptr; - long output_frames_before_processing = 0; + if (!draining) { + long got = 0; + T * out_unprocessed = nullptr; + long output_frames_before_processing = 0; + /* fill directly the input buffer of the output processor to save a copy */ + output_frames_before_processing = + output_processor->input_needed_for_output(output_frames_needed); - /* fill directly the input buffer of the output processor to save a copy */ - output_frames_before_processing = - output_processor->input_needed_for_output(output_frames_needed); + out_unprocessed = + output_processor->input_buffer(output_frames_before_processing); - out_unprocessed = - output_processor->input_buffer(output_frames_before_processing); + got = data_callback(stream, user_ptr, + nullptr, out_unprocessed, + output_frames_before_processing); - got = data_callback(stream, user_ptr, - nullptr, out_unprocessed, - output_frames_before_processing); + if (got < output_frames_before_processing) { + draining = true; - if (got < 0) { - return got; - } + if (got < 0) { + return got; + } + } - output_processor->written(got); + output_processor->written(got); + } /* Process the output. If not enough frames have been returned from the * callback, drain the processors. */ @@ -163,7 +193,10 @@ cubeb_resampler_speex /* process the input, and present exactly `output_frames_needed` in the * callback. */ input_processor->input(input_buffer, *input_frames_count); - resampled_input = input_processor->output(resampled_frame_count); + + size_t frames_resampled = 0; + resampled_input = input_processor->output(resampled_frame_count, &frames_resampled); + *input_frames_count = frames_resampled; long got = data_callback(stream, user_ptr, resampled_input, nullptr, resampled_frame_count); @@ -174,18 +207,22 @@ cubeb_resampler_speex return (*input_frames_count) * (got / resampled_frame_count); } - template long cubeb_resampler_speex ::fill_internal_duplex(T * in_buffer, long * input_frames_count, T * out_buffer, long output_frames_needed) { + if (draining) { + // discard input and drain any signal remaining in the resampler. + return output_processor->output(out_buffer, output_frames_needed); + } + /* The input data, after eventual resampling. This is passed to the callback. */ T * resampled_input = nullptr; /* The output buffer passed down in the callback, that might be resampled. */ T * out_unprocessed = nullptr; - size_t output_frames_before_processing = 0; + long output_frames_before_processing = 0; /* The number of frames returned from the callback. */ long got = 0; @@ -210,8 +247,11 @@ cubeb_resampler_speex /* process the input, and present exactly `output_frames_needed` in the * callback. */ input_processor->input(in_buffer, *input_frames_count); + + size_t frames_resampled = 0; resampled_input = - input_processor->output(output_frames_before_processing); + input_processor->output(output_frames_before_processing, &frames_resampled); + *input_frames_count = frames_resampled; } else { resampled_input = nullptr; } @@ -220,15 +260,25 @@ cubeb_resampler_speex resampled_input, out_unprocessed, output_frames_before_processing); - if (got < 0) { - return got; + if (got < output_frames_before_processing) { + draining = true; + + if (got < 0) { + return got; + } } output_processor->written(got); + input_processor->drop_audio_if_needed(); + /* Process the output. If not enough frames have been returned from the * callback, drain the processors. */ - return output_processor->output(out_buffer, output_frames_needed); + got = output_processor->output(out_buffer, output_frames_needed); + + output_processor->drop_audio_if_needed(); + + return got; } /* Resampler C API */ diff --git a/media/libcubeb/src/cubeb_resampler.h b/media/libcubeb/src/cubeb_resampler.h index 020ccc17a..f6b551373 100644 --- a/media/libcubeb/src/cubeb_resampler.h +++ b/media/libcubeb/src/cubeb_resampler.h @@ -25,8 +25,15 @@ typedef enum { * Create a resampler to adapt the requested sample rate into something that * is accepted by the audio backend. * @param stream A cubeb_stream instance supplied to the data callback. - * @param params Used to calculate bytes per frame and buffer size for resampling. - * @param target_rate The sampling rate after resampling. + * @param input_params Used to calculate bytes per frame and buffer size for + * resampling of the input side of the stream. NULL if input should not be + * resampled. + * @param output_params Used to calculate bytes per frame and buffer size for + * resampling of the output side of the stream. NULL if output should not be + * resampled. + * @param target_rate The sampling rate after resampling for the input side of + * the stream, and/or the sampling rate prior to resampling of the output side + * of the stream. * @param callback A callback to request data for resampling. * @param user_ptr User data supplied to the data callback. * @param quality Quality of the resampler. @@ -47,8 +54,8 @@ cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream, * @param input_buffer A buffer of input samples * @param input_frame_count The size of the buffer. Returns the number of frames * consumed. - * @param buffer The buffer to be filled. - * @param frames_needed Number of frames that should be produced. + * @param output_buffer The buffer to be filled. + * @param output_frames_needed Number of frames that should be produced. * @retval Number of frames that are actually produced. * @retval CUBEB_ERROR on error. */ diff --git a/media/libcubeb/src/cubeb_resampler_internal.h b/media/libcubeb/src/cubeb_resampler_internal.h index 3c37a04b9..fb69992ff 100644 --- a/media/libcubeb/src/cubeb_resampler_internal.h +++ b/media/libcubeb/src/cubeb_resampler_internal.h @@ -39,6 +39,13 @@ MOZ_END_STD_NAMESPACE /* This header file contains the internal C++ API of the resamplers, for testing. */ +// When dropping audio input frames to prevent building +// an input delay, this function returns the number of frames +// to keep in the buffer. +// @parameter sample_rate The sample rate of the stream. +// @return A number of frames to keep. +uint32_t min_buffered_audio_frame(uint32_t sample_rate); + int to_speex_quality(cubeb_resampler_quality q); struct cubeb_resampler { @@ -48,31 +55,6 @@ struct cubeb_resampler { virtual ~cubeb_resampler() {} }; -class noop_resampler : public cubeb_resampler { -public: - noop_resampler(cubeb_stream * s, - cubeb_data_callback cb, - void * ptr) - : stream(s) - , data_callback(cb) - , user_ptr(ptr) - { - } - - virtual long fill(void * input_buffer, long * input_frames_count, - void * output_buffer, long output_frames); - - virtual long latency() - { - return 0; - } - -private: - cubeb_stream * const stream; - const cubeb_data_callback data_callback; - void * const user_ptr; -}; - /** Base class for processors. This is just used to share methods for now. */ class processor { public: @@ -80,11 +62,11 @@ public: : channels(channels) {} protected: - size_t frames_to_samples(size_t frames) + size_t frames_to_samples(size_t frames) const { return frames * channels; } - size_t samples_to_frames(size_t samples) + size_t samples_to_frames(size_t samples) const { assert(!(samples % channels)); return samples / channels; @@ -93,6 +75,43 @@ protected: const uint32_t channels; }; +template +class passthrough_resampler : public cubeb_resampler + , public processor { +public: + passthrough_resampler(cubeb_stream * s, + cubeb_data_callback cb, + void * ptr, + uint32_t input_channels, + uint32_t sample_rate); + + virtual long fill(void * input_buffer, long * input_frames_count, + void * output_buffer, long output_frames); + + virtual long latency() + { + return 0; + } + + void drop_audio_if_needed() + { + uint32_t to_keep = min_buffered_audio_frame(sample_rate); + uint32_t available = samples_to_frames(internal_input_buffer.length()); + if (available > to_keep) { + internal_input_buffer.pop(nullptr, frames_to_samples(available - to_keep)); + } + } + +private: + cubeb_stream * const stream; + const cubeb_data_callback data_callback; + void * const user_ptr; + /* This allows to buffer some input to account for the fact that we buffer + * some inputs. */ + auto_array internal_input_buffer; + uint32_t sample_rate; +}; + /** Bidirectional resampler, can resample an input and an output stream, or just * an input stream or output stream. In this case a delay is inserted in the * opposite direction to keep the streams synchronized. */ @@ -138,6 +157,7 @@ private: cubeb_stream * const stream; const cubeb_data_callback data_callback; void * const user_ptr; + bool draining = false; }; /** Handles one way of a (possibly) duplex resampler, working on interleaved @@ -163,6 +183,7 @@ public: int quality) : processor(channels) , resampling_ratio(static_cast(source_rate) / target_rate) + , source_rate(source_rate) , additional_latency(0) , leftover_samples(0) { @@ -221,7 +242,7 @@ public: /** Returns a buffer containing exactly `output_frame_count` resampled frames. * The consumer should not hold onto the pointer. */ - T * output(size_t output_frame_count) + T * output(size_t output_frame_count, size_t * input_frames_used) { if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) { resampling_out_buffer.reserve(frames_to_samples(output_frame_count)); @@ -238,6 +259,7 @@ public: /* This shifts back any unresampled samples to the beginning of the input buffer. */ resampling_in_buffer.pop(nullptr, frames_to_samples(in_len)); + *input_frames_used = in_len; return resampling_out_buffer.data(); } @@ -261,7 +283,7 @@ public: * exactly `output_frame_count` resampled frames. This can return a number * slightly bigger than what is strictly necessary, but it guaranteed that the * number of output frames will be exactly equal. */ - uint32_t input_needed_for_output(uint32_t output_frame_count) + uint32_t input_needed_for_output(uint32_t output_frame_count) const { int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length()); int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length()); @@ -294,6 +316,16 @@ public: resampling_in_buffer.set_length(leftover_samples + frames_to_samples(written_frames)); } + + void drop_audio_if_needed() + { + // Keep at most 100ms buffered. + uint32_t available = samples_to_frames(resampling_in_buffer.length()); + uint32_t to_keep = min_buffered_audio_frame(source_rate); + if (available > to_keep) { + resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep)); + } + } private: /** Wrapper for the speex resampling functions to have a typed * interface. */ @@ -330,6 +362,7 @@ private: SpeexResamplerState * speex_resampler; /** Source rate / target rate. */ const float resampling_ratio; + const uint32_t source_rate; /** Storage for the input frames, to be resampled. Also contains * any unresampled frames after resampling. */ auto_array resampling_in_buffer; @@ -348,11 +381,13 @@ class delay_line : public processor { public: /** Constructor * @parameter frames the number of frames of delay. - * @parameter channels the number of channels of this delay line. */ - delay_line(uint32_t frames, uint32_t channels) + * @parameter channels the number of channels of this delay line. + * @parameter sample_rate sample-rate of the audio going through this delay line */ + delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate) : processor(channels) , length(frames) , leftover_samples(0) + , sample_rate(sample_rate) { /* Fill the delay line with some silent frames to add latency. */ delay_input_buffer.push_silence(frames * channels); @@ -375,7 +410,7 @@ public: * @parameter frames_needed the number of frames to be returned. * @return a buffer containing the delayed frames. The consumer should not * hold onto the pointer. */ - T * output(uint32_t frames_needed) + T * output(uint32_t frames_needed, size_t * input_frames_used) { if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) { delay_output_buffer.reserve(frames_to_samples(frames_needed)); @@ -385,6 +420,7 @@ public: delay_output_buffer.push(delay_input_buffer.data(), frames_to_samples(frames_needed)); delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed)); + *input_frames_used = frames_needed; return delay_output_buffer.data(); } @@ -426,7 +462,7 @@ public: * @parameter frames_needed the number of frames one want to write into the * delay_line * @returns the number of frames one will get. */ - size_t input_needed_for_output(uint32_t frames_needed) + size_t input_needed_for_output(uint32_t frames_needed) const { return frames_needed; } @@ -441,6 +477,15 @@ public: { return length; } + + void drop_audio_if_needed() + { + size_t available = samples_to_frames(delay_input_buffer.length()); + uint32_t to_keep = min_buffered_audio_frame(sample_rate); + if (available > to_keep) { + delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep)); + } + } private: /** The length, in frames, of this delay line */ uint32_t length; @@ -452,6 +497,7 @@ private: /** The output buffer. This is only ever used if using the ::output with a * single argument. */ auto_array delay_output_buffer; + uint32_t sample_rate; }; /** This sits behind the C API and is more typed. */ @@ -480,7 +526,10 @@ cubeb_resampler_create_internal(cubeb_stream * stream, (output_params && output_params->rate == target_rate)) || (input_params && !output_params && (input_params->rate == target_rate)) || (output_params && !input_params && (output_params->rate == target_rate))) { - return new noop_resampler(stream, callback, user_ptr); + return new passthrough_resampler(stream, callback, + user_ptr, + input_params ? input_params->channels : 0, + target_rate); } /* Determine if we need to resampler one or both directions, and create the @@ -512,13 +561,15 @@ cubeb_resampler_create_internal(cubeb_stream * stream, * other direction so that the streams are synchronized. */ if (input_resampler && !output_resampler && input_params && output_params) { output_delay.reset(new delay_line(input_resampler->latency(), - output_params->channels)); + output_params->channels, + output_params->rate)); if (!output_delay) { return NULL; } } else if (output_resampler && !input_resampler && input_params && output_params) { input_delay.reset(new delay_line(output_resampler->latency(), - input_params->channels)); + input_params->channels, + output_params->rate)); if (!input_delay) { return NULL; } diff --git a/media/libcubeb/src/cubeb_ringbuffer.h b/media/libcubeb/src/cubeb_ringbuffer.h new file mode 100644 index 000000000..b6696e886 --- /dev/null +++ b/media/libcubeb/src/cubeb_ringbuffer.h @@ -0,0 +1,495 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#ifndef CUBEB_RING_BUFFER_H +#define CUBEB_RING_BUFFER_H + +#include "cubeb_utils.h" +#include +#include +#include +#include +#include + +/** + * Single producer single consumer lock-free and wait-free ring buffer. + * + * This data structure allows producing data from one thread, and consuming it on + * another thread, safely and without explicit synchronization. If used on two + * threads, this data structure uses atomics for thread safety. It is possible + * to disable the use of atomics at compile time and only use this data + * structure on one thread. + * + * The role for the producer and the consumer must be constant, i.e., the + * producer should always be on one thread and the consumer should always be on + * another thread. + * + * Some words about the inner workings of this class: + * - Capacity is fixed. Only one allocation is performed, in the constructor. + * When reading and writing, the return value of the method allows checking if + * the ring buffer is empty or full. + * - We always keep the read index at least one element ahead of the write + * index, so we can distinguish between an empty and a full ring buffer: an + * empty ring buffer is when the write index is at the same position as the + * read index. A full buffer is when the write index is exactly one position + * before the read index. + * - We synchronize updates to the read index after having read the data, and + * the write index after having written the data. This means that the each + * thread can only touch a portion of the buffer that is not touched by the + * other thread. + * - Callers are expected to provide buffers. When writing to the queue, + * elements are copied into the internal storage from the buffer passed in. + * When reading from the queue, the user is expected to provide a buffer. + * Because this is a ring buffer, data might not be contiguous in memory, + * providing an external buffer to copy into is an easy way to have linear + * data for further processing. + */ +template +class ring_buffer_base +{ +public: + /** + * Constructor for a ring buffer. + * + * This performs an allocation, but is the only allocation that will happen + * for the life time of a `ring_buffer_base`. + * + * @param capacity The maximum number of element this ring buffer will hold. + */ + ring_buffer_base(int capacity) + /* One more element to distinguish from empty and full buffer. */ + : capacity_(capacity + 1) + { + assert(storage_capacity() < + std::numeric_limits::max() / 2 && + "buffer too large for the type of index used."); + assert(capacity_ > 0); + + data_.reset(new T[storage_capacity()]); + /* If this queue is using atomics, initializing those members as the last + * action in the constructor acts as a full barrier, and allow capacity() to + * be thread-safe. */ + write_index_ = 0; + read_index_ = 0; + } + /** + * Push `count` zero or default constructed elements in the array. + * + * Only safely called on the producer thread. + * + * @param count The number of elements to enqueue. + * @return The number of element enqueued. + */ + int enqueue_default(int count) + { + return enqueue(nullptr, count); + } + /** + * @brief Put an element in the queue + * + * Only safely called on the producer thread. + * + * @param element The element to put in the queue. + * + * @return 1 if the element was inserted, 0 otherwise. + */ + int enqueue(T& element) + { + return enqueue(&element, 1); + } + /** + * Push `count` elements in the ring buffer. + * + * Only safely called on the producer thread. + * + * @param elements a pointer to a buffer containing at least `count` elements. + * If `elements` is nullptr, zero or default constructed elements are enqueued. + * @param count The number of elements to read from `elements` + * @return The number of elements successfully coped from `elements` and inserted + * into the ring buffer. + */ + int enqueue(T * elements, int count) + { +#ifndef NDEBUG + assert_correct_thread(producer_id); +#endif + + int rd_idx = read_index_.load(std::memory_order::memory_order_relaxed); + int wr_idx = write_index_.load(std::memory_order::memory_order_relaxed); + + if (full_internal(rd_idx, wr_idx)) { + return 0; + } + + int to_write = + std::min(available_write_internal(rd_idx, wr_idx), count); + + /* First part, from the write index to the end of the array. */ + int first_part = std::min(storage_capacity() - wr_idx, + to_write); + /* Second part, from the beginning of the array */ + int second_part = to_write - first_part; + + if (elements) { + Copy(data_.get() + wr_idx, elements, first_part); + Copy(data_.get(), elements + first_part, second_part); + } else { + ConstructDefault(data_.get() + wr_idx, first_part); + ConstructDefault(data_.get(), second_part); + } + + write_index_.store(increment_index(wr_idx, to_write), std::memory_order::memory_order_release); + + return to_write; + } + /** + * Retrieve at most `count` elements from the ring buffer, and copy them to + * `elements`, if non-null. + * + * Only safely called on the consumer side. + * + * @param elements A pointer to a buffer with space for at least `count` + * elements. If `elements` is `nullptr`, `count` element will be discarded. + * @param count The maximum number of elements to dequeue. + * @return The number of elements written to `elements`. + */ + int dequeue(T * elements, int count) + { +#ifndef NDEBUG + assert_correct_thread(consumer_id); +#endif + + int wr_idx = write_index_.load(std::memory_order::memory_order_acquire); + int rd_idx = read_index_.load(std::memory_order::memory_order_relaxed); + + if (empty_internal(rd_idx, wr_idx)) { + return 0; + } + + int to_read = + std::min(available_read_internal(rd_idx, wr_idx), count); + + int first_part = std::min(storage_capacity() - rd_idx, to_read); + int second_part = to_read - first_part; + + if (elements) { + Copy(elements, data_.get() + rd_idx, first_part); + Copy(elements + first_part, data_.get(), second_part); + } + + read_index_.store(increment_index(rd_idx, to_read), std::memory_order::memory_order_relaxed); + + return to_read; + } + /** + * Get the number of available element for consuming. + * + * Only safely called on the consumer thread. + * + * @return The number of available elements for reading. + */ + int available_read() const + { +#ifndef NDEBUG + assert_correct_thread(consumer_id); +#endif + return available_read_internal(read_index_.load(std::memory_order::memory_order_relaxed), + write_index_.load(std::memory_order::memory_order_relaxed)); + } + /** + * Get the number of available elements for consuming. + * + * Only safely called on the producer thread. + * + * @return The number of empty slots in the buffer, available for writing. + */ + int available_write() const + { +#ifndef NDEBUG + assert_correct_thread(producer_id); +#endif + return available_write_internal(read_index_.load(std::memory_order::memory_order_relaxed), + write_index_.load(std::memory_order::memory_order_relaxed)); + } + /** + * Get the total capacity, for this ring buffer. + * + * Can be called safely on any thread. + * + * @return The maximum capacity of this ring buffer. + */ + int capacity() const + { + return storage_capacity() - 1; + } + /** + * Reset the consumer and producer thread identifier, in case the thread are + * being changed. This has to be externally synchronized. This is no-op when + * asserts are disabled. + */ + void reset_thread_ids() + { +#ifndef NDEBUG + consumer_id = producer_id = std::thread::id(); +#endif + } +private: + /** Return true if the ring buffer is empty. + * + * @param read_index the read index to consider + * @param write_index the write index to consider + * @return true if the ring buffer is empty, false otherwise. + **/ + bool empty_internal(int read_index, + int write_index) const + { + return write_index == read_index; + } + /** Return true if the ring buffer is full. + * + * This happens if the write index is exactly one element behind the read + * index. + * + * @param read_index the read index to consider + * @param write_index the write index to consider + * @return true if the ring buffer is full, false otherwise. + **/ + bool full_internal(int read_index, + int write_index) const + { + return (write_index + 1) % storage_capacity() == read_index; + } + /** + * Return the size of the storage. It is one more than the number of elements + * that can be stored in the buffer. + * + * @return the number of elements that can be stored in the buffer. + */ + int storage_capacity() const + { + return capacity_; + } + /** + * Returns the number of elements available for reading. + * + * @return the number of available elements for reading. + */ + int + available_read_internal(int read_index, + int write_index) const + { + if (write_index >= read_index) { + return write_index - read_index; + } else { + return write_index + storage_capacity() - read_index; + } + } + /** + * Returns the number of empty elements, available for writing. + * + * @return the number of elements that can be written into the array. + */ + int + available_write_internal(int read_index, + int write_index) const + { + /* We substract one element here to always keep at least one sample + * free in the buffer, to distinguish between full and empty array. */ + int rv = read_index - write_index - 1; + if (write_index >= read_index) { + rv += storage_capacity(); + } + return rv; + } + /** + * Increments an index, wrapping it around the storage. + * + * @param index a reference to the index to increment. + * @param increment the number by which `index` is incremented. + * @return the new index. + */ + int + increment_index(int index, int increment) const + { + assert(increment >= 0); + return (index + increment) % storage_capacity(); + } + /** + * @brief This allows checking that enqueue (resp. dequeue) are always called + * by the right thread. + * + * @param id the id of the thread that has called the calling method first. + */ +#ifndef NDEBUG + static void assert_correct_thread(std::thread::id& id) + { + if (id == std::thread::id()) { + id = std::this_thread::get_id(); + return; + } + assert(id == std::this_thread::get_id()); + } +#endif + /** Index at which the oldest element is at, in samples. */ + std::atomic read_index_; + /** Index at which to write new elements. `write_index` is always at + * least one element ahead of `read_index_`. */ + std::atomic write_index_; + /** Maximum number of elements that can be stored in the ring buffer. */ + const int capacity_; + /** Data storage */ + std::unique_ptr data_; +#ifndef NDEBUG + /** The id of the only thread that is allowed to read from the queue. */ + mutable std::thread::id consumer_id; + /** The id of the only thread that is allowed to write from the queue. */ + mutable std::thread::id producer_id; +#endif +}; + +/** + * Adapter for `ring_buffer_base` that exposes an interface in frames. + */ +template +class audio_ring_buffer_base +{ +public: + /** + * @brief Constructor. + * + * @param channel_count Number of channels. + * @param capacity_in_frames The capacity in frames. + */ + audio_ring_buffer_base(int channel_count, int capacity_in_frames) + : channel_count(channel_count) + , ring_buffer(frames_to_samples(capacity_in_frames)) + { + assert(channel_count > 0); + } + /** + * @brief Enqueue silence. + * + * Only safely called on the producer thread. + * + * @param frame_count The number of frames of silence to enqueue. + * @return The number of frames of silence actually written to the queue. + */ + int enqueue_default(int frame_count) + { + return samples_to_frames(ring_buffer.enqueue(nullptr, frames_to_samples(frame_count))); + } + /** + * @brief Enqueue `frames_count` frames of audio. + * + * Only safely called from the producer thread. + * + * @param [in] frames If non-null, the frames to enqueue. + * Otherwise, silent frames are enqueued. + * @param frame_count The number of frames to enqueue. + * + * @return The number of frames enqueued + */ + + int enqueue(T * frames, int frame_count) + { + return samples_to_frames(ring_buffer.enqueue(frames, frames_to_samples(frame_count))); + } + + /** + * @brief Removes `frame_count` frames from the buffer, and + * write them to `frames` if it is non-null. + * + * Only safely called on the consumer thread. + * + * @param frames If non-null, the frames are copied to `frames`. + * Otherwise, they are dropped. + * @param frame_count The number of frames to remove. + * + * @return The number of frames actually dequeud. + */ + int dequeue(T * frames, int frame_count) + { + return samples_to_frames(ring_buffer.dequeue(frames, frames_to_samples(frame_count))); + } + /** + * Get the number of available frames of audio for consuming. + * + * Only safely called on the consumer thread. + * + * @return The number of available frames of audio for reading. + */ + int available_read() const + { + return samples_to_frames(ring_buffer.available_read()); + } + /** + * Get the number of available frames of audio for consuming. + * + * Only safely called on the producer thread. + * + * @return The number of empty slots in the buffer, available for writing. + */ + int available_write() const + { + return samples_to_frames(ring_buffer.available_write()); + } + /** + * Get the total capacity, for this ring buffer. + * + * Can be called safely on any thread. + * + * @return The maximum capacity of this ring buffer. + */ + int capacity() const + { + return samples_to_frames(ring_buffer.capacity()); + } +private: + /** + * @brief Frames to samples conversion. + * + * @param frames The number of frames. + * + * @return A number of samples. + */ + int frames_to_samples(int frames) const + { + return frames * channel_count; + } + /** + * @brief Samples to frames conversion. + * + * @param samples The number of samples. + * + * @return A number of frames. + */ + int samples_to_frames(int samples) const + { + return samples / channel_count; + } + /** Number of channels of audio that will stream through this ring buffer. */ + int channel_count; + /** The underlying ring buffer that is used to store the data. */ + ring_buffer_base ring_buffer; +}; + +/** + * Lock-free instantiation of the `ring_buffer_base` type. This is safe to use + * from two threads, one producer, one consumer (that never change role), + * without explicit synchronization. + */ +template +using lock_free_queue = ring_buffer_base; +/** + * Lock-free instantiation of the `audio_ring_buffer` type. This is safe to use + * from two threads, one producer, one consumer (that never change role), + * without explicit synchronization. + */ +template +using lock_free_audio_ring_buffer = audio_ring_buffer_base; + +#endif // CUBEB_RING_BUFFER_H diff --git a/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c index 793789765..4a05bd845 100644 --- a/media/libcubeb/src/cubeb_sndio.c +++ b/media/libcubeb/src/cubeb_sndio.c @@ -4,6 +4,7 @@ * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ +#include #include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include #include #include "cubeb/cubeb.h" #include "cubeb-internal.h" @@ -21,39 +23,87 @@ #define DPR(...) do {} while(0) #endif +#ifdef DISABLE_LIBSNDIO_DLOPEN +#define WRAP(x) x +#else +#define WRAP(x) cubeb_##x +#define LIBSNDIO_API_VISIT(X) \ + X(sio_close) \ + X(sio_eof) \ + X(sio_getpar) \ + X(sio_initpar) \ + X(sio_onmove) \ + X(sio_open) \ + X(sio_pollfd) \ + X(sio_read) \ + X(sio_revents) \ + X(sio_setpar) \ + X(sio_start) \ + X(sio_stop) \ + X(sio_write) \ + +#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; +LIBSNDIO_API_VISIT(MAKE_TYPEDEF); +#undef MAKE_TYPEDEF +#endif + static struct cubeb_ops const sndio_ops; struct cubeb { struct cubeb_ops const * ops; + void * libsndio; }; struct cubeb_stream { + /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; - pthread_t th; /* to run real-time audio i/o */ - pthread_mutex_t mtx; /* protects hdl and pos */ - struct sio_hdl *hdl; /* link us to sndio */ - int active; /* cubec_start() called */ - int conv; /* need float->s16 conversion */ - unsigned char *buf; /* data is prepared here */ - unsigned int nfr; /* number of frames in buf */ - unsigned int bpf; /* bytes per frame */ - unsigned int pchan; /* number of play channels */ - uint64_t rdpos; /* frame number Joe hears right now */ - uint64_t wrpos; /* number of written frames */ + void * arg; /* user arg to {data,state}_cb */ + /**/ + pthread_t th; /* to run real-time audio i/o */ + pthread_mutex_t mtx; /* protects hdl and pos */ + struct sio_hdl *hdl; /* link us to sndio */ + int mode; /* bitmap of SIO_{PLAY,REC} */ + int active; /* cubec_start() called */ + int conv; /* need float->s16 conversion */ + unsigned char *rbuf; /* rec data consumed from here */ + unsigned char *pbuf; /* play data is prepared here */ + unsigned int nfr; /* number of frames in ibuf and obuf */ + unsigned int rbpf; /* rec bytes per frame */ + unsigned int pbpf; /* play bytes per frame */ + unsigned int rchan; /* number of rec channels */ + unsigned int pchan; /* number of play channels */ + unsigned int nblks; /* number of blocks in the buffer */ + uint64_t hwpos; /* frame number Joe hears right now */ + uint64_t swpos; /* number of frames produced/consumed */ cubeb_data_callback data_cb; /* cb to preapare data */ cubeb_state_callback state_cb; /* cb to notify about state changes */ - void *arg; /* user arg to {data,state}_cb */ + float volume; /* current volume */ }; static void -float_to_s16(void *ptr, long nsamp) +s16_setvol(void *ptr, long nsamp, float volume) +{ + int16_t *dst = ptr; + int32_t mult = volume * 32768; + int32_t s; + + while (nsamp-- > 0) { + s = *dst; + s = (s * mult) >> 15; + *(dst++) = s; + } +} + +static void +float_to_s16(void *ptr, long nsamp, float volume) { int16_t *dst = ptr; float *src = ptr; + float mult = volume * 32768; int s; while (nsamp-- > 0) { - s = lrintf(*(src++) * 32768); + s = lrintf(*(src++) * mult); if (s < -32768) s = -32768; else if (s > 32767) @@ -62,12 +112,24 @@ float_to_s16(void *ptr, long nsamp) } } +static void +s16_to_float(void *ptr, long nsamp) +{ + int16_t *src = ptr; + float *dst = ptr; + + src += nsamp; + dst += nsamp; + while (nsamp-- > 0) + *(--dst) = (1. / 32768) * *(--src); +} + static void sndio_onmove(void *arg, int delta) { cubeb_stream *s = (cubeb_stream *)arg; - s->rdpos += delta * s->bpf; + s->hwpos += delta; } static void * @@ -76,48 +138,99 @@ sndio_mainloop(void *arg) #define MAXFDS 8 struct pollfd pfds[MAXFDS]; cubeb_stream *s = arg; - int n, nfds, revents, state = CUBEB_STATE_STARTED; - size_t start = 0, end = 0; + int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED; + size_t pstart = 0, pend = 0, rstart = 0, rend = 0; long nfr; DPR("sndio_mainloop()\n"); s->state_cb(s, s->arg, CUBEB_STATE_STARTED); pthread_mutex_lock(&s->mtx); - if (!sio_start(s->hdl)) { + if (!WRAP(sio_start)(s->hdl)) { pthread_mutex_unlock(&s->mtx); return NULL; } DPR("sndio_mainloop(), started\n"); - start = end = s->nfr; + if (s->mode & SIO_PLAY) { + pstart = pend = s->nfr * s->pbpf; + prime = s->nblks; + if (s->mode & SIO_REC) { + memset(s->rbuf, 0, s->nfr * s->rbpf); + rstart = rend = s->nfr * s->rbpf; + } + } else { + prime = 0; + rstart = 0; + rend = s->nfr * s->rbpf; + } + for (;;) { if (!s->active) { DPR("sndio_mainloop() stopped\n"); state = CUBEB_STATE_STOPPED; break; } - if (start == end) { - if (end < s->nfr) { + + /* do we have a complete block? */ + if ((!(s->mode & SIO_PLAY) || pstart == pend) && + (!(s->mode & SIO_REC) || rstart == rend)) { + + if (eof) { DPR("sndio_mainloop() drained\n"); state = CUBEB_STATE_DRAINED; break; } + + if ((s->mode & SIO_REC) && s->conv) + s16_to_float(s->rbuf, s->nfr * s->rchan); + + /* invoke call-back, it returns less that s->nfr if done */ pthread_mutex_unlock(&s->mtx); - nfr = s->data_cb(s, s->arg, NULL, s->buf, s->nfr); + nfr = s->data_cb(s, s->arg, s->rbuf, s->pbuf, s->nfr); pthread_mutex_lock(&s->mtx); if (nfr < 0) { DPR("sndio_mainloop() cb err\n"); state = CUBEB_STATE_ERROR; break; } - if (s->conv) - float_to_s16(s->buf, nfr * s->pchan); - start = 0; - end = nfr * s->bpf; + s->swpos += nfr; + + /* was this last call-back invocation (aka end-of-stream) ? */ + if (nfr < s->nfr) { + + if (!(s->mode & SIO_PLAY) || nfr == 0) { + state = CUBEB_STATE_DRAINED; + break; + } + + /* need to write (aka drain) the partial play block we got */ + pend = nfr * s->pbpf; + eof = 1; + } + + if (prime > 0) + prime--; + + if (s->mode & SIO_PLAY) { + if (s->conv) + float_to_s16(s->pbuf, nfr * s->pchan, s->volume); + else + s16_setvol(s->pbuf, nfr * s->pchan, s->volume); + } + + if (s->mode & SIO_REC) + rstart = 0; + if (s->mode & SIO_PLAY) + pstart = 0; } - if (end == 0) - continue; - nfds = sio_pollfd(s->hdl, pfds, POLLOUT); + + events = 0; + if ((s->mode & SIO_REC) && rstart < rend && prime == 0) + events |= POLLIN; + if ((s->mode & SIO_PLAY) && pstart < pend) + events |= POLLOUT; + nfds = WRAP(sio_pollfd)(s->hdl, pfds, events); + if (nfds > 0) { pthread_mutex_unlock(&s->mtx); n = poll(pfds, nfds, -1); @@ -125,22 +238,40 @@ sndio_mainloop(void *arg) if (n < 0) continue; } - revents = sio_revents(s->hdl, pfds); - if (revents & POLLHUP) + + revents = WRAP(sio_revents)(s->hdl, pfds); + + if (revents & POLLHUP) { + state = CUBEB_STATE_ERROR; break; + } + if (revents & POLLOUT) { - n = sio_write(s->hdl, s->buf + start, end - start); - if (n == 0) { + n = WRAP(sio_write)(s->hdl, s->pbuf + pstart, pend - pstart); + if (n == 0 && WRAP(sio_eof)(s->hdl)) { DPR("sndio_mainloop() werr\n"); state = CUBEB_STATE_ERROR; break; } - s->wrpos += n; - start += n; + pstart += n; } + + if (revents & POLLIN) { + n = WRAP(sio_read)(s->hdl, s->rbuf + rstart, rend - rstart); + if (n == 0 && WRAP(sio_eof)(s->hdl)) { + DPR("sndio_mainloop() rerr\n"); + state = CUBEB_STATE_ERROR; + break; + } + rstart += n; + } + + /* skip rec block, if not recording (yet) */ + if (prime > 0 && (s->mode & SIO_REC)) + rstart = rend; } - sio_stop(s->hdl); - s->rdpos = s->wrpos; + WRAP(sio_stop)(s->hdl); + s->hwpos = s->swpos; pthread_mutex_unlock(&s->mtx); s->state_cb(s, s->arg, state); return NULL; @@ -149,8 +280,34 @@ sndio_mainloop(void *arg) /*static*/ int sndio_init(cubeb **context, char const *context_name) { + void * libsndio = NULL; + +#ifndef DISABLE_LIBSNDIO_DLOPEN + libsndio = dlopen("libsndio.so.7.0", RTLD_LAZY); + if (!libsndio) { + libsndio = dlopen("libsndio.so", RTLD_LAZY); + if (!libsndio) { + DPR("sndio_init(%s) failed dlopen(libsndio.so)\n", context_name); + return CUBEB_ERROR; + } + } + +#define LOAD(x) { \ + cubeb_##x = dlsym(libsndio, #x); \ + if (!cubeb_##x) { \ + DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \ + dlclose(libsndio); \ + return CUBEB_ERROR; \ + } \ + } + + LIBSNDIO_API_VISIT(LOAD); +#undef LOAD +#endif + DPR("sndio_init(%s)\n", context_name); *context = malloc(sizeof(*context)); + (*context)->libsndio = libsndio; (*context)->ops = &sndio_ops; (void)context_name; return CUBEB_OK; @@ -166,6 +323,8 @@ static void sndio_destroy(cubeb *context) { DPR("sndio_destroy()\n"); + if (context->libsndio) + dlclose(context->libsndio); free(context); } @@ -184,29 +343,49 @@ sndio_stream_init(cubeb * context, { cubeb_stream *s; struct sio_par wpar, rpar; - DPR("sndio_stream_init(%s)\n", stream_name); - size_t size; + cubeb_sample_format format; + int rate; + size_t bps; - assert(!input_stream_params && "not supported."); - if (input_device || output_device) { - /* Device selection not yet implemented. */ - return CUBEB_ERROR_DEVICE_UNAVAILABLE; - } + DPR("sndio_stream_init(%s)\n", stream_name); s = malloc(sizeof(cubeb_stream)); if (s == NULL) return CUBEB_ERROR; + memset(s, 0, sizeof(cubeb_stream)); + s->mode = 0; + if (input_stream_params) { + if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { + DPR("sndio_stream_init(), loopback not supported\n"); + goto err; + } + s->mode |= SIO_REC; + format = input_stream_params->format; + rate = input_stream_params->rate; + } + if (output_stream_params) { + if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { + DPR("sndio_stream_init(), loopback not supported\n"); + goto err; + } + s->mode |= SIO_PLAY; + format = output_stream_params->format; + rate = output_stream_params->rate; + } + if (s->mode == 0) { + DPR("sndio_stream_init(), neither playing nor recording\n"); + goto err; + } s->context = context; - s->hdl = sio_open(NULL, SIO_PLAY, 1); + s->hdl = WRAP(sio_open)(NULL, s->mode, 1); if (s->hdl == NULL) { - free(s); DPR("sndio_stream_init(), sio_open() failed\n"); - return CUBEB_ERROR; + goto err; } - sio_initpar(&wpar); + WRAP(sio_initpar)(&wpar); wpar.sig = 1; wpar.bits = 16; - switch (output_stream_params->format) { + switch (format) { case CUBEB_SAMPLE_S16LE: wpar.le = 1; break; @@ -218,53 +397,70 @@ sndio_stream_init(cubeb * context, break; default: DPR("sndio_stream_init() unsupported format\n"); - return CUBEB_ERROR_INVALID_FORMAT; + goto err; } - wpar.rate = output_stream_params->rate; - wpar.pchan = output_stream_params->channels; + wpar.rate = rate; + if (s->mode & SIO_REC) + wpar.rchan = input_stream_params->channels; + if (s->mode & SIO_PLAY) + wpar.pchan = output_stream_params->channels; wpar.appbufsz = latency_frames; - if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) { - sio_close(s->hdl); - free(s); + if (!WRAP(sio_setpar)(s->hdl, &wpar) || !WRAP(sio_getpar)(s->hdl, &rpar)) { DPR("sndio_stream_init(), sio_setpar() failed\n"); - return CUBEB_ERROR; + goto err; } if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig || rpar.rate != wpar.rate || - rpar.pchan != wpar.pchan) { - sio_close(s->hdl); - free(s); + ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) || + ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) { DPR("sndio_stream_init() unsupported params\n"); - return CUBEB_ERROR_INVALID_FORMAT; + goto err; } - sio_onmove(s->hdl, sndio_onmove, s); + WRAP(sio_onmove)(s->hdl, sndio_onmove, s); s->active = 0; s->nfr = rpar.round; - s->bpf = rpar.bps * rpar.pchan; + s->rbpf = rpar.bps * rpar.rchan; + s->pbpf = rpar.bps * rpar.pchan; + s->rchan = rpar.rchan; s->pchan = rpar.pchan; + s->nblks = rpar.bufsz / rpar.round; s->data_cb = data_callback; s->state_cb = state_callback; s->arg = user_ptr; - s->mtx = PTHREAD_MUTEX_INITIALIZER; - s->rdpos = s->wrpos = 0; - if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) { + s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + s->hwpos = s->swpos = 0; + if (format == CUBEB_SAMPLE_FLOAT32LE) { s->conv = 1; - size = rpar.round * rpar.pchan * sizeof(float); + bps = sizeof(float); } else { s->conv = 0; - size = rpar.round * rpar.pchan * rpar.bps; + bps = rpar.bps; } - s->buf = malloc(size); - if (s->buf == NULL) { - sio_close(s->hdl); - free(s); - return CUBEB_ERROR; + if (s->mode & SIO_PLAY) { + s->pbuf = malloc(bps * rpar.pchan * rpar.round); + if (s->pbuf == NULL) + goto err; } + if (s->mode & SIO_REC) { + s->rbuf = malloc(bps * rpar.rchan * rpar.round); + if (s->rbuf == NULL) + goto err; + } + s->volume = 1.; *stream = s; DPR("sndio_stream_init() end, ok\n"); (void)context; (void)stream_name; return CUBEB_OK; +err: + if (s->hdl) + WRAP(sio_close)(s->hdl); + if (s->pbuf) + free(s->pbuf); + if (s->rbuf) + free(s->pbuf); + free(s); + return CUBEB_ERROR; } static int @@ -280,16 +476,21 @@ sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) static int sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) { - // XXX Not yet implemented. - *rate = 44100; - + /* + * We've no device-independent prefered rate; any rate will work if + * sndiod is running. If it isn't, 48kHz is what is most likely to + * work as most (but not all) devices support it. + */ + *rate = 48000; return CUBEB_OK; } static int sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) { - // XXX Not yet implemented. + /* + * We've no device-independent minimum latency. + */ *latency_frames = 2048; return CUBEB_OK; @@ -299,7 +500,11 @@ static void sndio_stream_destroy(cubeb_stream *s) { DPR("sndio_stream_destroy()\n"); - sio_close(s->hdl); + WRAP(sio_close)(s->hdl); + if (s->mode & SIO_PLAY) + free(s->pbuf); + if (s->mode & SIO_REC) + free(s->rbuf); free(s); } @@ -335,8 +540,8 @@ static int sndio_stream_get_position(cubeb_stream *s, uint64_t *p) { pthread_mutex_lock(&s->mtx); - DPR("sndio_stream_get_position() %lld\n", s->rdpos); - *p = s->rdpos / s->bpf; + DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos); + *p = s->hwpos; pthread_mutex_unlock(&s->mtx); return CUBEB_OK; } @@ -346,7 +551,11 @@ sndio_stream_set_volume(cubeb_stream *s, float volume) { DPR("sndio_stream_set_volume(%f)\n", volume); pthread_mutex_lock(&s->mtx); - sio_setvol(s->hdl, SIO_MAXVOL * volume); + if (volume < 0.) + volume = 0.; + else if (volume > 1.0) + volume = 1.; + s->volume = volume; pthread_mutex_unlock(&s->mtx); return CUBEB_OK; } @@ -356,7 +565,47 @@ sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency) { // http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open // in the "Measuring the latency and buffers usage" paragraph. - *latency = (stm->wrpos - stm->rdpos) / stm->bpf; + *latency = stm->swpos - stm->hwpos; + return CUBEB_OK; +} + +static int +sndio_enumerate_devices(cubeb *context, cubeb_device_type type, + cubeb_device_collection *collection) +{ + static char dev[] = SIO_DEVANY; + cubeb_device_info *device; + + device = malloc(sizeof(cubeb_device_info)); + if (device == NULL) + return CUBEB_ERROR; + + device->devid = dev; /* passed to stream_init() */ + device->device_id = dev; /* printable in UI */ + device->friendly_name = dev; /* same, but friendly */ + device->group_id = dev; /* actual device if full-duplex */ + device->vendor_name = NULL; /* may be NULL */ + device->type = type; /* Input/Output */ + device->state = CUBEB_DEVICE_STATE_ENABLED; + device->preferred = CUBEB_DEVICE_PREF_ALL; + device->format = CUBEB_DEVICE_FMT_S16NE; + device->default_format = CUBEB_DEVICE_FMT_S16NE; + device->max_channels = 16; + device->default_rate = 48000; + device->min_rate = 4000; + device->max_rate = 192000; + device->latency_lo = 480; + device->latency_hi = 9600; + collection->device = device; + collection->count = 1; + return CUBEB_OK; +} + +static int +sndio_device_collection_destroy(cubeb * context, + cubeb_device_collection * collection) +{ + free(collection->device); return CUBEB_OK; } @@ -366,16 +615,17 @@ static struct cubeb_ops const sndio_ops = { .get_max_channel_count = sndio_get_max_channel_count, .get_min_latency = sndio_get_min_latency, .get_preferred_sample_rate = sndio_get_preferred_sample_rate, - .enumerate_devices = NULL, + .enumerate_devices = sndio_enumerate_devices, + .device_collection_destroy = sndio_device_collection_destroy, .destroy = sndio_destroy, .stream_init = sndio_stream_init, .stream_destroy = sndio_stream_destroy, .stream_start = sndio_stream_start, .stream_stop = sndio_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = sndio_stream_get_position, .stream_get_latency = sndio_stream_get_latency, .stream_set_volume = sndio_stream_set_volume, - .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, diff --git a/media/libcubeb/src/cubeb_strings.c b/media/libcubeb/src/cubeb_strings.c new file mode 100644 index 000000000..79d7d21b3 --- /dev/null +++ b/media/libcubeb/src/cubeb_strings.c @@ -0,0 +1,155 @@ +/* + * Copyright © 2011 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#include "cubeb_strings.h" + +#include +#include +#include + +#define CUBEB_STRINGS_INLINE_COUNT 4 + +struct cubeb_strings { + uint32_t size; + uint32_t count; + char ** data; + char * small_store[CUBEB_STRINGS_INLINE_COUNT]; +}; + +int +cubeb_strings_init(cubeb_strings ** strings) +{ + cubeb_strings* strs = NULL; + + if (!strings) { + return CUBEB_ERROR; + } + + strs = calloc(1, sizeof(cubeb_strings)); + assert(strs); + + if (!strs) { + return CUBEB_ERROR; + } + + strs->size = sizeof(strs->small_store) / sizeof(strs->small_store[0]); + strs->count = 0; + strs->data = strs->small_store; + + *strings = strs; + + return CUBEB_OK; +} + +void +cubeb_strings_destroy(cubeb_strings * strings) +{ + char ** sp = NULL; + char ** se = NULL; + + if (!strings) { + return; + } + + sp = strings->data; + se = sp + strings->count; + + for ( ; sp != se; sp++) { + if (*sp) { + free(*sp); + } + } + + if (strings->data != strings->small_store) { + free(strings->data); + } + + free(strings); +} + +/** Look for string in string storage. + @param strings Opaque pointer to interned string storage. + @param s String to look up. + @retval Read-only string or NULL if not found. */ +static char const * +cubeb_strings_lookup(cubeb_strings * strings, char const * s) +{ + char ** sp = NULL; + char ** se = NULL; + + if (!strings || !s) { + return NULL; + } + + sp = strings->data; + se = sp + strings->count; + + for ( ; sp != se; sp++) { + if (*sp && strcmp(*sp, s) == 0) { + return *sp; + } + } + + return NULL; +} + +static char const * +cubeb_strings_push(cubeb_strings * strings, char const * s) +{ + char * is = NULL; + + if (strings->count == strings->size) { + char ** new_data; + uint32_t value_size = sizeof(char const *); + uint32_t new_size = strings->size * 2; + if (!new_size || value_size > (uint32_t)-1 / new_size) { + // overflow + return NULL; + } + + if (strings->small_store == strings->data) { + // First time heap allocation. + new_data = malloc(new_size * value_size); + if (new_data) { + memcpy(new_data, strings->small_store, sizeof(strings->small_store)); + } + } else { + new_data = realloc(strings->data, new_size * value_size); + } + + if (!new_data) { + // out of memory + return NULL; + } + + strings->size = new_size; + strings->data = new_data; + } + + is = strdup(s); + strings->data[strings->count++] = is; + + return is; +} + +char const * +cubeb_strings_intern(cubeb_strings * strings, char const * s) +{ + char const * is = NULL; + + if (!strings || !s) { + return NULL; + } + + is = cubeb_strings_lookup(strings, s); + if (is) { + return is; + } + + return cubeb_strings_push(strings, s); +} + diff --git a/media/libcubeb/src/cubeb_strings.h b/media/libcubeb/src/cubeb_strings.h new file mode 100644 index 000000000..a918a01c5 --- /dev/null +++ b/media/libcubeb/src/cubeb_strings.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2011 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#ifndef CUBEB_STRINGS_H +#define CUBEB_STRINGS_H + +#include "cubeb/cubeb.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/** Opaque handle referencing interned string storage. */ +typedef struct cubeb_strings cubeb_strings; + +/** Initialize an interned string structure. + @param strings An out param where an opaque pointer to the + interned string storage will be returned. + @retval CUBEB_OK in case of success. + @retval CUBEB_ERROR in case of error. */ +CUBEB_EXPORT int cubeb_strings_init(cubeb_strings ** strings); + +/** Destroy an interned string structure freeing all associated memory. + @param strings An opaque pointer to the interned string storage to + destroy. */ +CUBEB_EXPORT void cubeb_strings_destroy(cubeb_strings * strings); + +/** Add string to internal storage. + @param strings Opaque pointer to interned string storage. + @param s String to add to storage. + @retval CUBEB_OK + @retval CUBEB_ERROR + */ +CUBEB_EXPORT char const * cubeb_strings_intern(cubeb_strings * strings, char const * s); + +#if defined(__cplusplus) +} +#endif + +#endif // !CUBEB_STRINGS_H diff --git a/media/libcubeb/src/cubeb_sun.c b/media/libcubeb/src/cubeb_sun.c index b768bca56..64ab0b5b1 100644 --- a/media/libcubeb/src/cubeb_sun.c +++ b/media/libcubeb/src/cubeb_sun.c @@ -1,504 +1,752 @@ /* - * Copyright (c) 2013, 2017 Ginn Chen + * Copyright © 2019 Nia Alarie * * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ -#include +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include -#include +#include +#include #include "cubeb/cubeb.h" #include "cubeb-internal.h" -/* Macros copied from audio_oss.h */ -/* - * CDDL HEADER START - * - * The contents of this file are subject to the terms of the - * Common Development and Distribution License (the "License"). - * You may not use this file except in compliance with the License. - * - * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE - * or http://www.opensolaris.org/os/licensing. - * See the License for the specific language governing permissions - * and limitations under the License. - * - * When distributing Covered Code, include this CDDL HEADER in each - * file and include the License file at usr/src/OPENSOLARIS.LICENSE. - * If applicable, add the following below this CDDL HEADER, with the - * fields enclosed by brackets "[]" replaced with your own identifying - * information: Portions Copyright [yyyy] [name of copyright owner] - * - * CDDL HEADER END - */ +#define BYTES_TO_FRAMES(bytes, channels) \ + (bytes / (channels * sizeof(int16_t))) + +#define FRAMES_TO_BYTES(frames, channels) \ + (frames * (channels * sizeof(int16_t))) + +/* Default to 4 + 1 for the default device. */ +#ifndef SUN_DEVICE_COUNT +#define SUN_DEVICE_COUNT (5) +#endif + +/* Supported well by most hardware. */ +#ifndef SUN_PREFER_RATE +#define SUN_PREFER_RATE (48000) +#endif + +/* Standard acceptable minimum. */ +#ifndef SUN_LATENCY_MS +#define SUN_LATENCY_MS (40) +#endif + +#ifndef SUN_DEFAULT_DEVICE +#define SUN_DEFAULT_DEVICE "/dev/audio" +#endif + +#ifndef SUN_POLL_TIMEOUT +#define SUN_POLL_TIMEOUT (1000) +#endif + +#ifndef SUN_BUFFER_FRAMES +#define SUN_BUFFER_FRAMES (32) +#endif + /* - * Copyright (C) 4Front Technologies 1996-2008. - * - * Copyright 2009 Sun Microsystems, Inc. All rights reserved. - * Use is subject to license terms. + * Supported on NetBSD regardless of hardware. */ -#define OSSIOCPARM_MASK 0x1fff /* parameters must be < 8192 bytes */ -#define OSSIOC_VOID 0x00000000 /* no parameters */ -#define OSSIOC_OUT 0x20000000 /* copy out parameters */ -#define OSSIOC_IN 0x40000000 /* copy in parameters */ -#define OSSIOC_INOUT (OSSIOC_IN|OSSIOC_OUT) -#define OSSIOC_SZ(t) ((sizeof (t) & OSSIOCPARM_MASK) << 16) -#define __OSSIO(x, y) ((int)(OSSIOC_VOID|(x<<8)|y)) -#define __OSSIOR(x, y, t) ((int)(OSSIOC_OUT|OSSIOC_SZ(t)|(x<<8)|y)) -#define __OSSIOWR(x, y, t) ((int)(OSSIOC_INOUT|OSSIOC_SZ(t)|(x<<8)|y)) -#define SNDCTL_DSP_SPEED __OSSIOWR('P', 2, int) -#define SNDCTL_DSP_CHANNELS __OSSIOWR('P', 6, int) -#define SNDCTL_DSP_SETFMT __OSSIOWR('P', 5, int) /* Selects ONE fmt */ -#define SNDCTL_DSP_GETODELAY __OSSIOR('P', 23, int) -#define SNDCTL_DSP_HALT_OUTPUT __OSSIO('P', 34) -#define AFMT_S16_LE 0x00000010 -#define AFMT_S16_BE 0x00000020 - -#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) -#define AFMT_S16_NE AFMT_S16_BE -#else -#define AFMT_S16_NE AFMT_S16_LE -#endif -#define DEFAULT_AUDIO_DEVICE "/dev/audio" -#define DEFAULT_DSP_DEVICE "/dev/dsp" +#ifndef SUN_MAX_CHANNELS +# ifdef __NetBSD__ +# define SUN_MAX_CHANNELS (12) +# else +# define SUN_MAX_CHANNELS (2) +# endif +#endif -#define BUF_SIZE_MS 10 +#ifndef SUN_MIN_RATE +#define SUN_MIN_RATE (1000) +#endif -#if defined(CUBEB_SUNAUDIO_DEBUG) -#define DPR(...) fprintf(stderr, __VA_ARGS__); -#else -#define DPR(...) do {} while(0) +#ifndef SUN_MAX_RATE +#define SUN_MAX_RATE (192000) #endif -static struct cubeb_ops const sunaudio_ops; +static struct cubeb_ops const sun_ops; struct cubeb { struct cubeb_ops const * ops; }; struct cubeb_stream { - cubeb * context; - pthread_t th; /* to run real-time audio i/o */ - pthread_mutex_t mutex; /* protects fd and frm_played */ - int fd; /* link us to sunaudio */ - int active; /* cubec_start() called */ - int conv; /* need float->s16 conversion */ - int using_oss; - unsigned char *buf; /* data is prepared here */ - unsigned int rate; - unsigned int n_channles; - unsigned int bytes_per_ch; - unsigned int n_frm; - unsigned int buffer_size; - int64_t frm_played; - cubeb_data_callback data_cb; /* cb to preapare data */ - cubeb_state_callback state_cb; /* cb to notify about state changes */ - void *arg; /* user arg to {data,state}_cb */ + struct cubeb * context; + void * user_ptr; + pthread_t thread; + pthread_mutex_t mutex; /* protects running, volume, frames_written */ + int floating; + int running; + int play_fd; + int record_fd; + float volume; + struct audio_info p_info; /* info for the play fd */ + struct audio_info r_info; /* info for the record fd */ + cubeb_data_callback data_cb; + cubeb_state_callback state_cb; + int16_t * play_buf; + int16_t * record_buf; + float * f_play_buf; + float * f_record_buf; + char input_name[32]; + char output_name[32]; + uint64_t frames_written; + uint64_t blocks_written; }; +int +sun_init(cubeb ** context, char const * context_name) +{ + cubeb * c; + + (void)context_name; + if ((c = calloc(1, sizeof(cubeb))) == NULL) { + return CUBEB_ERROR; + } + c->ops = &sun_ops; + *context = c; + return CUBEB_OK; +} + static void -float_to_s16(void *ptr, long nsamp) +sun_destroy(cubeb * context) { - int16_t *dst = ptr; - float *src = ptr; + free(context); +} - while (nsamp-- > 0) - *(dst++) = *(src++) * 32767; +static char const * +sun_get_backend_id(cubeb * context) +{ + return "sun"; } -static void * -sunaudio_mainloop(void *arg) +static int +sun_get_preferred_sample_rate(cubeb * context, uint32_t * rate) { - struct cubeb_stream *s = arg; - int state; + (void)context; - DPR("sunaudio_mainloop()\n"); + *rate = SUN_PREFER_RATE; + return CUBEB_OK; +} - s->state_cb(s, s->arg, CUBEB_STATE_STARTED); +static int +sun_get_max_channel_count(cubeb * context, uint32_t * max_channels) +{ + (void)context; - pthread_mutex_lock(&s->mutex); - DPR("sunaudio_mainloop(), started\n"); + *max_channels = SUN_MAX_CHANNELS; + return CUBEB_OK; +} - for (;;) { - if (!s->active) { - DPR("sunaudio_mainloop() stopped\n"); - state = CUBEB_STATE_STOPPED; - break; - } +static int +sun_get_min_latency(cubeb * context, cubeb_stream_params params, + uint32_t * latency_frames) +{ + (void)context; - if (!s->using_oss) { - audio_info_t info; - ioctl(s->fd, AUDIO_GETINFO, &info); - if (s->frm_played > info.play.samples + 3 * s->n_frm) { - pthread_mutex_unlock(&s->mutex); - struct timespec ts = {0, 10000}; // 10 ms - nanosleep(&ts, NULL); - pthread_mutex_lock(&s->mutex); - continue; - } - } + *latency_frames = SUN_LATENCY_MS * params.rate / 1000; + return CUBEB_OK; +} - pthread_mutex_unlock(&s->mutex); - unsigned int got = s->data_cb(s, s->arg, NULL, s->buf, s->n_frm); - DPR("sunaudio_mainloop() ask %d got %d\n", s->n_frm, got); - pthread_mutex_lock(&s->mutex); +static int +sun_get_hwinfo(const char * device, struct audio_info * format, + int * props, struct audio_device * dev) +{ + int fd = -1; - if (got < 0) { - DPR("sunaudio_mainloop() cb err\n"); - state = CUBEB_STATE_ERROR; - break; - } + if ((fd = open(device, O_RDONLY)) == -1) { + goto error; + } +#ifdef AUDIO_GETFORMAT + if (ioctl(fd, AUDIO_GETFORMAT, format) != 0) { + goto error; + } +#endif +#ifdef AUDIO_GETPROPS + if (ioctl(fd, AUDIO_GETPROPS, props) != 0) { + goto error; + } +#endif + if (ioctl(fd, AUDIO_GETDEV, dev) != 0) { + goto error; + } + close(fd); + return CUBEB_OK; +error: + if (fd != -1) { + close(fd); + } + return CUBEB_ERROR; +} - if (s->conv) { - float_to_s16(s->buf, got * s->n_channles); - } +/* + * XXX: PR kern/54264 + */ +static int +sun_prinfo_verify_sanity(struct audio_prinfo * prinfo) +{ + return prinfo->precision >= 8 && prinfo->precision <= 32 && + prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS && + prinfo->sample_rate < SUN_MAX_RATE && prinfo->sample_rate > SUN_MIN_RATE; +} - unsigned int avail = got * 2 * s->n_channles; // coverted to s16 - unsigned int pos = 0; +static int +sun_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection * collection) +{ + unsigned i; + cubeb_device_info device = {0}; + char dev[16] = SUN_DEFAULT_DEVICE; + char dev_friendly[64]; + struct audio_info hwfmt; + struct audio_device hwname; + struct audio_prinfo *prinfo = NULL; + int hwprops; + + collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info)); + if (collection->device == NULL) { + return CUBEB_ERROR; + } + collection->count = 0; - while (avail > 0 && s->active) { - int written = write(s->fd, s->buf + pos, avail); - if (written == -1) { - if (errno != EINTR && errno != EWOULDBLOCK) { - DPR("sunaudio_mainloop() write err\n"); - state = CUBEB_STATE_ERROR; - break; - } - pthread_mutex_unlock(&s->mutex); - struct timespec ts = {0, 10000}; // 10 ms - nanosleep(&ts, NULL); - pthread_mutex_lock(&s->mutex); - } else { - pos += written; - DPR("sunaudio_mainloop() write %d pos %d\n", written, pos); - s->frm_played += written / 2 / s->n_channles; - avail -= written; - } + for (i = 0; i < SUN_DEVICE_COUNT; ++i) { + if (i > 0) { + (void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1); } - - if ((got < s->n_frm)) { - DPR("sunaudio_mainloop() drained\n"); - state = CUBEB_STATE_DRAINED; + if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) { + continue; + } +#ifdef AUDIO_GETPROPS + device.type = 0; + if ((hwprops & AUDIO_PROP_CAPTURE) != 0 && + sun_prinfo_verify_sanity(&hwfmt.record)) { + /* the device supports recording, probably */ + device.type |= CUBEB_DEVICE_TYPE_INPUT; + } + if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 && + sun_prinfo_verify_sanity(&hwfmt.play)) { + /* the device supports playback, probably */ + device.type |= CUBEB_DEVICE_TYPE_OUTPUT; + } + switch (device.type) { + case 0: + /* device doesn't do input or output, aliens probably involved */ + continue; + case CUBEB_DEVICE_TYPE_INPUT: + if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) { + /* this device is input only, not scanning for those, skip it */ + continue; + } break; + case CUBEB_DEVICE_TYPE_OUTPUT: + if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) { + /* this device is output only, not scanning for those, skip it */ + continue; + } + break; + } + if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) { + prinfo = &hwfmt.record; + } + if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) { + prinfo = &hwfmt.play; } +#endif + if (i > 0) { + (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)", + hwname.name, hwname.version, hwname.config, i - 1); + } else { + (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)", + hwname.name, hwname.version, hwname.config); + } + device.devid = (void *)(uintptr_t)i; + device.device_id = strdup(dev); + device.friendly_name = strdup(dev_friendly); + device.group_id = strdup(dev); + device.vendor_name = strdup(hwname.name); + device.type = type; + device.state = CUBEB_DEVICE_STATE_ENABLED; + device.preferred = (i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; +#ifdef AUDIO_GETFORMAT + device.max_channels = prinfo->channels; + device.default_rate = prinfo->sample_rate; +#else + device.max_channels = 2; + device.default_rate = SUN_PREFER_RATE; +#endif + device.default_format = CUBEB_DEVICE_FMT_S16NE; + device.format = CUBEB_DEVICE_FMT_S16NE; + device.min_rate = SUN_MIN_RATE; + device.max_rate = SUN_MAX_RATE; + device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000; + device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000; + collection->device[collection->count++] = device; } + return CUBEB_OK; +} - pthread_mutex_unlock(&s->mutex); - s->state_cb(s, s->arg, state); +static int +sun_device_collection_destroy(cubeb * context, + cubeb_device_collection * collection) +{ + unsigned i; - return NULL; + for (i = 0; i < collection->count; ++i) { + free((char *)collection->device[i].device_id); + free((char *)collection->device[i].friendly_name); + free((char *)collection->device[i].group_id); + free((char *)collection->device[i].vendor_name); + } + free(collection->device); + return CUBEB_OK; } -/*static*/ int -sunaudio_init(cubeb **context, char const *context_name) +static int +sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params, + struct audio_info * info, struct audio_prinfo * prinfo) { - DPR("sunaudio_init(%s)\n", context_name); - *context = malloc(sizeof(*context)); - (*context)->ops = &sunaudio_ops; - (void)context_name; + prinfo->channels = params->channels; + prinfo->sample_rate = params->rate; + prinfo->precision = 16; +#ifdef AUDIO_ENCODING_SLINEAR_LE + switch (params->format) { + case CUBEB_SAMPLE_S16LE: + prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE; + break; + case CUBEB_SAMPLE_S16BE: + prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE; + break; + case CUBEB_SAMPLE_FLOAT32NE: + stream->floating = 1; + prinfo->encoding = AUDIO_ENCODING_SLINEAR; + break; + default: + LOG("Unsupported format"); + return CUBEB_ERROR_INVALID_FORMAT; + } +#else + switch (params->format) { + case CUBEB_SAMPLE_S16NE: + prinfo->encoding = AUDIO_ENCODING_LINEAR; + break; + case CUBEB_SAMPLE_FLOAT32NE: + stream->floating = 1; + prinfo->encoding = AUDIO_ENCODING_LINEAR; + break; + default: + LOG("Unsupported format"); + return CUBEB_ERROR_INVALID_FORMAT; + } +#endif + if (ioctl(fd, AUDIO_SETINFO, info) == -1) { + return CUBEB_ERROR; + } + if (ioctl(fd, AUDIO_GETINFO, info) == -1) { + return CUBEB_ERROR; + } return CUBEB_OK; } -static char const * -sunaudio_get_backend_id(cubeb *context) +static int +sun_stream_stop(cubeb_stream * s) { - return "sunaudio"; + pthread_mutex_lock(&s->mutex); + if (s->running) { + s->running = 0; + pthread_mutex_unlock(&s->mutex); + pthread_join(s->thread, NULL); + } else { + pthread_mutex_unlock(&s->mutex); + } + return CUBEB_OK; } static void -sunaudio_destroy(cubeb *context) +sun_stream_destroy(cubeb_stream * s) { - DPR("sunaudio_destroy()\n"); - free(context); + pthread_mutex_destroy(&s->mutex); + sun_stream_stop(s); + if (s->play_fd != -1) { + close(s->play_fd); + } + if (s->record_fd != -1) { + close(s->record_fd); + } + free(s->f_play_buf); + free(s->f_record_buf); + free(s->play_buf); + free(s->record_buf); + free(s); } -static int -sunaudio_stream_init(cubeb *context, - cubeb_stream **stream, - char const *stream_name, - cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency, - cubeb_data_callback data_callback, - cubeb_state_callback state_callback, - void *user_ptr) +static void +sun_float_to_linear(float * in, int16_t * out, + unsigned channels, long frames, float vol) { - struct cubeb_stream *s; - DPR("sunaudio_stream_init(%s)\n", stream_name); - size_t size; - - s = malloc(sizeof(struct cubeb_stream)); - if (s == NULL) - return CUBEB_ERROR; - s->context = context; - - // If UTAUDIODEV is set, use it with Sun Audio interface - char * sa_device_name = getenv("UTAUDIODEV"); - char * dsp_device_name = NULL; - if (!sa_device_name) { - dsp_device_name = getenv("AUDIODSP"); - if (!dsp_device_name) { - dsp_device_name = DEFAULT_DSP_DEVICE; - } - sa_device_name = getenv("AUDIODEV"); - if (!sa_device_name) { - sa_device_name = DEFAULT_AUDIO_DEVICE; + unsigned i, sample_count = frames * channels; + float multiplier = vol * 0x8000; + + for (i = 0; i < sample_count; ++i) { + int32_t sample = lrintf(in[i] * multiplier); + if (sample < -0x8000) { + out[i] = -0x8000; + } else if (sample > 0x7fff) { + out[i] = 0x7fff; + } else { + out[i] = sample; } } +} - s->using_oss = 0; - // Try to use OSS if available - if (dsp_device_name) { - s->fd = open(dsp_device_name, O_WRONLY | O_NONBLOCK); - if (s->fd >= 0) { - s->using_oss = 1; - } - } +static void +sun_linear_to_float(int16_t * in, float * out, + unsigned channels, long frames) +{ + unsigned i, sample_count = frames * channels; - // Try Sun Audio - if (!s->using_oss) { - s->fd = open(sa_device_name, O_WRONLY | O_NONBLOCK); + for (i = 0; i < sample_count; ++i) { + out[i] = (1.0 / 0x8000) * in[i]; } +} - if (s->fd < 0) { - free(s); - DPR("sunaudio_stream_init(), open() failed\n"); - return CUBEB_ERROR; +static void +sun_linear_set_vol(int16_t * buf, unsigned channels, long frames, float vol) +{ + unsigned i, sample_count = frames * channels; + int32_t multiplier = vol * 0x8000; + + for (i = 0; i < sample_count; ++i) { + buf[i] = (buf[i] * multiplier) >> 15; } +} - if (s->using_oss) { - if (ioctl(s->fd, SNDCTL_DSP_SPEED, &output_stream_params->rate) < 0) { - DPR("ioctl SNDCTL_DSP_SPEED failed.\n"); - close(s->fd); - free(s); - return CUBEB_ERROR_INVALID_FORMAT; +static void * +sun_io_routine(void * arg) +{ + cubeb_stream *s = arg; + cubeb_state state = CUBEB_STATE_STARTED; + size_t to_read = 0; + long to_write = 0; + size_t write_ofs = 0; + size_t read_ofs = 0; + int drain = 0; + + s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED); + while (state != CUBEB_STATE_ERROR) { + pthread_mutex_lock(&s->mutex); + if (!s->running) { + pthread_mutex_unlock(&s->mutex); + state = CUBEB_STATE_STOPPED; + break; } - - if (ioctl(s->fd, SNDCTL_DSP_CHANNELS, &output_stream_params->channels) < 0) { - DPR("ioctl SNDCTL_DSP_CHANNELS failed.\n"); - close(s->fd); - free(s); - return CUBEB_ERROR_INVALID_FORMAT; + pthread_mutex_unlock(&s->mutex); + if (s->floating) { + if (s->record_fd != -1) { + sun_linear_to_float(s->record_buf, s->f_record_buf, + s->r_info.record.channels, SUN_BUFFER_FRAMES); + } + to_write = s->data_cb(s, s->user_ptr, + s->f_record_buf, s->f_play_buf, SUN_BUFFER_FRAMES); + if (to_write == CUBEB_ERROR) { + state = CUBEB_STATE_ERROR; + break; + } + if (s->play_fd != -1) { + pthread_mutex_lock(&s->mutex); + sun_float_to_linear(s->f_play_buf, s->play_buf, + s->p_info.play.channels, to_write, s->volume); + pthread_mutex_unlock(&s->mutex); + } + } else { + to_write = s->data_cb(s, s->user_ptr, + s->record_buf, s->play_buf, SUN_BUFFER_FRAMES); + if (to_write == CUBEB_ERROR) { + state = CUBEB_STATE_ERROR; + break; + } + if (s->play_fd != -1) { + pthread_mutex_lock(&s->mutex); + sun_linear_set_vol(s->play_buf, s->p_info.play.channels, to_write, s->volume); + pthread_mutex_unlock(&s->mutex); + } } - - int format = AFMT_S16_NE; - if (ioctl(s->fd, SNDCTL_DSP_SETFMT, &format) < 0) { - DPR("ioctl SNDCTL_DSP_SETFMT failed.\n"); - close(s->fd); - free(s); - return CUBEB_ERROR_INVALID_FORMAT; + if (to_write < SUN_BUFFER_FRAMES) { + drain = 1; } - } else { - audio_info_t audio_info; - AUDIO_INITINFO(&audio_info) - audio_info.play.sample_rate = output_stream_params->rate; - audio_info.play.channels = output_stream_params->channels; - audio_info.play.encoding = AUDIO_ENCODING_LINEAR; - audio_info.play.precision = 16; - if (ioctl(s->fd, AUDIO_SETINFO, &audio_info) == -1) { - DPR("ioctl AUDIO_SETINFO failed.\n"); - close(s->fd); - free(s); - return CUBEB_ERROR_INVALID_FORMAT; + to_write = s->play_fd != -1 ? to_write : 0; + to_read = s->record_fd != -1 ? SUN_BUFFER_FRAMES : 0; + write_ofs = 0; + read_ofs = 0; + while (to_write > 0 || to_read > 0) { + size_t bytes; + ssize_t n, frames; + + if (to_write > 0) { + bytes = FRAMES_TO_BYTES(to_write, s->p_info.play.channels); + if ((n = write(s->play_fd, s->play_buf + write_ofs, bytes)) < 0) { + state = CUBEB_STATE_ERROR; + break; + } + frames = BYTES_TO_FRAMES(n, s->p_info.play.channels); + pthread_mutex_lock(&s->mutex); + s->frames_written += frames; + pthread_mutex_unlock(&s->mutex); + to_write -= frames; + write_ofs += frames; + } + if (to_read > 0) { + bytes = FRAMES_TO_BYTES(to_read, s->r_info.record.channels); + if ((n = read(s->record_fd, s->record_buf + read_ofs, bytes)) < 0) { + state = CUBEB_STATE_ERROR; + break; + } + frames = BYTES_TO_FRAMES(n, s->r_info.record.channels); + to_read -= frames; + read_ofs += frames; + } } - } - - s->conv = 0; - switch (output_stream_params->format) { - case CUBEB_SAMPLE_S16NE: - s->bytes_per_ch = 2; - break; - case CUBEB_SAMPLE_FLOAT32NE: - s->bytes_per_ch = 4; - s->conv = 1; + if (drain && state != CUBEB_STATE_ERROR) { + state = CUBEB_STATE_DRAINED; break; - default: - DPR("sunaudio_stream_init() unsupported format\n"); - close(s->fd); - free(s); - return CUBEB_ERROR_INVALID_FORMAT; + } } + s->state_cb(s, s->user_ptr, state); + return NULL; +} - s->active = 0; - s->rate = output_stream_params->rate; - s->n_channles = output_stream_params->channels; - s->data_cb = data_callback; +static int +sun_stream_init(cubeb * context, + cubeb_stream ** stream, + char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned latency_frames, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr) +{ + int ret = CUBEB_OK; + cubeb_stream *s = NULL; + + (void)stream_name; + (void)latency_frames; + if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) { + ret = CUBEB_ERROR; + goto error; + } + s->record_fd = -1; + s->play_fd = -1; + if (input_device != 0) { + snprintf(s->input_name, sizeof(s->input_name), + "/dev/audio%zu", (uintptr_t)input_device - 1); + } else { + snprintf(s->input_name, sizeof(s->input_name), "%s", SUN_DEFAULT_DEVICE); + } + if (output_device != 0) { + snprintf(s->output_name, sizeof(s->output_name), + "/dev/audio%zu", (uintptr_t)output_device - 1); + } else { + snprintf(s->output_name, sizeof(s->output_name), "%s", SUN_DEFAULT_DEVICE); + } + if (input_stream_params != NULL) { + if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { + LOG("Loopback not supported"); + ret = CUBEB_ERROR_NOT_SUPPORTED; + goto error; + } + if (s->record_fd == -1) { + if ((s->record_fd = open(s->input_name, O_RDONLY)) == -1) { + LOG("Audio device cannot be opened as read-only"); + ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; + goto error; + } + } + AUDIO_INITINFO(&s->r_info); +#ifdef AUMODE_RECORD + s->r_info.mode = AUMODE_RECORD; +#endif + if ((ret = sun_copy_params(s->record_fd, s, input_stream_params, + &s->r_info, &s->r_info.record)) != CUBEB_OK) { + LOG("Setting record params failed"); + goto error; + } + } + if (output_stream_params != NULL) { + if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { + LOG("Loopback not supported"); + ret = CUBEB_ERROR_NOT_SUPPORTED; + goto error; + } + if (s->play_fd == -1) { + if ((s->play_fd = open(s->output_name, O_WRONLY)) == -1) { + LOG("Audio device cannot be opened as write-only"); + ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; + goto error; + } + } + AUDIO_INITINFO(&s->p_info); +#ifdef AUMODE_PLAY + s->p_info.mode = AUMODE_PLAY; +#endif + if ((ret = sun_copy_params(s->play_fd, s, output_stream_params, + &s->p_info, &s->p_info.play)) != CUBEB_OK) { + LOG("Setting play params failed"); + goto error; + } + } + s->context = context; + s->volume = 1.0; s->state_cb = state_callback; - s->arg = user_ptr; + s->data_cb = data_callback; + s->user_ptr = user_ptr; if (pthread_mutex_init(&s->mutex, NULL) != 0) { - free(s); - return CUBEB_ERROR; + LOG("Failed to create mutex"); + goto error; } - s->frm_played = 0; - s->n_frm = s->rate * BUF_SIZE_MS / 1000; - s->buffer_size = s->bytes_per_ch * s->n_channles * s->n_frm; - s->buf = malloc(s->buffer_size); - if (s->buf == NULL) { - close(s->fd); - free(s); - return CUBEB_ERROR; + if (s->play_fd != -1 && (s->play_buf = calloc(SUN_BUFFER_FRAMES, + s->p_info.play.channels * sizeof(int16_t))) == NULL) { + ret = CUBEB_ERROR; + goto error; + } + if (s->record_fd != -1 && (s->record_buf = calloc(SUN_BUFFER_FRAMES, + s->r_info.record.channels * sizeof(int16_t))) == NULL) { + ret = CUBEB_ERROR; + goto error; + } + if (s->floating) { + if (s->play_fd != -1 && (s->f_play_buf = calloc(SUN_BUFFER_FRAMES, + s->p_info.play.channels * sizeof(float))) == NULL) { + ret = CUBEB_ERROR; + goto error; + } + if (s->record_fd != -1 && (s->f_record_buf = calloc(SUN_BUFFER_FRAMES, + s->r_info.record.channels * sizeof(float))) == NULL) { + ret = CUBEB_ERROR; + goto error; + } } - *stream = s; - DPR("sunaudio_stream_init() end, ok\n"); return CUBEB_OK; -} - -static void -sunaudio_stream_destroy(cubeb_stream *s) -{ - DPR("sunaudio_stream_destroy()\n"); - if (s->fd > 0) { - // Flush buffer - if (s->using_oss) { - ioctl(s->fd, SNDCTL_DSP_HALT_OUTPUT); - } else { - ioctl(s->fd, I_FLUSH); - } - close(s->fd); +error: + if (s != NULL) { + sun_stream_destroy(s); } - free(s->buf); - free(s); + return ret; } static int -sunaudio_stream_start(cubeb_stream *s) +sun_stream_start(cubeb_stream * s) { - int err; - - DPR("sunaudio_stream_start()\n"); - s->active = 1; - err = pthread_create(&s->th, NULL, sunaudio_mainloop, s); - if (err) { - s->active = 0; + s->running = 1; + if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) { + LOG("Couldn't create thread"); return CUBEB_ERROR; } return CUBEB_OK; } static int -sunaudio_stream_stop(cubeb_stream *s) +sun_stream_get_position(cubeb_stream * s, uint64_t * position) { - void *dummy; +#ifdef AUDIO_GETOOFFS + struct audio_offset offset; - DPR("sunaudio_stream_stop()\n"); - if (s->active) { - s->active = 0; - pthread_join(s->th, &dummy); + if (ioctl(s->play_fd, AUDIO_GETOOFFS, &offset) == -1) { + return CUBEB_ERROR; } + s->blocks_written += offset.deltablks; + *position = BYTES_TO_FRAMES(s->blocks_written * s->p_info.blocksize, + s->p_info.play.channels); return CUBEB_OK; -} - -static int -sunaudio_stream_get_position(cubeb_stream *s, uint64_t *p) -{ - int rv = CUBEB_OK; +#else pthread_mutex_lock(&s->mutex); - if (s->active && s->fd > 0) { - if (s->using_oss) { - int delay; - ioctl(s->fd, SNDCTL_DSP_GETODELAY, &delay); - int64_t t = s->frm_played - delay / s->n_channles / 2; - if (t < 0) { - *p = 0; - } else { - *p = t; - } - } else { - audio_info_t info; - ioctl(s->fd, AUDIO_GETINFO, &info); - *p = info.play.samples; - } - DPR("sunaudio_stream_get_position() %lld\n", *p); - } else { - rv = CUBEB_ERROR; - } + *position = s->frames_written; pthread_mutex_unlock(&s->mutex); - return rv; + return CUBEB_OK; +#endif } static int -sunaudio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) +sun_stream_get_latency(cubeb_stream * stream, uint32_t * latency) { - if (!ctx || !max_channels) - return CUBEB_ERROR; +#ifdef AUDIO_GETBUFINFO + struct audio_info info; - *max_channels = 2; + if (ioctl(stream->play_fd, AUDIO_GETBUFINFO, &info) == -1) { + return CUBEB_ERROR; + } + *latency = BYTES_TO_FRAMES(info.play.seek + info.blocksize, + info.play.channels); return CUBEB_OK; +#else + cubeb_stream_params params; + + params.rate = stream->p_info.play.sample_rate; + + return sun_get_min_latency(NULL, params, latency); +#endif } static int -sunaudio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) +sun_stream_set_volume(cubeb_stream * stream, float volume) { - if (!ctx || !rate) - return CUBEB_ERROR; - - // XXX Not yet implemented. - *rate = 44100; - + pthread_mutex_lock(&stream->mutex); + stream->volume = volume; + pthread_mutex_unlock(&stream->mutex); return CUBEB_OK; } static int -sunaudio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) +sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device) { - if (!ctx || !latency_ms) + *device = calloc(1, sizeof(cubeb_device)); + if (*device == NULL) { return CUBEB_ERROR; - - // XXX Not yet implemented. - *latency_ms = 20; - + } + (*device)->input_name = stream->record_fd != -1 ? + strdup(stream->input_name) : NULL; + (*device)->output_name = stream->play_fd != -1 ? + strdup(stream->output_name) : NULL; return CUBEB_OK; } static int -sunaudio_stream_get_latency(cubeb_stream * s, uint32_t * latency) +sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device) { - if (!s || !latency) - return CUBEB_ERROR; - - int rv = CUBEB_OK; - pthread_mutex_lock(&s->mutex); - if (s->active && s->fd > 0) { - if (s->using_oss) { - int delay; - ioctl(s->fd, SNDCTL_DSP_GETODELAY, &delay); - *latency = delay / s->n_channles / 2 / s->rate; - } else { - audio_info_t info; - ioctl(s->fd, AUDIO_GETINFO, &info); - *latency = (s->frm_played - info.play.samples) / s->rate; - } - DPR("sunaudio_stream_get_position() %lld\n", *p); - } else { - rv = CUBEB_ERROR; - } - pthread_mutex_unlock(&s->mutex); - return rv; + (void)stream; + free(device->input_name); + free(device->output_name); + free(device); + return CUBEB_OK; } -static struct cubeb_ops const sunaudio_ops = { - .init = sunaudio_init, - .get_backend_id = sunaudio_get_backend_id, - .destroy = sunaudio_destroy, - .get_preferred_sample_rate = sunaudio_get_preferred_sample_rate, - .stream_init = sunaudio_stream_init, - .stream_destroy = sunaudio_stream_destroy, - .stream_start = sunaudio_stream_start, - .stream_stop = sunaudio_stream_stop, - .stream_get_position = sunaudio_stream_get_position, - .get_max_channel_count = sunaudio_get_max_channel_count, - .get_min_latency = sunaudio_get_min_latency, - .stream_get_latency = sunaudio_stream_get_latency +static struct cubeb_ops const sun_ops = { + .init = sun_init, + .get_backend_id = sun_get_backend_id, + .get_max_channel_count = sun_get_max_channel_count, + .get_min_latency = sun_get_min_latency, + .get_preferred_sample_rate = sun_get_preferred_sample_rate, + .enumerate_devices = sun_enumerate_devices, + .device_collection_destroy = sun_device_collection_destroy, + .destroy = sun_destroy, + .stream_init = sun_stream_init, + .stream_destroy = sun_stream_destroy, + .stream_start = sun_stream_start, + .stream_stop = sun_stream_stop, + .stream_reset_default_device = NULL, + .stream_get_position = sun_stream_get_position, + .stream_get_latency = sun_stream_get_latency, + .stream_set_volume = sun_stream_set_volume, + .stream_get_current_device = sun_get_current_device, + .stream_device_destroy = sun_stream_device_destroy, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL }; diff --git a/media/libcubeb/src/cubeb_utils.cpp b/media/libcubeb/src/cubeb_utils.cpp new file mode 100644 index 000000000..85572a9fe --- /dev/null +++ b/media/libcubeb/src/cubeb_utils.cpp @@ -0,0 +1,23 @@ +/* + * Copyright © 2018 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#include "cubeb_utils.h" + +size_t cubeb_sample_size(cubeb_sample_format format) +{ + switch (format) { + case CUBEB_SAMPLE_S16LE: + case CUBEB_SAMPLE_S16BE: + return sizeof(int16_t); + case CUBEB_SAMPLE_FLOAT32LE: + case CUBEB_SAMPLE_FLOAT32BE: + return sizeof(float); + default: + // should never happen as all cases are handled above. + assert(false); + } +} diff --git a/media/libcubeb/src/cubeb_utils.h b/media/libcubeb/src/cubeb_utils.h index d8e9928fe..df6751155 100644 --- a/media/libcubeb/src/cubeb_utils.h +++ b/media/libcubeb/src/cubeb_utils.h @@ -8,11 +8,16 @@ #if !defined(CUBEB_UTILS) #define CUBEB_UTILS +#include "cubeb/cubeb.h" + +#ifdef __cplusplus + #include #include #include +#include #include -#if defined(WIN32) +#if defined(_WIN32) #include "cubeb_utils_win.h" #else #include "cubeb_utils_unix.h" @@ -23,6 +28,7 @@ template void PodCopy(T * destination, const T * source, size_t count) { static_assert(std::is_trivial::value, "Requires trivial type"); + assert(destination && source); memcpy(destination, source, count * sizeof(T)); } @@ -31,6 +37,7 @@ template void PodMove(T * destination, const T * source, size_t count) { static_assert(std::is_trivial::value, "Requires trivial type"); + assert(destination && source); memmove(destination, source, count * sizeof(T)); } @@ -39,9 +46,67 @@ template void PodZero(T * destination, size_t count) { static_assert(std::is_trivial::value, "Requires trivial type"); + assert(destination); memset(destination, 0, count * sizeof(T)); } +namespace { +template +void Copy(T * destination, const T * source, size_t count, Trait) +{ + for (size_t i = 0; i < count; i++) { + destination[i] = source[i]; + } +} + +template +void Copy(T * destination, const T * source, size_t count, std::true_type) +{ + PodCopy(destination, source, count); +} +} + +/** + * This allows copying a number of elements from a `source` pointer to a + * `destination` pointer, using `memcpy` if it is safe to do so, or a loop that + * calls the constructors and destructors otherwise. + */ +template +void Copy(T * destination, const T * source, size_t count) +{ + assert(destination && source); + Copy(destination, source, count, typename std::is_trivial::type()); +} + +namespace { +template +void ConstructDefault(T * destination, size_t count, Trait) +{ + for (size_t i = 0; i < count; i++) { + destination[i] = T(); + } +} + +template +void ConstructDefault(T * destination, + size_t count, std::true_type) +{ + PodZero(destination, count); +} +} + +/** + * This allows zeroing (using memset) or default-constructing a number of + * elements calling the constructors and destructors if necessary. + */ +template +void ConstructDefault(T * destination, size_t count) +{ + assert(destination); + ConstructDefault(destination, count, + typename std::is_arithmetic::type()); +} + template class auto_array { @@ -63,6 +128,11 @@ public: return data_; } + T * end() const + { + return data_ + length_; + } + const T& at(size_t index) const { assert(index < length_ && "out of range"); @@ -198,18 +268,76 @@ private: size_t length_; }; -struct auto_lock { - explicit auto_lock(owned_critical_section & lock) - : lock(lock) - { - lock.enter(); +struct auto_array_wrapper { + virtual void push(void * elements, size_t length) = 0; + virtual size_t length() = 0; + virtual void push_silence(size_t length) = 0; + virtual bool pop(size_t length) = 0; + virtual void * data() = 0; + virtual void * end() = 0; + virtual void clear() = 0; + virtual bool reserve(size_t capacity) = 0; + virtual void set_length(size_t length) = 0; + virtual ~auto_array_wrapper() {} +}; + +template +struct auto_array_wrapper_impl : public auto_array_wrapper { + auto_array_wrapper_impl() {} + + explicit auto_array_wrapper_impl(uint32_t size) + : ar(size) + {} + + void push(void * elements, size_t length) override { + ar.push(static_cast(elements), length); } - ~auto_lock() - { - lock.leave(); + + size_t length() override { + return ar.length(); + } + + void push_silence(size_t length) override { + ar.push_silence(length); + } + + bool pop(size_t length) override { + return ar.pop(nullptr, length); + } + + void * data() override { + return ar.data(); + } + + void * end() override { + return ar.end(); + } + + void clear() override { + ar.clear(); + } + + bool reserve(size_t capacity) override { + return ar.reserve(capacity); } + + void set_length(size_t length) override { + ar.set_length(length); + } + + ~auto_array_wrapper_impl() { + ar.clear(); + } + private: - owned_critical_section & lock; + auto_array ar; }; +extern "C" { + size_t cubeb_sample_size(cubeb_sample_format format); +} + +using auto_lock = std::lock_guard; +#endif // __cplusplus + #endif /* CUBEB_UTILS */ diff --git a/media/libcubeb/src/cubeb_utils_unix.h b/media/libcubeb/src/cubeb_utils_unix.h index 80219d58b..4876d015f 100644 --- a/media/libcubeb/src/cubeb_utils_unix.h +++ b/media/libcubeb/src/cubeb_utils_unix.h @@ -48,7 +48,7 @@ public: #endif } - void enter() + void lock() { #ifndef NDEBUG int r = @@ -59,7 +59,7 @@ public: #endif } - void leave() + void unlock() { #ifndef NDEBUG int r = diff --git a/media/libcubeb/src/cubeb_utils_win.h b/media/libcubeb/src/cubeb_utils_win.h index 2b094cd93..0112ad6d3 100644 --- a/media/libcubeb/src/cubeb_utils_win.h +++ b/media/libcubeb/src/cubeb_utils_win.h @@ -29,7 +29,7 @@ public: DeleteCriticalSection(&critical_section); } - void enter() + void lock() { EnterCriticalSection(&critical_section); #ifndef NDEBUG @@ -38,7 +38,7 @@ public: #endif } - void leave() + void unlock() { #ifndef NDEBUG /* GetCurrentThreadId cannot return 0: it is not a the valid thread id */ diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp index e88d6becd..6acf1c1cc 100644 --- a/media/libcubeb/src/cubeb_wasapi.cpp +++ b/media/libcubeb/src/cubeb_wasapi.cpp @@ -4,6 +4,7 @@ * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ +#define _WIN32_WINNT 0x0600 #define NOMINMAX #include @@ -23,20 +24,61 @@ #include #include #include +#include #include "cubeb/cubeb.h" #include "cubeb-internal.h" +#include "cubeb_mixer.h" #include "cubeb_resampler.h" +#include "cubeb_strings.h" #include "cubeb_utils.h" -/* devicetopology.h missing in MinGW. */ -#ifndef __devicetopology_h__ -#include "cubeb_devicetopology.h" +// Windows 10 exposes the IAudioClient3 interface to create low-latency streams. +// Copy the interface definition from audioclient.h here to make the code simpler +// and so that we can still access IAudioClient3 via COM if cubeb was compiled +// against an older SDK. +#ifndef __IAudioClient3_INTERFACE_DEFINED__ +#define __IAudioClient3_INTERFACE_DEFINED__ +MIDL_INTERFACE("7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42") +IAudioClient3 : public IAudioClient +{ +public: + virtual HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod( + /* [annotation][in] */ + _In_ const WAVEFORMATEX *pFormat, + /* [annotation][out] */ + _Out_ UINT32 *pDefaultPeriodInFrames, + /* [annotation][out] */ + _Out_ UINT32 *pFundamentalPeriodInFrames, + /* [annotation][out] */ + _Out_ UINT32 *pMinPeriodInFrames, + /* [annotation][out] */ + _Out_ UINT32 *pMaxPeriodInFrames) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod( + /* [unique][annotation][out] */ + _Out_ WAVEFORMATEX **ppFormat, + /* [annotation][out] */ + _Out_ UINT32 *pCurrentPeriodInFrames) = 0; + + virtual HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream( + /* [annotation][in] */ + _In_ DWORD StreamFlags, + /* [annotation][in] */ + _In_ UINT32 PeriodInFrames, + /* [annotation][in] */ + _In_ const WAVEFORMATEX *pFormat, + /* [annotation][in] */ + _In_opt_ LPCGUID AudioSessionGuid) = 0; +}; +#ifdef __CRT_UUID_DECL +// Required for MinGW +__CRT_UUID_DECL(IAudioClient3, 0x7ED4EE07, 0x8E67, 0x4CD4, 0x8C, 0x1A, 0x2B, 0x7A, 0x59, 0x87, 0xAD, 0x42) #endif - -/* Taken from winbase.h, Not in MinGW. */ -#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION -#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only +#endif +// Copied from audioclient.h in the Windows 10 SDK +#ifndef AUDCLNT_E_ENGINE_PERIODICITY_LOCKED +#define AUDCLNT_E_ENGINE_PERIODICITY_LOCKED AUDCLNT_ERR(0x028) #endif #ifndef PKEY_Device_FriendlyName @@ -47,6 +89,15 @@ DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e #endif namespace { +struct com_heap_ptr_deleter { + void operator()(void * ptr) const noexcept { + CoTaskMemFree(ptr); + } +}; + +template +using com_heap_ptr = std::unique_ptr; + template constexpr size_t ARRAY_LENGTH(T(&)[N]) @@ -54,51 +105,83 @@ ARRAY_LENGTH(T(&)[N]) return N; } -void -SafeRelease(HANDLE handle) -{ - if (handle) { - CloseHandle(handle); - } -} +template +class no_addref_release : public T { + ULONG STDMETHODCALLTYPE AddRef() = 0; + ULONG STDMETHODCALLTYPE Release() = 0; +}; template -void SafeRelease(T * ptr) -{ - if (ptr) { - ptr->Release(); +class com_ptr { +public: + com_ptr() noexcept = default; + + com_ptr(com_ptr const & other) noexcept = delete; + com_ptr & operator=(com_ptr const & other) noexcept = delete; + T ** operator&() const noexcept = delete; + + ~com_ptr() noexcept { + release(); } -} -struct auto_com { - auto_com() { - result = CoInitializeEx(NULL, COINIT_MULTITHREADED); - } - ~auto_com() { - if (result == RPC_E_CHANGED_MODE) { - // This is not an error, COM was not initialized by this function, so it is - // not necessary to uninit it. - LOG("COM was already initialized in STA."); - } else if (result == S_FALSE) { - // This is not an error. We are allowed to call CoInitializeEx more than - // once, as long as it is matches by an CoUninitialize call. - // We do that in the dtor which is guaranteed to be called. - LOG("COM was already initialized in MTA"); - } - if (SUCCEEDED(result)) { - CoUninitialize(); + com_ptr(com_ptr && other) noexcept + : ptr(other.ptr) + { + other.ptr = nullptr; + } + + com_ptr & operator=(com_ptr && other) noexcept { + if (ptr != other.ptr) { + release(); + ptr = other.ptr; + other.ptr = nullptr; } + return *this; + } + + explicit operator bool() const noexcept { + return nullptr != ptr; } - bool ok() { - return result == RPC_E_CHANGED_MODE || SUCCEEDED(result); + + no_addref_release * operator->() const noexcept { + return static_cast *>(ptr); } + + T * get() const noexcept { + return ptr; + } + + T ** receive() noexcept { + XASSERT(ptr == nullptr); + return &ptr; + } + + void ** receive_vpp() noexcept { + return reinterpret_cast(receive()); + } + + com_ptr & operator=(std::nullptr_t) noexcept { + release(); + return *this; + } + + void reset(T * p = nullptr) noexcept { + release(); + ptr = p; + } + private: - HRESULT result; -}; + void release() noexcept { + T * temp = ptr; -typedef HANDLE (WINAPI *set_mm_thread_characteristics_function)( - const char * TaskName, LPDWORD TaskIndex); -typedef BOOL (WINAPI *revert_mm_thread_characteristics_function)(HANDLE handle); + if (temp) { + ptr = nullptr; + temp->Release(); + } + } + + T * ptr = nullptr; +}; extern cubeb_ops const wasapi_ops; @@ -106,19 +189,28 @@ int wasapi_stream_stop(cubeb_stream * stm); int wasapi_stream_start(cubeb_stream * stm); void close_wasapi_stream(cubeb_stream * stm); int setup_wasapi_stream(cubeb_stream * stm); -static char * wstr_to_utf8(const wchar_t * str); -static std::unique_ptr utf8_to_wstr(char* str); +ERole pref_to_role(cubeb_stream_prefs param); +static char const * wstr_to_utf8(wchar_t const * str); +static std::unique_ptr utf8_to_wstr(char const * str); } -struct cubeb -{ - cubeb_ops const * ops; - /* Library dynamically opened to increase the render thread priority, and - the two function pointers we need. */ - HMODULE mmcss_module; - set_mm_thread_characteristics_function set_mm_thread_characteristics; - revert_mm_thread_characteristics_function revert_mm_thread_characteristics; +class wasapi_collection_notification_client; +class monitor_device_notifications; + +struct cubeb { + cubeb_ops const * ops = &wasapi_ops; + cubeb_strings * device_ids; + /* Device enumerator to get notifications when the + device collection change. */ + com_ptr device_collection_enumerator; + com_ptr collection_notification_client; + /* Collection changed for input (capture) devices. */ + cubeb_device_collection_changed_callback input_collection_changed_callback = nullptr; + void * input_collection_changed_user_ptr = nullptr; + /* Collection changed for output (render) devices. */ + cubeb_device_collection_changed_callback output_collection_changed_callback = nullptr; + void * output_collection_changed_user_ptr = nullptr; }; class wasapi_endpoint_notification_client; @@ -132,27 +224,35 @@ class wasapi_endpoint_notification_client; */ typedef bool (*wasapi_refill_callback)(cubeb_stream * stm); -struct cubeb_stream -{ - cubeb * context; +struct cubeb_stream { + /* Note: Must match cubeb_stream layout in cubeb.c. */ + cubeb * context = nullptr; + void * user_ptr = nullptr; + /**/ + /* Mixer pameters. We need to convert the input stream to this samplerate/channel layout, as WASAPI does not resample nor upmix itself. */ - cubeb_stream_params input_mix_params; - cubeb_stream_params output_mix_params; + cubeb_stream_params input_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; + cubeb_stream_params output_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; /* Stream parameters. This is what the client requested, * and what will be presented in the callback. */ - cubeb_stream_params input_stream_params; - cubeb_stream_params output_stream_params; + cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; + cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; + /* A MMDevice role for this stream: either communication or console here. */ + ERole role; /* The input and output device, or NULL for default. */ - cubeb_devid input_device; - cubeb_devid output_device; + std::unique_ptr input_device; + std::unique_ptr output_device; /* The latency initially requested for this stream, in frames. */ - unsigned latency; - cubeb_state_callback state_callback; - cubeb_data_callback data_callback; - wasapi_refill_callback refill_callback; - void * user_ptr; + unsigned latency = 0; + cubeb_state_callback state_callback = nullptr; + cubeb_data_callback data_callback = nullptr; + wasapi_refill_callback refill_callback = nullptr; + /* True when a loopback device is requested with no output device. In this + case a dummy output device is opened to drive the loopback, but should not + be exposed. */ + bool has_dummy_output = false; /* Lifetime considerations: - client, render_client, audio_clock and audio_stream_volume are interface pointer to the IAudioClient. @@ -160,70 +260,324 @@ struct cubeb_stream mix_buffer are the same as the cubeb_stream instance. */ /* Main handle on the WASAPI stream. */ - IAudioClient * output_client; + com_ptr output_client; /* Interface pointer to use the event-driven interface. */ - IAudioRenderClient * render_client; + com_ptr render_client; /* Interface pointer to use the volume facilities. */ - IAudioStreamVolume * audio_stream_volume; + com_ptr audio_stream_volume; /* Interface pointer to use the stream audio clock. */ - IAudioClock * audio_clock; + com_ptr audio_clock; /* Frames written to the stream since it was opened. Reset on device change. Uses mix_params.rate. */ - UINT64 frames_written; + UINT64 frames_written = 0; /* Frames written to the (logical) stream since it was first created. Updated on device change. Uses stream_params.rate. */ - UINT64 total_frames_written; + UINT64 total_frames_written = 0; /* Last valid reported stream position. Used to ensure the position reported by stream_get_position increases monotonically. */ - UINT64 prev_position; + UINT64 prev_position = 0; /* Device enumerator to be able to be notified when the default device change. */ - IMMDeviceEnumerator * device_enumerator; + com_ptr device_enumerator; /* Device notification client, to be able to be notified when the default audio device changes and route the audio to the new default audio output device */ - wasapi_endpoint_notification_client * notification_client; + com_ptr notification_client; /* Main andle to the WASAPI capture stream. */ - IAudioClient * input_client; + com_ptr input_client; /* Interface to use the event driven capture interface */ - IAudioCaptureClient * capture_client; + com_ptr capture_client; /* This event is set by the stream_stop and stream_destroy function, so the render loop can exit properly. */ - HANDLE shutdown_event; + HANDLE shutdown_event = 0; /* Set by OnDefaultDeviceChanged when a stream reconfiguration is required. The reconfiguration is handled by the render loop thread. */ - HANDLE reconfigure_event; + HANDLE reconfigure_event = 0; /* This is set by WASAPI when we should refill the stream. */ - HANDLE refill_event; + HANDLE refill_event = 0; /* This is set by WASAPI when we should read from the input stream. In * practice, we read from the input stream in the output callback, so * this is not used, but it is necessary to start getting input data. */ - HANDLE input_available_event; + HANDLE input_available_event = 0; /* Each cubeb_stream has its own thread. */ - HANDLE thread; + HANDLE thread = 0; /* The lock protects all members that are touched by the render thread or change during a device reset, including: audio_clock, audio_stream_volume, client, frames_written, mix_params, total_frames_written, prev_position. */ owned_critical_section stream_reset_lock; /* Maximum number of frames that can be passed down in a callback. */ - uint32_t input_buffer_frame_count; + uint32_t input_buffer_frame_count = 0; /* Maximum number of frames that can be requested in a callback. */ - uint32_t output_buffer_frame_count; + uint32_t output_buffer_frame_count = 0; /* Resampler instance. Resampling will only happen if necessary. */ - cubeb_resampler * resampler; - /* A buffer for up/down mixing multi-channel audio. */ - float * mix_buffer; + std::unique_ptr resampler = { nullptr, cubeb_resampler_destroy }; + /* Mixer interfaces */ + std::unique_ptr output_mixer = { nullptr, cubeb_mixer_destroy }; + std::unique_ptr input_mixer = { nullptr, cubeb_mixer_destroy }; + /* A buffer for up/down mixing multi-channel audio output. */ + std::vector mix_buffer; /* WASAPI input works in "packets". We re-linearize the audio packets * into this buffer before handing it to the resampler. */ - auto_array linear_input_buffer; + std::unique_ptr linear_input_buffer; + /* Bytes per sample. This multiplied by the number of channels is the number + * of bytes per frame. */ + size_t bytes_per_sample = 0; + /* WAVEFORMATEXTENSIBLE sub-format: either PCM or float. */ + GUID waveformatextensible_sub_format = GUID_NULL; /* Stream volume. Set via stream_set_volume and used to reset volume on device changes. */ - float volume; + float volume = 1.0; /* True if the stream is draining. */ - bool draining; + bool draining = false; /* True when we've destroyed the stream. This pointer is leaked on stream * destruction if we could not join the thread. */ - std::atomic*> emergency_bailout; + std::atomic*> emergency_bailout { nullptr }; + /* Synchronizes render thread start to ensure safe access to emergency_bailout. */ + HANDLE thread_ready_event = 0; +}; + +class monitor_device_notifications { +public: + monitor_device_notifications(cubeb * context) + : cubeb_context(context) + { + create_thread(); + } + + ~monitor_device_notifications() + { + SetEvent(shutdown); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + + CloseHandle(input_changed); + CloseHandle(output_changed); + CloseHandle(shutdown); + } + + void notify(EDataFlow flow) + { + XASSERT(cubeb_context); + if (flow == eCapture && cubeb_context->input_collection_changed_callback) { + bool res = SetEvent(input_changed); + if (!res) { + LOG("Failed to set input changed event"); + } + return; + } + if (flow == eRender && cubeb_context->output_collection_changed_callback) { + bool res = SetEvent(output_changed); + if (!res) { + LOG("Failed to set output changed event"); + } + } + } +private: + static unsigned int __stdcall + thread_proc(LPVOID args) + { + XASSERT(args); + static_cast(args) + ->notification_thread_loop(); + return 0; + } + + void notification_thread_loop() + { + struct auto_com { + auto_com() { + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + XASSERT(SUCCEEDED(hr)); + } + ~auto_com() { + CoUninitialize(); + } + } com; + + HANDLE wait_array[3] = { + input_changed, + output_changed, + shutdown, + }; + + while (true) { + Sleep(200); + + DWORD wait_result = WaitForMultipleObjects(ARRAY_LENGTH(wait_array), + wait_array, + FALSE, + INFINITE); + if (wait_result == WAIT_OBJECT_0) { // input changed + cubeb_context->input_collection_changed_callback(cubeb_context, + cubeb_context->input_collection_changed_user_ptr); + } else if (wait_result == WAIT_OBJECT_0 + 1) { // output changed + cubeb_context->output_collection_changed_callback(cubeb_context, + cubeb_context->output_collection_changed_user_ptr); + } else if (wait_result == WAIT_OBJECT_0 + 2) { // shutdown + break; + } else { + LOG("Unexpected result %lu", wait_result); + } + } // loop + } + + void create_thread() + { + output_changed = CreateEvent(nullptr, 0, 0, nullptr); + if (!output_changed) { + LOG("Failed to create output changed event."); + return; + } + + input_changed = CreateEvent(nullptr, 0, 0, nullptr); + if (!input_changed) { + LOG("Failed to create input changed event."); + return; + } + + shutdown = CreateEvent(nullptr, 0, 0, nullptr); + if (!shutdown) { + LOG("Failed to create shutdown event."); + return; + } + + thread = (HANDLE) _beginthreadex(nullptr, + 256 * 1024, + thread_proc, + this, + STACK_SIZE_PARAM_IS_A_RESERVATION, + nullptr); + if (!thread) { + LOG("Failed to create thread."); + return; + } + } + + HANDLE thread = INVALID_HANDLE_VALUE; + HANDLE output_changed = INVALID_HANDLE_VALUE; + HANDLE input_changed = INVALID_HANDLE_VALUE; + HANDLE shutdown = INVALID_HANDLE_VALUE; + + cubeb * cubeb_context = nullptr; +}; + +class wasapi_collection_notification_client : public IMMNotificationClient +{ +public: + /* The implementation of MSCOM was copied from MSDN. */ + ULONG STDMETHODCALLTYPE + AddRef() + { + return InterlockedIncrement(&ref_count); + } + + ULONG STDMETHODCALLTYPE + Release() + { + ULONG ulRef = InterlockedDecrement(&ref_count); + if (0 == ulRef) { + delete this; + } + return ulRef; + } + + HRESULT STDMETHODCALLTYPE + QueryInterface(REFIID riid, VOID **ppvInterface) + { + if (__uuidof(IUnknown) == riid) { + AddRef(); + *ppvInterface = (IUnknown*)this; + } else if (__uuidof(IMMNotificationClient) == riid) { + AddRef(); + *ppvInterface = (IMMNotificationClient*)this; + } else { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; + } + + wasapi_collection_notification_client(cubeb * context) + : ref_count(1) + , cubeb_context(context) + , monitor_notifications(context) + { + XASSERT(cubeb_context); + } + + virtual ~wasapi_collection_notification_client() + { } + + HRESULT STDMETHODCALLTYPE + OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) + { + LOG("collection: Audio device default changed, id = %S.", device_id); + return S_OK; + } + + /* The remaining methods are not implemented, they simply log when called (if + log is enabled), for debugging. */ + HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id) + { + LOG("collection: Audio device added."); + return S_OK; + }; + + HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id) + { + LOG("collection: Audio device removed."); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE + OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) + { + XASSERT(cubeb_context->output_collection_changed_callback || + cubeb_context->input_collection_changed_callback); + LOG("collection: Audio device state changed, id = %S, state = %lu.", device_id, new_state); + EDataFlow flow; + HRESULT hr = GetDataFlow(device_id, &flow); + if (FAILED(hr)) { + return hr; + } + monitor_notifications.notify(flow); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE + OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) + { + //Audio device property value changed. + return S_OK; + } + +private: + HRESULT GetDataFlow(LPCWSTR device_id, EDataFlow * flow) + { + com_ptr device; + com_ptr endpoint; + + HRESULT hr = cubeb_context->device_collection_enumerator + ->GetDevice(device_id, device.receive()); + if (FAILED(hr)) { + LOG("collection: Could not get device: %lx", hr); + return hr; + } + + hr = device->QueryInterface(IID_PPV_ARGS(endpoint.receive())); + if (FAILED(hr)) { + LOG("collection: Could not get endpoint: %lx", hr); + return hr; + } + + return endpoint->GetDataFlow(flow); + } + + /* refcount for this instance, necessary to implement MSCOM semantics. */ + LONG ref_count; + + cubeb * cubeb_context = nullptr; + monitor_device_notifications monitor_notifications; }; class wasapi_endpoint_notification_client : public IMMNotificationClient @@ -262,9 +616,10 @@ public: return S_OK; } - wasapi_endpoint_notification_client(HANDLE event) + wasapi_endpoint_notification_client(HANDLE event, ERole role) : ref_count(1) , reconfigure_event(event) + , role(role) { } virtual ~wasapi_endpoint_notification_client() @@ -273,16 +628,16 @@ public: HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) { - LOG("Audio device default changed."); + LOG("endpoint: Audio device default changed."); /* we only support a single stream type for now. */ - if (flow != eRender && role != eConsole) { + if (flow != eRender && role != this->role) { return S_OK; } BOOL ok = SetEvent(reconfigure_event); if (!ok) { - LOG("SetEvent on reconfigure_event failed: %x", GetLastError()); + LOG("endpoint: SetEvent on reconfigure_event failed: %lx", GetLastError()); } return S_OK; @@ -292,36 +647,54 @@ public: log is enabled), for debugging. */ HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id) { - LOG("Audio device added."); + LOG("endpoint: Audio device added."); return S_OK; }; HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id) { - LOG("Audio device removed."); + LOG("endpoint: Audio device removed."); return S_OK; } HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) { - LOG("Audio device state changed."); + LOG("endpoint: Audio device state changed."); return S_OK; } HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) { - LOG("Audio device property value changed."); + //Audio device property value changed. return S_OK; } private: /* refcount for this instance, necessary to implement MSCOM semantics. */ LONG ref_count; HANDLE reconfigure_event; + ERole role; }; namespace { + +char const * +intern_device_id(cubeb * ctx, wchar_t const * id) +{ + XASSERT(id); + + char const * tmp = wstr_to_utf8(id); + if (!tmp) + return nullptr; + + char const * interned = cubeb_strings_intern(ctx->device_ids, tmp); + + free((void *) tmp); + + return interned; +} + bool has_input(cubeb_stream * stm) { return stm->input_stream_params.rate != 0; @@ -332,22 +705,33 @@ bool has_output(cubeb_stream * stm) return stm->output_stream_params.rate != 0; } -bool should_upmix(cubeb_stream_params & stream, cubeb_stream_params & mixer) +double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer) { - return mixer.channels > stream.channels; + return double(stream.rate) / mixer.rate; } -bool should_downmix(cubeb_stream_params & stream, cubeb_stream_params & mixer) -{ - return mixer.channels < stream.channels; -} +/* Convert the channel layout into the corresponding KSAUDIO_CHANNEL_CONFIG. + See more: https://msdn.microsoft.com/en-us/library/windows/hardware/ff537083(v=vs.85).aspx */ -double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer) +cubeb_channel_layout +mask_to_channel_layout(WAVEFORMATEX const * fmt) { - return double(stream.rate) / mixer.rate; + cubeb_channel_layout mask = 0; + + if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + WAVEFORMATEXTENSIBLE const * ext = reinterpret_cast(fmt); + mask = ext->dwChannelMask; + } else if (fmt->wFormatTag == WAVE_FORMAT_PCM || + fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { + if (fmt->nChannels == 1) { + mask = CHANNEL_FRONT_CENTER; + } else if (fmt->nChannels == 2) { + mask = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT; + } + } + return mask; } - uint32_t get_rate(cubeb_stream * stm) { @@ -356,99 +740,21 @@ get_rate(cubeb_stream * stm) } uint32_t -ms_to_hns(uint32_t ms) -{ - return ms * 10000; -} - -uint32_t -hns_to_ms(REFERENCE_TIME hns) -{ - return static_cast(hns / 10000); -} - -double -hns_to_s(REFERENCE_TIME hns) +hns_to_frames(uint32_t rate, REFERENCE_TIME hns) { - return static_cast(hns) / 10000000; + return std::ceil((hns - 1) / 10000000.0 * rate); } uint32_t hns_to_frames(cubeb_stream * stm, REFERENCE_TIME hns) { - return hns_to_ms(hns * get_rate(stm)) / 1000; -} - -uint32_t -hns_to_frames(uint32_t rate, REFERENCE_TIME hns) -{ - return hns_to_ms(hns * rate) / 1000; + return hns_to_frames(get_rate(stm), hns); } REFERENCE_TIME frames_to_hns(cubeb_stream * stm, uint32_t frames) { - return frames * 1000 / get_rate(stm); -} - -/* Upmix function, copies a mono channel into L and R */ -template -void -mono_to_stereo(T * in, long insamples, T * out, int32_t out_channels) -{ - for (int i = 0, j = 0; i < insamples; ++i, j += out_channels) { - out[j] = out[j + 1] = in[i]; - } -} - -template -void -upmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels) -{ - XASSERT(out_channels >= in_channels && in_channels > 0); - - /* Either way, if we have 2 or more channels, the first two are L and R. */ - /* If we are playing a mono stream over stereo speakers, copy the data over. */ - if (in_channels == 1 && out_channels >= 2) { - mono_to_stereo(in, inframes, out, out_channels); - } else { - /* Copy through. */ - for (int i = 0, o = 0; i < inframes * in_channels; - i += in_channels, o += out_channels) { - for (int j = 0; j < in_channels; ++j) { - out[o + j] = in[i + j]; - } - } - } - - /* Check if more channels. */ - if (out_channels <= 2) { - return; - } - - /* Put silence in remaining channels. */ - for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) { - for (int j = 2; j < out_channels; ++j) { - out[o + j] = 0.0; - } - } -} - -template -void -downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels) -{ - XASSERT(in_channels >= out_channels); - /* We could use a downmix matrix here, applying mixing weight based on the - channel, but directsound and winmm simply drop the channels that cannot be - rendered by the hardware, so we do the same for consistency. */ - long out_index = 0; - for (long i = 0; i < inframes * in_channels; i += in_channels) { - for (int j = 0; j < out_channels; ++j) { - out[out_index + j] = in[i + j]; - } - out_index += out_channels; - } + return std::ceil(frames * 10000000.0 / get_rate(stm)); } /* This returns the size of a frame in the stream, before the eventual upmix @@ -456,30 +762,31 @@ downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channel static size_t frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames) { - size_t stream_frame_size = stm->output_stream_params.channels * sizeof(float); - return stream_frame_size * frames; + // This is called only when we has a output client. + XASSERT(has_output(stm)); + return stm->output_stream_params.channels * stm->bytes_per_sample * frames; } /* This function handles the processing of the input and output audio, * converting it to rate and channel layout specified at initialization. * It then calls the data callback, via the resampler. */ long -refill(cubeb_stream * stm, float * input_buffer, long input_frames_count, - float * output_buffer, long output_frames_needed) +refill(cubeb_stream * stm, void * input_buffer, long input_frames_count, + void * output_buffer, long output_frames_needed) { + XASSERT(!stm->draining); /* If we need to upmix after resampling, resample into the mix buffer to - avoid a copy. */ - float * dest = nullptr; - if (has_output(stm)) { - if (should_upmix(stm->output_stream_params, stm->output_mix_params) || - should_downmix(stm->output_stream_params, stm->output_mix_params)) { - dest = stm->mix_buffer; + avoid a copy. Avoid exposing output if it is a dummy stream. */ + void * dest = nullptr; + if (has_output(stm) && !stm->has_dummy_output) { + if (stm->output_mixer) { + dest = stm->mix_buffer.data(); } else { dest = output_buffer; } } - long out_frames = cubeb_resampler_fill(stm->resampler, + long out_frames = cubeb_resampler_fill(stm->resampler.get(), input_buffer, &input_frames_count, dest, @@ -492,47 +799,51 @@ refill(cubeb_stream * stm, float * input_buffer, long input_frames_count, stm->frames_written += out_frames; } - /* Go in draining mode if we got fewer frames than requested. */ - if (out_frames < output_frames_needed) { + /* Go in draining mode if we got fewer frames than requested. If the stream + has no output we still expect the callback to return number of frames read + from input, otherwise we stop. */ + if ((out_frames < output_frames_needed) || + (!has_output(stm) && out_frames < input_frames_count)) { LOG("start draining."); stm->draining = true; } /* If this is not true, there will be glitches. It is alright to have produced less frames if we are draining, though. */ - XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm)); - - if (has_output(stm)) { - if (should_upmix(stm->output_stream_params, stm->output_mix_params)) { - upmix(dest, out_frames, output_buffer, - stm->output_stream_params.channels, stm->output_mix_params.channels); - } else if (should_downmix(stm->output_stream_params, stm->output_mix_params)) { - downmix(dest, out_frames, output_buffer, - stm->output_stream_params.channels, stm->output_mix_params.channels); + XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm) || stm->has_dummy_output); + + // We don't bother mixing dummy output as it will be silenced, otherwise mix output if needed + if (!stm->has_dummy_output && has_output(stm) && stm->output_mixer) { + XASSERT(dest == stm->mix_buffer.data()); + size_t dest_size = + out_frames * stm->output_stream_params.channels * stm->bytes_per_sample; + XASSERT(dest_size <= stm->mix_buffer.size()); + size_t output_buffer_size = + out_frames * stm->output_mix_params.channels * stm->bytes_per_sample; + int ret = cubeb_mixer_mix(stm->output_mixer.get(), + out_frames, + dest, + dest_size, + output_buffer, + output_buffer_size); + if (ret < 0) { + LOG("Error remixing content (%d)", ret); } } return out_frames; } +int wasapi_stream_reset_default_device(cubeb_stream * stm); + /* This helper grabs all the frames available from a capture client, put them in * linear_input_buffer. linear_input_buffer should be cleared before the - * callback exits. */ + * callback exits. This helper does not work with exclusive mode streams. */ bool get_input_buffer(cubeb_stream * stm) { - HRESULT hr; - UINT32 padding_in; - XASSERT(has_input(stm)); - hr = stm->input_client->GetCurrentPadding(&padding_in); - if (FAILED(hr)) { - LOG("Failed to get padding"); - return false; - } - XASSERT(padding_in <= stm->input_buffer_frame_count); - UINT32 total_available_input = padding_in; - + HRESULT hr; BYTE * input_packet = NULL; DWORD flags; UINT64 dev_pos; @@ -540,73 +851,97 @@ bool get_input_buffer(cubeb_stream * stm) /* Get input packets until we have captured enough frames, and put them in a * contiguous buffer. */ uint32_t offset = 0; - while (offset != total_available_input) { - hr = stm->capture_client->GetNextPacketSize(&next); + // If the input stream is event driven we should only ever expect to read a + // single packet each time. However, if we're pulling from the stream we may + // need to grab multiple packets worth of frames that have accumulated (so + // need a loop). + for (hr = stm->capture_client->GetNextPacketSize(&next); + next > 0; + hr = stm->capture_client->GetNextPacketSize(&next)) { + if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { + // Application can recover from this error. More info + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx + LOG("Device invalidated error, reset default device"); + wasapi_stream_reset_default_device(stm); + return true; + } + if (FAILED(hr)) { - LOG("cannot get next packet size: %x", hr); + LOG("cannot get next packet size: %lx", hr); return false; } - /* This can happen if the capture stream has stopped. Just return in this - * case. */ - if (!next) { - break; - } - UINT32 packet_size; + UINT32 frames; hr = stm->capture_client->GetBuffer(&input_packet, - &packet_size, + &frames, &flags, &dev_pos, NULL); if (FAILED(hr)) { - LOG("GetBuffer failed for capture: %x", hr); + LOG("GetBuffer failed for capture: %lx", hr); return false; } - XASSERT(packet_size == next); + XASSERT(frames == next); + + UINT32 input_stream_samples = frames * stm->input_stream_params.channels; + // We do not explicitly handle the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY + // flag. There a two primary (non exhaustive) scenarios we anticipate this + // flag being set in: + // - The first GetBuffer after Start has this flag undefined. In this + // case the flag may be set but is meaningless and can be ignored. + // - If a glitch is introduced into the input. This should not happen + // for event based inputs, and should be mitigated by using a dummy + // stream to drive input in the case of input only loopback. Without + // a dummy output, input only loopback would glitch on silence. However, + // the dummy input should push silence to the loopback and prevent + // discontinuities. See https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/ + // As the first scenario can be ignored, and we anticipate the second + // scenario is mitigated, we ignore the flag. + // For more info: https://msdn.microsoft.com/en-us/library/windows/desktop/dd370859(v=vs.85).aspx, + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd371458(v=vs.85).aspx if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { - LOG("insert silence: ps=%u", packet_size); - stm->linear_input_buffer.push_silence(packet_size * stm->input_stream_params.channels); + LOG("insert silence: ps=%u", frames); + stm->linear_input_buffer->push_silence(input_stream_samples); } else { - if (should_upmix(stm->input_mix_params, stm->input_stream_params)) { - bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() + - packet_size * stm->input_stream_params.channels); - assert(ok); - upmix(reinterpret_cast(input_packet), packet_size, - stm->linear_input_buffer.data() + stm->linear_input_buffer.length(), - stm->input_mix_params.channels, - stm->input_stream_params.channels); - stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels); - } else if (should_downmix(stm->input_mix_params, stm->input_stream_params)) { - bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() + - packet_size * stm->input_stream_params.channels); - assert(ok); - downmix(reinterpret_cast(input_packet), packet_size, - stm->linear_input_buffer.data() + stm->linear_input_buffer.length(), - stm->input_mix_params.channels, - stm->input_stream_params.channels); - stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels); + if (stm->input_mixer) { + bool ok = stm->linear_input_buffer->reserve( + stm->linear_input_buffer->length() + input_stream_samples); + XASSERT(ok); + size_t input_packet_size = + frames * stm->input_mix_params.channels * + cubeb_sample_size(stm->input_mix_params.format); + size_t linear_input_buffer_size = + input_stream_samples * + cubeb_sample_size(stm->input_stream_params.format); + cubeb_mixer_mix(stm->input_mixer.get(), + frames, + input_packet, + input_packet_size, + stm->linear_input_buffer->end(), + linear_input_buffer_size); + stm->linear_input_buffer->set_length( + stm->linear_input_buffer->length() + input_stream_samples); } else { - stm->linear_input_buffer.push(reinterpret_cast(input_packet), - packet_size * stm->input_stream_params.channels); + stm->linear_input_buffer->push( + input_packet, input_stream_samples); } } - hr = stm->capture_client->ReleaseBuffer(packet_size); + hr = stm->capture_client->ReleaseBuffer(frames); if (FAILED(hr)) { LOG("FAILED to release intput buffer"); return false; } - offset += packet_size; + offset += input_stream_samples; } - assert(stm->linear_input_buffer.length() >= total_available_input && - offset == total_available_input); + XASSERT(stm->linear_input_buffer->length() >= offset); return true; } /* Get an output buffer from the render_client. It has to be released before * exiting the callback. */ -bool get_output_buffer(cubeb_stream * stm, float *& buffer, size_t & frame_count) +bool get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count) { UINT32 padding_out; HRESULT hr; @@ -614,10 +949,19 @@ bool get_output_buffer(cubeb_stream * stm, float *& buffer, size_t & frame_count XASSERT(has_output(stm)); hr = stm->output_client->GetCurrentPadding(&padding_out); + if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { + // Application can recover from this error. More info + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx + LOG("Device invalidated error, reset default device"); + wasapi_stream_reset_default_device(stm); + return true; + } + if (FAILED(hr)) { - LOG("Failed to get padding: %x", hr); + LOG("Failed to get padding: %lx", hr); return false; } + XASSERT(padding_out <= stm->output_buffer_frame_count); if (stm->draining) { @@ -639,7 +983,7 @@ bool get_output_buffer(cubeb_stream * stm, float *& buffer, size_t & frame_count return false; } - buffer = reinterpret_cast(output_buffer); + buffer = output_buffer; return true; } @@ -651,7 +995,7 @@ bool refill_callback_duplex(cubeb_stream * stm) { HRESULT hr; - float * output_buffer = nullptr; + void * output_buffer = nullptr; size_t output_frames = 0; size_t input_frames; bool rv; @@ -663,7 +1007,7 @@ refill_callback_duplex(cubeb_stream * stm) return rv; } - input_frames = stm->linear_input_buffer.length() / stm->input_stream_params.channels; + input_frames = stm->linear_input_buffer->length() / stm->input_stream_params.channels; if (!input_frames) { return true; } @@ -680,29 +1024,43 @@ refill_callback_duplex(cubeb_stream * stm) return true; } - // When WASAPI has not filled the input buffer yet, send silence. - double output_duration = double(output_frames) / stm->output_mix_params.rate; - double input_duration = double(stm->linear_input_buffer.length() / stm->input_stream_params.channels) / stm->input_mix_params.rate; - if (input_duration < output_duration) { - size_t padding = size_t(round((output_duration - input_duration) * stm->input_mix_params.rate)); - LOG("padding silence: out=%f in=%f pad=%u", output_duration, input_duration, padding); - stm->linear_input_buffer.push_front_silence(padding * stm->input_stream_params.channels); + /* Wait for draining is not important on duplex. */ + if (stm->draining) { + return false; } - LOGV("Duplex callback: input frames: %zu, output frames: %zu", - stm->linear_input_buffer.length(), output_frames); + if (stm->has_dummy_output) { + ALOGV("Duplex callback (dummy output): input frames: %Iu, output frames: %Iu", + input_frames, output_frames); - refill(stm, - stm->linear_input_buffer.data(), - stm->linear_input_buffer.length(), - output_buffer, - output_frames); + // We don't want to expose the dummy output to the callback so don't pass + // the output buffer (it will be released later with silence in it) + refill(stm, + stm->linear_input_buffer->data(), + input_frames, + nullptr, + 0); + } else { + ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu", + input_frames, output_frames); - stm->linear_input_buffer.clear(); + refill(stm, + stm->linear_input_buffer->data(), + input_frames, + output_buffer, + output_frames); + } - hr = stm->render_client->ReleaseBuffer(output_frames, 0); + stm->linear_input_buffer->clear(); + + if (stm->has_dummy_output) { + // If output is a dummy output, make sure it's silent + hr = stm->render_client->ReleaseBuffer(output_frames, AUDCLNT_BUFFERFLAGS_SILENT); + } else { + hr = stm->render_client->ReleaseBuffer(output_frames, 0); + } if (FAILED(hr)) { - LOG("failed to release buffer: %x", hr); + LOG("failed to release buffer: %lx", hr); return false; } return true; @@ -711,7 +1069,8 @@ refill_callback_duplex(cubeb_stream * stm) bool refill_callback_input(cubeb_stream * stm) { - bool rv, consumed_all_buffer; + bool rv; + size_t input_frames; XASSERT(has_input(stm) && !has_output(stm)); @@ -720,24 +1079,24 @@ refill_callback_input(cubeb_stream * stm) return rv; } - // This can happen at the very beginning of the stream. - if (!stm->linear_input_buffer.length()) { + input_frames = stm->linear_input_buffer->length() / stm->input_stream_params.channels; + if (!input_frames) { return true; } - LOGV("Input callback: input frames: %zu", stm->linear_input_buffer.length()); + ALOGV("Input callback: input frames: %Iu", input_frames); long read = refill(stm, - stm->linear_input_buffer.data(), - stm->linear_input_buffer.length(), + stm->linear_input_buffer->data(), + input_frames, nullptr, 0); - consumed_all_buffer = read == stm->linear_input_buffer.length(); + XASSERT(read >= 0); - stm->linear_input_buffer.clear(); + stm->linear_input_buffer->clear(); - return consumed_all_buffer; + return !stm->draining; } bool @@ -745,7 +1104,7 @@ refill_callback_output(cubeb_stream * stm) { bool rv; HRESULT hr; - float * output_buffer = nullptr; + void * output_buffer = nullptr; size_t output_frames = 0; XASSERT(!has_input(stm) && has_output(stm)); @@ -765,19 +1124,19 @@ refill_callback_output(cubeb_stream * stm) output_buffer, output_frames); - LOGV("Output callback: output frames requested: %zu, got %ld", - output_frames, got); + ALOGV("Output callback: output frames requested: %Iu, got %ld", + output_frames, got); XASSERT(got >= 0); - XASSERT(got == output_frames || stm->draining); + XASSERT(size_t(got) == output_frames || stm->draining); hr = stm->render_client->ReleaseBuffer(got, 0); if (FAILED(hr)) { - LOG("failed to release buffer: %x", hr); + LOG("failed to release buffer: %lx", hr); return false; } - return got == output_frames || stm->draining; + return size_t(got) == output_frames || stm->draining; } static unsigned int __stdcall @@ -786,6 +1145,13 @@ wasapi_stream_render_loop(LPVOID stream) cubeb_stream * stm = static_cast(stream); std::atomic * emergency_bailout = stm->emergency_bailout; + // Signal wasapi_stream_start that we've copied emergency_bailout. + BOOL ok = SetEvent(stm->thread_ready_event); + if (!ok) { + LOG("thread_ready SetEvent failed: %lx", GetLastError()); + return 0; + } + bool is_playing = true; HANDLE wait_array[4] = { stm->shutdown_event, @@ -796,25 +1162,22 @@ wasapi_stream_render_loop(LPVOID stream) HANDLE mmcss_handle = NULL; HRESULT hr = 0; DWORD mmcss_task_index = 0; - auto_com com; - if (!com.ok()) { - LOG("COM initialization failed on render_loop thread."); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return 0; - } + struct auto_com { + auto_com() { + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + XASSERT(SUCCEEDED(hr)); + } + ~auto_com() { + CoUninitialize(); + } + } com; /* We could consider using "Pro Audio" here for WebAudio and maybe WebRTC. */ - mmcss_handle = - stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index); + mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index); if (!mmcss_handle) { /* This is not fatal, but we might glitch under heavy load. */ - LOG("Unable to use mmcss to bump the render thread priority: %x", GetLastError()); - } - - // This has already been nulled out, simply exit. - if (!emergency_bailout) { - is_playing = false; + LOG("Unable to use mmcss to bump the render thread priority: %lx", GetLastError()); } /* WaitForMultipleObjects timeout can trigger in cases where we don't want to @@ -822,7 +1185,7 @@ wasapi_stream_render_loop(LPVOID stream) the timeout error handling only when the timeout_limit is reached, which is reset on each successful loop. */ unsigned timeout_count = 0; - const unsigned timeout_limit = 5; + const unsigned timeout_limit = 3; while (is_playing) { // We want to check the emergency bailout variable before a // and after the WaitForMultipleObject, because the handles WaitForMultipleObjects @@ -883,18 +1246,28 @@ wasapi_stream_render_loop(LPVOID stream) } XASSERT(stm->output_client || stm->input_client); if (stm->output_client) { - stm->output_client->Start(); + hr = stm->output_client->Start(); + if (FAILED(hr)) { + LOG("Error starting output after reconfigure, error: %lx", hr); + is_playing = false; + continue; + } LOG("Output started after reconfigure."); } if (stm->input_client) { - stm->input_client->Start(); + hr = stm->input_client->Start(); + if (FAILED(hr)) { + LOG("Error starting input after reconfiguring, error: %lx", hr); + is_playing = false; + continue; + } LOG("Input started after reconfigure."); } break; } case WAIT_OBJECT_0 + 2: /* refill */ - XASSERT(has_input(stm) && has_output(stm) || - !has_input(stm) && has_output(stm)); + XASSERT((has_input(stm) && has_output(stm)) || + (!has_input(stm) && has_output(stm))); is_playing = stm->refill_callback(stm); break; case WAIT_OBJECT_0 + 3: /* input available */ @@ -910,7 +1283,7 @@ wasapi_stream_render_loop(LPVOID stream) } break; default: - LOG("case %d not handled in render loop.", waitResult); + LOG("case %lu not handled in render loop.", waitResult); abort(); } } @@ -919,41 +1292,31 @@ wasapi_stream_render_loop(LPVOID stream) stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); } - stm->context->revert_mm_thread_characteristics(mmcss_handle); + if (mmcss_handle) { + AvRevertMmThreadCharacteristics(mmcss_handle); + } return 0; } void wasapi_destroy(cubeb * context); -HANDLE WINAPI set_mm_thread_characteristics_noop(const char *, LPDWORD mmcss_task_index) -{ - return (HANDLE)1; -} - -BOOL WINAPI revert_mm_thread_characteristics_noop(HANDLE mmcss_handle) -{ - return true; -} - HRESULT register_notification_client(cubeb_stream * stm) { HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&stm->device_enumerator)); + IID_PPV_ARGS(stm->device_enumerator.receive())); if (FAILED(hr)) { - LOG("Could not get device enumerator: %x", hr); + LOG("Could not get device enumerator: %lx", hr); return hr; } - stm->notification_client = new wasapi_endpoint_notification_client(stm->reconfigure_event); + stm->notification_client.reset(new wasapi_endpoint_notification_client(stm->reconfigure_event, stm->role)); - hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client); + hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client.get()); if (FAILED(hr)) { - LOG("Could not register endpoint notification callback: %x", hr); - SafeRelease(stm->notification_client); + LOG("Could not register endpoint notification callback: %lx", hr); stm->notification_client = nullptr; - SafeRelease(stm->device_enumerator); stm->device_enumerator = nullptr; } @@ -969,62 +1332,93 @@ HRESULT unregister_notification_client(cubeb_stream * stm) return S_OK; } - hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(stm->notification_client); + hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(stm->notification_client.get()); + if (FAILED(hr)) { + // We can't really do anything here, we'll probably leak the + // notification client, but we can at least release the enumerator. + stm->device_enumerator = nullptr; + return S_OK; + } + + stm->notification_client = nullptr; + stm->device_enumerator = nullptr; + + return S_OK; +} + +HRESULT get_endpoint(com_ptr & device, LPCWSTR devid) +{ + com_ptr enumerator; + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), + NULL, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(enumerator.receive())); + if (FAILED(hr)) { + LOG("Could not get device enumerator: %lx", hr); + return hr; + } + + hr = enumerator->GetDevice(devid, device.receive()); if (FAILED(hr)) { - // We can't really do anything here, we'll probably leak the - // notification client, but we can at least release the enumerator. - SafeRelease(stm->device_enumerator); - return S_OK; + LOG("Could not get device: %lx", hr); + return hr; } - SafeRelease(stm->notification_client); - SafeRelease(stm->device_enumerator); - return S_OK; } -HRESULT get_endpoint(IMMDevice ** device, LPCWSTR devid) +HRESULT register_collection_notification_client(cubeb * context) { - IMMDeviceEnumerator * enumerator; HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&enumerator)); + IID_PPV_ARGS(context->device_collection_enumerator.receive())); if (FAILED(hr)) { - LOG("Could not get device enumerator: %x", hr); + LOG("Could not get device enumerator: %lx", hr); return hr; } - hr = enumerator->GetDevice(devid, device); + context->collection_notification_client.reset(new wasapi_collection_notification_client(context)); + + hr = context->device_collection_enumerator->RegisterEndpointNotificationCallback( + context->collection_notification_client.get()); + if (FAILED(hr)) { + LOG("Could not register endpoint notification callback: %lx", hr); + context->collection_notification_client.reset(); + context->device_collection_enumerator.reset(); + } + + return hr; +} + +HRESULT unregister_collection_notification_client(cubeb * context) +{ + HRESULT hr = context->device_collection_enumerator-> + UnregisterEndpointNotificationCallback(context->collection_notification_client.get()); if (FAILED(hr)) { - LOG("Could not get device: %x", hr); - SafeRelease(enumerator); return hr; } - SafeRelease(enumerator); + context->collection_notification_client = nullptr; + context->device_collection_enumerator = nullptr; - return S_OK; + return hr; } -HRESULT get_default_endpoint(IMMDevice ** device, EDataFlow direction) +HRESULT get_default_endpoint(com_ptr & device, EDataFlow direction, ERole role) { - IMMDeviceEnumerator * enumerator; + com_ptr enumerator; HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&enumerator)); + IID_PPV_ARGS(enumerator.receive())); if (FAILED(hr)) { - LOG("Could not get device enumerator: %x", hr); + LOG("Could not get device enumerator: %lx", hr); return hr; } - hr = enumerator->GetDefaultAudioEndpoint(direction, eConsole, device); + hr = enumerator->GetDefaultAudioEndpoint(direction, role, device.receive()); if (FAILED(hr)) { - LOG("Could not get default audio endpoint: %x", hr); - SafeRelease(enumerator); + LOG("Could not get default audio endpoint: %lx", hr); return hr; } - SafeRelease(enumerator); - return ERROR_SUCCESS; } @@ -1043,14 +1437,14 @@ current_stream_delay(cubeb_stream * stm) UINT64 freq; HRESULT hr = stm->audio_clock->GetFrequency(&freq); if (FAILED(hr)) { - LOG("GetFrequency failed: %x", hr); + LOG("GetFrequency failed: %lx", hr); return 0; } UINT64 pos; hr = stm->audio_clock->GetPosition(&pos, NULL); if (FAILED(hr)) { - LOG("GetPosition failed: %x", hr); + LOG("GetPosition failed: %lx", hr); return 0; } @@ -1073,8 +1467,8 @@ stream_set_volume(cubeb_stream * stm, float volume) uint32_t channels; HRESULT hr = stm->audio_stream_volume->GetChannelCount(&channels); - if (hr != S_OK) { - LOG("could not get the channel count: %x", hr); + if (FAILED(hr)) { + LOG("could not get the channel count: %lx", hr); return CUBEB_ERROR; } @@ -1089,8 +1483,8 @@ stream_set_volume(cubeb_stream * stm, float volume) } hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes); - if (hr != S_OK) { - LOG("could not set the channels volume: %x", hr); + if (FAILED(hr)) { + LOG("could not set the channels volume: %lx", hr); return CUBEB_ERROR; } @@ -1101,49 +1495,27 @@ stream_set_volume(cubeb_stream * stm, float volume) extern "C" { int wasapi_init(cubeb ** context, char const * context_name) { - HRESULT hr; - auto_com com; - if (!com.ok()) { - return CUBEB_ERROR; - } - /* We don't use the device yet, but need to make sure we can initialize one so that this backend is not incorrectly enabled on platforms that don't support WASAPI. */ - IMMDevice * device; - hr = get_default_endpoint(&device, eRender); + com_ptr device; + HRESULT hr = get_default_endpoint(device, eRender, eConsole); if (FAILED(hr)) { - LOG("Could not get device: %x", hr); - return CUBEB_ERROR; + XASSERT(hr != CO_E_NOTINITIALIZED); + LOG("It wasn't able to find a default rendering device: %lx", hr); + hr = get_default_endpoint(device, eCapture, eConsole); + if (FAILED(hr)) { + LOG("It wasn't able to find a default capture device: %lx", hr); + return CUBEB_ERROR; + } } - SafeRelease(device); - cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb)); - if (!ctx) { - return CUBEB_ERROR; - } + cubeb * ctx = new cubeb(); ctx->ops = &wasapi_ops; - - ctx->mmcss_module = LoadLibraryA("Avrt.dll"); - - if (ctx->mmcss_module) { - ctx->set_mm_thread_characteristics = - (set_mm_thread_characteristics_function) GetProcAddress( - ctx->mmcss_module, "AvSetMmThreadCharacteristicsA"); - ctx->revert_mm_thread_characteristics = - (revert_mm_thread_characteristics_function) GetProcAddress( - ctx->mmcss_module, "AvRevertMmThreadCharacteristics"); - if (!(ctx->set_mm_thread_characteristics && ctx->revert_mm_thread_characteristics)) { - LOG("Could not load AvSetMmThreadCharacteristics or AvRevertMmThreadCharacteristics: %x", GetLastError()); - FreeLibrary(ctx->mmcss_module); - } - } else { - // This is not a fatal error, but we might end up glitching when - // the system is under high load. - LOG("Could not load Avrt.dll"); - ctx->set_mm_thread_characteristics = &set_mm_thread_characteristics_noop; - ctx->revert_mm_thread_characteristics = &revert_mm_thread_characteristics_noop; + if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) { + delete ctx; + return CUBEB_ERROR; } *context = ctx; @@ -1170,31 +1542,22 @@ bool stop_and_join_render_thread(cubeb_stream * stm) BOOL ok = SetEvent(stm->shutdown_event); if (!ok) { - LOG("Destroy SetEvent failed: %d", GetLastError()); + LOG("Destroy SetEvent failed: %lx", GetLastError()); } /* Wait five seconds for the rendering thread to return. It's supposed to * check its event loop very often, five seconds is rather conservative. */ DWORD r = WaitForSingleObject(stm->thread, 5000); - if (r == WAIT_TIMEOUT) { + if (r != WAIT_OBJECT_0) { /* Something weird happened, leak the thread and continue the shutdown * process. */ *(stm->emergency_bailout) = true; // We give the ownership to the rendering thread. stm->emergency_bailout = nullptr; - LOG("Destroy WaitForSingleObject on thread timed out," - " leaking the thread: %d", GetLastError()); - rv = false; - } - if (r == WAIT_FAILED) { - *(stm->emergency_bailout) = true; - // We give the ownership to the rendering thread. - stm->emergency_bailout = nullptr; - LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError()); + LOG("Destroy WaitForSingleObject on thread failed: %lx, %lx", r, GetLastError()); rv = false; } - // Only attempts to close and null out the thread and event if the // WaitForSingleObject above succeeded, so that calling this function again // attemps to clean up the thread and event each time. @@ -1212,10 +1575,11 @@ bool stop_and_join_render_thread(cubeb_stream * stm) void wasapi_destroy(cubeb * context) { - if (context->mmcss_module) { - FreeLibrary(context->mmcss_module); + if (context->device_ids) { + cubeb_strings_destroy(context->device_ids); } - free(context); + + delete context; } char const * wasapi_get_backend_id(cubeb * context) @@ -1226,209 +1590,285 @@ char const * wasapi_get_backend_id(cubeb * context) int wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) { - HRESULT hr; - IAudioClient * client; - WAVEFORMATEX * mix_format; - auto_com com; - if (!com.ok()) { - return CUBEB_ERROR; - } - XASSERT(ctx && max_channels); - IMMDevice * device; - hr = get_default_endpoint(&device, eRender); + com_ptr device; + HRESULT hr = get_default_endpoint(device, eRender, eConsole); if (FAILED(hr)) { return CUBEB_ERROR; } + com_ptr client; hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, - NULL, (void **)&client); - SafeRelease(device); + NULL, client.receive_vpp()); if (FAILED(hr)) { return CUBEB_ERROR; } - hr = client->GetMixFormat(&mix_format); + WAVEFORMATEX * tmp = nullptr; + hr = client->GetMixFormat(&tmp); if (FAILED(hr)) { - SafeRelease(client); return CUBEB_ERROR; } + com_heap_ptr mix_format(tmp); *max_channels = mix_format->nChannels; - CoTaskMemFree(mix_format); - SafeRelease(client); - return CUBEB_OK; } int wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) { - HRESULT hr; - IAudioClient * client; - REFERENCE_TIME default_period; - auto_com com; - if (!com.ok()) { - return CUBEB_ERROR; - } - - if (params.format != CUBEB_SAMPLE_FLOAT32NE) { + if (params.format != CUBEB_SAMPLE_FLOAT32NE && params.format != CUBEB_SAMPLE_S16NE) { return CUBEB_ERROR_INVALID_FORMAT; } - IMMDevice * device; - hr = get_default_endpoint(&device, eRender); + ERole role = pref_to_role(params.prefs); + + com_ptr device; + HRESULT hr = get_default_endpoint(device, eRender, role); if (FAILED(hr)) { - LOG("Could not get default endpoint: %x", hr); + LOG("Could not get default endpoint: %lx", hr); return CUBEB_ERROR; } + com_ptr client; hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, - NULL, (void **)&client); - SafeRelease(device); + NULL, client.receive_vpp()); if (FAILED(hr)) { - LOG("Could not activate device for latency: %x", hr); + LOG("Could not activate device for latency: %lx", hr); return CUBEB_ERROR; } - /* The second parameter is for exclusive mode, that we don't use. */ - hr = client->GetDevicePeriod(&default_period, NULL); + REFERENCE_TIME minimum_period; + REFERENCE_TIME default_period; + hr = client->GetDevicePeriod(&default_period, &minimum_period); if (FAILED(hr)) { - SafeRelease(client); - LOG("Could not get device period: %x", hr); + LOG("Could not get device period: %lx", hr); return CUBEB_ERROR; } - LOG("default device period: %lld", default_period); + LOG("default device period: %I64d, minimum device period: %I64d", default_period, minimum_period); - /* According to the docs, the best latency we can achieve is by synchronizing - the stream and the engine. + /* If we're on Windows 10, we can use IAudioClient3 to get minimal latency. + Otherwise, according to the docs, the best latency we can achieve is by + synchronizing the stream and the engine. http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */ - *latency_frames = hns_to_frames(params.rate, default_period); + #ifdef _WIN32_WINNT_WIN10 + *latency_frames = hns_to_frames(params.rate, minimum_period); + #else + *latency_frames = hns_to_frames(params.rate, default_period); + #endif LOG("Minimum latency in frames: %u", *latency_frames); - SafeRelease(client); - return CUBEB_OK; } int wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) { - HRESULT hr; - IAudioClient * client; - WAVEFORMATEX * mix_format; - auto_com com; - if (!com.ok()) { - return CUBEB_ERROR; - } - - IMMDevice * device; - hr = get_default_endpoint(&device, eRender); + com_ptr device; + HRESULT hr = get_default_endpoint(device, eRender, eConsole); if (FAILED(hr)) { return CUBEB_ERROR; } + com_ptr client; hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, - NULL, (void **)&client); - SafeRelease(device); + NULL, client.receive_vpp()); if (FAILED(hr)) { return CUBEB_ERROR; } - hr = client->GetMixFormat(&mix_format); + WAVEFORMATEX * tmp = nullptr; + hr = client->GetMixFormat(&tmp); if (FAILED(hr)) { - SafeRelease(client); return CUBEB_ERROR; } + com_heap_ptr mix_format(tmp); *rate = mix_format->nSamplesPerSec; LOG("Preferred sample rate for output: %u", *rate); - CoTaskMemFree(mix_format); - SafeRelease(client); - return CUBEB_OK; } void wasapi_stream_destroy(cubeb_stream * stm); -/* Based on the mix format and the stream format, try to find a way to play - what the user requested. */ static void -handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cubeb_stream_params * stream_params) +waveformatex_update_derived_properties(WAVEFORMATEX * format) { - /* Common case: the hardware is stereo. Up-mixing and down-mixing will be - handled in the callback. */ - if ((*mix_format)->nChannels <= 2) { - return; + format->nBlockAlign = format->wBitsPerSample * format->nChannels / 8; + format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign; + if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(format); + format_pcm->Samples.wValidBitsPerSample = format->wBitsPerSample; } +} +/* Based on the mix format and the stream format, try to find a way to play + what the user requested. */ +static void +handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptr & mix_format, const cubeb_stream_params * stream_params) +{ + com_ptr & audio_client = (direction == eRender) ? stm->output_client : stm->input_client; + XASSERT(audio_client); /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1], so the reinterpret_cast below should be safe. In practice, this is not true, and we just want to bail out and let the rest of the code find a good conversion path instead of trying to make WASAPI do it by itself. [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/ - if ((*mix_format)->wFormatTag != WAVE_FORMAT_EXTENSIBLE) { + if (mix_format->wFormatTag != WAVE_FORMAT_EXTENSIBLE) { return; } - WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(*mix_format); + WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(mix_format.get()); /* Stash a copy of the original mix format in case we need to restore it later. */ WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm; - /* The hardware is in surround mode, we want to only use front left and front - right. Try that, and check if it works. */ - switch (stream_params->channels) { - case 1: /* Mono */ - format_pcm->dwChannelMask = KSAUDIO_SPEAKER_MONO; - break; - case 2: /* Stereo */ - format_pcm->dwChannelMask = KSAUDIO_SPEAKER_STEREO; - break; - default: - XASSERT(false && "Channel layout not supported."); - break; - } - (*mix_format)->nChannels = stream_params->channels; - (*mix_format)->nBlockAlign = ((*mix_format)->wBitsPerSample * (*mix_format)->nChannels) / 8; - (*mix_format)->nAvgBytesPerSec = (*mix_format)->nSamplesPerSec * (*mix_format)->nBlockAlign; - format_pcm->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - (*mix_format)->wBitsPerSample = 32; - format_pcm->Samples.wValidBitsPerSample = (*mix_format)->wBitsPerSample; + /* Get the channel mask by the channel layout. + If the layout is not supported, we will get a closest settings below. */ + format_pcm->dwChannelMask = stream_params->layout; + mix_format->nChannels = stream_params->channels; + waveformatex_update_derived_properties(mix_format.get()); /* Check if wasapi will accept our channel layout request. */ WAVEFORMATEX * closest; - HRESULT hr = stm->output_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, - *mix_format, - &closest); + HRESULT hr = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, + mix_format.get(), + &closest); if (hr == S_FALSE) { - /* Not supported, but WASAPI gives us a suggestion. Use it, and handle the - eventual upmix/downmix ourselves */ + /* Channel layout not supported, but WASAPI gives us a suggestion. Use it, + and handle the eventual upmix/downmix ourselves. Ignore the subformat of + the suggestion, since it seems to always be IEEE_FLOAT. */ LOG("Using WASAPI suggested format: channels: %d", closest->nChannels); + XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE); WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast(closest); - XASSERT(closest_pcm->SubFormat == format_pcm->SubFormat); - CoTaskMemFree(*mix_format); - *mix_format = closest; + format_pcm->dwChannelMask = closest_pcm->dwChannelMask; + mix_format->nChannels = closest->nChannels; + waveformatex_update_derived_properties(mix_format.get()); } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { /* Not supported, no suggestion. This should not happen, but it does in the field with some sound cards. We restore the mix format, and let the rest of the code figure out the right conversion path. */ - *reinterpret_cast(*mix_format) = hw_mix_format; + XASSERT(mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE); + *reinterpret_cast(mix_format.get()) = hw_mix_format; } else if (hr == S_OK) { LOG("Requested format accepted by WASAPI."); } else { - LOG("IsFormatSupported unhandled error: %x", hr); + LOG("IsFormatSupported unhandled error: %lx", hr); + } +} + +static bool +initialize_iaudioclient3(com_ptr & audio_client, + cubeb_stream * stm, + const com_heap_ptr & mix_format, + DWORD flags, + EDataFlow direction) +{ + com_ptr audio_client3; + audio_client->QueryInterface(audio_client3.receive()); + if (!audio_client3) { + LOG("Could not get IAudioClient3 interface"); + return false; + } + + if (flags & AUDCLNT_STREAMFLAGS_LOOPBACK) { + // IAudioClient3 doesn't work with loopback streams, and will return error + // 88890021: AUDCLNT_E_INVALID_STREAM_FLAG + LOG("Audio stream is loopback, not using IAudioClient3"); + return false; + } + + // IAudioClient3 doesn't support AUDCLNT_STREAMFLAGS_NOPERSIST, and will return + // AUDCLNT_E_INVALID_STREAM_FLAG. This is undocumented. + flags = flags ^ AUDCLNT_STREAMFLAGS_NOPERSIST; + + // Some people have reported glitches with capture streams: + // http://blog.nirbheek.in/2018/03/low-latency-audio-on-windows-with.html + if (direction == eCapture) { + LOG("Audio stream is capture, not using IAudioClient3"); + return false; + } + + // Possibly initialize a shared-mode stream using IAudioClient3. Initializing + // a stream this way lets you request lower latencies, but also locks the global + // WASAPI engine at that latency. + // - If we request a shared-mode stream, streams created with IAudioClient will + // have their latency adjusted to match. When the shared-mode stream is + // closed, they'll go back to normal. + // - If there's already a shared-mode stream running, then we cannot request + // the engine change to a different latency - we have to match it. + // - It's antisocial to lock the WASAPI engine at its default latency. If we + // would do this, then stop and use IAudioClient instead. + + HRESULT hr; + uint32_t default_period = 0, fundamental_period = 0, min_period = 0, max_period = 0; + hr = audio_client3->GetSharedModeEnginePeriod(mix_format.get(), &default_period, &fundamental_period, &min_period, &max_period); + if (FAILED(hr)) { + LOG("Could not get shared mode engine period: error: %lx", hr); + return false; + } + uint32_t requested_latency = stm->latency; + if (requested_latency >= default_period) { + LOG("Requested latency %i greater than default latency %i, not using IAudioClient3", requested_latency, default_period); + return false; + } + LOG("Got shared mode engine period: default=%i fundamental=%i min=%i max=%i", default_period, fundamental_period, min_period, max_period); + // Snap requested latency to a valid value + uint32_t old_requested_latency = requested_latency; + if (requested_latency < min_period) { + requested_latency = min_period; + } + requested_latency -= (requested_latency - min_period) % fundamental_period; + if (requested_latency != old_requested_latency) { + LOG("Requested latency %i was adjusted to %i", old_requested_latency, requested_latency); + } + + hr = audio_client3->InitializeSharedAudioStream(flags, requested_latency, mix_format.get(), NULL); + if (SUCCEEDED(hr)) { + return true; + } + else if (hr == AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) { + LOG("Got AUDCLNT_E_ENGINE_PERIODICITY_LOCKED, adjusting latency request"); + } else { + LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr); + return false; + } + + uint32_t current_period = 0; + WAVEFORMATEX* current_format = nullptr; + // We have to pass a valid WAVEFORMATEX** and not nullptr, otherwise + // GetCurrentSharedModeEnginePeriod will return E_POINTER + hr = audio_client3->GetCurrentSharedModeEnginePeriod(¤t_format, ¤t_period); + CoTaskMemFree(current_format); + if (FAILED(hr)) { + LOG("Could not get current shared mode engine period: error: %lx", hr); + return false; + } + + if (current_period >= default_period) { + LOG("Current shared mode engine period %i too high, not using IAudioClient", current_period); + return false; + } + + hr = audio_client3->InitializeSharedAudioStream(flags, current_period, mix_format.get(), NULL); + if (SUCCEEDED(hr)) { + LOG("Current shared mode engine period is %i instead of requested %i", current_period, requested_latency); + return true; } + + LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr); + return false; } #define DIRECTION_NAME (direction == eCapture ? "capture" : "render") @@ -1436,18 +1876,22 @@ handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cub template int setup_wasapi_stream_one_side(cubeb_stream * stm, cubeb_stream_params * stream_params, - cubeb_devid devid, + wchar_t const * devid, EDataFlow direction, REFIID riid, - IAudioClient ** audio_client, + com_ptr & audio_client, uint32_t * buffer_frame_count, HANDLE & event, - T ** render_or_capture_client, + T & render_or_capture_client, cubeb_stream_params * mix_params) { - IMMDevice * device; - WAVEFORMATEX * mix_format; + com_ptr device; HRESULT hr; + bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK; + if (is_loopback && direction != eCapture) { + LOG("Loopback pref can only be used with capture streams!\n"); + return CUBEB_ERROR; + } stm->stream_reset_lock.assert_current_thread_owns(); bool try_again = false; @@ -1455,35 +1899,46 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, // possibilities. do { if (devid) { - std::unique_ptr id(utf8_to_wstr(reinterpret_cast(devid))); - hr = get_endpoint(&device, id.get()); + hr = get_endpoint(device, devid); if (FAILED(hr)) { - LOG("Could not get %s endpoint, error: %x\n", DIRECTION_NAME, hr); + LOG("Could not get %s endpoint, error: %lx\n", DIRECTION_NAME, hr); return CUBEB_ERROR; } - } - else { - hr = get_default_endpoint(&device, direction); + } else { + // If caller has requested loopback but not specified a device, look for + // the default render device. Otherwise look for the default device + // appropriate to the direction. + hr = get_default_endpoint(device, is_loopback ? eRender : direction, pref_to_role(stream_params->prefs)); if (FAILED(hr)) { - LOG("Could not get default %s endpoint, error: %x\n", DIRECTION_NAME, hr); + if (is_loopback) { + LOG("Could not get default render endpoint for loopback, error: %lx\n", hr); + } else { + LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME, hr); + } return CUBEB_ERROR; } } /* Get a client. We will get all other interfaces we need from * this pointer. */ - hr = device->Activate(__uuidof(IAudioClient), - CLSCTX_INPROC_SERVER, - NULL, (void **)audio_client); - SafeRelease(device); + // hr = device->Activate(__uuidof(IAudioClient3), + // CLSCTX_INPROC_SERVER, + // NULL, audio_client.receive_vpp()); + // if (hr == E_NOINTERFACE) { + hr = device->Activate(__uuidof(IAudioClient), + CLSCTX_INPROC_SERVER, + NULL, audio_client.receive_vpp()); + //} + if (FAILED(hr)) { LOG("Could not activate the device to get an audio" - " client for %s: error: %x\n", DIRECTION_NAME, hr); + " client for %s: error: %lx\n", DIRECTION_NAME, hr); // A particular device can't be activated because it has been // unplugged, try fall back to the default audio device. if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED) { LOG("Trying again with the default %s audio device.", DIRECTION_NAME); devid = nullptr; + device = nullptr; try_again = true; } else { return CUBEB_ERROR; @@ -1495,62 +1950,85 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, /* We have to distinguish between the format the mixer uses, * and the format the stream we want to play uses. */ - hr = (*audio_client)->GetMixFormat(&mix_format); + WAVEFORMATEX * tmp = nullptr; + hr = audio_client->GetMixFormat(&tmp); if (FAILED(hr)) { LOG("Could not fetch current mix format from the audio" - " client for %s: error: %x", DIRECTION_NAME, hr); + " client for %s: error: %lx", DIRECTION_NAME, hr); return CUBEB_ERROR; } + com_heap_ptr mix_format(tmp); - handle_channel_layout(stm, &mix_format, stream_params); + mix_format->wBitsPerSample = stm->bytes_per_sample * 8; + if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(mix_format.get()); + format_pcm->SubFormat = stm->waveformatextensible_sub_format; + } + waveformatex_update_derived_properties(mix_format.get()); + + /* Set channel layout only when there're more than two channels. Otherwise, + * use the default setting retrieved from the stream format of the audio + * engine's internal processing by GetMixFormat. */ + if (mix_format->nChannels > 2) { + handle_channel_layout(stm, direction, mix_format, stream_params); + } - /* Shared mode WASAPI always supports float32 sample format, so this - * is safe. */ - mix_params->format = CUBEB_SAMPLE_FLOAT32NE; + mix_params->format = stream_params->format; mix_params->rate = mix_format->nSamplesPerSec; mix_params->channels = mix_format->nChannels; - LOG("Setup requested=[f=%d r=%u c=%u] mix=[f=%d r=%u c=%u]", + mix_params->layout = mask_to_channel_layout(mix_format.get()); + + LOG("Setup requested=[f=%d r=%u c=%u l=%u] mix=[f=%d r=%u c=%u l=%u]", stream_params->format, stream_params->rate, stream_params->channels, - mix_params->format, mix_params->rate, mix_params->channels); - - hr = (*audio_client)->Initialize(AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK | - AUDCLNT_STREAMFLAGS_NOPERSIST, - frames_to_hns(stm, stm->latency), - 0, - mix_format, - NULL); + stream_params->layout, + mix_params->format, mix_params->rate, mix_params->channels, + mix_params->layout); + + DWORD flags = AUDCLNT_STREAMFLAGS_NOPERSIST; + + // Check if a loopback device should be requested. Note that event callbacks + // do not work with loopback devices, so only request these if not looping. + if (is_loopback) { + flags |= AUDCLNT_STREAMFLAGS_LOOPBACK; + } else { + flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; + } + + // if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) { + // LOG("Initialized with IAudioClient3"); + // } else { + hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, + flags, + frames_to_hns(stm, stm->latency), + 0, + mix_format.get(), + NULL); + // } if (FAILED(hr)) { - LOG("Unable to initialize audio client for %s: %x.", DIRECTION_NAME, hr); + LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr); return CUBEB_ERROR; } - CoTaskMemFree(mix_format); - - hr = (*audio_client)->GetBufferSize(buffer_frame_count); + hr = audio_client->GetBufferSize(buffer_frame_count); if (FAILED(hr)) { LOG("Could not get the buffer size from the client" - " for %s %x.", DIRECTION_NAME, hr); + " for %s %lx.", DIRECTION_NAME, hr); return CUBEB_ERROR; } - // Input is up/down mixed when depacketized in get_input_buffer. - if (has_output(stm) && - (should_upmix(*stream_params, *mix_params) || - should_downmix(*stream_params, *mix_params))) { - stm->mix_buffer = (float *)malloc(frames_to_bytes_before_mix(stm, *buffer_frame_count)); - } - - hr = (*audio_client)->SetEventHandle(event); - if (FAILED(hr)) { - LOG("Could set the event handle for the %s client %x.", - DIRECTION_NAME, hr); - return CUBEB_ERROR; + // Events are used if not looping back + if (!is_loopback) { + hr = audio_client->SetEventHandle(event); + if (FAILED(hr)) { + LOG("Could set the event handle for the %s client %lx.", + DIRECTION_NAME, hr); + return CUBEB_ERROR; + } } - hr = (*audio_client)->GetService(riid, (void **)render_or_capture_client); + hr = audio_client->GetService(riid, render_or_capture_client.receive_vpp()); if (FAILED(hr)) { - LOG("Could not get the %s client %x.", DIRECTION_NAME, hr); + LOG("Could not get the %s client %lx.", DIRECTION_NAME, hr); return CUBEB_ERROR; } @@ -1561,66 +2039,94 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, int setup_wasapi_stream(cubeb_stream * stm) { - HRESULT hr; int rv; stm->stream_reset_lock.assert_current_thread_owns(); - auto_com com; - if (!com.ok()) { - LOG("Failure to initialize COM."); - return CUBEB_ERROR; - } - XASSERT((!stm->output_client || !stm->input_client) && "WASAPI stream already setup, close it first."); if (has_input(stm)) { - LOG("Setup capture: device=%x", (int)stm->input_device); + LOG("(%p) Setup capture: device=%p", stm, stm->input_device.get()); rv = setup_wasapi_stream_one_side(stm, &stm->input_stream_params, - stm->input_device, + stm->input_device.get(), eCapture, __uuidof(IAudioCaptureClient), - &stm->input_client, + stm->input_client, &stm->input_buffer_frame_count, stm->input_available_event, - &stm->capture_client, + stm->capture_client, &stm->input_mix_params); if (rv != CUBEB_OK) { LOG("Failure to open the input side."); return rv; } + + // We initializing an input stream, buffer ahead two buffers worth of silence. + // This delays the input side slightly, but allow to not glitch when no input + // is available when calling into the resampler to call the callback: the input + // refill event will be set shortly after to compensate for this lack of data. + // In debug, four buffers are used, to avoid tripping up assertions down the line. +#if !defined(DEBUG) + const int silent_buffer_count = 2; +#else + const int silent_buffer_count = 6; +#endif + stm->linear_input_buffer->push_silence(stm->input_buffer_frame_count * + stm->input_stream_params.channels * + silent_buffer_count); + } + + // If we don't have an output device but are requesting a loopback device, + // we attempt to open that same device in output mode in order to drive the + // loopback via the output events. + stm->has_dummy_output = false; + if (!has_output(stm) && stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) { + stm->output_stream_params.rate = stm->input_stream_params.rate; + stm->output_stream_params.channels = stm->input_stream_params.channels; + stm->output_stream_params.layout = stm->input_stream_params.layout; + if (stm->input_device) { + size_t len = wcslen(stm->input_device.get()); + std::unique_ptr tmp(new wchar_t[len + 1]); + if (wcsncpy_s(tmp.get(), len + 1, stm->input_device.get(), len) != 0) { + LOG("Failed to copy device identifier while copying input stream" + " configuration to output stream configuration to drive loopback."); + return CUBEB_ERROR; + } + stm->output_device = move(tmp); + } + stm->has_dummy_output = true; } if (has_output(stm)) { - LOG("Setup render: device=%x", (int)stm->output_device); + LOG("(%p) Setup render: device=%p", stm, stm->output_device.get()); rv = setup_wasapi_stream_one_side(stm, &stm->output_stream_params, - stm->output_device, + stm->output_device.get(), eRender, __uuidof(IAudioRenderClient), - &stm->output_client, + stm->output_client, &stm->output_buffer_frame_count, stm->refill_event, - &stm->render_client, + stm->render_client, &stm->output_mix_params); if (rv != CUBEB_OK) { LOG("Failure to open the output side."); return rv; } - hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume), - (void **)&stm->audio_stream_volume); + HRESULT hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume), + stm->audio_stream_volume.receive_vpp()); if (FAILED(hr)) { - LOG("Could not get the IAudioStreamVolume: %x", hr); + LOG("Could not get the IAudioStreamVolume: %lx", hr); return CUBEB_ERROR; } XASSERT(stm->frames_written == 0); hr = stm->output_client->GetService(__uuidof(IAudioClock), - (void **)&stm->audio_clock); + stm->audio_clock.receive_vpp()); if (FAILED(hr)) { - LOG("Could not get the IAudioClock: %x", hr); + LOG("Could not get the IAudioClock: %lx", hr); return CUBEB_ERROR; } @@ -1635,7 +2141,7 @@ int setup_wasapi_stream(cubeb_stream * stm) * the highest sample rate available. */ int32_t target_sample_rate; if (has_input(stm) && has_output(stm)) { - assert(stm->input_stream_params.rate == stm->output_stream_params.rate); + XASSERT(stm->input_stream_params.rate == stm->output_stream_params.rate); target_sample_rate = stm->input_stream_params.rate; } else if (has_input(stm)) { target_sample_rate = stm->input_stream_params.rate; @@ -1655,14 +2161,14 @@ int setup_wasapi_stream(cubeb_stream * stm) cubeb_stream_params output_params = stm->output_mix_params; output_params.channels = stm->output_stream_params.channels; - stm->resampler = + stm->resampler.reset( cubeb_resampler_create(stm, has_input(stm) ? &input_params : nullptr, has_output(stm) ? &output_params : nullptr, target_sample_rate, stm->data_callback, stm->user_ptr, - CUBEB_RESAMPLER_QUALITY_DESKTOP); + CUBEB_RESAMPLER_QUALITY_DESKTOP)); if (!stm->resampler) { LOG("Could not get a resampler"); return CUBEB_ERROR; @@ -1678,9 +2184,52 @@ int setup_wasapi_stream(cubeb_stream * stm) stm->refill_callback = refill_callback_output; } + // Create input mixer. + if (has_input(stm) && + ((stm->input_mix_params.layout != CUBEB_LAYOUT_UNDEFINED && + stm->input_mix_params.layout != stm->input_stream_params.layout) || + (stm->input_mix_params.channels != stm->input_stream_params.channels))) { + if (stm->input_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) { + LOG("Input stream using undefined layout! Any mixing may be " + "unpredictable!\n"); + } + stm->input_mixer.reset(cubeb_mixer_create(stm->input_stream_params.format, + stm->input_mix_params.channels, + stm->input_mix_params.layout, + stm->input_stream_params.channels, + stm->input_stream_params.layout)); + assert(stm->input_mixer); + } + + // Create output mixer. + if (has_output(stm) && stm->output_mix_params.layout != stm->output_stream_params.layout) { + if (stm->output_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) { + LOG("Output stream using undefined layout! Any mixing may be unpredictable!\n"); + } + stm->output_mixer.reset(cubeb_mixer_create(stm->output_stream_params.format, + stm->output_stream_params.channels, + stm->output_stream_params.layout, + stm->output_mix_params.channels, + stm->output_mix_params.layout)); + assert(stm->output_mixer); + // Input is up/down mixed when depacketized in get_input_buffer. + stm->mix_buffer.resize( + frames_to_bytes_before_mix(stm, stm->output_buffer_frame_count)); + } + return CUBEB_OK; } +ERole +pref_to_role(cubeb_stream_prefs prefs) +{ + if (prefs & CUBEB_STREAM_PREF_VOICE) { + return eCommunications; + } + + return eConsole; +} + int wasapi_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, @@ -1691,92 +2240,100 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, unsigned int latency_frames, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) { - HRESULT hr; int rv; - auto_com com; - if (!com.ok()) { - return CUBEB_ERROR; - } XASSERT(context && stream && (input_stream_params || output_stream_params)); - if (output_stream_params && output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE || - input_stream_params && input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE) { - LOG("Invalid format, %p %p %d %d", - output_stream_params, input_stream_params, - output_stream_params && output_stream_params->format, - input_stream_params && input_stream_params->format); + if (output_stream_params && input_stream_params && + output_stream_params->format != input_stream_params->format) { return CUBEB_ERROR_INVALID_FORMAT; } - cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream)); - - XASSERT(stm); + std::unique_ptr stm(new cubeb_stream(), wasapi_stream_destroy); stm->context = context; stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; - stm->draining = false; + + if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_VOICE || + stm->input_stream_params.prefs & CUBEB_STREAM_PREF_VOICE) { + stm->role = eCommunications; + } else { + stm->role = eConsole; + } + if (input_stream_params) { stm->input_stream_params = *input_stream_params; - stm->input_device = input_device; + stm->input_device = utf8_to_wstr(reinterpret_cast(input_device)); } if (output_stream_params) { stm->output_stream_params = *output_stream_params; - stm->output_device = output_device; + stm->output_device = utf8_to_wstr(reinterpret_cast(output_device)); } - stm->latency = latency_frames; - stm->volume = 1.0; + switch (output_stream_params ? output_stream_params->format : input_stream_params->format) { + case CUBEB_SAMPLE_S16NE: + stm->bytes_per_sample = sizeof(short); + stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_PCM; + stm->linear_input_buffer.reset(new auto_array_wrapper_impl); + break; + case CUBEB_SAMPLE_FLOAT32NE: + stm->bytes_per_sample = sizeof(float); + stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + stm->linear_input_buffer.reset(new auto_array_wrapper_impl); + break; + default: + return CUBEB_ERROR_INVALID_FORMAT; + } - // Placement new to call ctor. - new (&stm->stream_reset_lock) owned_critical_section(); + stm->latency = latency_frames; stm->reconfigure_event = CreateEvent(NULL, 0, 0, NULL); if (!stm->reconfigure_event) { - LOG("Can't create the reconfigure event, error: %x", GetLastError()); - wasapi_stream_destroy(stm); + LOG("Can't create the reconfigure event, error: %lx", GetLastError()); return CUBEB_ERROR; } /* Unconditionally create the two events so that the wait logic is simpler. */ stm->refill_event = CreateEvent(NULL, 0, 0, NULL); if (!stm->refill_event) { - LOG("Can't create the refill event, error: %x", GetLastError()); - wasapi_stream_destroy(stm); + LOG("Can't create the refill event, error: %lx", GetLastError()); return CUBEB_ERROR; } stm->input_available_event = CreateEvent(NULL, 0, 0, NULL); if (!stm->input_available_event) { - LOG("Can't create the input available event , error: %x", GetLastError()); - wasapi_stream_destroy(stm); + LOG("Can't create the input available event , error: %lx", GetLastError()); return CUBEB_ERROR; } - { /* Locking here is not strictly necessary, because we don't have a notification client that can reset the stream yet, but it lets us assert that the lock is held in the function. */ auto_lock lock(stm->stream_reset_lock); - rv = setup_wasapi_stream(stm); + rv = setup_wasapi_stream(stm.get()); } if (rv != CUBEB_OK) { - wasapi_stream_destroy(stm); return rv; } - hr = register_notification_client(stm); - if (FAILED(hr)) { - /* this is not fatal, we can still play audio, but we won't be able - to keep using the default audio endpoint if it changes. */ - LOG("failed to register notification client, %x", hr); + if (!((input_stream_params ? + (input_stream_params->prefs & CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) : 0) || + (output_stream_params ? + (output_stream_params->prefs & CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) : 0))) { + HRESULT hr = register_notification_client(stm.get()); + if (FAILED(hr)) { + /* this is not fatal, we can still play audio, but we won't be able + to keep using the default audio endpoint if it changes. */ + LOG("failed to register notification client, %lx", hr); + } } - *stream = stm; + *stream = stm.release(); + LOG("Stream init succesfull (%p)", *stream); return CUBEB_OK; } @@ -1786,61 +2343,55 @@ void close_wasapi_stream(cubeb_stream * stm) stm->stream_reset_lock.assert_current_thread_owns(); - SafeRelease(stm->output_client); - stm->output_client = NULL; - SafeRelease(stm->input_client); - stm->input_client = NULL; + stm->output_client = nullptr; + stm->render_client = nullptr; - SafeRelease(stm->render_client); - stm->render_client = NULL; + stm->input_client = nullptr; + stm->capture_client = nullptr; - SafeRelease(stm->capture_client); - stm->capture_client = NULL; + stm->audio_stream_volume = nullptr; - SafeRelease(stm->audio_stream_volume); - stm->audio_stream_volume = NULL; - - SafeRelease(stm->audio_clock); - stm->audio_clock = NULL; + stm->audio_clock = nullptr; stm->total_frames_written += static_cast(round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, stm->output_mix_params))); stm->frames_written = 0; - if (stm->resampler) { - cubeb_resampler_destroy(stm->resampler); - stm->resampler = NULL; - } - - free(stm->mix_buffer); - stm->mix_buffer = NULL; + stm->resampler.reset(); + stm->output_mixer.reset(); + stm->input_mixer.reset(); + stm->mix_buffer.clear(); } void wasapi_stream_destroy(cubeb_stream * stm) { XASSERT(stm); + LOG("Stream destroy (%p)", stm); - // Only free stm->emergency_bailout if we could not join the thread. - // If we could not join the thread, stm->emergency_bailout is true + // Only free stm->emergency_bailout if we could join the thread. + // If we could not join the thread, stm->emergency_bailout is true // and is still alive until the thread wakes up and exits cleanly. if (stop_and_join_render_thread(stm)) { delete stm->emergency_bailout.load(); stm->emergency_bailout = nullptr; } - unregister_notification_client(stm); + if (stm->notification_client) { + unregister_notification_client(stm); + } + + CloseHandle(stm->reconfigure_event); + CloseHandle(stm->refill_event); + CloseHandle(stm->input_available_event); - SafeRelease(stm->reconfigure_event); - SafeRelease(stm->refill_event); - SafeRelease(stm->input_available_event); + // The variables intialized in wasapi_stream_init, + // must be destroyed in wasapi_stream_destroy. + stm->linear_input_buffer.reset(); { auto_lock lock(stm->stream_reset_lock); close_wasapi_stream(stm); } - // Need to call dtor to free the resource in owned_critical_section. - stm->stream_reset_lock.~owned_critical_section(); - - free(stm); + delete stm; } enum StreamDirection { @@ -1860,7 +2411,7 @@ int stream_start_one_side(cubeb_stream * stm, StreamDirection dir) BOOL ok = ResetEvent(stm->reconfigure_event); if (!ok) { - LOG("resetting reconfig event failed for %s stream: %x", + LOG("resetting reconfig event failed for %s stream: %lx", dir == OUTPUT ? "output" : "input", GetLastError()); } @@ -1873,12 +2424,12 @@ int stream_start_one_side(cubeb_stream * stm, StreamDirection dir) HRESULT hr2 = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start(); if (FAILED(hr2)) { - LOG("could not start the %s stream after reconfig: %x", + LOG("could not start the %s stream after reconfig: %lx", dir == OUTPUT ? "output" : "input", hr); return CUBEB_ERROR; } } else if (FAILED(hr)) { - LOG("could not start the %s stream: %x.", + LOG("could not start the %s stream: %lx.", dir == OUTPUT ? "output" : "input", hr); return CUBEB_ERROR; } @@ -1911,16 +2462,31 @@ int wasapi_stream_start(cubeb_stream * stm) stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL); if (!stm->shutdown_event) { - LOG("Can't create the shutdown event, error: %x", GetLastError()); + LOG("Can't create the shutdown event, error: %lx", GetLastError()); + return CUBEB_ERROR; + } + + stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL); + if (!stm->thread_ready_event) { + LOG("Can't create the thread_ready event, error: %lx", GetLastError()); return CUBEB_ERROR; } + cubeb_async_log_reset_threads(); stm->thread = (HANDLE) _beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); if (stm->thread == NULL) { LOG("could not create WASAPI render thread."); return CUBEB_ERROR; } + // Wait for wasapi_stream_render_loop to signal that emergency_bailout has + // been read, avoiding a bailout situation where we could free `stm` + // before wasapi_stream_render_loop had a chance to run. + HRESULT hr = WaitForSingleObject(stm->thread_ready_event, INFINITE); + XASSERT(hr == WAIT_OBJECT_0); + CloseHandle(stm->thread_ready_event); + stm->thread_ready_event = 0; + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); return CUBEB_OK; @@ -1950,21 +2516,32 @@ int wasapi_stream_stop(cubeb_stream * stm) } } - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); } if (stop_and_join_render_thread(stm)) { - // This is null if we've given the pointer to the other thread - if (stm->emergency_bailout.load()) { - delete stm->emergency_bailout.load(); - stm->emergency_bailout = nullptr; - } + delete stm->emergency_bailout.load(); + stm->emergency_bailout = nullptr; + } else { + // If we could not join the thread, put the stream in error. + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + return CUBEB_ERROR; } return CUBEB_OK; } +int wasapi_stream_reset_default_device(cubeb_stream * stm) +{ + XASSERT(stm && stm->reconfigure_event); + BOOL ok = SetEvent(stm->reconfigure_event); + if (!ok) { + LOG("SetEvent on reconfigure_event failed: %lx", GetLastError()); + return CUBEB_ERROR; + } + return CUBEB_OK; +} + int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position) { XASSERT(stm && position); @@ -2037,253 +2614,329 @@ int wasapi_stream_set_volume(cubeb_stream * stm, float volume) return CUBEB_OK; } -static char * +static char const * wstr_to_utf8(LPCWSTR str) { - char * ret = NULL; - int size; - - size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, 0, NULL, NULL); - if (size > 0) { - ret = static_cast(malloc(size)); - ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL); + int size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, NULL, NULL); + if (size <= 0) { + return nullptr; } + char * ret = static_cast(malloc(size)); + ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL); return ret; } -static std::unique_ptr -utf8_to_wstr(char* str) +static std::unique_ptr +utf8_to_wstr(char const * str) { - std::unique_ptr ret; - int size; - - size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); - if (size > 0) { - ret.reset(new wchar_t[size]); - ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size); + int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); + if (size <= 0) { + return nullptr; } - return std::move(ret); + std::unique_ptr ret(new wchar_t[size]); + ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size); + return ret; } -static IMMDevice * +static com_ptr wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev) { - IMMDevice * ret = NULL; - IDeviceTopology * devtopo = NULL; - IConnector * connector = NULL; - - if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&devtopo)) && - SUCCEEDED(devtopo->GetConnector(0, &connector))) { - LPWSTR filterid; - if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&filterid))) { - if (FAILED(enumerator->GetDevice(filterid, &ret))) + com_ptr ret; + com_ptr devtopo; + com_ptr connector; + + if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, devtopo.receive_vpp())) && + SUCCEEDED(devtopo->GetConnector(0, connector.receive()))) { + wchar_t * tmp = nullptr; + if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&tmp))) { + com_heap_ptr filterid(tmp); + if (FAILED(enumerator->GetDevice(filterid.get(), ret.receive()))) ret = NULL; - CoTaskMemFree(filterid); } } - SafeRelease(connector); - SafeRelease(devtopo); return ret; } static BOOL wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id, - IMMDeviceEnumerator * enumerator) + IMMDeviceEnumerator * enumerator) { BOOL ret = FALSE; - IMMDevice * dev; + com_ptr dev; HRESULT hr; - hr = enumerator->GetDefaultAudioEndpoint(flow, role, &dev); + hr = enumerator->GetDefaultAudioEndpoint(flow, role, dev.receive()); if (SUCCEEDED(hr)) { - LPWSTR defdevid = NULL; - if (SUCCEEDED(dev->GetId(&defdevid))) - ret = (wcscmp(defdevid, device_id) == 0); - if (defdevid != NULL) - CoTaskMemFree(defdevid); - SafeRelease(dev); + wchar_t * tmp = nullptr; + if (SUCCEEDED(dev->GetId(&tmp))) { + com_heap_ptr defdevid(tmp); + ret = (wcscmp(defdevid.get(), device_id) == 0); + } } return ret; } -static cubeb_device_info * -wasapi_create_device(IMMDeviceEnumerator * enumerator, IMMDevice * dev) +int +wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator * enumerator, IMMDevice * dev) { - IMMEndpoint * endpoint = NULL; - IMMDevice * devnode = NULL; - IAudioClient * client = NULL; - cubeb_device_info * ret = NULL; + com_ptr endpoint; + com_ptr devnode; + com_ptr client; EDataFlow flow; - LPWSTR device_id = NULL; DWORD state = DEVICE_STATE_NOTPRESENT; - IPropertyStore * propstore = NULL; - PROPVARIANT propvar; + com_ptr propstore; REFERENCE_TIME def_period, min_period; HRESULT hr; - PropVariantInit(&propvar); + struct prop_variant : public PROPVARIANT { + prop_variant() { PropVariantInit(this); } + ~prop_variant() { PropVariantClear(this); } + prop_variant(prop_variant const &) = delete; + prop_variant & operator=(prop_variant const &) = delete; + }; - hr = dev->QueryInterface(IID_PPV_ARGS(&endpoint)); - if (FAILED(hr)) goto done; + hr = dev->QueryInterface(IID_PPV_ARGS(endpoint.receive())); + if (FAILED(hr)) return CUBEB_ERROR; hr = endpoint->GetDataFlow(&flow); - if (FAILED(hr)) goto done; + if (FAILED(hr)) return CUBEB_ERROR; - hr = dev->GetId(&device_id); - if (FAILED(hr)) goto done; + wchar_t * tmp = nullptr; + hr = dev->GetId(&tmp); + if (FAILED(hr)) return CUBEB_ERROR; + com_heap_ptr device_id(tmp); + + char const * device_id_intern = intern_device_id(ctx, device_id.get()); + if (!device_id_intern) { + return CUBEB_ERROR; + } - hr = dev->OpenPropertyStore(STGM_READ, &propstore); - if (FAILED(hr)) goto done; + hr = dev->OpenPropertyStore(STGM_READ, propstore.receive()); + if (FAILED(hr)) return CUBEB_ERROR; hr = dev->GetState(&state); - if (FAILED(hr)) goto done; - - ret = (cubeb_device_info *)calloc(1, sizeof(cubeb_device_info)); + if (FAILED(hr)) return CUBEB_ERROR; - ret->devid = ret->device_id = wstr_to_utf8(device_id); - hr = propstore->GetValue(PKEY_Device_FriendlyName, &propvar); + ret.device_id = device_id_intern; + ret.devid = reinterpret_cast(ret.device_id); + prop_variant namevar; + hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar); if (SUCCEEDED(hr)) - ret->friendly_name = wstr_to_utf8(propvar.pwszVal); + ret.friendly_name = wstr_to_utf8(namevar.pwszVal); devnode = wasapi_get_device_node(enumerator, dev); - if (devnode != NULL) { - IPropertyStore * ps = NULL; - hr = devnode->OpenPropertyStore(STGM_READ, &ps); - if (FAILED(hr)) goto done; + if (devnode) { + com_ptr ps; + hr = devnode->OpenPropertyStore(STGM_READ, ps.receive()); + if (FAILED(hr)) return CUBEB_ERROR; - PropVariantClear(&propvar); - hr = ps->GetValue(PKEY_Device_InstanceId, &propvar); + prop_variant instancevar; + hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar); if (SUCCEEDED(hr)) { - ret->group_id = wstr_to_utf8(propvar.pwszVal); + ret.group_id = wstr_to_utf8(instancevar.pwszVal); } - SafeRelease(ps); } - ret->preferred = CUBEB_DEVICE_PREF_NONE; - if (wasapi_is_default_device(flow, eConsole, device_id, enumerator)) - ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_MULTIMEDIA); - if (wasapi_is_default_device(flow, eCommunications, device_id, enumerator)) - ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_VOICE); - if (wasapi_is_default_device(flow, eConsole, device_id, enumerator)) - ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_NOTIFICATION); + ret.preferred = CUBEB_DEVICE_PREF_NONE; + if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) + ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA); + if (wasapi_is_default_device(flow, eCommunications, device_id.get(), enumerator)) + ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE); + if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) + ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_NOTIFICATION); - if (flow == eRender) ret->type = CUBEB_DEVICE_TYPE_OUTPUT; - else if (flow == eCapture) ret->type = CUBEB_DEVICE_TYPE_INPUT; + if (flow == eRender) ret.type = CUBEB_DEVICE_TYPE_OUTPUT; + else if (flow == eCapture) ret.type = CUBEB_DEVICE_TYPE_INPUT; switch (state) { case DEVICE_STATE_ACTIVE: - ret->state = CUBEB_DEVICE_STATE_ENABLED; + ret.state = CUBEB_DEVICE_STATE_ENABLED; break; case DEVICE_STATE_UNPLUGGED: - ret->state = CUBEB_DEVICE_STATE_UNPLUGGED; + ret.state = CUBEB_DEVICE_STATE_UNPLUGGED; break; default: - ret->state = CUBEB_DEVICE_STATE_DISABLED; + ret.state = CUBEB_DEVICE_STATE_DISABLED; break; }; - ret->format = CUBEB_DEVICE_FMT_F32NE; /* cubeb only supports 32bit float at the moment */ - ret->default_format = CUBEB_DEVICE_FMT_F32NE; - PropVariantClear(&propvar); - hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &propvar); - if (SUCCEEDED(hr) && propvar.vt == VT_BLOB) { - if (propvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) { - const PCMWAVEFORMAT * pcm = reinterpret_cast(propvar.blob.pBlobData); + ret.format = static_cast(CUBEB_DEVICE_FMT_F32NE | CUBEB_DEVICE_FMT_S16NE); + ret.default_format = CUBEB_DEVICE_FMT_F32NE; + prop_variant fmtvar; + hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &fmtvar); + if (SUCCEEDED(hr) && fmtvar.vt == VT_BLOB) { + if (fmtvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) { + const PCMWAVEFORMAT * pcm = reinterpret_cast(fmtvar.blob.pBlobData); - ret->max_rate = ret->min_rate = ret->default_rate = pcm->wf.nSamplesPerSec; - ret->max_channels = pcm->wf.nChannels; - } else if (propvar.blob.cbSize >= sizeof(WAVEFORMATEX)) { - WAVEFORMATEX* wfx = reinterpret_cast(propvar.blob.pBlobData); + ret.max_rate = ret.min_rate = ret.default_rate = pcm->wf.nSamplesPerSec; + ret.max_channels = pcm->wf.nChannels; + } else if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX)) { + WAVEFORMATEX* wfx = reinterpret_cast(fmtvar.blob.pBlobData); - if (propvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize || + if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize || wfx->wFormatTag == WAVE_FORMAT_PCM) { - ret->max_rate = ret->min_rate = ret->default_rate = wfx->nSamplesPerSec; - ret->max_channels = wfx->nChannels; + ret.max_rate = ret.min_rate = ret.default_rate = wfx->nSamplesPerSec; + ret.max_channels = wfx->nChannels; } } } - if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&client)) && + if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, client.receive_vpp())) && SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) { - ret->latency_lo = hns_to_frames(ret->default_rate, min_period); - ret->latency_hi = hns_to_frames(ret->default_rate, def_period); + ret.latency_lo = hns_to_frames(ret.default_rate, min_period); + ret.latency_hi = hns_to_frames(ret.default_rate, def_period); } else { - ret->latency_lo = 0; - ret->latency_hi = 0; - } - SafeRelease(client); - -done: - SafeRelease(devnode); - SafeRelease(endpoint); - SafeRelease(propstore); - if (device_id != NULL) - CoTaskMemFree(device_id); - PropVariantClear(&propvar); - return ret; + ret.latency_lo = 0; + ret.latency_hi = 0; + } + + return CUBEB_OK; } static int wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection ** out) + cubeb_device_collection * out) { - auto_com com; - IMMDeviceEnumerator * enumerator; - IMMDeviceCollection * collection; - IMMDevice * dev; - cubeb_device_info * cur; + com_ptr enumerator; + com_ptr collection; HRESULT hr; UINT cc, i; EDataFlow flow; - *out = NULL; - - if (!com.ok()) - return CUBEB_ERROR; - hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, - CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator)); + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(enumerator.receive())); if (FAILED(hr)) { - LOG("Could not get device enumerator: %x", hr); + LOG("Could not get device enumerator: %lx", hr); return CUBEB_ERROR; } if (type == CUBEB_DEVICE_TYPE_OUTPUT) flow = eRender; else if (type == CUBEB_DEVICE_TYPE_INPUT) flow = eCapture; - else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_INPUT)) flow = eAll; + else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) flow = eAll; else return CUBEB_ERROR; - hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL, &collection); + hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL, collection.receive()); if (FAILED(hr)) { - LOG("Could not enumerate audio endpoints: %x", hr); + LOG("Could not enumerate audio endpoints: %lx", hr); return CUBEB_ERROR; } hr = collection->GetCount(&cc); if (FAILED(hr)) { - LOG("IMMDeviceCollection::GetCount() failed: %x", hr); + LOG("IMMDeviceCollection::GetCount() failed: %lx", hr); return CUBEB_ERROR; } - *out = (cubeb_device_collection *) malloc(sizeof(cubeb_device_collection) + - sizeof(cubeb_device_info*) * (cc > 0 ? cc - 1 : 0)); - if (!*out) { + cubeb_device_info * devices = new cubeb_device_info[cc]; + if (!devices) return CUBEB_ERROR; - } - (*out)->count = 0; + + PodZero(devices, cc); + out->count = 0; for (i = 0; i < cc; i++) { - hr = collection->Item(i, &dev); + com_ptr dev; + hr = collection->Item(i, dev.receive()); + if (FAILED(hr)) { + LOG("IMMDeviceCollection::Item(%u) failed: %lx", i-1, hr); + continue; + } + if (wasapi_create_device(context, devices[out->count], + enumerator.get(), dev.get()) == CUBEB_OK) { + out->count += 1; + } + } + + out->device = devices; + return CUBEB_OK; +} + +static int +wasapi_device_collection_destroy(cubeb * /*ctx*/, cubeb_device_collection * collection) +{ + XASSERT(collection); + + for (size_t n = 0; n < collection->count; n++) { + cubeb_device_info& dev = collection->device[n]; + delete [] dev.friendly_name; + delete [] dev.group_id; + } + + delete [] collection->device; + return CUBEB_OK; +} + +static int +wasapi_register_device_collection_changed(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback collection_changed_callback, + void * user_ptr) +{ + if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (collection_changed_callback) { + // Make sure it has been unregistered first. + XASSERT(((devtype & CUBEB_DEVICE_TYPE_INPUT) && + !context->input_collection_changed_callback) || + ((devtype & CUBEB_DEVICE_TYPE_OUTPUT) && + !context->output_collection_changed_callback)); + + // Stop the notification client. Notifications arrive on + // a separate thread. We stop them here to avoid + // synchronization issues during the update. + if (context->device_collection_enumerator.get()) { + HRESULT hr = unregister_collection_notification_client(context); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + } + + if (devtype & CUBEB_DEVICE_TYPE_INPUT) { + context->input_collection_changed_callback = collection_changed_callback; + context->input_collection_changed_user_ptr = user_ptr; + } + if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { + context->output_collection_changed_callback = collection_changed_callback; + context->output_collection_changed_user_ptr = user_ptr; + } + + HRESULT hr = register_collection_notification_client(context); + if (FAILED(hr)) { + return CUBEB_ERROR; + } + } else { + if (!context->device_collection_enumerator.get()) { + // Already unregistered, ignore it. + return CUBEB_OK; + } + + HRESULT hr = unregister_collection_notification_client(context); if (FAILED(hr)) { - LOG("IMMDeviceCollection::Item(%u) failed: %x", i-1, hr); - } else if ((cur = wasapi_create_device(enumerator, dev)) != NULL) { - (*out)->device[(*out)->count++] = cur; + return CUBEB_ERROR; + } + if (devtype & CUBEB_DEVICE_TYPE_INPUT) { + context->input_collection_changed_callback = nullptr; + context->input_collection_changed_user_ptr = nullptr; + } + if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { + context->output_collection_changed_callback = nullptr; + context->output_collection_changed_user_ptr = nullptr; + } + + // If after the updates we still have registered + // callbacks restart the notification client. + if (context->input_collection_changed_callback || + context->output_collection_changed_callback) { + hr = register_collection_notification_client(context); + if (FAILED(hr)) { + return CUBEB_ERROR; + } } } - SafeRelease(collection); - SafeRelease(enumerator); return CUBEB_OK; } @@ -2294,18 +2947,19 @@ cubeb_ops const wasapi_ops = { /*.get_min_latency =*/ wasapi_get_min_latency, /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate, /*.enumerate_devices =*/ wasapi_enumerate_devices, + /*.device_collection_destroy =*/ wasapi_device_collection_destroy, /*.destroy =*/ wasapi_destroy, /*.stream_init =*/ wasapi_stream_init, /*.stream_destroy =*/ wasapi_stream_destroy, /*.stream_start =*/ wasapi_stream_start, /*.stream_stop =*/ wasapi_stream_stop, + /*.stream_reset_default_device =*/ wasapi_stream_reset_default_device, /*.stream_get_position =*/ wasapi_stream_get_position, /*.stream_get_latency =*/ wasapi_stream_get_latency, /*.stream_set_volume =*/ wasapi_stream_set_volume, - /*.stream_set_panning =*/ NULL, /*.stream_get_current_device =*/ NULL, /*.stream_device_destroy =*/ NULL, /*.stream_register_device_changed_callback =*/ NULL, - /*.register_device_collection_changed =*/ NULL + /*.register_device_collection_changed =*/ wasapi_register_device_collection_changed, }; } // namespace anonymous diff --git a/media/libcubeb/src/cubeb_winmm.c b/media/libcubeb/src/cubeb_winmm.c index 585d11e89..e064ca079 100644 --- a/media/libcubeb/src/cubeb_winmm.c +++ b/media/libcubeb/src/cubeb_winmm.c @@ -4,7 +4,6 @@ * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ -#define __MSVCRT_VERSION__ 0x0700 #undef WINVER #define WINVER 0x0501 #undef WIN32_LEAN_AND_MEAN @@ -93,11 +92,13 @@ struct cubeb { }; struct cubeb_stream { + /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; + void * user_ptr; + /**/ cubeb_stream_params params; cubeb_data_callback data_callback; cubeb_state_callback state_callback; - void * user_ptr; WAVEHDR buffers[NBUFS]; size_t buffer_size; int next_buffer; @@ -402,6 +403,7 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n XASSERT(context); XASSERT(stream); + XASSERT(output_stream_params); if (input_stream_params) { /* Capture support not yet implemented. */ @@ -413,6 +415,11 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n return CUBEB_ERROR_DEVICE_UNAVAILABLE; } + if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { + /* Loopback is not supported */ + return CUBEB_ERROR_NOT_SUPPORTED; + } + *stream = NULL; memset(&wfx, 0, sizeof(wfx)); @@ -512,7 +519,6 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n return CUBEB_ERROR; } - for (i = 0; i < NBUFS; ++i) { WAVEHDR * hdr = &stm->buffers[i]; @@ -769,7 +775,6 @@ winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats) } } - #define MM_S16_MASK (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4M16 | \ WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16) static int @@ -803,11 +808,11 @@ winmm_query_supported_formats(UINT devid, DWORD formats, static char * guid_to_cstr(LPGUID guid) { - char * ret = malloc(sizeof(char) * 40); + char * ret = malloc(40); if (!ret) { return NULL; } - _snprintf_s(ret, sizeof(char) * 40, _TRUNCATE, + _snprintf_s(ret, 40, _TRUNCATE, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid->Data1, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], @@ -821,12 +826,12 @@ winmm_query_preferred_out_device(UINT devid) DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status; cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE; - if (waveOutMessage((HWAVEOUT)(size_t)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, + if (waveOutMessage((HWAVEOUT) WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && devid == mmpref) ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION; - if (waveOutMessage((HWAVEOUT)(size_t)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, + if (waveOutMessage((HWAVEOUT) WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && devid == compref) ret |= CUBEB_DEVICE_PREF_VOICE; @@ -837,7 +842,7 @@ winmm_query_preferred_out_device(UINT devid) static char * device_id_idx(UINT devid) { - char * ret = (char *)malloc(sizeof(char)*16); + char * ret = malloc(16); if (!ret) { return NULL; } @@ -845,16 +850,11 @@ device_id_idx(UINT devid) return ret; } -static cubeb_device_info * -winmm_create_device_from_outcaps2(LPWAVEOUTCAPS2A caps, UINT devid) +static void +winmm_create_device_from_outcaps2(cubeb_device_info * ret, LPWAVEOUTCAPS2A caps, UINT devid) { - cubeb_device_info * ret; - - ret = calloc(1, sizeof(cubeb_device_info)); - if (!ret) { - return NULL; - } - ret->devid = (cubeb_devid)(size_t)devid; + XASSERT(ret); + ret->devid = (cubeb_devid) devid; ret->device_id = device_id_idx(devid); ret->friendly_name = _strdup(caps->szPname); ret->group_id = guid_to_cstr(&caps->ProductGuid); @@ -869,23 +869,16 @@ winmm_create_device_from_outcaps2(LPWAVEOUTCAPS2A caps, UINT devid) winmm_query_supported_formats(devid, caps->dwFormats, &ret->format, &ret->default_format); - /* Hardcoed latency estimates... */ + /* Hardcoded latency estimates... */ ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_hi = 200 * ret->default_rate / 1000; - - return ret; } -static cubeb_device_info * -winmm_create_device_from_outcaps(LPWAVEOUTCAPSA caps, UINT devid) +static void +winmm_create_device_from_outcaps(cubeb_device_info * ret, LPWAVEOUTCAPSA caps, UINT devid) { - cubeb_device_info * ret; - - ret = calloc(1, sizeof(cubeb_device_info)); - if (!ret) { - return NULL; - } - ret->devid = (cubeb_devid)(size_t)devid; + XASSERT(ret); + ret->devid = (cubeb_devid) devid; ret->device_id = device_id_idx(devid); ret->friendly_name = _strdup(caps->szPname); ret->group_id = NULL; @@ -900,11 +893,9 @@ winmm_create_device_from_outcaps(LPWAVEOUTCAPSA caps, UINT devid) winmm_query_supported_formats(devid, caps->dwFormats, &ret->format, &ret->default_format); - /* Hardcoed latency estimates... */ + /* Hardcoded latency estimates... */ ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_hi = 200 * ret->default_rate / 1000; - - return ret; } static cubeb_device_pref @@ -913,12 +904,12 @@ winmm_query_preferred_in_device(UINT devid) DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status; cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE; - if (waveInMessage((HWAVEIN)(size_t)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, + if (waveInMessage((HWAVEIN) WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && devid == mmpref) ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION; - if (waveInMessage((HWAVEIN)(size_t)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, + if (waveInMessage((HWAVEIN) WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && devid == compref) ret |= CUBEB_DEVICE_PREF_VOICE; @@ -926,16 +917,11 @@ winmm_query_preferred_in_device(UINT devid) return ret; } -static cubeb_device_info * -winmm_create_device_from_incaps2(LPWAVEINCAPS2A caps, UINT devid) +static void +winmm_create_device_from_incaps2(cubeb_device_info * ret, LPWAVEINCAPS2A caps, UINT devid) { - cubeb_device_info * ret; - - ret = calloc(1, sizeof(cubeb_device_info)); - if (!ret) { - return NULL; - } - ret->devid = (cubeb_devid)(size_t)devid; + XASSERT(ret); + ret->devid = (cubeb_devid) devid; ret->device_id = device_id_idx(devid); ret->friendly_name = _strdup(caps->szPname); ret->group_id = guid_to_cstr(&caps->ProductGuid); @@ -950,23 +936,16 @@ winmm_create_device_from_incaps2(LPWAVEINCAPS2A caps, UINT devid) winmm_query_supported_formats(devid, caps->dwFormats, &ret->format, &ret->default_format); - /* Hardcoed latency estimates... */ + /* Hardcoded latency estimates... */ ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_hi = 200 * ret->default_rate / 1000; - - return ret; } -static cubeb_device_info * -winmm_create_device_from_incaps(LPWAVEINCAPSA caps, UINT devid) +static void +winmm_create_device_from_incaps(cubeb_device_info * ret, LPWAVEINCAPSA caps, UINT devid) { - cubeb_device_info * ret; - - ret = calloc(1, sizeof(cubeb_device_info)); - if (!ret) { - return NULL; - } - ret->devid = (cubeb_devid)(size_t)devid; + XASSERT(ret); + ret->devid = (cubeb_devid) devid; ret->device_id = device_id_idx(devid); ret->friendly_name = _strdup(caps->szPname); ret->group_id = NULL; @@ -981,29 +960,25 @@ winmm_create_device_from_incaps(LPWAVEINCAPSA caps, UINT devid) winmm_query_supported_formats(devid, caps->dwFormats, &ret->format, &ret->default_format); - /* Hardcoed latency estimates... */ + /* Hardcoded latency estimates... */ ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_hi = 200 * ret->default_rate / 1000; - - return ret; } static int winmm_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection ** collection) + cubeb_device_collection * collection) { UINT i, incount, outcount, total; - cubeb_device_info * cur; + cubeb_device_info * devices; + cubeb_device_info * dev; outcount = waveOutGetNumDevs(); incount = waveInGetNumDevs(); total = outcount + incount; - if (total > 0) { - total -= 1; - } - *collection = malloc(sizeof(cubeb_device_collection) + - sizeof(cubeb_device_info*) * total); - (*collection)->count = 0; + + devices = calloc(total, sizeof(cubeb_device_info)); + collection->count = 0; if (type & CUBEB_DEVICE_TYPE_OUTPUT) { WAVEOUTCAPSA woc; @@ -1013,12 +988,13 @@ winmm_enumerate_devices(cubeb * context, cubeb_device_type type, ZeroMemory(&woc2, sizeof(woc2)); for (i = 0; i < outcount; i++) { - if ((waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) == MMSYSERR_NOERROR && - (cur = winmm_create_device_from_outcaps2(&woc2, i)) != NULL) || - (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR && - (cur = winmm_create_device_from_outcaps(&woc, i)) != NULL) - ) { - (*collection)->device[(*collection)->count++] = cur; + dev = &devices[collection->count]; + if (waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) == MMSYSERR_NOERROR) { + winmm_create_device_from_outcaps2(dev, &woc2, i); + collection->count += 1; + } else if (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR) { + winmm_create_device_from_outcaps(dev, &woc, i); + collection->count += 1; } } } @@ -1031,16 +1007,39 @@ winmm_enumerate_devices(cubeb * context, cubeb_device_type type, ZeroMemory(&wic2, sizeof(wic2)); for (i = 0; i < incount; i++) { - if ((waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) == MMSYSERR_NOERROR && - (cur = winmm_create_device_from_incaps2(&wic2, i)) != NULL) || - (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR && - (cur = winmm_create_device_from_incaps(&wic, i)) != NULL) - ) { - (*collection)->device[(*collection)->count++] = cur; + dev = &devices[collection->count]; + if (waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) == MMSYSERR_NOERROR) { + winmm_create_device_from_incaps2(dev, &wic2, i); + collection->count += 1; + } else if (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR) { + winmm_create_device_from_incaps(dev, &wic, i); + collection->count += 1; } } } + collection->device = devices; + + return CUBEB_OK; +} + +static int +winmm_device_collection_destroy(cubeb * ctx, + cubeb_device_collection * collection) +{ + uint32_t i; + XASSERT(collection); + + (void) ctx; + + for (i = 0; i < collection->count; i++) { + free((void *) collection->device[i].device_id); + free((void *) collection->device[i].friendly_name); + free((void *) collection->device[i].group_id); + free((void *) collection->device[i].vendor_name); + } + + free(collection->device); return CUBEB_OK; } @@ -1051,15 +1050,16 @@ static struct cubeb_ops const winmm_ops = { /*.get_min_latency=*/ winmm_get_min_latency, /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate, /*.enumerate_devices =*/ winmm_enumerate_devices, + /*.device_collection_destroy =*/ winmm_device_collection_destroy, /*.destroy =*/ winmm_destroy, /*.stream_init =*/ winmm_stream_init, /*.stream_destroy =*/ winmm_stream_destroy, /*.stream_start =*/ winmm_stream_start, /*.stream_stop =*/ winmm_stream_stop, + /*.stream_reset_default_device =*/ NULL, /*.stream_get_position =*/ winmm_stream_get_position, /*.stream_get_latency = */ winmm_stream_get_latency, /*.stream_set_volume =*/ winmm_stream_set_volume, - /*.stream_set_panning =*/ NULL, /*.stream_get_current_device =*/ NULL, /*.stream_device_destroy =*/ NULL, /*.stream_register_device_changed_callback=*/ NULL, diff --git a/media/libcubeb/src/moz.build b/media/libcubeb/src/moz.build index b6d07126a..be56876c6 100644 --- a/media/libcubeb/src/moz.build +++ b/media/libcubeb/src/moz.build @@ -10,7 +10,11 @@ Library('cubeb') SOURCES += [ 'cubeb.c', - 'cubeb_panner.cpp' + 'cubeb_log.cpp', + 'cubeb_mixer.cpp', + 'cubeb_panner.cpp', + 'cubeb_strings.c', + 'cubeb_utils.cpp' ] if CONFIG['MOZ_ALSA']: @@ -76,6 +80,7 @@ if CONFIG['OS_TARGET'] == 'WINNT': if CONFIG['OS_TARGET'] == 'Android': SOURCES += ['cubeb_opensl.c'] SOURCES += ['cubeb_resampler.cpp'] + SOURCES += ['cubeb-jni.cpp'] DEFINES['USE_OPENSL'] = True SOURCES += [ 'cubeb_audiotrack.c', @@ -85,6 +90,7 @@ if CONFIG['OS_TARGET'] == 'Android': FINAL_LIBRARY = 'gkmedias' CFLAGS += CONFIG['MOZ_ALSA_CFLAGS'] +CFLAGS += CONFIG['MOZ_JACK_CFLAGS'] CFLAGS += CONFIG['MOZ_PULSEAUDIO_CFLAGS'] # We allow warnings for third-party code that can be updated from upstream. diff --git a/media/libcubeb/unresampled-frames.patch b/media/libcubeb/unresampled-frames.patch deleted file mode 100644 index 714f3d4ba..000000000 --- a/media/libcubeb/unresampled-frames.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 46d12e9ae6fa9c233bc32812b13185ee7df8d3fd Mon Sep 17 00:00:00 2001 -From: Paul Adenot -Date: Thu, 10 Nov 2016 06:20:16 +0100 -Subject: [PATCH] Prevent underflowing the number of input frames needed in - input when resampling. (#188) - ---- - src/cubeb_resampler_internal.h | 12 +++++++++--- - 1 file changed, 9 insertions(+), 3 deletions(-) - -diff --git a/src/cubeb_resampler_internal.h b/src/cubeb_resampler_internal.h -index e165cc2..3c37a04 100644 ---- a/src/cubeb_resampler_internal.h -+++ b/src/cubeb_resampler_internal.h -@@ -263,9 +263,15 @@ public: - * number of output frames will be exactly equal. */ - uint32_t input_needed_for_output(uint32_t output_frame_count) - { -- return (uint32_t)ceilf((output_frame_count - samples_to_frames(resampling_out_buffer.length())) -- * resampling_ratio); -- -+ int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length()); -+ int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length()); -+ float input_frames_needed = -+ (output_frame_count - unresampled_frames_left) * resampling_ratio -+ - resampled_frames_left; -+ if (input_frames_needed < 0) { -+ return 0; -+ } -+ return (uint32_t)ceilf(input_frames_needed); - } - - /** Returns a pointer to the input buffer, that contains empty space for at --- -2.7.4 - diff --git a/media/libcubeb/update.sh b/media/libcubeb/update.sh index 235b963e2..0e8d21456 100755 --- a/media/libcubeb/update.sh +++ b/media/libcubeb/update.sh @@ -1,6 +1,11 @@ +#!/bin/bash # Usage: sh update.sh + set -e +[[ -n "$1" ]] || ( echo "syntax: $0 update_src_directory"; exit 1 ) +[[ -e "$1/src/cubeb.c" ]] || ( echo "$1: cubeb not found"; exit 1 ) + cp $1/AUTHORS . cp $1/LICENSE . cp $1/README.md . @@ -11,21 +16,32 @@ cp $1/src/cubeb-internal.h src cp $1/src/cubeb-speex-resampler.h src cp $1/src/cubeb.c src cp $1/src/cubeb_alsa.c src -cp $1/src/cubeb_log.h src +cp $1/src/cubeb_array_queue.h src cp $1/src/cubeb_audiotrack.c src cp $1/src/cubeb_audiounit.cpp src -cp $1/src/cubeb_osx_run_loop.h src cp $1/src/cubeb_jack.cpp src +cp $1/src/cubeb_log.cpp src +cp $1/src/cubeb_log.h src +cp $1/src/cubeb_mixer.cpp src +cp $1/src/cubeb_mixer.h src cp $1/src/cubeb_opensl.c src -cp $1/src/cubeb_panner.cpp src -cp $1/src/cubeb_panner.h src +cp $1/src/cubeb-jni.cpp src +cp $1/src/cubeb-jni.h src +cp $1/src/android/cubeb-output-latency.h src/android +cp $1/src/android/cubeb_media_library.h src/android +cp $1/src/cubeb_osx_run_loop.h src cp $1/src/cubeb_pulse.c src cp $1/src/cubeb_resampler.cpp src cp $1/src/cubeb_resampler.h src cp $1/src/cubeb_resampler_internal.h src cp $1/src/cubeb_ring_array.h src +cp $1/src/cubeb_ringbuffer.h src cp $1/src/cubeb_sndio.c src +cp $1/src/cubeb_strings.c src +cp $1/src/cubeb_strings.h src +cp $1/src/cubeb_sun.c src cp $1/src/cubeb_utils.h src +cp $1/src/cubeb_utils.cpp src cp $1/src/cubeb_utils_unix.h src cp $1/src/cubeb_utils_win.h src cp $1/src/cubeb_wasapi.cpp src @@ -43,7 +59,12 @@ cp $1/test/test_utils.cpp tests/test_utils.cpp if [ -d $1/.git ]; then rev=$(cd $1 && git rev-parse --verify HEAD) + date=$(cd $1 && git show -s --format=%ci HEAD) dirty=$(cd $1 && git diff-index --name-only HEAD) + set +e + pre_rev=$(grep -o '[[:xdigit:]]\{40\}' moz.yaml) + commits=$(cd $1 && git log --pretty=format:'%h - %s' $pre_rev..$rev) + set -e fi if [ -n "$rev" ]; then @@ -52,38 +73,18 @@ if [ -n "$rev" ]; then version=$version-dirty echo "WARNING: updating from a dirty git repository." fi - sed -i.bak -e "/The git commit ID used was/ s/[0-9a-f]\{40\}\(-dirty\)\{0,1\}\./$version./" README_MOZILLA - rm README_MOZILLA.bak + sed -i.bak -e "s/^ *release:.*/ release: \"$version ($date)\"/" moz.yaml + if [[ ! "$( grep "$version" moz.yaml )" ]]; then + echo "Updating moz.yaml failed." + exit 1 + fi + rm moz.yaml.bak + [[ -n "$commits" ]] && echo -e "Pick commits:\n$commits" else - echo "Remember to update README_MOZILLA with the version details." + echo "Remember to update moz.yaml with the version details." fi -echo "Applying a patch on top of $version" -patch -p1 < ./unresampled-frames.patch - -echo "Applying a patch on top of $version" -patch -p1 < ./bug1302231_emergency_bailout.patch - -echo "Applying a patch on top of $version" -patch -p1 < ./osx-linearize-operations.patch - -echo "Applying a patch on top of $version" -patch -p1 < ./prevent-double-free.patch - -echo "Applying a patch on top of $version" -patch -p1 < ./bug1292803_pulse_assert.patch - -echo "Applying a patch on top of $version" -patch -p1 < ./uplift-wasapi-part-to-beta.patch - -echo "Applying a patch on top of $version" -patch -p3 < ./fix-crashes.patch - -echo "Applying a patch on top of $version" -patch -p3 < ./uplift-part-of-f07ee6d-esr52.patch - -echo "Applying a patch on top of $version" -patch -p3 < ./uplift-system-listener-patch.patch - -echo "Applying a patch on top of $version" -patch -p1 < ./uplift-patch-7a4c711.patch +echo "Applying disable-assert.patch on top of $rev" +patch -p3 < disable-assert.patch +echo "Applying disable-iaudioclient3.patch on top of $rev" +patch -p3 < disable-iaudioclient3.patch diff --git a/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch b/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch deleted file mode 100644 index 0eb1aca82..000000000 --- a/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch +++ /dev/null @@ -1,167 +0,0 @@ -# HG changeset patch -# User Alex Chronopoulos -# Parent 00c051cd38c7a6cb3178fd0890d52056f83abfdc -Bug 1345049 - Uplift part of cubeb upstream f07ee6d to esr52. r=padenot. a=xxxxxx - -diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp ---- a/media/libcubeb/src/cubeb_audiounit.cpp -+++ b/media/libcubeb/src/cubeb_audiounit.cpp -@@ -590,33 +590,43 @@ audiounit_get_input_device_id(AudioDevic - device_id); - if (r != noErr) { - return CUBEB_ERROR; - } - - return CUBEB_OK; - } - -+static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); -+static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); -+ - static int - audiounit_reinit_stream(cubeb_stream * stm, bool is_started) - { -+ auto_lock context_lock(stm->context->mutex); - if (is_started) { - audiounit_stream_stop_internal(stm); - } - - { - auto_lock lock(stm->mutex); -+ float volume = 0.0; -+ int vol_rv = audiounit_stream_get_volume(stm, &volume); - - audiounit_close_stream(stm); - - if (audiounit_setup_stream(stm) != CUBEB_OK) { - LOG("(%p) Stream reinit failed.", stm); - return CUBEB_ERROR; - } - -+ if (vol_rv == CUBEB_OK) { -+ audiounit_stream_set_volume(stm, volume); -+ } -+ - // Reset input frames to force new stream pre-buffer - // silence if needed, check `is_extra_input_needed()` - stm->frames_read = 0; - - // If the stream was running, start it again. - if (is_started) { - audiounit_stream_start_internal(stm); - } -@@ -1007,20 +1017,22 @@ audiounit_get_preferred_sample_rate(cube - static OSStatus audiounit_remove_device_listener(cubeb * context); - - static void - audiounit_destroy(cubeb * ctx) - { - // Disabling this assert for bug 1083664 -- we seem to leak a stream - // assert(ctx->active_streams == 0); - -- /* Unregister the callback if necessary. */ -- if(ctx->collection_changed_callback) { -+ { - auto_lock lock(ctx->mutex); -- audiounit_remove_device_listener(ctx); -+ /* Unregister the callback if necessary. */ -+ if(ctx->collection_changed_callback) { -+ audiounit_remove_device_listener(ctx); -+ } - } - - ctx->~cubeb(); - free(ctx); - } - - static void audiounit_stream_destroy(cubeb_stream * stm); - -@@ -1861,17 +1873,17 @@ audiounit_close_stream(cubeb_stream *stm - cubeb_resampler_destroy(stm->resampler); - } - - static void - audiounit_stream_destroy(cubeb_stream * stm) - { - stm->shutdown = true; - -- auto_lock context_locl(stm->context->mutex); -+ auto_lock context_lock(stm->context->mutex); - audiounit_stream_stop_internal(stm); - - { - auto_lock lock(stm->mutex); - audiounit_close_stream(stm); - } - - #if !TARGET_OS_IPHONE -@@ -1905,17 +1917,17 @@ audiounit_stream_start_internal(cubeb_st - } - - static int - audiounit_stream_start(cubeb_stream * stm) - { - stm->shutdown = false; - stm->draining = false; - -- auto_lock context_locl(stm->context->mutex); -+ auto_lock context_lock(stm->context->mutex); - audiounit_stream_start_internal(stm); - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); - - LOG("Cubeb stream (%p) started successfully.", stm); - return CUBEB_OK; - } - -@@ -1933,17 +1945,17 @@ audiounit_stream_stop_internal(cubeb_str - } - } - - static int - audiounit_stream_stop(cubeb_stream * stm) - { - stm->shutdown = true; - -- auto_lock context_locl(stm->context->mutex); -+ auto_lock context_lock(stm->context->mutex); - audiounit_stream_stop_internal(stm); - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - - LOG("Cubeb stream (%p) stopped successfully.", stm); - return CUBEB_OK; - } - -@@ -2030,16 +2042,31 @@ audiounit_stream_get_latency(cubeb_strea - } - - *latency = stm->hw_latency_frames + stm->current_latency_frames; - - return CUBEB_OK; - #endif - } - -+static int -+audiounit_stream_get_volume(cubeb_stream * stm, float * volume) -+{ -+ assert(stm->output_unit); -+ OSStatus r = AudioUnitGetParameter(stm->output_unit, -+ kHALOutputParam_Volume, -+ kAudioUnitScope_Global, -+ 0, volume); -+ if (r != noErr) { -+ LOG("AudioUnitGetParameter/kHALOutputParam_Volume rv=%d", r); -+ return CUBEB_ERROR; -+ } -+ return CUBEB_OK; -+} -+ - int audiounit_stream_set_volume(cubeb_stream * stm, float volume) - { - OSStatus r; - - r = AudioUnitSetParameter(stm->output_unit, - kHALOutputParam_Volume, - kAudioUnitScope_Global, - 0, volume, 0); diff --git a/media/libcubeb/uplift-patch-7a4c711.patch b/media/libcubeb/uplift-patch-7a4c711.patch deleted file mode 100644 index 188bdf8b2..000000000 --- a/media/libcubeb/uplift-patch-7a4c711.patch +++ /dev/null @@ -1,69 +0,0 @@ -From 7a4c711d6e998b451326a0a87dd2e9dab5a257ef Mon Sep 17 00:00:00 2001 -From: Alex Chronopoulos -Date: Mon, 15 May 2017 16:47:26 +0300 -Subject: [PATCH] audiounit: synchronize destroy stream and reinit (Bug - 1361657) - ---- - src/cubeb_audiounit.cpp | 22 +++++++++++++++------- - 1 file changed, 15 insertions(+), 7 deletions(-) - -diff --git a/src/cubeb_audiounit.cpp b/src/cubeb_audiounit.cpp -index 8aa40d54..331bc735 100644 ---- a/src/cubeb_audiounit.cpp -+++ b/src/cubeb_audiounit.cpp -@@ -603,6 +603,7 @@ audiounit_get_input_device_id(AudioDeviceID * device_id) - - static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); - static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); -+static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm); - - static int - audiounit_reinit_stream(cubeb_stream * stm) -@@ -612,6 +613,11 @@ audiounit_reinit_stream(cubeb_stream * stm) - audiounit_stream_stop_internal(stm); - } - -+ int r = audiounit_uninstall_device_changed_callback(stm); -+ if (r != CUBEB_OK) { -+ LOG("(%p) Could not uninstall the device changed callback", stm); -+ } -+ - { - auto_lock lock(stm->mutex); - float volume = 0.0; -@@ -2516,11 +2522,6 @@ audiounit_close_stream(cubeb_stream *stm) - { - stm->mutex.assert_current_thread_owns(); - -- int r = audiounit_uninstall_device_changed_callback(stm); -- if (r != CUBEB_OK) { -- LOG("(%p) Could not uninstall the device changed callback", stm); -- } -- - if (stm->input_unit) { - AudioUnitUninitialize(stm->input_unit); - AudioComponentInstanceDispose(stm->input_unit); -@@ -2554,13 +2555,20 @@ audiounit_stream_destroy(cubeb_stream * stm) - LOG("(%p) Could not uninstall the device changed callback", stm); - } - -+ r = audiounit_uninstall_device_changed_callback(stm); -+ if (r != CUBEB_OK) { -+ LOG("(%p) Could not uninstall the device changed callback", stm); -+ } -+ - auto_lock context_lock(stm->context->mutex); - audiounit_stream_stop_internal(stm); - -- { -+ // Execute close in serial queue to avoid collision -+ // with reinit when un/plug devices -+ dispatch_sync(stm->context->serial_queue, ^() { - auto_lock lock(stm->mutex); - audiounit_close_stream(stm); -- } -+ }); - - assert(stm->context->active_streams >= 1); - stm->context->active_streams -= 1; diff --git a/media/libcubeb/uplift-system-listener-patch.patch b/media/libcubeb/uplift-system-listener-patch.patch deleted file mode 100644 index 5064d7fb3..000000000 --- a/media/libcubeb/uplift-system-listener-patch.patch +++ /dev/null @@ -1,402 +0,0 @@ -diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp ---- a/media/libcubeb/src/cubeb_audiounit.cpp -+++ b/media/libcubeb/src/cubeb_audiounit.cpp -@@ -594,20 +594,20 @@ audiounit_get_input_device_id(AudioDevic - - return CUBEB_OK; - } - - static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); - static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); - - static int --audiounit_reinit_stream(cubeb_stream * stm, bool is_started) -+audiounit_reinit_stream(cubeb_stream * stm) - { - auto_lock context_lock(stm->context->mutex); -- if (is_started) { -+ if (!stm->shutdown) { - audiounit_stream_stop_internal(stm); - } - - { - auto_lock lock(stm->mutex); - float volume = 0.0; - int vol_rv = audiounit_stream_get_volume(stm, &volume); - -@@ -622,32 +622,30 @@ audiounit_reinit_stream(cubeb_stream * s - audiounit_stream_set_volume(stm, volume); - } - - // Reset input frames to force new stream pre-buffer - // silence if needed, check `is_extra_input_needed()` - stm->frames_read = 0; - - // If the stream was running, start it again. -- if (is_started) { -+ if (!stm->shutdown) { - audiounit_stream_start_internal(stm); - } - } - return CUBEB_OK; - } - - static OSStatus - audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count, - const AudioObjectPropertyAddress * addresses, - void * user) - { - cubeb_stream * stm = (cubeb_stream*) user; - stm->switching_device = true; -- // Note if the stream was running or not -- bool was_running = !stm->shutdown; - - LOG("(%p) Audio device changed, %d events.", stm, address_count); - for (UInt32 i = 0; i < address_count; i++) { - switch(addresses[i].mSelector) { - case kAudioHardwarePropertyDefaultOutputDevice: { - LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i); - // Allow restart to choose the new default - stm->output_device = nullptr; -@@ -666,19 +664,20 @@ audiounit_property_listener_callback(Aud - if (stm->is_default_input) { - LOG("It's the default input device, ignore the event"); - return noErr; - } - // Allow restart to choose the new default. Event register only for input. - stm->input_device = nullptr; - } - break; -- case kAudioDevicePropertyDataSource: -- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i); -- break; -+ case kAudioDevicePropertyDataSource: { -+ LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i); -+ return noErr; -+ } - } - } - - for (UInt32 i = 0; i < address_count; i++) { - switch(addresses[i].mSelector) { - case kAudioHardwarePropertyDefaultOutputDevice: - case kAudioHardwarePropertyDefaultInputDevice: - case kAudioDevicePropertyDeviceIsAlive: -@@ -691,17 +690,17 @@ audiounit_property_listener_callback(Aud - break; - } - } - } - - // Use a new thread, through the queue, to avoid deadlock when calling - // Get/SetProperties method from inside notify callback - dispatch_async(stm->context->serial_queue, ^() { -- if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) { -+ if (audiounit_reinit_stream(stm) != CUBEB_OK) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - LOG("(%p) Could not reopen the stream after switching.", stm); - } - stm->switching_device = false; - }); - - return noErr; - } -@@ -752,27 +751,16 @@ audiounit_install_device_changed_callbac - } - - r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, - kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); - if (r != noErr) { - PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource", r); - return CUBEB_ERROR; - } -- -- /* This event will notify us when the default audio device changes, -- * for example when the user plugs in a USB headset and the system chooses it -- * automatically as the default, or when another device is chosen in the -- * dropdown list. */ -- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, -- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); -- if (r != noErr) { -- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice", r); -- return CUBEB_ERROR; -- } - } - - if (stm->input_unit) { - /* This event will notify us when the data source on the input device changes. */ - AudioDeviceID input_dev_id; - r = audiounit_get_input_device_id(&input_dev_id); - if (r != noErr) { - return CUBEB_ERROR; -@@ -780,78 +768,112 @@ audiounit_install_device_changed_callbac - - r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, - kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); - if (r != noErr) { - PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource", r); - return CUBEB_ERROR; - } - -- /* This event will notify us when the default input device changes. */ -- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, -- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); -- if (r != noErr) { -- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice", r); -- return CUBEB_ERROR; -- } -- - /* Event to notify when the input is going away. */ - AudioDeviceID dev = stm->input_device ? reinterpret_cast(stm->input_device) : - audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); - r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive, - kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); - if (r != noErr) { - PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive", r); - return CUBEB_ERROR; - } - } - - return CUBEB_OK; - } - - static int -+audiounit_install_system_changed_callback(cubeb_stream * stm) -+{ -+ OSStatus r; -+ -+ if (stm->output_unit) { -+ /* This event will notify us when the default audio device changes, -+ * for example when the user plugs in a USB headset and the system chooses it -+ * automatically as the default, or when another device is chosen in the -+ * dropdown list. */ -+ r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, -+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); -+ if (r != noErr) { -+ LOG("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv=%d", r); -+ return CUBEB_ERROR; -+ } -+ } -+ -+ if (stm->input_unit) { -+ /* This event will notify us when the default input device changes. */ -+ r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, -+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); -+ if (r != noErr) { -+ LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r); -+ return CUBEB_ERROR; -+ } -+ } -+ -+ return CUBEB_OK; -+} -+ -+static int - audiounit_uninstall_device_changed_callback(cubeb_stream * stm) - { - OSStatus r; - - if (stm->output_unit) { - AudioDeviceID output_dev_id; - r = audiounit_get_output_device_id(&output_dev_id); - if (r != noErr) { - return CUBEB_ERROR; - } - - r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, - kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); - if (r != noErr) { - return CUBEB_ERROR; - } -- -- r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, -- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); -- if (r != noErr) { -- return CUBEB_ERROR; -- } - } - - if (stm->input_unit) { - AudioDeviceID input_dev_id; - r = audiounit_get_input_device_id(&input_dev_id); - if (r != noErr) { - return CUBEB_ERROR; - } - - r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, - kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); - if (r != noErr) { - return CUBEB_ERROR; - } -- -+ } -+ return CUBEB_OK; -+} -+ -+static int -+audiounit_uninstall_system_changed_callback(cubeb_stream * stm) -+{ -+ OSStatus r; -+ -+ if (stm->output_unit) { -+ r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, -+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); -+ if (r != noErr) { -+ return CUBEB_ERROR; -+ } -+ } -+ -+ if (stm->input_unit) { - r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, -- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); -+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); - if (r != noErr) { - return CUBEB_ERROR; - } - } - return CUBEB_OK; - } - - /* Get the acceptable buffer size (in frames) that this device can work with. */ -@@ -1764,16 +1786,22 @@ audiounit_setup_stream(cubeb_stream * st - - if (stm->input_unit && stm->output_unit) { - // According to the I/O hardware rate it is expected a specific pattern of callbacks - // for example is input is 44100 and output is 48000 we expected no more than 2 - // out callback in a row. - stm->expected_output_callbacks_in_a_row = ceilf(stm->output_hw_rate / stm->input_hw_rate); - } - -+ r = audiounit_install_device_changed_callback(stm); -+ if (r != CUBEB_OK) { -+ LOG("(%p) Could not install the device change callback.", stm); -+ return r; -+ } -+ - return CUBEB_OK; - } - - static int - audiounit_stream_init(cubeb * context, - cubeb_stream ** stream, - char const * /* stream_name */, - cubeb_devid input_device, -@@ -1838,31 +1866,37 @@ audiounit_stream_init(cubeb * context, - } - - if (r != CUBEB_OK) { - LOG("(%p) Could not setup the audiounit stream.", stm); - audiounit_stream_destroy(stm); - return r; - } - -- r = audiounit_install_device_changed_callback(stm); -+ r = audiounit_install_system_changed_callback(stm); - if (r != CUBEB_OK) { - LOG("(%p) Could not install the device change callback.", stm); - return r; - } - - *stream = stm; - LOG("Cubeb stream (%p) init successful.", stm); - return CUBEB_OK; - } - - static void - audiounit_close_stream(cubeb_stream *stm) - { - stm->mutex.assert_current_thread_owns(); -+ -+ int r = audiounit_uninstall_device_changed_callback(stm); -+ if (r != CUBEB_OK) { -+ LOG("(%p) Could not uninstall the device changed callback", stm); -+ } -+ - if (stm->input_unit) { - AudioUnitUninitialize(stm->input_unit); - AudioComponentInstanceDispose(stm->input_unit); - } - - audiounit_destroy_input_linear_buffer(stm); - - if (stm->output_unit) { -@@ -1873,31 +1907,29 @@ audiounit_close_stream(cubeb_stream *stm - cubeb_resampler_destroy(stm->resampler); - } - - static void - audiounit_stream_destroy(cubeb_stream * stm) - { - stm->shutdown = true; - -+ int r = audiounit_uninstall_system_changed_callback(stm); -+ if (r != CUBEB_OK) { -+ LOG("(%p) Could not uninstall the device changed callback", stm); -+ } -+ - auto_lock context_lock(stm->context->mutex); - audiounit_stream_stop_internal(stm); - - { - auto_lock lock(stm->mutex); - audiounit_close_stream(stm); - } - --#if !TARGET_OS_IPHONE -- int r = audiounit_uninstall_device_changed_callback(stm); -- if (r != CUBEB_OK) { -- LOG("(%p) Could not uninstall the device changed callback", stm); -- } --#endif -- - assert(stm->context->active_streams >= 1); - stm->context->active_streams -= 1; - - LOG("Cubeb stream (%p) destroyed successful.", stm); - - stm->~cubeb_stream(); - free(stm); - } -@@ -1914,20 +1946,20 @@ audiounit_stream_start_internal(cubeb_st - r = AudioOutputUnitStart(stm->output_unit); - assert(r == 0); - } - } - - static int - audiounit_stream_start(cubeb_stream * stm) - { -+ auto_lock context_lock(stm->context->mutex); - stm->shutdown = false; - stm->draining = false; - -- auto_lock context_lock(stm->context->mutex); - audiounit_stream_start_internal(stm); - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); - - LOG("Cubeb stream (%p) started successfully.", stm); - return CUBEB_OK; - } - -@@ -1943,19 +1975,19 @@ audiounit_stream_stop_internal(cubeb_str - r = AudioOutputUnitStop(stm->output_unit); - assert(r == 0); - } - } - - static int - audiounit_stream_stop(cubeb_stream * stm) - { -+ auto_lock context_lock(stm->context->mutex); - stm->shutdown = true; - -- auto_lock context_lock(stm->context->mutex); - audiounit_stream_stop_internal(stm); - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - - LOG("Cubeb stream (%p) stopped successfully.", stm); - return CUBEB_OK; - } - diff --git a/media/libcubeb/uplift-wasapi-part-to-beta.patch b/media/libcubeb/uplift-wasapi-part-to-beta.patch deleted file mode 100644 index 90e827830..000000000 --- a/media/libcubeb/uplift-wasapi-part-to-beta.patch +++ /dev/null @@ -1,118 +0,0 @@ -# HG changeset patch -# User Alex Chronopoulos -# Parent b7bb31e5a851d6f8e142c39dc077e3774719eced -Bug 1342363 - Uplift wasapi fixes in Beta. r?kinetik - -diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp ---- a/media/libcubeb/src/cubeb_wasapi.cpp -+++ b/media/libcubeb/src/cubeb_wasapi.cpp -@@ -807,16 +807,20 @@ wasapi_stream_render_loop(LPVOID stream) - maybe WebRTC. */ - mmcss_handle = - stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index); - if (!mmcss_handle) { - /* This is not fatal, but we might glitch under heavy load. */ - LOG("Unable to use mmcss to bump the render thread priority: %x", GetLastError()); - } - -+ // This has already been nulled out, simply exit. -+ if (!emergency_bailout) { -+ is_playing = false; -+ } - - /* WaitForMultipleObjects timeout can trigger in cases where we don't want to - treat it as a timeout, such as across a system sleep/wake cycle. Trigger - the timeout error handling only when the timeout_limit is reached, which is - reset on each successful loop. */ - unsigned timeout_count = 0; - const unsigned timeout_limit = 5; - while (is_playing) { -@@ -1158,22 +1162,26 @@ bool stop_and_join_render_thread(cubeb_s - - /* Wait five seconds for the rendering thread to return. It's supposed to - * check its event loop very often, five seconds is rather conservative. */ - DWORD r = WaitForSingleObject(stm->thread, 5000); - if (r == WAIT_TIMEOUT) { - /* Something weird happened, leak the thread and continue the shutdown - * process. */ - *(stm->emergency_bailout) = true; -+ // We give the ownership to the rendering thread. -+ stm->emergency_bailout = nullptr; - LOG("Destroy WaitForSingleObject on thread timed out," - " leaking the thread: %d", GetLastError()); - rv = false; - } - if (r == WAIT_FAILED) { - *(stm->emergency_bailout) = true; -+ // We give the ownership to the rendering thread. -+ stm->emergency_bailout = nullptr; - LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError()); - rv = false; - } - - - // Only attempts to close and null out the thread and event if the - // WaitForSingleObject above succeeded, so that calling this function again - // attemps to clean up the thread and event each time. -@@ -1798,19 +1806,16 @@ void wasapi_stream_destroy(cubeb_stream - XASSERT(stm); - - // Only free stm->emergency_bailout if we could not join the thread. - // If we could not join the thread, stm->emergency_bailout is true - // and is still alive until the thread wakes up and exits cleanly. - if (stop_and_join_render_thread(stm)) { - delete stm->emergency_bailout.load(); - stm->emergency_bailout = nullptr; -- } else { -- // If we're leaking, it must be that this is true. -- assert(*(stm->emergency_bailout)); - } - - unregister_notification_client(stm); - - SafeRelease(stm->reconfigure_event); - SafeRelease(stm->refill_event); - SafeRelease(stm->input_available_event); - -@@ -1865,21 +1870,21 @@ int stream_start_one_side(cubeb_stream * - return CUBEB_ERROR; - } - - return CUBEB_OK; - } - - int wasapi_stream_start(cubeb_stream * stm) - { -+ auto_lock lock(stm->stream_reset_lock); -+ - XASSERT(stm && !stm->thread && !stm->shutdown_event); - XASSERT(stm->output_client || stm->input_client); - -- auto_lock lock(stm->stream_reset_lock); -- - stm->emergency_bailout = new std::atomic(false); - - if (stm->output_client) { - int rv = stream_start_one_side(stm, OUTPUT); - if (rv != CUBEB_OK) { - return rv; - } - } -@@ -1932,16 +1937,17 @@ int wasapi_stream_stop(cubeb_stream * st - } - } - - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - } - - if (stop_and_join_render_thread(stm)) { -+ // This is null if we've given the pointer to the other thread - if (stm->emergency_bailout.load()) { - delete stm->emergency_bailout.load(); - stm->emergency_bailout = nullptr; - } - } - - return CUBEB_OK; - } -- cgit v1.2.3 From 22b35fa8e923d52a3fa785993c28c3e63cd1ee1e Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Sat, 2 Nov 2019 11:59:57 -0400 Subject: Issue #1267 - Part 2: fix libcubeb bindings in dom --- dom/media/CubebUtils.cpp | 4 +++- dom/media/GraphDriver.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dom/media/CubebUtils.cpp b/dom/media/CubebUtils.cpp index 0f0167d9c..8f1ea0179 100644 --- a/dom/media/CubebUtils.cpp +++ b/dom/media/CubebUtils.cpp @@ -55,6 +55,7 @@ bool sCubebPlaybackLatencyPrefSet; bool sCubebMSGLatencyPrefSet; bool sAudioStreamInitEverSucceeded = false; StaticAutoPtr sBrandName; +StaticAutoPtr sCubebBackendName; const char kBrandBundleURL[] = "chrome://branding/locale/brand.properties"; @@ -226,7 +227,7 @@ cubeb* GetCubebContextUnlocked() sBrandName, "Did not initialize sbrandName, and not on the main thread?"); } - int rv = cubeb_init(&sCubebContext, sBrandName); + int rv = cubeb_init(&sCubebContext, sBrandName, sCubebBackendName); NS_WARNING_ASSERTION(rv == CUBEB_OK, "Could not get a cubeb context."); sCubebState = (rv == CUBEB_OK) ? CubebState::Initialized : CubebState::Uninitialized; @@ -292,6 +293,7 @@ void ShutdownLibrary() sCubebContext = nullptr; } sBrandName = nullptr; + sCubebBackendName = nullptr; // This will ensure we don't try to re-create a context. sCubebState = CubebState::Shutdown; } diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp index 37c692a4b..aa75918f1 100644 --- a/dom/media/GraphDriver.cpp +++ b/dom/media/GraphDriver.cpp @@ -656,7 +656,7 @@ AudioCallbackDriver::Init() if (latencyPref) { latency_frames = latencyPref.value(); } else { - if (cubeb_get_min_latency(cubebContext, output, &latency_frames) != CUBEB_OK) { + if (cubeb_get_min_latency(cubebContext, &output, &latency_frames) != CUBEB_OK) { NS_WARNING("Could not get minimal latency from cubeb."); } } -- cgit v1.2.3 From db98e3efff6087b690805358e6f4fda118ec9627 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 3 Nov 2019 15:12:34 +0100 Subject: Issue #146 - Part 3: Create nsDisplayTableFixedPosition to avoid display list collisions when processing the background image of a table. --- layout/base/nsDisplayList.cpp | 63 ++++++++++++++++++++++++++++++++++++++++-- layout/base/nsDisplayList.h | 45 ++++++++++++++++++++++++++++-- layout/tables/nsTableFrame.cpp | 8 ++++-- 3 files changed, 108 insertions(+), 8 deletions(-) diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 744153831..6b792a779 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -80,6 +80,8 @@ #include "nsPluginFrame.h" #include "DisplayItemScrollClip.h" #include "nsSVGMaskFrame.h" +#include "nsTableCellFrame.h" +#include "nsTableColFrame.h" // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to // GetTickCount(). @@ -2632,7 +2634,8 @@ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuil nsDisplayList* aList, bool aAllowWillPaintBorderOptimization, nsStyleContext* aStyleContext, - const nsRect& aBackgroundOriginRect) + const nsRect& aBackgroundOriginRect, + nsIFrame* aSecondaryReferenceFrame) { nsStyleContext* bgSC = aStyleContext; const nsStyleBackground* bg = nullptr; @@ -2752,8 +2755,17 @@ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuil new (aBuilder) nsDisplayBackgroundImage(aBuilder, aFrame, i, bgOriginRect, bg); if (bgItem->ShouldFixToViewport(aBuilder)) { - thisItemList.AppendNewToTop( - nsDisplayFixedPosition::CreateForFixedBackground(aBuilder, aFrame, bgItem, i)); + if (aSecondaryReferenceFrame) { + thisItemList.AppendNewToTop( + nsDisplayTableFixedPosition::CreateForFixedBackground(aBuilder, + aSecondaryReferenceFrame, + bgItem, + i, + aFrame)); + } else { + thisItemList.AppendNewToTop( + nsDisplayFixedPosition::CreateForFixedBackground(aBuilder, aFrame, bgItem, i)); + } } else { thisItemList.AppendNewToTop(bgItem); } @@ -5277,6 +5289,51 @@ bool nsDisplayFixedPosition::TryMerge(nsDisplayItem* aItem) { return true; } +TableType +GetTableTypeFromFrame(nsIFrame* aFrame) +{ + nsIAtom* type = aFrame->GetType(); + if (type == nsGkAtoms::tableFrame) { + return TableType::TABLE; + } else if (type == nsGkAtoms::tableColFrame) { + return TableType::TABLE_COL; + } else if (type == nsGkAtoms::tableColGroupFrame) { + return TableType::TABLE_COL_GROUP; + } else if (type == nsGkAtoms::tableRowFrame) { + return TableType::TABLE_ROW; + } else if (type == nsGkAtoms::tableRowGroupFrame) { + return TableType::TABLE_ROW_GROUP; + } else if (type == nsGkAtoms::tableCellFrame) { + return TableType::TABLE_CELL; + } else { + MOZ_ASSERT_UNREACHABLE("Invalid frame."); + return TableType::TABLE; + } +} + +nsDisplayTableFixedPosition::nsDisplayTableFixedPosition(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList, + uint32_t aIndex, + nsIFrame* aAncestorFrame) + : nsDisplayFixedPosition(aBuilder, aFrame, aList, aIndex) + , mTableType(GetTableTypeFromFrame(aAncestorFrame)) +{ +} + +/* static */ nsDisplayTableFixedPosition* +nsDisplayTableFixedPosition::CreateForFixedBackground(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayBackgroundImage* aImage, + uint32_t aIndex, + nsIFrame* aAncestorFrame) +{ + nsDisplayList temp; + temp.AppendToTop(aImage); + + return new (aBuilder) nsDisplayTableFixedPosition(aBuilder, aFrame, &temp, aIndex + 1, aAncestorFrame); +} + nsDisplayStickyPosition::nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList) diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index 9431e2cc0..69d13ded9 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -2738,7 +2738,8 @@ public: nsDisplayList* aList, bool aAllowWillPaintBorderOptimization = true, nsStyleContext* aStyleContext = nullptr, - const nsRect& aBackgroundOriginRect = nsRect()); + const nsRect& aBackgroundOriginRect = nsRect(), + nsIFrame* aSecondaryReferenceFrame = nullptr); virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager, @@ -2838,6 +2839,25 @@ protected: bool mShouldTreatAsFixed; }; +enum class TableType : uint8_t { + TABLE, + TABLE_COL, + TABLE_COL_GROUP, + TABLE_ROW, + TABLE_ROW_GROUP, + TABLE_CELL, + + TABLE_TYPE_MAX +}; + +enum class TableTypeBits : uint8_t { + COUNT = 3 +}; + +static_assert( + static_cast(TableType::TABLE_TYPE_MAX) < (1 << (static_cast(TableTypeBits::COUNT) + 1)), + "TableType cannot fit with TableTypeBits::COUNT"); +TableType GetTableTypeFromFrame(nsIFrame* aFrame); /** * A display item to paint the native theme background for a frame. @@ -3736,7 +3756,7 @@ public: return mAnimatedGeometryRootForScrollMetadata; } -private: +protected: // For background-attachment:fixed nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, uint32_t aIndex); @@ -3747,6 +3767,27 @@ private: bool mIsFixedBackground; }; +class nsDisplayTableFixedPosition : public nsDisplayFixedPosition +{ +public: + static nsDisplayTableFixedPosition* CreateForFixedBackground(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayBackgroundImage* aImage, + uint32_t aIndex, + nsIFrame* aAncestorFrame); + + virtual uint32_t GetPerFrameKey() override { + return (mIndex << (nsDisplayItem::TYPE_BITS + static_cast(TableTypeBits::COUNT))) | + (static_cast(mTableType) << nsDisplayItem::TYPE_BITS) | + nsDisplayItem::GetPerFrameKey(); + } +protected: + nsDisplayTableFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsDisplayList* aList, uint32_t aIndex, nsIFrame* aAncestorFrame); + + TableType mTableType; +}; + /** * This creates an empty scrollable layer. It has no child layers. * It is used to record the existence of a scrollable frame in the layer diff --git a/layout/tables/nsTableFrame.cpp b/layout/tables/nsTableFrame.cpp index 06a05292f..890d050fd 100644 --- a/layout/tables/nsTableFrame.cpp +++ b/layout/tables/nsTableFrame.cpp @@ -1194,13 +1194,14 @@ PaintRowBackground(nsTableRowFrame* aRow, const nsDisplayListSet& aLists, const nsPoint& aOffset = nsPoint()) { - // Compute background rect by iterating all cell frame. + // Compute background rect by iterating over all cell frames. for (nsTableCellFrame* cell = aRow->GetFirstCell(); cell; cell = cell->GetNextCell()) { auto cellRect = cell->GetRectRelativeToSelf() + cell->GetNormalPosition() + aOffset; nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, cellRect, aLists.BorderBackground(), true, nullptr, - aFrame->GetRectRelativeToSelf()); + aFrame->GetRectRelativeToSelf(), + cell); } } @@ -1232,7 +1233,8 @@ PaintRowGroupBackgroundByColIdx(nsTableRowGroupFrame* aRowGroup, nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, cellRect, aLists.BorderBackground(), true, nullptr, - aFrame->GetRectRelativeToSelf()); + aFrame->GetRectRelativeToSelf(), + cell); } } } -- cgit v1.2.3 From ee663e2930650d181be1f7792952dfc32baf0bf0 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 3 Nov 2019 17:25:37 +0100 Subject: Issue #146 - Part 3: Create nsDisplayTableBackgroundImage to avoid display list collisions when processing the background image of a table. --- layout/base/nsDisplayList.cpp | 45 ++++++++++++++++++++++++++++++++++++------- layout/base/nsDisplayList.h | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 6b792a779..1579e6970 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -2751,9 +2751,19 @@ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuil } nsDisplayList thisItemList; - nsDisplayBackgroundImage* bgItem = - new (aBuilder) nsDisplayBackgroundImage(aBuilder, aFrame, i, bgOriginRect, bg); - + nsDisplayBackgroundImage* bgItem; + if (aSecondaryReferenceFrame) { + bgItem = + new (aBuilder) nsDisplayTableBackgroundImage(aBuilder, + aFrame, + i, + bgOriginRect, + bg, + aSecondaryReferenceFrame); + } else { + bgItem = + new (aBuilder) nsDisplayBackgroundImage(aBuilder, aFrame, i, bgOriginRect, bg); + } if (bgItem->ShouldFixToViewport(aBuilder)) { if (aSecondaryReferenceFrame) { thisItemList.AppendNewToTop( @@ -2906,7 +2916,7 @@ nsDisplayBackgroundImage::ImageLayerization nsDisplayBackgroundImage::ShouldCreateOwnLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager) { - nsIFrame* backgroundStyleFrame = nsCSSRendering::FindBackgroundStyleFrame(mFrame); + nsIFrame* backgroundStyleFrame = nsCSSRendering::FindBackgroundStyleFrame(StyleFrame()); if (ActiveLayerTracker::IsBackgroundPositionAnimated(aBuilder, backgroundStyleFrame)) { return WHENEVER_POSSIBLE; @@ -3161,16 +3171,16 @@ nsDisplayBackgroundImage::PaintInternal(nsDisplayListBuilder* aBuilder, StyleGeometryBox clip = mBackgroundStyle->mImage.mLayers[mLayer].mClip; if (clip == StyleGeometryBox::Text) { - if (!GenerateAndPushTextMask(mFrame, aCtx, mBackgroundRect, aBuilder)) { + if (!GenerateAndPushTextMask(StyleFrame(), aCtx, mBackgroundRect, aBuilder)) { return; } } nsCSSRendering::PaintBGParams params = - nsCSSRendering::PaintBGParams::ForSingleLayer(*mFrame->PresContext(), + nsCSSRendering::PaintBGParams::ForSingleLayer(*StyleFrame()->PresContext(), *aCtx, aBounds, mBackgroundRect, - mFrame, flags, mLayer, + StyleFrame(), flags, mLayer, CompositionOp::OP_OVER); params.bgClipRect = aClipRect; image::DrawResult result = @@ -3272,6 +3282,27 @@ nsDisplayBackgroundImage::GetPerFrameKey() nsDisplayItem::GetPerFrameKey(); } +nsDisplayTableBackgroundImage::nsDisplayTableBackgroundImage(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + uint32_t aLayer, + const nsRect& aBackgroundRect, + const nsStyleBackground* aBackgroundStyle, + nsIFrame* aCellFrame) + : nsDisplayBackgroundImage(aBuilder, aFrame, aLayer, aBackgroundRect, aBackgroundStyle) + , mStyleFrame(aFrame) + , mTableType(GetTableTypeFromFrame(mStyleFrame)) +{ + mFrame = aCellFrame; +} + +bool +nsDisplayTableBackgroundImage::IsInvalid(nsRect& aRect) +{ + bool result = mStyleFrame ? mStyleFrame->IsInvalid(aRect) : false; + aRect += ToReferenceFrame(); + return result; +} + nsDisplayThemedBackground::nsDisplayThemedBackground(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const nsRect& aBackgroundRect) diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index 69d13ded9..e9047b113 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -2814,6 +2814,8 @@ protected: void PaintInternal(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx, const nsRect& aBounds, nsRect* aClipRect); + virtual nsIFrame* StyleFrame() { return mFrame; } + // Determine whether we want to be separated into our own layer, independent // of whether this item can actually be layerized. enum ImageLayerization { @@ -2859,6 +2861,41 @@ static_assert( "TableType cannot fit with TableTypeBits::COUNT"); TableType GetTableTypeFromFrame(nsIFrame* aFrame); +/** + * A display item to paint background image for table. For table parts, such + * as row, row group, col, col group, when drawing its background, we'll + * create separate background image display item for its containning cell. + * Those background image display items will reference to same DisplayItemData + * if we keep the mFrame point to cell's ancestor frame. We don't want to this + * happened bacause share same DisplatItemData will cause many bugs. So that + * we let mFrame point to cell frame and store the table type of the ancestor + * frame. And use mFrame and table type as key to generate DisplayItemData to + * avoid sharing DisplayItemData. + * + * Also store ancestor frame as mStyleFrame for all rendering informations. + */ +class nsDisplayTableBackgroundImage : public nsDisplayBackgroundImage { +public: + nsDisplayTableBackgroundImage(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + uint32_t aLayer, + const nsRect& aBackgroundRect, + const nsStyleBackground* aBackgroundStyle, + nsIFrame* aCellFrame); + + virtual uint32_t GetPerFrameKey() override { + return (static_cast(mTableType) << nsDisplayItem::TYPE_BITS) | + nsDisplayItem::GetPerFrameKey(); + } + + virtual bool IsInvalid(nsRect& aRect) override; +protected: + virtual nsIFrame* StyleFrame() override { return mStyleFrame; } + + nsIFrame* mStyleFrame; + TableType mTableType; +}; + /** * A display item to paint the native theme background for a frame. */ -- cgit v1.2.3 From f1b043af1dd09b2a5235ee88be9a5b547697fa5b Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 3 Nov 2019 18:49:57 +0100 Subject: Issue #146 - Part 4: Adjust tests for fixes. This also adds a reftest for border radius on collapsed borders (should be ignored according to the CSS3 standard). We didn't do this before, except on internal elements. --- layout/reftests/table-background/reftest.list | 6 +++--- .../table-bordercollapse/bc_borderradius-ref.html | 17 +++++++++++++++++ .../reftests/table-bordercollapse/bc_borderradius.html | 18 ++++++++++++++++++ layout/reftests/table-bordercollapse/reftest.list | 4 +++- 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 layout/reftests/table-bordercollapse/bc_borderradius-ref.html create mode 100644 layout/reftests/table-bordercollapse/bc_borderradius.html diff --git a/layout/reftests/table-background/reftest.list b/layout/reftests/table-background/reftest.list index eb2817ca0..68dc43e95 100644 --- a/layout/reftests/table-background/reftest.list +++ b/layout/reftests/table-background/reftest.list @@ -44,11 +44,11 @@ fuzzy-if(d2d,1,1083) fuzzy-if(skiaContent,1,2200) == border-collapse-opacity-tab fails == border-collapse-opacity-table-column-group.html border-collapse-opacity-table-column-group-ref.html # bug 424274 fails == border-collapse-opacity-table-column.html border-collapse-opacity-table-column-ref.html # bug 424274 fuzzy-if(d2d,1,16359) fuzzy-if(skiaContent,1,17000) == border-collapse-opacity-table-row-group.html border-collapse-opacity-table-row-group-ref.html -fuzzy-if(d2d,1,5453) fuzzy-if(skiaContent,1,11000) == border-collapse-opacity-table-row.html border-collapse-opacity-table-row-ref.html +fuzzy-if(d2d,1,11000) fuzzy-if(skiaContent,1,11000) == border-collapse-opacity-table-row.html border-collapse-opacity-table-row-ref.html fuzzy-if(d2d||skiaContent,1,60000) == border-collapse-opacity-table.html border-collapse-opacity-table-ref.html fuzzy-if(d2d,1,2478) fuzzy-if(skiaContent,1,2500) == border-separate-opacity-table-cell.html border-separate-opacity-table-cell-ref.html -fails == border-separate-opacity-table-column-group.html border-separate-opacity-table-column-group-ref.html # bug 424274 -fails == border-separate-opacity-table-column.html border-separate-opacity-table-column-ref.html # bug 424274 +fuzzy-if(d2d,1,38000) == border-separate-opacity-table-column-group.html border-separate-opacity-table-column-group-ref.html # bug 424274 +fuzzy-if(d2d,1,13000) == border-separate-opacity-table-column.html border-separate-opacity-table-column-ref.html # bug 424274 fuzzy-if(d2d,1,37170) fuzzy-if(skiaContent,1,38000) == border-separate-opacity-table-row-group.html border-separate-opacity-table-row-group-ref.html fuzzy-if(d2d,1,12390) fuzzy-if(skiaContent,1,13000) == border-separate-opacity-table-row.html border-separate-opacity-table-row-ref.html fuzzy-if(d2d||skiaContent,1,95000) == border-separate-opacity-table.html border-separate-opacity-table-ref.html diff --git a/layout/reftests/table-bordercollapse/bc_borderradius-ref.html b/layout/reftests/table-bordercollapse/bc_borderradius-ref.html new file mode 100644 index 000000000..c7b041f79 --- /dev/null +++ b/layout/reftests/table-bordercollapse/bc_borderradius-ref.html @@ -0,0 +1,17 @@ + + +
 
 
b
DoneSunOS x86 +Contributed code! +Arthur Jones <aljones@lbl.gov>
+? +Philip Pokorny <ppokorny@mindspring.com>
+
+The word I hear is that this is working and done +
Done Irix
+ + + +
diff --git a/layout/reftests/table-bordercollapse/bc_borderradius.html b/layout/reftests/table-bordercollapse/bc_borderradius.html new file mode 100644 index 000000000..3ae2a89d8 --- /dev/null +++ b/layout/reftests/table-bordercollapse/bc_borderradius.html @@ -0,0 +1,18 @@ + + + + + + +
diff --git a/layout/reftests/table-bordercollapse/reftest.list b/layout/reftests/table-bordercollapse/reftest.list index 8356dc54b..5ca6f305a 100644 --- a/layout/reftests/table-bordercollapse/reftest.list +++ b/layout/reftests/table-bordercollapse/reftest.list @@ -20,6 +20,7 @@ == bc_dyn_table3.html bc_dyn_table3_ref.html == bc_borderoffset1.html bc_borderoffset1_ref.html == bc_borderoffset2.html bc_borderoffset2_ref.html +== bc_borderradius.html bc_borderradius-ref.html == frame_above_rules_all.html frame_above_rules_all_ref.html == frame_above_rules_cols.html frame_above_rules_cols_ref.html == frame_above_rules_groups.html frame_above_rules_groups_ref.html @@ -104,4 +105,5 @@ fuzzy(255,40) == border-style-outset-becomes-groove.html border-style-outset-bec # So get 40 pixels of fuzz, 20 at each beveled corner (because the border width # is 20px). fuzzy(255,40) == border-style-inset-becomes-ridge.html border-style-inset-becomes-ridge-ref.html -fuzzy(2,8301) == 1324524.html 1324524-ref.html +fuzzy(2,11000) == 1324524.html 1324524-ref.html + -- cgit v1.2.3 From ce11d5cae866f24b8f7435cdc3725cfd2748595b Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 3 Nov 2019 19:09:47 +0100 Subject: Issue #146 - Part 5: Treat table row groups as containing blocks. This aligns our behavior with Gecko/Blink. --- layout/generic/StickyScrollContainer.cpp | 8 ++++++++ layout/generic/nsFrame.cpp | 3 +++ 2 files changed, 11 insertions(+) diff --git a/layout/generic/StickyScrollContainer.cpp b/layout/generic/StickyScrollContainer.cpp index ca68992c3..ff8ebcfef 100644 --- a/layout/generic/StickyScrollContainer.cpp +++ b/layout/generic/StickyScrollContainer.cpp @@ -176,6 +176,14 @@ StickyScrollContainer::ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick, nsRect rect = nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame->GetParent()); + // Note: Table row groups aren't supposed to be containing blocks, but we treat + // them as such anyway. + // Not having this basically disables position:sticky on table cells, which + // would be really unfortunate, and doesn't match what other browsers do. + if (cbFrame != scrolledFrame && cbFrame->GetType() == nsGkAtoms::tableRowGroupFrame) { + cbFrame = cbFrame->GetContainingBlock(); + } + // Containing block limits for the position of aFrame relative to its parent. // The margin box of the sticky element stays within the content box of the // contaning-block element. diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index a531dea07..d4bcf22ed 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -6629,6 +6629,9 @@ GetNearestBlockContainer(nsIFrame* frame) // Since the parent of such a block is either a normal block or // another such pseudo, this shouldn't cause anything bad to happen. // Also the anonymous blocks inside table cells are not containing blocks. + // + // If we ever start skipping table row groups from being containing blocks, + // we need to remove the containing block assignment in StickyScrollContainer . while (frame->IsFrameOfType(nsIFrame::eLineParticipant) || frame->IsBlockWrapper() || // Table rows are not containing blocks either -- cgit v1.2.3 From 5ab2da700448caf181dae7dec2f53997faf118ea Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 3 Nov 2019 19:43:51 +0100 Subject: Issue #146 - Part 6: Allow `position: sticky` on table elements. --- layout/base/RestyleManagerBase.cpp | 9 --------- layout/generic/StickyScrollContainer.cpp | 2 +- layout/generic/nsFrame.cpp | 5 +---- layout/tables/nsTableRowFrame.cpp | 11 +++++------ 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/layout/base/RestyleManagerBase.cpp b/layout/base/RestyleManagerBase.cpp index 6770f9464..6ef048a19 100644 --- a/layout/base/RestyleManagerBase.cpp +++ b/layout/base/RestyleManagerBase.cpp @@ -474,15 +474,6 @@ RecomputePosition(nsIFrame* aFrame) if (display->IsRelativelyPositionedStyle()) { // Move the frame if (display->mPosition == NS_STYLE_POSITION_STICKY) { - if (display->IsInnerTableStyle()) { - // We don't currently support sticky positioning of inner table - // elements (bug 975644). Bail. - // - // When this is fixed, remove the null-check for the computed - // offsets in nsTableRowFrame::ReflowChildren. - return true; - } - // Update sticky positioning for an entire element at once, starting with // the first continuation or ib-split sibling. // It's rare that the frame we already have isn't already the first diff --git a/layout/generic/StickyScrollContainer.cpp b/layout/generic/StickyScrollContainer.cpp index ff8ebcfef..c5ed44e92 100644 --- a/layout/generic/StickyScrollContainer.cpp +++ b/layout/generic/StickyScrollContainer.cpp @@ -45,7 +45,7 @@ StickyScrollContainer::GetStickyScrollContainerForFrame(nsIFrame* aFrame) // return nullptr; } - auto frame = static_cast(do_QueryFrame(scrollFrame)); + nsIFrame* frame = do_QueryFrame(scrollFrame); StickyScrollContainer* s = frame->GetProperty(StickyScrollContainerProperty()); if (!s) { diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index d4bcf22ed..bbbb5c332 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -562,15 +562,12 @@ nsFrame::Init(nsIContent* aContent, } if (disp->mPosition == NS_STYLE_POSITION_STICKY && !aPrevInFlow && - !(mState & NS_FRAME_IS_NONDISPLAY) && - !disp->IsInnerTableStyle()) { + !(mState & NS_FRAME_IS_NONDISPLAY)) { // Note that we only add first continuations, but we really only // want to add first continuation-or-ib-split-siblings. But since we // don't yet know if we're a later part of a block-in-inline split, // we'll just add later members of a block-in-inline split here, and // then StickyScrollContainer will remove them later. - // We don't currently support relative positioning of inner table - // elements (bug 35168), so exclude them from sticky positioning too. StickyScrollContainer* ssc = StickyScrollContainer::GetStickyScrollContainerForFrame(this); if (ssc) { diff --git a/layout/tables/nsTableRowFrame.cpp b/layout/tables/nsTableRowFrame.cpp index 05b9deee9..ea2477b73 100644 --- a/layout/tables/nsTableRowFrame.cpp +++ b/layout/tables/nsTableRowFrame.cpp @@ -926,12 +926,11 @@ nsTableRowFrame::ReflowChildren(nsPresContext* aPresContext, // be merged into the else below if we can.) nsMargin* computedOffsetProp = kidFrame->GetProperty(nsIFrame::ComputedOffsetProperty()); - // Bug 975644: a position:sticky kid can end up with a null - // property value here. - LogicalMargin computedOffsets(wm, computedOffsetProp ? - *computedOffsetProp : nsMargin()); - ReflowInput::ApplyRelativePositioning(kidFrame, wm, computedOffsets, - &kidPosition, containerSize); + + // On our fist reflow sticky children may not have the property yet (we + // need to reflow the children first to size the scroll frame). + LogicalMargin computedOffsets(wm, computedOffsetProp ? *computedOffsetProp : nsMargin()); + ReflowInput::ApplyRelativePositioning(kidFrame, wm, computedOffsets, &kidPosition, containerSize); } // In vertical-rl mode, we are likely to have containerSize.width = 0 -- cgit v1.2.3 From 76052e837cd49879e2e738e575984c340f176859 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 3 Nov 2019 19:45:49 +0100 Subject: Issue #146 - Part 7: Remove no longer relevant reftest. --- .../position-sticky/inner-table-1-ref.html | 26 ---------------- layout/reftests/position-sticky/inner-table-1.html | 35 ---------------------- layout/reftests/position-sticky/reftest.list | 1 - 3 files changed, 62 deletions(-) delete mode 100644 layout/reftests/position-sticky/inner-table-1-ref.html delete mode 100644 layout/reftests/position-sticky/inner-table-1.html diff --git a/layout/reftests/position-sticky/inner-table-1-ref.html b/layout/reftests/position-sticky/inner-table-1-ref.html deleted file mode 100644 index 379841fae..000000000 --- a/layout/reftests/position-sticky/inner-table-1-ref.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - -
a
b
c
d
- - diff --git a/layout/reftests/position-sticky/inner-table-1.html b/layout/reftests/position-sticky/inner-table-1.html deleted file mode 100644 index 212e658fd..000000000 --- a/layout/reftests/position-sticky/inner-table-1.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - CSS Test: Sticky Positioning - inner table elements - - - - - - - - - - - - - - - - - - - - - -
a
b
c
d
- - diff --git a/layout/reftests/position-sticky/reftest.list b/layout/reftests/position-sticky/reftest.list index 2705d08fd..01e7b1638 100644 --- a/layout/reftests/position-sticky/reftest.list +++ b/layout/reftests/position-sticky/reftest.list @@ -48,4 +48,3 @@ fails == column-contain-1a.html column-contain-1-ref.html fuzzy-if(skiaContent,1,22) fuzzy-if(winWidget&&!layersGPUAccelerated,116,1320) fuzzy-if(Android,8,1533) == block-in-inline-2.html block-in-inline-2-ref.html fuzzy-if(Android,8,630) fuzzy-if(OSX>=1008,1,11) fuzzy-if(skiaContent,1,220) fuzzy-if(winWidget&&!layersGPUAccelerated,116,1320) == block-in-inline-3.html block-in-inline-3-ref.html == block-in-inline-continuations.html block-in-inline-continuations-ref.html -fuzzy-if(winWidget&&!layersGPUAccelerated,140,140) == inner-table-1.html inner-table-1-ref.html -- cgit v1.2.3 From 732fe36241032a1793e09209ccc06cb720225b9e Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Sun, 3 Nov 2019 14:14:19 -0500 Subject: Revert "Issue #1267 - Part 2: fix libcubeb bindings in dom" This reverts commit 22b35fa8e923d52a3fa785993c28c3e63cd1ee1e. --- dom/media/CubebUtils.cpp | 4 +--- dom/media/GraphDriver.cpp | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dom/media/CubebUtils.cpp b/dom/media/CubebUtils.cpp index 8f1ea0179..0f0167d9c 100644 --- a/dom/media/CubebUtils.cpp +++ b/dom/media/CubebUtils.cpp @@ -55,7 +55,6 @@ bool sCubebPlaybackLatencyPrefSet; bool sCubebMSGLatencyPrefSet; bool sAudioStreamInitEverSucceeded = false; StaticAutoPtr sBrandName; -StaticAutoPtr sCubebBackendName; const char kBrandBundleURL[] = "chrome://branding/locale/brand.properties"; @@ -227,7 +226,7 @@ cubeb* GetCubebContextUnlocked() sBrandName, "Did not initialize sbrandName, and not on the main thread?"); } - int rv = cubeb_init(&sCubebContext, sBrandName, sCubebBackendName); + int rv = cubeb_init(&sCubebContext, sBrandName); NS_WARNING_ASSERTION(rv == CUBEB_OK, "Could not get a cubeb context."); sCubebState = (rv == CUBEB_OK) ? CubebState::Initialized : CubebState::Uninitialized; @@ -293,7 +292,6 @@ void ShutdownLibrary() sCubebContext = nullptr; } sBrandName = nullptr; - sCubebBackendName = nullptr; // This will ensure we don't try to re-create a context. sCubebState = CubebState::Shutdown; } diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp index aa75918f1..37c692a4b 100644 --- a/dom/media/GraphDriver.cpp +++ b/dom/media/GraphDriver.cpp @@ -656,7 +656,7 @@ AudioCallbackDriver::Init() if (latencyPref) { latency_frames = latencyPref.value(); } else { - if (cubeb_get_min_latency(cubebContext, &output, &latency_frames) != CUBEB_OK) { + if (cubeb_get_min_latency(cubebContext, output, &latency_frames) != CUBEB_OK) { NS_WARNING("Could not get minimal latency from cubeb."); } } -- cgit v1.2.3 From 1960d6e08a949ceed50e6a18240d40a7ecee879c Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Sun, 3 Nov 2019 14:14:26 -0500 Subject: Revert "Issue #1267 - Part 1: Update libcubeb to a1200c34." This reverts commit d162ecbaffe845c9707da5d2f6cab11f343ef00e. --- media/libcubeb/AUTHORS | 1 - media/libcubeb/bug1292803_pulse_assert.patch | 46 + media/libcubeb/bug1302231_emergency_bailout.patch | 140 + media/libcubeb/disable-assert.patch | 23 - media/libcubeb/disable-iaudioclient3.patch | 64 - media/libcubeb/fix-crashes.patch | 71 + media/libcubeb/include/cubeb.h | 255 +- media/libcubeb/osx-linearize-operations.patch | 968 +++++++ media/libcubeb/prevent-double-free.patch | 46 + media/libcubeb/src/android/cubeb-output-latency.h | 76 - media/libcubeb/src/android/cubeb_media_library.h | 62 - media/libcubeb/src/android/sles_definitions.h | 39 +- media/libcubeb/src/cubeb-internal.h | 17 +- media/libcubeb/src/cubeb-jni-instances.h | 34 - media/libcubeb/src/cubeb-jni.cpp | 68 - media/libcubeb/src/cubeb-jni.h | 10 - media/libcubeb/src/cubeb.c | 233 +- media/libcubeb/src/cubeb_alsa.c | 560 +--- media/libcubeb/src/cubeb_array_queue.h | 97 - media/libcubeb/src/cubeb_assert.h | 17 - media/libcubeb/src/cubeb_audiotrack.c | 15 +- media/libcubeb/src/cubeb_audiounit.cpp | 3172 ++++++++------------- media/libcubeb/src/cubeb_jack.cpp | 160 +- media/libcubeb/src/cubeb_log.cpp | 144 - media/libcubeb/src/cubeb_log.h | 18 +- media/libcubeb/src/cubeb_mixer.cpp | 663 ----- media/libcubeb/src/cubeb_mixer.h | 37 - media/libcubeb/src/cubeb_opensl.c | 1570 +++------- media/libcubeb/src/cubeb_pulse.c | 488 +--- media/libcubeb/src/cubeb_resampler.cpp | 122 +- media/libcubeb/src/cubeb_resampler.h | 15 +- media/libcubeb/src/cubeb_resampler_internal.h | 123 +- media/libcubeb/src/cubeb_ringbuffer.h | 495 ---- media/libcubeb/src/cubeb_sndio.c | 416 +-- media/libcubeb/src/cubeb_strings.c | 155 - media/libcubeb/src/cubeb_strings.h | 44 - media/libcubeb/src/cubeb_sun.c | 1006 +++---- media/libcubeb/src/cubeb_utils.cpp | 23 - media/libcubeb/src/cubeb_utils.h | 148 +- media/libcubeb/src/cubeb_utils_unix.h | 4 +- media/libcubeb/src/cubeb_utils_win.h | 4 +- media/libcubeb/src/cubeb_wasapi.cpp | 2280 ++++++--------- media/libcubeb/src/cubeb_winmm.c | 160 +- media/libcubeb/src/moz.build | 8 +- media/libcubeb/unresampled-frames.patch | 36 + media/libcubeb/update.sh | 73 +- media/libcubeb/uplift-part-of-f07ee6d-esr52.patch | 167 ++ media/libcubeb/uplift-patch-7a4c711.patch | 69 + media/libcubeb/uplift-system-listener-patch.patch | 402 +++ media/libcubeb/uplift-wasapi-part-to-beta.patch | 118 + 50 files changed, 5602 insertions(+), 9360 deletions(-) create mode 100644 media/libcubeb/bug1292803_pulse_assert.patch create mode 100644 media/libcubeb/bug1302231_emergency_bailout.patch delete mode 100644 media/libcubeb/disable-assert.patch delete mode 100644 media/libcubeb/disable-iaudioclient3.patch create mode 100644 media/libcubeb/fix-crashes.patch create mode 100644 media/libcubeb/osx-linearize-operations.patch create mode 100644 media/libcubeb/prevent-double-free.patch delete mode 100644 media/libcubeb/src/android/cubeb-output-latency.h delete mode 100644 media/libcubeb/src/android/cubeb_media_library.h delete mode 100644 media/libcubeb/src/cubeb-jni-instances.h delete mode 100644 media/libcubeb/src/cubeb-jni.cpp delete mode 100644 media/libcubeb/src/cubeb-jni.h delete mode 100644 media/libcubeb/src/cubeb_array_queue.h delete mode 100644 media/libcubeb/src/cubeb_assert.h delete mode 100644 media/libcubeb/src/cubeb_log.cpp delete mode 100644 media/libcubeb/src/cubeb_mixer.cpp delete mode 100644 media/libcubeb/src/cubeb_mixer.h delete mode 100644 media/libcubeb/src/cubeb_ringbuffer.h delete mode 100644 media/libcubeb/src/cubeb_strings.c delete mode 100644 media/libcubeb/src/cubeb_strings.h delete mode 100644 media/libcubeb/src/cubeb_utils.cpp create mode 100644 media/libcubeb/unresampled-frames.patch create mode 100644 media/libcubeb/uplift-part-of-f07ee6d-esr52.patch create mode 100644 media/libcubeb/uplift-patch-7a4c711.patch create mode 100644 media/libcubeb/uplift-system-listener-patch.patch create mode 100644 media/libcubeb/uplift-wasapi-part-to-beta.patch diff --git a/media/libcubeb/AUTHORS b/media/libcubeb/AUTHORS index f0f959522..0fde65baa 100644 --- a/media/libcubeb/AUTHORS +++ b/media/libcubeb/AUTHORS @@ -13,4 +13,3 @@ Landry Breuil Jacek Caban Paul Hancock Ted Mielczarek -Chun-Min Chang diff --git a/media/libcubeb/bug1292803_pulse_assert.patch b/media/libcubeb/bug1292803_pulse_assert.patch new file mode 100644 index 000000000..8dee88777 --- /dev/null +++ b/media/libcubeb/bug1292803_pulse_assert.patch @@ -0,0 +1,46 @@ +commit 2c7617f5ca20b764c605e19af490889c761e65e2 +Author: Matthew Gregan +Date: Thu Nov 10 19:07:07 2016 +1300 + + pulse: Bail early from pulse_defer_event_cb when shutting down. + + When starting a stream, trigger_user_callback may be called from + stream_write_callback and immediately enter a drain situation, creating + a drain timer and setting shutdown to true. If pulse_defer_event_cb + then runs without checking for shutdown, it can overwrite the current + timer with a new timer, resulting in a leaked timer and a null pointer + assertion. + +diff --git a/src/cubeb_pulse.c b/src/cubeb_pulse.c +index 5b61bda..86f2ba3 100644 +--- a/src/cubeb_pulse.c ++++ b/src/cubeb_pulse.c +@@ -181,9 +181,9 @@ static void + stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u) + { + (void)a; +- (void)e; + (void)tv; + cubeb_stream * stm = u; ++ assert(stm->drain_timer == e); + stream_state_change_callback(stm, CUBEB_STATE_DRAINED); + /* there's no pa_rttime_free, so use this instead. */ + a->time_free(stm->drain_timer); +@@ -267,6 +267,7 @@ trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cub + assert(r == 0 || r == -PA_ERR_NODATA); + /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */ + /* arbitrary safety margin: double the current latency. */ ++ assert(!stm->drain_timer); + stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm); + stm->shutdown = 1; + return; +@@ -851,6 +852,9 @@ pulse_defer_event_cb(pa_mainloop_api * a, void * userdata) + { + (void)a; + cubeb_stream * stm = userdata; ++ if (stm->shutdown) { ++ return; ++ } + size_t writable_size = WRAP(pa_stream_writable_size)(stm->output_stream); + trigger_user_callback(stm->output_stream, NULL, writable_size, stm); + } diff --git a/media/libcubeb/bug1302231_emergency_bailout.patch b/media/libcubeb/bug1302231_emergency_bailout.patch new file mode 100644 index 000000000..82152c23f --- /dev/null +++ b/media/libcubeb/bug1302231_emergency_bailout.patch @@ -0,0 +1,140 @@ +From 37ce70d4400a2ab6b59ee432b41d4ffcc9d136ff Mon Sep 17 00:00:00 2001 +From: Paul Adenot +Date: Thu, 10 Nov 2016 21:45:14 +0100 +Subject: [PATCH] Bail out safely from the rendering loop if we could not join + the rendering thread in time (#187) + +Bail out safely from the rendering loop if we could not join the rendering thread in time. +--- + src/cubeb_wasapi.cpp | 41 ++++++++++++++++++++++++++++++++++++----- + 1 file changed, 36 insertions(+), 5 deletions(-) + +diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp +index 9e689b9..519d5ca 100644 +--- a/src/cubeb_wasapi.cpp ++++ b/src/cubeb_wasapi.cpp +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include "cubeb/cubeb.h" + #include "cubeb-internal.h" +@@ -220,9 +221,11 @@ struct cubeb_stream + float volume; + /* True if the stream is draining. */ + bool draining; ++ /* True when we've destroyed the stream. This pointer is leaked on stream ++ * destruction if we could not join the thread. */ ++ std::atomic*> emergency_bailout; + }; + +- + class wasapi_endpoint_notification_client : public IMMNotificationClient + { + public: +@@ -781,6 +784,7 @@ static unsigned int __stdcall + wasapi_stream_render_loop(LPVOID stream) + { + cubeb_stream * stm = static_cast(stream); ++ std::atomic * emergency_bailout = stm->emergency_bailout; + + bool is_playing = true; + HANDLE wait_array[4] = { +@@ -820,6 +824,10 @@ wasapi_stream_render_loop(LPVOID stream) + wait_array, + FALSE, + 1000); ++ if (*emergency_bailout) { ++ delete emergency_bailout; ++ return 0; ++ } + if (waitResult != WAIT_TIMEOUT) { + timeout_count = 0; + } +@@ -1134,12 +1142,13 @@ int wasapi_init(cubeb ** context, char const * context_name) + } + + namespace { +-void stop_and_join_render_thread(cubeb_stream * stm) ++bool stop_and_join_render_thread(cubeb_stream * stm) + { ++ bool rv = true; + LOG("Stop and join render thread."); + if (!stm->thread) { + LOG("No thread present."); +- return; ++ return true; + } + + BOOL ok = SetEvent(stm->shutdown_event); +@@ -1153,11 +1162,15 @@ void stop_and_join_render_thread(cubeb_stream * stm) + if (r == WAIT_TIMEOUT) { + /* Something weird happened, leak the thread and continue the shutdown + * process. */ ++ *(stm->emergency_bailout) = true; + LOG("Destroy WaitForSingleObject on thread timed out," + " leaking the thread: %d", GetLastError()); ++ rv = false; + } + if (r == WAIT_FAILED) { ++ *(stm->emergency_bailout) = true; + LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError()); ++ rv = false; + } + + LOG("Closing thread."); +@@ -1167,6 +1180,8 @@ void stop_and_join_render_thread(cubeb_stream * stm) + + CloseHandle(stm->shutdown_event); + stm->shutdown_event = 0; ++ ++ return rv; + } + + void wasapi_destroy(cubeb * context) +@@ -1775,7 +1790,16 @@ void wasapi_stream_destroy(cubeb_stream * stm) + { + XASSERT(stm); + +- stop_and_join_render_thread(stm); ++ // Only free stm->emergency_bailout if we could not join the thread. ++ // If we could not join the thread, stm->emergency_bailout is true ++ // and is still alive until the thread wakes up and exits cleanly. ++ if (stop_and_join_render_thread(stm)) { ++ delete stm->emergency_bailout.load(); ++ stm->emergency_bailout = nullptr; ++ } else { ++ // If we're leaking, it must be that this is true. ++ assert(*(stm->emergency_bailout)); ++ } + + unregister_notification_client(stm); + +@@ -1844,6 +1868,8 @@ int wasapi_stream_start(cubeb_stream * stm) + + auto_lock lock(stm->stream_reset_lock); + ++ stm->emergency_bailout = new std::atomic(false); ++ + if (stm->output_client) { + int rv = stream_start_one_side(stm, OUTPUT); + if (rv != CUBEB_OK) { +@@ -1903,7 +1929,12 @@ int wasapi_stream_stop(cubeb_stream * stm) + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + } + +- stop_and_join_render_thread(stm); ++ if (stop_and_join_render_thread(stm)) { ++ if (stm->emergency_bailout.load()) { ++ delete stm->emergency_bailout.load(); ++ stm->emergency_bailout = nullptr; ++ } ++ } + + return CUBEB_OK; + } +-- +2.7.4 + diff --git a/media/libcubeb/disable-assert.patch b/media/libcubeb/disable-assert.patch deleted file mode 100644 index 6fae3a766..000000000 --- a/media/libcubeb/disable-assert.patch +++ /dev/null @@ -1,23 +0,0 @@ -diff --git a/media/libcubeb/src/cubeb_resampler.cpp b/media/libcubeb/src/cubeb_resampler.cpp ---- a/media/libcubeb/src/cubeb_resampler.cpp -+++ b/media/libcubeb/src/cubeb_resampler.cpp -@@ -50,18 +50,17 @@ passthrough_resampler::passthrough_re - - template - long passthrough_resampler::fill(void * input_buffer, long * input_frames_count, - void * output_buffer, long output_frames) - { - if (input_buffer) { - assert(input_frames_count); - } -- assert((input_buffer && output_buffer && -- *input_frames_count + static_cast(samples_to_frames(internal_input_buffer.length())) >= output_frames) || -+ assert((input_buffer && output_buffer) || - (output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) || - (input_buffer && !output_buffer && output_frames == 0)); - - if (input_buffer) { - if (!output_buffer) { - output_frames = *input_frames_count; - } - internal_input_buffer.push(static_cast(input_buffer), diff --git a/media/libcubeb/disable-iaudioclient3.patch b/media/libcubeb/disable-iaudioclient3.patch deleted file mode 100644 index 658806b26..000000000 --- a/media/libcubeb/disable-iaudioclient3.patch +++ /dev/null @@ -1,64 +0,0 @@ -diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp ---- a/media/libcubeb/src/cubeb_wasapi.cpp -+++ b/media/libcubeb/src/cubeb_wasapi.cpp -@@ -1916,24 +1916,24 @@ int setup_wasapi_stream_one_side(cubeb_s - LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME, hr); - } - return CUBEB_ERROR; - } - } - - /* Get a client. We will get all other interfaces we need from - * this pointer. */ -- hr = device->Activate(__uuidof(IAudioClient3), -- CLSCTX_INPROC_SERVER, -- NULL, audio_client.receive_vpp()); -- if (hr == E_NOINTERFACE) { -+ // hr = device->Activate(__uuidof(IAudioClient3), -+ // CLSCTX_INPROC_SERVER, -+ // NULL, audio_client.receive_vpp()); -+ // if (hr == E_NOINTERFACE) { - hr = device->Activate(__uuidof(IAudioClient), - CLSCTX_INPROC_SERVER, - NULL, audio_client.receive_vpp()); -- } -+ //} - - if (FAILED(hr)) { - LOG("Could not activate the device to get an audio" - " client for %s: error: %lx\n", DIRECTION_NAME, hr); - // A particular device can't be activated because it has been - // unplugged, try fall back to the default audio device. - if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED) { - LOG("Trying again with the default %s audio device.", DIRECTION_NAME); -@@ -1989,26 +1989,26 @@ int setup_wasapi_stream_one_side(cubeb_s - // Check if a loopback device should be requested. Note that event callbacks - // do not work with loopback devices, so only request these if not looping. - if (is_loopback) { - flags |= AUDCLNT_STREAMFLAGS_LOOPBACK; - } else { - flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; - } - -- if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) { -- LOG("Initialized with IAudioClient3"); -- } else { -+ // if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) { -+ // LOG("Initialized with IAudioClient3"); -+ // } else { - hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, - flags, - frames_to_hns(stm, stm->latency), - 0, - mix_format.get(), - NULL); -- } -+ // } - if (FAILED(hr)) { - LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr); - return CUBEB_ERROR; - } - - hr = audio_client->GetBufferSize(buffer_frame_count); - if (FAILED(hr)) { - LOG("Could not get the buffer size from the client" diff --git a/media/libcubeb/fix-crashes.patch b/media/libcubeb/fix-crashes.patch new file mode 100644 index 000000000..b23501fcf --- /dev/null +++ b/media/libcubeb/fix-crashes.patch @@ -0,0 +1,71 @@ +This patch fixes three different crashes, one crash per chunk in this patch, +in the same order. +- Bug 1342389 +- Bug 1345147 +- Bug 1347453 + +diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp +--- a/media/libcubeb/src/cubeb_wasapi.cpp ++++ b/media/libcubeb/src/cubeb_wasapi.cpp +@@ -878,16 +878,23 @@ wasapi_stream_render_loop(LPVOID stream) + + /* WaitForMultipleObjects timeout can trigger in cases where we don't want to + treat it as a timeout, such as across a system sleep/wake cycle. Trigger + the timeout error handling only when the timeout_limit is reached, which is + reset on each successful loop. */ + unsigned timeout_count = 0; + const unsigned timeout_limit = 5; + while (is_playing) { ++ // We want to check the emergency bailout variable before a ++ // and after the WaitForMultipleObject, because the handles WaitForMultipleObjects ++ // is going to wait on might have been closed already. ++ if (*emergency_bailout) { ++ delete emergency_bailout; ++ return 0; ++ } + DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array), + wait_array, + FALSE, + 1000); + if (*emergency_bailout) { + delete emergency_bailout; + return 0; + } +@@ -1199,16 +1206,22 @@ bool stop_and_join_render_thread(cubeb_s + { + bool rv = true; + LOG("Stop and join render thread."); + if (!stm->thread) { + LOG("No thread present."); + return true; + } + ++ // If we've already leaked the thread, just return, ++ // there is not much we can do. ++ if (!stm->emergency_bailout.load()) { ++ return false; ++ } ++ + BOOL ok = SetEvent(stm->shutdown_event); + if (!ok) { + LOG("Destroy SetEvent failed: %lx", GetLastError()); + } + + /* Wait five seconds for the rendering thread to return. It's supposed to + * check its event loop very often, five seconds is rather conservative. */ + DWORD r = WaitForSingleObject(stm->thread, 5000); +diff --git a/media/libcubeb/update.sh b/media/libcubeb/update.sh +--- a/media/libcubeb/update.sh ++++ b/media/libcubeb/update.sh +@@ -66,8 +66,11 @@ fi + echo "Applying a patch on top of $version" + patch -p1 < ./wasapi-drift-fix-passthrough-resampler.patch + + echo "Applying a patch on top of $version" + patch -p1 < ./audiounit-drift-fix.patch + + echo "Applying a patch on top of $version" + patch -p1 < ./uplift-wasapi-fixes-aurora.patch ++ ++echo "Applying a patch on top of $version" ++patch -p3 < ./fix-crashes.patch diff --git a/media/libcubeb/include/cubeb.h b/media/libcubeb/include/cubeb.h index e6cf8dd87..449b39c55 100644 --- a/media/libcubeb/include/cubeb.h +++ b/media/libcubeb/include/cubeb.h @@ -8,7 +8,6 @@ #define CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382 #include -#include #include "cubeb_export.h" #if defined(__cplusplus) @@ -33,11 +32,11 @@ extern "C" { cubeb * app_ctx; cubeb_init(&app_ctx, "Example Application"); int rv; - uint32_t rate; - uint32_t latency_frames; + int rate; + int latency_frames; uint64_t ts; - rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames); + rv = cubeb_get_min_latency(app_ctx, output_params, &latency_frames); if (rv != CUBEB_OK) { fprintf(stderr, "Could not get minimum latency"); return rv; @@ -53,20 +52,16 @@ extern "C" { output_params.format = CUBEB_SAMPLE_FLOAT32NE; output_params.rate = rate; output_params.channels = 2; - output_params.layout = CUBEB_LAYOUT_UNDEFINED; - output_params.prefs = CUBEB_STREAM_PREF_NONE; cubeb_stream_params input_params; - input_params.format = CUBEB_SAMPLE_FLOAT32NE; - input_params.rate = rate; - input_params.channels = 1; - input_params.layout = CUBEB_LAYOUT_UNDEFINED; - input_params.prefs = CUBEB_STREAM_PREF_NONE; + output_params.format = CUBEB_SAMPLE_FLOAT32NE; + output_params.rate = rate; + output_params.channels = 1; cubeb_stream * stm; rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1", - NULL, &input_params, - NULL, &output_params, + NULL, input_params, + NULL, output_params, latency_frames, data_cb, state_cb, NULL); @@ -104,7 +99,7 @@ extern "C" { for (i = 0; i < nframes; ++i) { for (c = 0; c < 2; ++c) { - out[i][c] = in[i]; + buf[i][c] = in[i]; } } return nframes; @@ -148,9 +143,31 @@ typedef enum { #endif } cubeb_sample_format; +#if defined(__ANDROID__) +/** + * This maps to the underlying stream types on supported platforms, e.g. + * Android. + */ +typedef enum { + CUBEB_STREAM_TYPE_VOICE_CALL = 0, + CUBEB_STREAM_TYPE_SYSTEM = 1, + CUBEB_STREAM_TYPE_RING = 2, + CUBEB_STREAM_TYPE_MUSIC = 3, + CUBEB_STREAM_TYPE_ALARM = 4, + CUBEB_STREAM_TYPE_NOTIFICATION = 5, + CUBEB_STREAM_TYPE_BLUETOOTH_SCO = 6, + CUBEB_STREAM_TYPE_SYSTEM_ENFORCED = 7, + CUBEB_STREAM_TYPE_DTMF = 8, + CUBEB_STREAM_TYPE_TTS = 9, + CUBEB_STREAM_TYPE_FM = 10, + + CUBEB_STREAM_TYPE_MAX +} cubeb_stream_type; +#endif + /** An opaque handle used to refer a particular input or output device * across calls. */ -typedef void const * cubeb_devid; +typedef void * cubeb_devid; /** Level (verbosity) of logging for a particular cubeb context. */ typedef enum { @@ -159,93 +176,15 @@ typedef enum { CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance implications. */ } cubeb_log_level; -typedef enum { - CHANNEL_UNKNOWN = 0, - CHANNEL_FRONT_LEFT = 1 << 0, - CHANNEL_FRONT_RIGHT = 1 << 1, - CHANNEL_FRONT_CENTER = 1 << 2, - CHANNEL_LOW_FREQUENCY = 1 << 3, - CHANNEL_BACK_LEFT = 1 << 4, - CHANNEL_BACK_RIGHT = 1 << 5, - CHANNEL_FRONT_LEFT_OF_CENTER = 1 << 6, - CHANNEL_FRONT_RIGHT_OF_CENTER = 1 << 7, - CHANNEL_BACK_CENTER = 1 << 8, - CHANNEL_SIDE_LEFT = 1 << 9, - CHANNEL_SIDE_RIGHT = 1 << 10, - CHANNEL_TOP_CENTER = 1 << 11, - CHANNEL_TOP_FRONT_LEFT = 1 << 12, - CHANNEL_TOP_FRONT_CENTER = 1 << 13, - CHANNEL_TOP_FRONT_RIGHT = 1 << 14, - CHANNEL_TOP_BACK_LEFT = 1 << 15, - CHANNEL_TOP_BACK_CENTER = 1 << 16, - CHANNEL_TOP_BACK_RIGHT = 1 << 17 -} cubeb_channel; - -typedef uint32_t cubeb_channel_layout; -// Some common layout definitions. -enum { - CUBEB_LAYOUT_UNDEFINED = 0, // Indicate the speaker's layout is undefined. - CUBEB_LAYOUT_MONO = CHANNEL_FRONT_CENTER, - CUBEB_LAYOUT_MONO_LFE = CUBEB_LAYOUT_MONO | CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT, - CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_3F = - CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_FRONT_CENTER, - CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_2F1 = - CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_BACK_CENTER, - CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | - CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER, - CUBEB_LAYOUT_3F1_LFE = CUBEB_LAYOUT_3F1 | CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_2F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | - CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT, - CUBEB_LAYOUT_2F2_LFE = CUBEB_LAYOUT_2F2 | CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_QUAD = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | - CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT, - CUBEB_LAYOUT_QUAD_LFE = CUBEB_LAYOUT_QUAD | CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_3F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | - CHANNEL_FRONT_CENTER | CHANNEL_SIDE_LEFT | - CHANNEL_SIDE_RIGHT, - CUBEB_LAYOUT_3F2_LFE = CUBEB_LAYOUT_3F2 | CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_3F2_BACK = CUBEB_LAYOUT_QUAD | CHANNEL_FRONT_CENTER, - CUBEB_LAYOUT_3F2_LFE_BACK = CUBEB_LAYOUT_3F2_BACK | CHANNEL_LOW_FREQUENCY, - CUBEB_LAYOUT_3F3R_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | - CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY | - CHANNEL_BACK_CENTER | CHANNEL_SIDE_LEFT | - CHANNEL_SIDE_RIGHT, - CUBEB_LAYOUT_3F4_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | - CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY | - CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT | - CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT, -}; - -/** Miscellaneous stream preferences. */ -typedef enum { - CUBEB_STREAM_PREF_NONE = 0x00, /**< No stream preferences are requested. */ - CUBEB_STREAM_PREF_LOOPBACK = 0x01, /**< Request a loopback stream. Should be - specified on the input params and an - output device to loopback from should - be passed in place of an input device. */ - CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02, /**< Disable switching - default device on OS - changes. */ - CUBEB_STREAM_PREF_VOICE = 0x04 /**< This stream is going to transport voice data. - Depending on the backend and platform, this can - change the audio input or output devices - selected, as well as the quality of the stream, - for example to accomodate bluetooth SCO modes on - bluetooth devices. */ -} cubeb_stream_prefs; - /** Stream format initialization parameters. */ typedef struct { - cubeb_sample_format format; /**< Requested sample format. One of - #cubeb_sample_format. */ - uint32_t rate; /**< Requested sample rate. Valid range is [1000, 192000]. */ - uint32_t channels; /**< Requested channel count. Valid range is [1, 8]. */ - cubeb_channel_layout layout; /**< Requested channel layout. This must be consistent with the provided channels. CUBEB_LAYOUT_UNDEFINED if unknown */ - cubeb_stream_prefs prefs; /**< Requested preferences. */ + cubeb_sample_format format; /**< Requested sample format. One of + #cubeb_sample_format. */ + unsigned int rate; /**< Requested sample rate. Valid range is [1000, 192000]. */ + unsigned int channels; /**< Requested channel count. Valid range is [1, 8]. */ +#if defined(__ANDROID__) + cubeb_stream_type stream_type; /**< Used to map Android audio stream types */ +#endif } cubeb_stream_params; /** Audio device description */ @@ -332,16 +271,15 @@ typedef enum { } cubeb_device_pref; /** This structure holds the characteristics - * of an input or output audio device. It is obtained using - * `cubeb_enumerate_devices`, which returns these structures via - * `cubeb_device_collection` and must be destroyed via - * `cubeb_device_collection_destroy`. */ + * of an input or output audio device. It can be obtained using + * `cubeb_enumerate_devices`, and must be destroyed using + * `cubeb_device_info_destroy`. */ typedef struct { cubeb_devid devid; /**< Device identifier handle. */ - char const * device_id; /**< Device identifier which might be presented in a UI. */ - char const * friendly_name; /**< Friendly device name which might be presented in a UI. */ - char const * group_id; /**< Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */ - char const * vendor_name; /**< Optional vendor name, may be NULL. */ + char * device_id; /**< Device identifier which might be presented in a UI. */ + char * friendly_name; /**< Friendly device name which might be presented in a UI. */ + char * group_id; /**< Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */ + char * vendor_name; /**< Optional vendor name, may be NULL. */ cubeb_device_type type; /**< Type of device (Input/Output). */ cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */ @@ -349,21 +287,19 @@ typedef struct { cubeb_device_fmt format; /**< Sample format supported. */ cubeb_device_fmt default_format; /**< The default sample format for this device. */ - uint32_t max_channels; /**< Channels. */ - uint32_t default_rate; /**< Default/Preferred sample rate. */ - uint32_t max_rate; /**< Maximum sample rate supported. */ - uint32_t min_rate; /**< Minimum sample rate supported. */ + unsigned int max_channels; /**< Channels. */ + unsigned int default_rate; /**< Default/Preferred sample rate. */ + unsigned int max_rate; /**< Maximum sample rate supported. */ + unsigned int min_rate; /**< Minimum sample rate supported. */ - uint32_t latency_lo; /**< Lowest possible latency in frames. */ - uint32_t latency_hi; /**< Higest possible latency in frames. */ + unsigned int latency_lo; /**< Lowest possible latency in frames. */ + unsigned int latency_hi; /**< Higest possible latency in frames. */ } cubeb_device_info; -/** Device collection. - * Returned by `cubeb_enumerate_devices` and destroyed by - * `cubeb_device_collection_destroy`. */ +/** Device collection. */ typedef struct { - cubeb_device_info * device; /**< Array of pointers to device info. */ - size_t count; /**< Device count in collection. */ + uint32_t count; /**< Device count in collection. */ + cubeb_device_info * device[1]; /**< Array of pointers to device info. */ } cubeb_device_collection; /** User supplied data callback. @@ -380,17 +316,13 @@ typedef struct { @param output_buffer A pointer to a buffer to be filled with audio samples, or nullptr if this is an input-only stream. @param nframes The number of frames of the two buffer. - @retval If the stream has output, this is the number of frames written to - the output buffer. In this case, if this number is less than - nframes then the stream will start to drain. If the stream is - input only, then returning nframes indicates data has been read. - In this case, a value less than nframes will result in the stream - being stopped. + @retval Number of frames written to the output buffer. If this number is + less than nframes, then the stream will start to drain. @retval CUBEB_ERROR on error, in which case the data callback will stop and the stream will enter a shutdown state. */ typedef long (* cubeb_data_callback)(cubeb_stream * stream, void * user_ptr, - void const * input_buffer, + const void * input_buffer, void * output_buffer, long nframes); @@ -410,33 +342,23 @@ typedef void (* cubeb_device_changed_callback)(void * user_ptr); /** * User supplied callback called when the underlying device collection changed. * @param context A pointer to the cubeb context. - * @param user_ptr The pointer passed to cubeb_register_device_collection_changed. */ + * @param user_ptr The pointer passed to cubeb_stream_init. */ typedef void (* cubeb_device_collection_changed_callback)(cubeb * context, void * user_ptr); /** User supplied callback called when a message needs logging. */ -typedef void (* cubeb_log_callback)(char const * fmt, ...); +typedef void (* cubeb_log_callback)(const char * fmt, ...); /** Initialize an application context. This will perform any library or application scoped initialization. - - Note: On Windows platforms, COM must be initialized in MTA mode on - any thread that will call the cubeb API. - @param context A out param where an opaque pointer to the application context will be returned. @param context_name A name for the context. Depending on the platform this can appear in different locations. - @param backend_name The name of the cubeb backend user desires to select. - Accepted values self-documented in cubeb.c: init_oneshot - If NULL, a default ordering is used for backend choice. - A valid choice overrides all other possible backends, - so long as the backend was included at compile time. @retval CUBEB_OK in case of success. @retval CUBEB_ERROR in case of error, for example because the host has no audio hardware. */ -CUBEB_EXPORT int cubeb_init(cubeb ** context, char const * context_name, - char const * backend_name); +CUBEB_EXPORT int cubeb_init(cubeb ** context, char const * context_name); /** Get a read-only string identifying this context's current backend. @param context A pointer to the cubeb context. @@ -454,7 +376,7 @@ CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_cha /** Get the minimal latency value, in frames, that is guaranteed to work when creating a stream for the specified sample rate. This is platform, - hardware and backend dependent. + hardware and backend dependant. @param context A pointer to the cubeb context. @param params On some backends, the minimum achievable latency depends on the characteristics of the stream. @@ -464,11 +386,11 @@ CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_cha @retval CUBEB_ERROR_INVALID_PARAMETER @retval CUBEB_ERROR_NOT_SUPPORTED */ CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context, - cubeb_stream_params * params, + cubeb_stream_params params, uint32_t * latency_frames); /** Get the preferred sample rate for this backend: this is hardware and - platform dependent, and can avoid resampling, and/or trigger fastpaths. + platform dependant, and can avoid resampling, and/or trigger fastpaths. @param context A pointer to the cubeb context. @param rate The samplerate (in Hz) the current configuration prefers. @retval CUBEB_OK @@ -512,7 +434,7 @@ CUBEB_EXPORT int cubeb_stream_init(cubeb * context, cubeb_stream_params * input_stream_params, cubeb_devid output_device, cubeb_stream_params * output_stream_params, - uint32_t latency_frames, + unsigned int latency_frames, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr); @@ -534,14 +456,6 @@ CUBEB_EXPORT int cubeb_stream_start(cubeb_stream * stream); @retval CUBEB_ERROR */ CUBEB_EXPORT int cubeb_stream_stop(cubeb_stream * stream); -/** Reset stream to the default device. - @param stream - @retval CUBEB_OK - @retval CUBEB_ERROR_INVALID_PARAMETER - @retval CUBEB_ERROR_NOT_SUPPORTED - @retval CUBEB_ERROR */ -CUBEB_EXPORT int cubeb_stream_reset_default_device(cubeb_stream * stream); - /** Get the current stream playback position. @param stream @param position Playback position in frames. @@ -568,6 +482,20 @@ CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * late @retval CUBEB_ERROR_NOT_SUPPORTED */ CUBEB_EXPORT int cubeb_stream_set_volume(cubeb_stream * stream, float volume); +/** If the stream is stereo, set the left/right panning. If the stream is mono, + this has no effect. + @param stream the stream for which to change the panning + @param panning a number from -1.0 to 1.0. -1.0 means that the stream is + fully mixed in the left channel, 1.0 means the stream is fully + mixed in the right channel. 0.0 is equal power in the right and + left channel (default). + @retval CUBEB_OK + @retval CUBEB_ERROR_INVALID_PARAMETER if stream is null or if panning is + outside the [-1.0, 1.0] range. + @retval CUBEB_ERROR_NOT_SUPPORTED + @retval CUBEB_ERROR stream is not mono nor stereo */ +CUBEB_EXPORT int cubeb_stream_set_panning(cubeb_stream * stream, float panning); + /** Get the current output device for this stream. @param stm the stream for which to query the current output device @param device a pointer in which the current output device will be stored. @@ -598,11 +526,6 @@ CUBEB_EXPORT int cubeb_stream_device_destroy(cubeb_stream * stream, CUBEB_EXPORT int cubeb_stream_register_device_changed_callback(cubeb_stream * stream, cubeb_device_changed_callback device_changed_callback); -/** Return the user data pointer registered with the stream with cubeb_stream_init. - @param stream the stream for which to retrieve user data pointer. - @retval user data pointer */ -CUBEB_EXPORT void * cubeb_stream_user_ptr(cubeb_stream * stream); - /** Returns enumerated devices. @param context @param devtype device type to include @@ -612,26 +535,26 @@ CUBEB_EXPORT void * cubeb_stream_user_ptr(cubeb_stream * stream); @retval CUBEB_ERROR_NOT_SUPPORTED */ CUBEB_EXPORT int cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype, - cubeb_device_collection * collection); + cubeb_device_collection ** collection); /** Destroy a cubeb_device_collection, and its `cubeb_device_info`. - @param context @param collection collection to destroy @retval CUBEB_OK @retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer */ -CUBEB_EXPORT int cubeb_device_collection_destroy(cubeb * context, - cubeb_device_collection * collection); +CUBEB_EXPORT int cubeb_device_collection_destroy(cubeb_device_collection * collection); + +/** Destroy a cubeb_device_info structure. + @param info pointer to device info structure + @retval CUBEB_OK + @retval CUBEB_ERROR_INVALID_PARAMETER if info is an invalid pointer */ +CUBEB_EXPORT int cubeb_device_info_destroy(cubeb_device_info * info); /** Registers a callback which is called when the system detects a new device or a device is removed. @param context - @param devtype device type to include. Different callbacks and user pointers - can be registered for each devtype. The hybrid devtype - `CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT` is also valid - and will register the provided callback and user pointer in both sides. + @param devtype device type to include @param callback a function called whenever the system device list changes. - Passing NULL allow to unregister a function. You have to unregister - first before you register a new callback. + Passing NULL allow to unregister a function @param user_ptr pointer to user specified data which will be present in subsequent callbacks. @retval CUBEB_ERROR_NOT_SUPPORTED */ diff --git a/media/libcubeb/osx-linearize-operations.patch b/media/libcubeb/osx-linearize-operations.patch new file mode 100644 index 000000000..9f4f31bca --- /dev/null +++ b/media/libcubeb/osx-linearize-operations.patch @@ -0,0 +1,968 @@ +From: Paul Adenot +Subject: Linearize operations on AudioUnits to sidestep a deadlock. + +--- + +diff --git a/src/cubeb_audiounit.cpp b/src/cubeb_audiounit.cpp +--- a/src/cubeb_audiounit.cpp ++++ b/src/cubeb_audiounit.cpp +@@ -53,40 +53,45 @@ typedef UInt32 AudioFormatFlags; + + #define AU_OUT_BUS 0 + #define AU_IN_BUS 1 + + #define PRINT_ERROR_CODE(str, r) do { \ + LOG("System call failed: %s (rv: %d)", str, r); \ + } while(0) + ++const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb"; ++ + /* Testing empirically, some headsets report a minimal latency that is very + * low, but this does not work in practice. Lie and say the minimum is 256 + * frames. */ + const uint32_t SAFE_MIN_LATENCY_FRAMES = 256; + const uint32_t SAFE_MAX_LATENCY_FRAMES = 512; + + void audiounit_stream_stop_internal(cubeb_stream * stm); + void audiounit_stream_start_internal(cubeb_stream * stm); +-static void close_audiounit_stream(cubeb_stream * stm); +-static int setup_audiounit_stream(cubeb_stream * stm); ++static void audiounit_close_stream(cubeb_stream *stm); ++static int audiounit_setup_stream(cubeb_stream *stm); + + extern cubeb_ops const audiounit_ops; + + struct cubeb { + cubeb_ops const * ops; + owned_critical_section mutex; + std::atomic active_streams; ++ uint32_t global_latency_frames = 0; + int limit_streams; + cubeb_device_collection_changed_callback collection_changed_callback; + void * collection_changed_user_ptr; + /* Differentiate input from output devices. */ + cubeb_device_type collection_changed_devtype; + uint32_t devtype_device_count; + AudioObjectID * devtype_device_array; ++ // The queue is asynchronously deallocated once all references to it are released ++ dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL); + }; + + class auto_array_wrapper + { + public: + explicit auto_array_wrapper(auto_array * ar) + : float_ar(ar) + , short_ar(nullptr) +@@ -205,16 +210,17 @@ struct cubeb_stream { + cubeb_resampler * resampler; + /* This is the number of output callback we got in a row. This is usually one, + * but can be two when the input and output rate are different, and more when + * a device has been plugged or unplugged, as there can be some time before + * the device is ready. */ + std::atomic output_callback_in_a_row; + /* This is true if a device change callback is currently running. */ + std::atomic switching_device; ++ std::atomic buffer_size_change_state{ false }; + }; + + bool has_input(cubeb_stream * stm) + { + return stm->input_stream_params.rate != 0; + } + + bool has_output(cubeb_stream * stm) +@@ -256,16 +262,24 @@ audiotimestamp_to_latency(AudioTimeStamp + + uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime); + uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); + + return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL; + } + + static void ++audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames) ++{ ++ stm->mutex.assert_current_thread_owns(); ++ assert(stm->context->active_streams == 1); ++ stm->context->global_latency_frames = latency_frames; ++} ++ ++static void + audiounit_make_silent(AudioBuffer * ioData) + { + assert(ioData); + assert(ioData->mData); + memset(ioData->mData, 0, ioData->mDataByteSize); + } + + static OSStatus +@@ -576,29 +590,54 @@ audiounit_get_input_device_id(AudioDevic + device_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + return CUBEB_OK; + } + ++static int ++audiounit_reinit_stream(cubeb_stream * stm, bool is_started) ++{ ++ if (is_started) { ++ audiounit_stream_stop_internal(stm); ++ } ++ ++ { ++ auto_lock lock(stm->mutex); ++ ++ audiounit_close_stream(stm); ++ ++ if (audiounit_setup_stream(stm) != CUBEB_OK) { ++ LOG("(%p) Stream reinit failed.", stm); ++ return CUBEB_ERROR; ++ } ++ ++ // Reset input frames to force new stream pre-buffer ++ // silence if needed, check `is_extra_input_needed()` ++ stm->frames_read = 0; ++ ++ // If the stream was running, start it again. ++ if (is_started) { ++ audiounit_stream_start_internal(stm); ++ } ++ } ++ return CUBEB_OK; ++} ++ + static OSStatus + audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count, + const AudioObjectPropertyAddress * addresses, + void * user) + { + cubeb_stream * stm = (cubeb_stream*) user; +- int rv; +- bool was_running = false; +- + stm->switching_device = true; +- + // Note if the stream was running or not +- was_running = !stm->shutdown; ++ bool was_running = !stm->shutdown; + + LOG("(%p) Audio device changed, %d events.", stm, address_count); + for (UInt32 i = 0; i < address_count; i++) { + switch(addresses[i].mSelector) { + case kAudioHardwarePropertyDefaultOutputDevice: { + LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i); + // Allow restart to choose the new default + stm->output_device = nullptr; +@@ -639,38 +678,25 @@ audiounit_property_listener_callback(Aud + if (stm->device_changed_callback) { + stm->device_changed_callback(stm->user_ptr); + } + break; + } + } + } + +- // This means the callback won't be called again. +- audiounit_stream_stop_internal(stm); +- +- { +- auto_lock lock(stm->mutex); +- close_audiounit_stream(stm); +- rv = setup_audiounit_stream(stm); +- if (rv != CUBEB_OK) { +- LOG("(%p) Could not reopen a stream after switching.", stm); ++ // Use a new thread, through the queue, to avoid deadlock when calling ++ // Get/SetProperties method from inside notify callback ++ dispatch_async(stm->context->serial_queue, ^() { ++ if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); +- return noErr; ++ LOG("(%p) Could not reopen the stream after switching.", stm); + } +- +- stm->frames_read = 0; +- +- // If the stream was running, start it again. +- if (was_running) { +- audiounit_stream_start_internal(stm); +- } +- } +- +- stm->switching_device = false; ++ stm->switching_device = false; ++ }); + + return noErr; + } + + OSStatus + audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector, + AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener) + { +@@ -1155,18 +1181,17 @@ audiounit_init_input_linear_buffer(cubeb + + static void + audiounit_destroy_input_linear_buffer(cubeb_stream * stream) + { + delete stream->input_linear_buffer; + } + + static uint32_t +-audiounit_clamp_latency(cubeb_stream * stm, +- uint32_t latency_frames) ++audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) + { + // For the 1st stream set anything within safe min-max + assert(stm->context->active_streams > 0); + if (stm->context->active_streams == 1) { + return std::max(std::min(latency_frames, SAFE_MAX_LATENCY_FRAMES), + SAFE_MIN_LATENCY_FRAMES); + } + +@@ -1219,26 +1244,374 @@ audiounit_clamp_latency(cubeb_stream * s + } else { + upper_latency_limit = SAFE_MAX_LATENCY_FRAMES; + } + + return std::max(std::min(latency_frames, upper_latency_limit), + SAFE_MIN_LATENCY_FRAMES); + } + ++/* ++ * Change buffer size is prone to deadlock thus we change it ++ * following the steps: ++ * - register a listener for the buffer size property ++ * - change the property ++ * - wait until the listener is executed ++ * - property has changed, remove the listener ++ * */ ++static void ++buffer_size_changed_callback(void * inClientData, ++ AudioUnit inUnit, ++ AudioUnitPropertyID inPropertyID, ++ AudioUnitScope inScope, ++ AudioUnitElement inElement) ++{ ++ cubeb_stream * stm = (cubeb_stream *)inClientData; ++ ++ AudioUnit au = inUnit; ++ AudioUnitScope au_scope = kAudioUnitScope_Input; ++ AudioUnitElement au_element = inElement; ++ const char * au_type = "output"; ++ ++ if (au == stm->input_unit) { ++ au_scope = kAudioUnitScope_Output; ++ au_type = "input"; ++ } ++ ++ switch (inPropertyID) { ++ ++ case kAudioDevicePropertyBufferFrameSize: { ++ if (inScope != au_scope) { ++ break; ++ } ++ UInt32 new_buffer_size; ++ UInt32 outSize = sizeof(UInt32); ++ OSStatus r = AudioUnitGetProperty(au, ++ kAudioDevicePropertyBufferFrameSize, ++ au_scope, ++ au_element, ++ &new_buffer_size, ++ &outSize); ++ if (r != noErr) { ++ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current buffer size", stm); ++ } else { ++ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size = %d for scope %d", stm, ++ au_type, new_buffer_size, inScope); ++ } ++ stm->buffer_size_change_state = true; ++ break; ++ } ++ } ++} ++ ++enum set_buffer_size_side { ++ INPUT, ++ OUTPUT, ++}; ++ + static int +-setup_audiounit_stream(cubeb_stream * stm) ++audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side) ++{ ++ AudioUnit au = stm->output_unit; ++ AudioUnitScope au_scope = kAudioUnitScope_Input; ++ AudioUnitElement au_element = AU_OUT_BUS; ++ const char * au_type = "output"; ++ ++ if (set_side == INPUT) { ++ au = stm->input_unit; ++ au_scope = kAudioUnitScope_Output; ++ au_element = AU_IN_BUS; ++ au_type = "input"; ++ } ++ ++ uint32_t buffer_frames = 0; ++ UInt32 size = sizeof(buffer_frames); ++ int r = AudioUnitGetProperty(au, ++ kAudioDevicePropertyBufferFrameSize, ++ au_scope, ++ au_element, ++ &buffer_frames, ++ &size); ++ if (r != noErr) { ++ if (set_side == INPUT) { ++ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r); ++ } else { ++ PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r); ++ } ++ return CUBEB_ERROR; ++ } ++ ++ if (new_size_frames == buffer_frames) { ++ LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames); ++ return CUBEB_OK; ++ } ++ ++ r = AudioUnitAddPropertyListener(au, ++ kAudioDevicePropertyBufferFrameSize, ++ buffer_size_changed_callback, ++ stm); ++ if (r != noErr) { ++ if (set_side == INPUT) { ++ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); ++ } else { ++ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); ++ } ++ return CUBEB_ERROR; ++ } ++ ++ stm->buffer_size_change_state = false; ++ ++ r = AudioUnitSetProperty(au, ++ kAudioDevicePropertyBufferFrameSize, ++ au_scope, ++ au_element, ++ &new_size_frames, ++ sizeof(new_size_frames)); ++ if (r != noErr) { ++ if (set_side == INPUT) { ++ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r); ++ } else { ++ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r); ++ } ++ ++ r = AudioUnitRemovePropertyListenerWithUserData(au, ++ kAudioDevicePropertyBufferFrameSize, ++ buffer_size_changed_callback, ++ stm); ++ if (r != noErr) { ++ if (set_side == INPUT) { ++ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); ++ } else { ++ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); ++ } ++ } ++ ++ return CUBEB_ERROR; ++ } ++ ++ int count = 0; ++ while (!stm->buffer_size_change_state && count++ < 30) { ++ struct timespec req, rem; ++ req.tv_sec = 0; ++ req.tv_nsec = 100000000L; // 0.1 sec ++ if (nanosleep(&req , &rem) < 0 ) { ++ LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time %ld nano secs \n", stm, rem.tv_nsec); ++ } ++ LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count); ++ } ++ ++ r = AudioUnitRemovePropertyListenerWithUserData(au, ++ kAudioDevicePropertyBufferFrameSize, ++ buffer_size_changed_callback, ++ stm); ++ if (r != noErr) { ++ return CUBEB_ERROR; ++ if (set_side == INPUT) { ++ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); ++ } else { ++ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); ++ } ++ } ++ ++ if (!stm->buffer_size_change_state && count >= 30) { ++ LOG("(%p) Error, did not get buffer size change callback ...", stm); ++ return CUBEB_ERROR; ++ } ++ ++ LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames); ++ return CUBEB_OK; ++} ++ ++static int ++audiounit_configure_input(cubeb_stream * stm) ++{ ++ int r = 0; ++ UInt32 size; ++ AURenderCallbackStruct aurcbs_in; ++ ++ LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.", ++ stm, stm->input_stream_params.rate, stm->input_stream_params.channels, ++ stm->input_stream_params.format, stm->latency_frames); ++ ++ /* Get input device sample rate. */ ++ AudioStreamBasicDescription input_hw_desc; ++ size = sizeof(AudioStreamBasicDescription); ++ r = AudioUnitGetProperty(stm->input_unit, ++ kAudioUnitProperty_StreamFormat, ++ kAudioUnitScope_Input, ++ AU_IN_BUS, ++ &input_hw_desc, ++ &size); ++ if (r != noErr) { ++ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r); ++ return CUBEB_ERROR; ++ } ++ stm->input_hw_rate = input_hw_desc.mSampleRate; ++ LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate); ++ ++ /* Set format description according to the input params. */ ++ r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params); ++ if (r != CUBEB_OK) { ++ LOG("(%p) Setting format description for input failed.", stm); ++ return r; ++ } ++ ++ // Use latency to set buffer size ++ stm->input_buffer_frames = stm->latency_frames; ++ r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT); ++ if (r != CUBEB_OK) { ++ LOG("(%p) Error in change input buffer size.", stm); ++ return CUBEB_ERROR; ++ } ++ ++ AudioStreamBasicDescription src_desc = stm->input_desc; ++ /* Input AudioUnit must be configured with device's sample rate. ++ we will resample inside input callback. */ ++ src_desc.mSampleRate = stm->input_hw_rate; ++ ++ r = AudioUnitSetProperty(stm->input_unit, ++ kAudioUnitProperty_StreamFormat, ++ kAudioUnitScope_Output, ++ AU_IN_BUS, ++ &src_desc, ++ sizeof(AudioStreamBasicDescription)); ++ if (r != noErr) { ++ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r); ++ return CUBEB_ERROR; ++ } ++ ++ /* Frames per buffer in the input callback. */ ++ r = AudioUnitSetProperty(stm->input_unit, ++ kAudioUnitProperty_MaximumFramesPerSlice, ++ kAudioUnitScope_Global, ++ AU_IN_BUS, ++ &stm->input_buffer_frames, ++ sizeof(UInt32)); ++ if (r != noErr) { ++ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r); ++ return CUBEB_ERROR; ++ } ++ ++ // Input only capacity ++ unsigned int array_capacity = 1; ++ if (has_output(stm)) { ++ // Full-duplex increase capacity ++ array_capacity = 8; ++ } ++ if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) { ++ return CUBEB_ERROR; ++ } ++ ++ assert(stm->input_unit != NULL); ++ aurcbs_in.inputProc = audiounit_input_callback; ++ aurcbs_in.inputProcRefCon = stm; ++ ++ r = AudioUnitSetProperty(stm->input_unit, ++ kAudioOutputUnitProperty_SetInputCallback, ++ kAudioUnitScope_Global, ++ AU_OUT_BUS, ++ &aurcbs_in, ++ sizeof(aurcbs_in)); ++ if (r != noErr) { ++ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r); ++ return CUBEB_ERROR; ++ } ++ LOG("(%p) Input audiounit init successfully.", stm); ++ ++ return CUBEB_OK; ++} ++ ++static int ++audiounit_configure_output(cubeb_stream * stm) ++{ ++ int r; ++ AURenderCallbackStruct aurcbs_out; ++ UInt32 size; ++ ++ ++ LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.", ++ stm, stm->output_stream_params.rate, stm->output_stream_params.channels, ++ stm->output_stream_params.format, stm->latency_frames); ++ ++ r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params); ++ if (r != CUBEB_OK) { ++ LOG("(%p) Could not initialize the audio stream description.", stm); ++ return r; ++ } ++ ++ /* Get output device sample rate. */ ++ AudioStreamBasicDescription output_hw_desc; ++ size = sizeof(AudioStreamBasicDescription); ++ memset(&output_hw_desc, 0, size); ++ r = AudioUnitGetProperty(stm->output_unit, ++ kAudioUnitProperty_StreamFormat, ++ kAudioUnitScope_Output, ++ AU_OUT_BUS, ++ &output_hw_desc, ++ &size); ++ if (r != noErr) { ++ PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r); ++ return CUBEB_ERROR; ++ } ++ stm->output_hw_rate = output_hw_desc.mSampleRate; ++ LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate); ++ ++ r = AudioUnitSetProperty(stm->output_unit, ++ kAudioUnitProperty_StreamFormat, ++ kAudioUnitScope_Input, ++ AU_OUT_BUS, ++ &stm->output_desc, ++ sizeof(AudioStreamBasicDescription)); ++ if (r != noErr) { ++ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r); ++ return CUBEB_ERROR; ++ } ++ ++ r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT); ++ if (r != CUBEB_OK) { ++ LOG("(%p) Error in change output buffer size.", stm); ++ return CUBEB_ERROR; ++ } ++ ++ /* Frames per buffer in the input callback. */ ++ r = AudioUnitSetProperty(stm->output_unit, ++ kAudioUnitProperty_MaximumFramesPerSlice, ++ kAudioUnitScope_Global, ++ AU_OUT_BUS, ++ &stm->latency_frames, ++ sizeof(UInt32)); ++ if (r != noErr) { ++ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r); ++ return CUBEB_ERROR; ++ } ++ ++ assert(stm->output_unit != NULL); ++ aurcbs_out.inputProc = audiounit_output_callback; ++ aurcbs_out.inputProcRefCon = stm; ++ r = AudioUnitSetProperty(stm->output_unit, ++ kAudioUnitProperty_SetRenderCallback, ++ kAudioUnitScope_Global, ++ AU_OUT_BUS, ++ &aurcbs_out, ++ sizeof(aurcbs_out)); ++ if (r != noErr) { ++ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r); ++ return CUBEB_ERROR; ++ } ++ ++ LOG("(%p) Output audiounit init successfully.", stm); ++ return CUBEB_OK; ++} ++ ++static int ++audiounit_setup_stream(cubeb_stream * stm) + { + stm->mutex.assert_current_thread_owns(); + +- int r; +- AURenderCallbackStruct aurcbs_in; +- AURenderCallbackStruct aurcbs_out; +- UInt32 size; +- ++ int r = 0; + if (has_input(stm)) { + r = audiounit_create_unit(&stm->input_unit, true, + &stm->input_stream_params, + stm->input_device); + if (r != CUBEB_OK) { + LOG("(%p) AudioUnit creation for input failed.", stm); + return r; + } +@@ -1249,180 +1622,46 @@ setup_audiounit_stream(cubeb_stream * st + &stm->output_stream_params, + stm->output_device); + if (r != CUBEB_OK) { + LOG("(%p) AudioUnit creation for output failed.", stm); + return r; + } + } + ++ /* Latency cannot change if another stream is operating in parallel. In this case ++ * latecy is set to the other stream value. */ ++ if (stm->context->active_streams > 1) { ++ LOG("(%p) More than one active stream, use global latency.", stm); ++ stm->latency_frames = stm->context->global_latency_frames; ++ } else { ++ /* Silently clamp the latency down to the platform default, because we ++ * synthetize the clock from the callbacks, and we want the clock to update ++ * often. */ ++ stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames); ++ assert(stm->latency_frames); // Ungly error check ++ audiounit_set_global_latency(stm, stm->latency_frames); ++ } ++ + /* Setup Input Stream! */ + if (has_input(stm)) { +- LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.", +- stm, stm->input_stream_params.rate, stm->input_stream_params.channels, +- stm->input_stream_params.format, stm->latency_frames); +- /* Get input device sample rate. */ +- AudioStreamBasicDescription input_hw_desc; +- size = sizeof(AudioStreamBasicDescription); +- r = AudioUnitGetProperty(stm->input_unit, +- kAudioUnitProperty_StreamFormat, +- kAudioUnitScope_Input, +- AU_IN_BUS, +- &input_hw_desc, +- &size); +- if (r != noErr) { +- PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r); +- return CUBEB_ERROR; +- } +- stm->input_hw_rate = input_hw_desc.mSampleRate; +- LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate); +- +- /* Set format description according to the input params. */ +- r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params); ++ r = audiounit_configure_input(stm); + if (r != CUBEB_OK) { +- LOG("(%p) Setting format description for input failed.", stm); ++ LOG("(%p) Configure audiounit input failed.", stm); + return r; + } +- +- // Use latency to set buffer size +- stm->input_buffer_frames = stm->latency_frames; +- LOG("(%p) Input buffer frame count %u.", stm, unsigned(stm->input_buffer_frames)); +- r = AudioUnitSetProperty(stm->input_unit, +- kAudioDevicePropertyBufferFrameSize, +- kAudioUnitScope_Output, +- AU_IN_BUS, +- &stm->input_buffer_frames, +- sizeof(UInt32)); +- if (r != noErr) { +- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r); +- return CUBEB_ERROR; +- } +- +- AudioStreamBasicDescription src_desc = stm->input_desc; +- /* Input AudioUnit must be configured with device's sample rate. +- we will resample inside input callback. */ +- src_desc.mSampleRate = stm->input_hw_rate; +- +- r = AudioUnitSetProperty(stm->input_unit, +- kAudioUnitProperty_StreamFormat, +- kAudioUnitScope_Output, +- AU_IN_BUS, +- &src_desc, +- sizeof(AudioStreamBasicDescription)); +- if (r != noErr) { +- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r); +- return CUBEB_ERROR; +- } +- +- /* Frames per buffer in the input callback. */ +- r = AudioUnitSetProperty(stm->input_unit, +- kAudioUnitProperty_MaximumFramesPerSlice, +- kAudioUnitScope_Output, +- AU_IN_BUS, +- &stm->input_buffer_frames, +- sizeof(UInt32)); +- if (r != noErr) { +- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r); +- return CUBEB_ERROR; +- } +- +- // Input only capacity +- unsigned int array_capacity = 1; +- if (has_output(stm)) { +- // Full-duplex increase capacity +- array_capacity = 8; +- } +- if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) { +- return CUBEB_ERROR; +- } +- +- assert(stm->input_unit != NULL); +- aurcbs_in.inputProc = audiounit_input_callback; +- aurcbs_in.inputProcRefCon = stm; +- +- r = AudioUnitSetProperty(stm->input_unit, +- kAudioOutputUnitProperty_SetInputCallback, +- kAudioUnitScope_Global, +- AU_OUT_BUS, +- &aurcbs_in, +- sizeof(aurcbs_in)); +- if (r != noErr) { +- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r); +- return CUBEB_ERROR; +- } +- LOG("(%p) Input audiounit init successfully.", stm); + } + + /* Setup Output Stream! */ + if (has_output(stm)) { +- LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.", +- stm, stm->output_stream_params.rate, stm->output_stream_params.channels, +- stm->output_stream_params.format, stm->latency_frames); +- r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params); ++ r = audiounit_configure_output(stm); + if (r != CUBEB_OK) { +- LOG("(%p) Could not initialize the audio stream description.", stm); ++ LOG("(%p) Configure audiounit output failed.", stm); + return r; + } +- +- /* Get output device sample rate. */ +- AudioStreamBasicDescription output_hw_desc; +- size = sizeof(AudioStreamBasicDescription); +- memset(&output_hw_desc, 0, size); +- r = AudioUnitGetProperty(stm->output_unit, +- kAudioUnitProperty_StreamFormat, +- kAudioUnitScope_Output, +- AU_OUT_BUS, +- &output_hw_desc, +- &size); +- if (r != noErr) { +- PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r); +- return CUBEB_ERROR; +- } +- stm->output_hw_rate = output_hw_desc.mSampleRate; +- LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate); +- +- r = AudioUnitSetProperty(stm->output_unit, +- kAudioUnitProperty_StreamFormat, +- kAudioUnitScope_Input, +- AU_OUT_BUS, +- &stm->output_desc, +- sizeof(AudioStreamBasicDescription)); +- if (r != noErr) { +- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r); +- return CUBEB_ERROR; +- } +- +- // Use latency to calculate buffer size +- uint32_t output_buffer_frames = stm->latency_frames; +- LOG("(%p) Output buffer frame count %u.", stm, output_buffer_frames); +- r = AudioUnitSetProperty(stm->output_unit, +- kAudioDevicePropertyBufferFrameSize, +- kAudioUnitScope_Input, +- AU_OUT_BUS, +- &output_buffer_frames, +- sizeof(output_buffer_frames)); +- if (r != noErr) { +- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r); +- return CUBEB_ERROR; +- } +- +- assert(stm->output_unit != NULL); +- aurcbs_out.inputProc = audiounit_output_callback; +- aurcbs_out.inputProcRefCon = stm; +- r = AudioUnitSetProperty(stm->output_unit, +- kAudioUnitProperty_SetRenderCallback, +- kAudioUnitScope_Global, +- AU_OUT_BUS, +- &aurcbs_out, +- sizeof(aurcbs_out)); +- if (r != noErr) { +- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r); +- return CUBEB_ERROR; +- } +- LOG("(%p) Output audiounit init successfully.", stm); + } + + // Setting the latency doesn't work well for USB headsets (eg. plantronics). + // Keep the default latency for now. + #if 0 + buffer_size = latency; + + /* Get the range of latency this particular device can work with, and clamp +@@ -1535,63 +1774,60 @@ audiounit_stream_init(cubeb * context, + void * user_ptr) + { + cubeb_stream * stm; + int r; + + assert(context); + *stream = NULL; + ++ assert(latency_frames > 0); + if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) { + LOG("Reached the stream limit of %d", CUBEB_STREAM_MAX); + return CUBEB_ERROR; + } +- context->active_streams += 1; + + stm = (cubeb_stream *) calloc(1, sizeof(cubeb_stream)); + assert(stm); + // Placement new to call the ctors of cubeb_stream members. + new (stm) cubeb_stream(); + + /* These could be different in the future if we have both + * full-duplex stream and different devices for input vs output. */ + stm->context = context; + stm->data_callback = data_callback; + stm->state_callback = state_callback; + stm->user_ptr = user_ptr; ++ stm->latency_frames = latency_frames; + stm->device_changed_callback = NULL; + if (input_stream_params) { + stm->input_stream_params = *input_stream_params; + stm->input_device = input_device; + stm->is_default_input = input_device == nullptr || + (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) == + reinterpret_cast(input_device)); + } + if (output_stream_params) { + stm->output_stream_params = *output_stream_params; + stm->output_device = output_device; + } + + /* Init data members where necessary */ + stm->hw_latency_frames = UINT64_MAX; + +- /* Silently clamp the latency down to the platform default, because we +- * synthetize the clock from the callbacks, and we want the clock to update +- * often. */ +- stm->latency_frames = audiounit_clamp_latency(stm, latency_frames); +- assert(latency_frames > 0); +- + stm->switching_device = false; + ++ auto_lock context_lock(context->mutex); + { + // It's not critical to lock here, because no other thread has been started + // yet, but it allows to assert that the lock has been taken in +- // `setup_audiounit_stream`. ++ // `audiounit_setup_stream`. ++ context->active_streams += 1; + auto_lock lock(stm->mutex); +- r = setup_audiounit_stream(stm); ++ r = audiounit_setup_stream(stm); + } + + if (r != CUBEB_OK) { + LOG("(%p) Could not setup the audiounit stream.", stm); + audiounit_stream_destroy(stm); + return r; + } + +@@ -1602,17 +1838,17 @@ audiounit_stream_init(cubeb * context, + } + + *stream = stm; + LOG("Cubeb stream (%p) init successful.", stm); + return CUBEB_OK; + } + + static void +-close_audiounit_stream(cubeb_stream * stm) ++audiounit_close_stream(cubeb_stream *stm) + { + stm->mutex.assert_current_thread_owns(); + if (stm->input_unit) { + AudioUnitUninitialize(stm->input_unit); + AudioComponentInstanceDispose(stm->input_unit); + } + + audiounit_destroy_input_linear_buffer(stm); +@@ -1625,33 +1861,36 @@ close_audiounit_stream(cubeb_stream * st + cubeb_resampler_destroy(stm->resampler); + } + + static void + audiounit_stream_destroy(cubeb_stream * stm) + { + stm->shutdown = true; + ++ auto_lock context_locl(stm->context->mutex); + audiounit_stream_stop_internal(stm); + + { + auto_lock lock(stm->mutex); +- close_audiounit_stream(stm); ++ audiounit_close_stream(stm); + } + + #if !TARGET_OS_IPHONE + int r = audiounit_uninstall_device_changed_callback(stm); + if (r != CUBEB_OK) { + LOG("(%p) Could not uninstall the device changed callback", stm); + } + #endif + + assert(stm->context->active_streams >= 1); + stm->context->active_streams -= 1; + ++ LOG("Cubeb stream (%p) destroyed successful.", stm); ++ + stm->~cubeb_stream(); + free(stm); + } + + void + audiounit_stream_start_internal(cubeb_stream * stm) + { + OSStatus r; +@@ -1666,16 +1905,17 @@ audiounit_stream_start_internal(cubeb_st + } + + static int + audiounit_stream_start(cubeb_stream * stm) + { + stm->shutdown = false; + stm->draining = false; + ++ auto_lock context_locl(stm->context->mutex); + audiounit_stream_start_internal(stm); + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); + + LOG("Cubeb stream (%p) started successfully.", stm); + return CUBEB_OK; + } + +@@ -1693,16 +1933,17 @@ audiounit_stream_stop_internal(cubeb_str + } + } + + static int + audiounit_stream_stop(cubeb_stream * stm) + { + stm->shutdown = true; + ++ auto_lock context_locl(stm->context->mutex); + audiounit_stream_stop_internal(stm); + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + + LOG("Cubeb stream (%p) stopped successfully.", stm); + return CUBEB_OK; + } + diff --git a/media/libcubeb/prevent-double-free.patch b/media/libcubeb/prevent-double-free.patch new file mode 100644 index 000000000..aa5356d7f --- /dev/null +++ b/media/libcubeb/prevent-double-free.patch @@ -0,0 +1,46 @@ +From f82f15635e09aac4f07d2ddac3d53c84b593d911 Mon Sep 17 00:00:00 2001 +From: Paul Adenot +Date: Mon, 16 Jan 2017 04:49:41 -0800 +Subject: [PATCH 1/1] Prevent double-free when doing an emergency bailout from + the rendering thread. + +This caused gecko bug 1326176. + +This was caused by the fact that we would null out `stm->thread` when in +fact it was still running, so we would delete `stm->emergency_bailout` +twice, because we would return true from `stop_and_join_thread`. +--- + src/cubeb_wasapi.cpp | 15 ++++++++++----- + 1 file changed, 10 insertions(+), 5 deletions(-) + +diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp +index 63c12ac..2920b5d 100644 +--- a/src/cubeb_wasapi.cpp ++++ b/src/cubeb_wasapi.cpp +@@ -1230,13 +1230,18 @@ bool stop_and_join_render_thread(cubeb_stream * stm) + rv = false; + } + +- LOG("Closing thread."); + +- CloseHandle(stm->thread); +- stm->thread = NULL; ++ // Only attempts to close and null out the thread and event if the ++ // WaitForSingleObject above succeeded, so that calling this function again ++ // attemps to clean up the thread and event each time. ++ if (rv) { ++ LOG("Closing thread."); ++ CloseHandle(stm->thread); ++ stm->thread = NULL; + +- CloseHandle(stm->shutdown_event); +- stm->shutdown_event = 0; ++ CloseHandle(stm->shutdown_event); ++ stm->shutdown_event = 0; ++ } + + return rv; + } +-- +2.7.4 + diff --git a/media/libcubeb/src/android/cubeb-output-latency.h b/media/libcubeb/src/android/cubeb-output-latency.h deleted file mode 100644 index a824fc1c2..000000000 --- a/media/libcubeb/src/android/cubeb-output-latency.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef _CUBEB_OUTPUT_LATENCY_H_ -#define _CUBEB_OUTPUT_LATENCY_H_ - -#include -#include "cubeb_media_library.h" -#include "../cubeb-jni.h" - -struct output_latency_function { - media_lib * from_lib; - cubeb_jni * from_jni; - int version; -}; - -typedef struct output_latency_function output_latency_function; - -const int ANDROID_JELLY_BEAN_MR1_4_2 = 17; - -output_latency_function * -cubeb_output_latency_load_method(int version) -{ - output_latency_function * ol = NULL; - ol = calloc(1, sizeof(output_latency_function)); - - ol->version = version; - - if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){ - ol->from_jni = cubeb_jni_init(); - return ol; - } - - ol->from_lib = cubeb_load_media_library(); - return ol; -} - -bool -cubeb_output_latency_method_is_loaded(output_latency_function * ol) -{ - assert(ol && (ol->from_jni || ol->from_lib)); - if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){ - return !!ol->from_jni; - } - - return !!ol->from_lib; -} - -void -cubeb_output_latency_unload_method(output_latency_function * ol) -{ - if (!ol) { - return; - } - - if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_jni) { - cubeb_jni_destroy(ol->from_jni); - } - - if (ol->version <= ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_lib) { - cubeb_close_media_library(ol->from_lib); - } - - free(ol); -} - -uint32_t -cubeb_get_output_latency(output_latency_function * ol) -{ - assert(cubeb_output_latency_method_is_loaded(ol)); - - if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){ - return cubeb_get_output_latency_from_jni(ol->from_jni); - } - - return cubeb_get_output_latency_from_media_library(ol->from_lib); -} - -#endif // _CUBEB_OUTPUT_LATENCY_H_ diff --git a/media/libcubeb/src/android/cubeb_media_library.h b/media/libcubeb/src/android/cubeb_media_library.h deleted file mode 100644 index ab21b779d..000000000 --- a/media/libcubeb/src/android/cubeb_media_library.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef _CUBEB_MEDIA_LIBRARY_H_ -#define _CUBEB_MEDIA_LIBRARY_H_ - -struct media_lib { - void * libmedia; - int32_t (* get_output_latency)(uint32_t * latency, int stream_type); -}; - -typedef struct media_lib media_lib; - -media_lib * -cubeb_load_media_library() -{ - media_lib ml = {0}; - ml.libmedia = dlopen("libmedia.so", RTLD_LAZY); - if (!ml.libmedia) { - return NULL; - } - - // Get the latency, in ms, from AudioFlinger. First, try the most recent signature. - // status_t AudioSystem::getOutputLatency(uint32_t* latency, audio_stream_type_t streamType) - ml.get_output_latency = - dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t"); - if (!ml.get_output_latency) { - // In case of failure, try the signature from legacy version. - // status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType) - ml.get_output_latency = - dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); - if (!ml.get_output_latency) { - return NULL; - } - } - - media_lib * rv = NULL; - rv = calloc(1, sizeof(media_lib)); - assert(rv); - *rv = ml; - return rv; -} - -void -cubeb_close_media_library(media_lib * ml) -{ - dlclose(ml->libmedia); - ml->libmedia = NULL; - ml->get_output_latency = NULL; - free(ml); -} - -uint32_t -cubeb_get_output_latency_from_media_library(media_lib * ml) -{ - uint32_t latency = 0; - const int audio_stream_type_music = 3; - int32_t r = ml->get_output_latency(&latency, audio_stream_type_music); - if (r) { - return 0; - } - return latency; -} - -#endif // _CUBEB_MEDIA_LIBRARY_H_ diff --git a/media/libcubeb/src/android/sles_definitions.h b/media/libcubeb/src/android/sles_definitions.h index 06d2e8d49..1b1ace567 100644 --- a/media/libcubeb/src/android/sles_definitions.h +++ b/media/libcubeb/src/android/sles_definitions.h @@ -43,9 +43,10 @@ #define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32) 0x00000003) /** uses the main microphone tuned for audio communications */ #define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32) 0x00000004) -/** uses the main microphone unprocessed */ -#define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32) 0x00000005) +/** Audio recording get session ID (read only) */ +/** Audio recording get session ID key */ +#define SL_ANDROID_KEY_RECORDING_SESSION_ID ((const SLchar*) "androidRecordingSessionId") /*---------------------------------------------------------------------------*/ /* Android AudioPlayer configuration */ @@ -68,35 +69,9 @@ #define SL_ANDROID_STREAM_ALARM ((SLint32) 0x00000004) /* same as android.media.AudioManager.STREAM_NOTIFICATION */ #define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005) - - -/*---------------------------------------------------------------------------*/ -/* Android AudioPlayer and AudioRecorder configuration */ -/*---------------------------------------------------------------------------*/ - -/** Audio Performance mode. - * Performance mode tells the framework how to configure the audio path - * for a player or recorder according to application performance and - * functional requirements. - * It affects the output or input latency based on acceptable tradeoffs on - * battery drain and use of pre or post processing effects. - * Performance mode should be set before realizing the object and should be - * read after realizing the object to check if the requested mode could be - * granted or not. - */ -/** Audio Performance mode key */ -#define SL_ANDROID_KEY_PERFORMANCE_MODE ((const SLchar*) "androidPerformanceMode") - -/** Audio performance values */ -/* No specific performance requirement. Allows HW and SW pre/post processing. */ -#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32) 0x00000000) -/* Priority given to latency. No HW or software pre/post processing. - * This is the default if no performance mode is specified. */ -#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32) 0x00000001) -/* Priority given to latency while still allowing HW pre and post processing. */ -#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32) 0x00000002) -/* Priority given to power saving if latency is not a concern. - * Allows HW and SW pre/post processing. */ -#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32) 0x00000003) +/* same as android.media.AudioManager.STREAM_BLUETOOTH_SCO */ +#define SL_ANDROID_STREAM_BLUETOOTH_SCO ((SLint32) 0x00000006) +/* same as android.media.AudioManager.STREAM_SYSTEM_ENFORCED */ +#define SL_ANDROID_STREAM_SYSTEM_ENFORCED ((SLint32) 0x00000007) #endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */ diff --git a/media/libcubeb/src/cubeb-internal.h b/media/libcubeb/src/cubeb-internal.h index 312a9ea3a..dfcc186c5 100644 --- a/media/libcubeb/src/cubeb-internal.h +++ b/media/libcubeb/src/cubeb-internal.h @@ -9,7 +9,6 @@ #include "cubeb/cubeb.h" #include "cubeb_log.h" -#include "cubeb_assert.h" #include #include @@ -29,6 +28,9 @@ extern "C" { #endif +/* Crash the caller. */ +void cubeb_crash() CLANG_ANALYZER_NORETURN; + #if defined(__cplusplus) } #endif @@ -42,9 +44,7 @@ struct cubeb_ops { uint32_t * latency_ms); int (* get_preferred_sample_rate)(cubeb * context, uint32_t * rate); int (* enumerate_devices)(cubeb * context, cubeb_device_type type, - cubeb_device_collection * collection); - int (* device_collection_destroy)(cubeb * context, - cubeb_device_collection * collection); + cubeb_device_collection ** collection); void (* destroy)(cubeb * context); int (* stream_init)(cubeb * context, cubeb_stream ** stream, @@ -60,10 +60,10 @@ struct cubeb_ops { void (* stream_destroy)(cubeb_stream * stream); int (* stream_start)(cubeb_stream * stream); int (* stream_stop)(cubeb_stream * stream); - int (* stream_reset_default_device)(cubeb_stream * stream); int (* stream_get_position)(cubeb_stream * stream, uint64_t * position); int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency); int (* stream_set_volume)(cubeb_stream * stream, float volumes); + int (* stream_set_panning)(cubeb_stream * stream, float panning); int (* stream_get_current_device)(cubeb_stream * stream, cubeb_device ** const device); int (* stream_device_destroy)(cubeb_stream * stream, @@ -76,4 +76,11 @@ struct cubeb_ops { void * user_ptr); }; +#define XASSERT(expr) do { \ + if (!(expr)) { \ + fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \ + cubeb_crash(); \ + } \ + } while (0) + #endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */ diff --git a/media/libcubeb/src/cubeb-jni-instances.h b/media/libcubeb/src/cubeb-jni-instances.h deleted file mode 100644 index 19c5c29da..000000000 --- a/media/libcubeb/src/cubeb-jni-instances.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef _CUBEB_JNI_INSTANCES_H_ -#define _CUBEB_JNI_INSTANCES_H_ - -#include "GeneratedJNIWrappers.h" -#include "mozilla/jni/Utils.h" - -/* - * The methods in this file offer a way to pass in the required - * JNI instances in the cubeb library. By default they return NULL. - * In this case part of the cubeb API that depends on JNI - * will return CUBEB_ERROR_NOT_SUPPORTED. Currently only one - * method depends on that: - * - * cubeb_stream_get_position() - * - * Users that want to use that cubeb API method must "override" - * the methods bellow to return a valid instance of JavaVM - * and application's Context object. - * */ - -JNIEnv * -cubeb_get_jni_env_for_thread() -{ - return mozilla::jni::GetEnvForThread(); -} - -jobject -cubeb_jni_get_context_instance() -{ - auto context = mozilla::java::GeckoAppShell::GetApplicationContext(); - return context.Forget(); -} - -#endif //_CUBEB_JNI_INSTANCES_H_ diff --git a/media/libcubeb/src/cubeb-jni.cpp b/media/libcubeb/src/cubeb-jni.cpp deleted file mode 100644 index a5066967a..000000000 --- a/media/libcubeb/src/cubeb-jni.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "jni.h" -#include -#include "cubeb-jni-instances.h" - -#define AUDIO_STREAM_TYPE_MUSIC 3 - -struct cubeb_jni { - jobject s_audio_manager_obj = nullptr; - jclass s_audio_manager_class = nullptr; - jmethodID s_get_output_latency_id = nullptr; -}; - -extern "C" -cubeb_jni * -cubeb_jni_init() -{ - jobject ctx_obj = cubeb_jni_get_context_instance(); - JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); - if (!jni_env || !ctx_obj) { - return nullptr; - } - - cubeb_jni * cubeb_jni_ptr = new cubeb_jni; - assert(cubeb_jni_ptr); - - // Find the audio manager object and make it global to call it from another method - jclass context_class = jni_env->FindClass("android/content/Context"); - jfieldID audio_service_field = jni_env->GetStaticFieldID(context_class, "AUDIO_SERVICE", "Ljava/lang/String;"); - jstring jstr = (jstring)jni_env->GetStaticObjectField(context_class, audio_service_field); - jmethodID get_system_service_id = jni_env->GetMethodID(context_class, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); - jobject audio_manager_obj = jni_env->CallObjectMethod(ctx_obj, get_system_service_id, jstr); - cubeb_jni_ptr->s_audio_manager_obj = reinterpret_cast(jni_env->NewGlobalRef(audio_manager_obj)); - - // Make the audio manager class a global reference in order to preserve method id - jclass audio_manager_class = jni_env->FindClass("android/media/AudioManager"); - cubeb_jni_ptr->s_audio_manager_class = reinterpret_cast(jni_env->NewGlobalRef(audio_manager_class)); - cubeb_jni_ptr->s_get_output_latency_id = jni_env->GetMethodID (audio_manager_class, "getOutputLatency", "(I)I"); - - jni_env->DeleteLocalRef(ctx_obj); - jni_env->DeleteLocalRef(context_class); - jni_env->DeleteLocalRef(jstr); - jni_env->DeleteLocalRef(audio_manager_obj); - jni_env->DeleteLocalRef(audio_manager_class); - - return cubeb_jni_ptr; -} - -extern "C" -int cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr) -{ - assert(cubeb_jni_ptr); - JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); - return jni_env->CallIntMethod(cubeb_jni_ptr->s_audio_manager_obj, cubeb_jni_ptr->s_get_output_latency_id, AUDIO_STREAM_TYPE_MUSIC); //param: AudioManager.STREAM_MUSIC -} - -extern "C" -void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr) -{ - assert(cubeb_jni_ptr); - - JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); - assert(jni_env); - - jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_obj); - jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_class); - - delete cubeb_jni_ptr; -} diff --git a/media/libcubeb/src/cubeb-jni.h b/media/libcubeb/src/cubeb-jni.h deleted file mode 100644 index 8c7ddb6ac..000000000 --- a/media/libcubeb/src/cubeb-jni.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef _CUBEB_JNI_H_ -#define _CUBEB_JNI_H_ - -typedef struct cubeb_jni cubeb_jni; - -cubeb_jni * cubeb_jni_init(); -int cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr); -void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr); - -#endif // _CUBEB_JNI_H_ diff --git a/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c index 3ad39dee0..a239319a4 100644 --- a/media/libcubeb/src/cubeb.c +++ b/media/libcubeb/src/cubeb.c @@ -8,23 +8,20 @@ #include #include #include -#include #include "cubeb/cubeb.h" #include "cubeb-internal.h" #define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0]))) +cubeb_log_level g_log_level; +cubeb_log_callback g_log_callback; + struct cubeb { struct cubeb_ops * ops; }; struct cubeb_stream { - /* - * Note: All implementations of cubeb_stream must keep the following - * layout. - */ struct cubeb * context; - void * user_ptr; }; #if defined(USE_PULSE) @@ -48,9 +45,6 @@ int wasapi_init(cubeb ** context, char const * context_name); #if defined(USE_SNDIO) int sndio_init(cubeb ** context, char const * context_name); #endif -#if defined(USE_SUN) -int sun_init(cubeb ** context, char const * context_name); -#endif #if defined(USE_OPENSL) int opensl_init(cubeb ** context, char const * context_name); #endif @@ -60,6 +54,10 @@ int audiotrack_init(cubeb ** context, char const * context_name); #if defined(USE_KAI) int kai_init(cubeb ** context, char const * context_name); #endif +#if defined(USE_SUN) +int sunaudio_init(cubeb ** context, char const * context_name); +#endif + static int validate_stream_params(cubeb_stream_params * input_stream_params, @@ -68,7 +66,7 @@ validate_stream_params(cubeb_stream_params * input_stream_params, XASSERT(input_stream_params || output_stream_params); if (output_stream_params) { if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 || - output_stream_params->channels < 1 || output_stream_params->channels > UINT8_MAX) { + output_stream_params->channels < 1 || output_stream_params->channels > 8) { return CUBEB_ERROR_INVALID_FORMAT; } } @@ -101,6 +99,8 @@ validate_stream_params(cubeb_stream_params * input_stream_params, return CUBEB_ERROR_INVALID_FORMAT; } + + static int validate_latency(int latency) { @@ -111,75 +111,15 @@ validate_latency(int latency) } int -cubeb_init(cubeb ** context, char const * context_name, char const * backend_name) +cubeb_init(cubeb ** context, char const * context_name) { - int (* init_oneshot)(cubeb **, char const *) = NULL; - - if (backend_name != NULL) { - if (!strcmp(backend_name, "pulse")) { -#if defined(USE_PULSE) - init_oneshot = pulse_init; -#endif - } else if (!strcmp(backend_name, "jack")) { + int (* init[])(cubeb **, char const *) = { #if defined(USE_JACK) - init_oneshot = jack_init; -#endif - } else if (!strcmp(backend_name, "alsa")) { -#if defined(USE_ALSA) - init_oneshot = alsa_init; -#endif - } else if (!strcmp(backend_name, "audiounit")) { -#if defined(USE_AUDIOUNIT) - init_oneshot = audiounit_init; -#endif - } else if (!strcmp(backend_name, "wasapi")) { -#if defined(USE_WASAPI) - init_oneshot = wasapi_init; -#endif - } else if (!strcmp(backend_name, "winmm")) { -#if defined(USE_WINMM) - init_oneshot = winmm_init; -#endif - } else if (!strcmp(backend_name, "sndio")) { -#if defined(USE_SNDIO) - init_oneshot = sndio_init; -#endif - } else if (!strcmp(backend_name, "sun")) { -#if defined(USE_SUN) - init_oneshot = sun_init; -#endif - } else if (!strcmp(backend_name, "opensl")) { -#if defined(USE_OPENSL) - init_oneshot = opensl_init; -#endif - } else if (!strcmp(backend_name, "audiotrack")) { -#if defined(USE_AUDIOTRACK) - init_oneshot = audiotrack_init; -#endif - } else if (!strcmp(backend_name, "kai")) { -#if defined(USE_KAI) - init_oneshot = kai_init; + jack_init, #endif - } else { - /* Already set */ - } - } - - int (* default_init[])(cubeb **, char const *) = { - /* - * init_oneshot must be at the top to allow user - * to override all other choices - */ - init_oneshot, #if defined(USE_PULSE) pulse_init, #endif -#if defined(USE_JACK) - jack_init, -#endif -#if defined(USE_SNDIO) - sndio_init, -#endif #if defined(USE_ALSA) alsa_init, #endif @@ -192,8 +132,8 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam #if defined(USE_WINMM) winmm_init, #endif -#if defined(USE_SUN) - sun_init, +#if defined(USE_SNDIO) + sndio_init, #endif #if defined(USE_OPENSL) opensl_init, @@ -203,6 +143,9 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam #endif #if defined(USE_KAI) kai_init, +#endif +#if defined(USE_SUN) + sunaudio_init, #endif }; int i; @@ -211,10 +154,10 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam return CUBEB_ERROR_INVALID_PARAMETER; } -#define OK(fn) assert((* context)->ops->fn) - for (i = 0; i < NELEMS(default_init); ++i) { - if (default_init[i] && default_init[i](context, context_name) == CUBEB_OK) { + for (i = 0; i < NELEMS(init); ++i) { + if (init[i](context, context_name) == CUBEB_OK) { /* Assert that the minimal API is implemented. */ +#define OK(fn) assert((* context)->ops->fn) OK(get_backend_id); OK(destroy); OK(stream_init); @@ -225,6 +168,7 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam return CUBEB_OK; } } + return CUBEB_ERROR; } @@ -253,9 +197,9 @@ cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels) } int -cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params, uint32_t * latency_ms) +cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms) { - if (!context || !params || !latency_ms) { + if (!context || !latency_ms) { return CUBEB_ERROR_INVALID_PARAMETER; } @@ -263,7 +207,7 @@ cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params, uint32_t * return CUBEB_ERROR_NOT_SUPPORTED; } - return context->ops->get_min_latency(context, *params, latency_ms); + return context->ops->get_min_latency(context, params, latency_ms); } int @@ -303,7 +247,7 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n { int r; - if (!context || !stream || !data_callback || !state_callback) { + if (!context || !stream) { return CUBEB_ERROR_INVALID_PARAMETER; } @@ -312,24 +256,15 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n return r; } - r = context->ops->stream_init(context, stream, stream_name, - input_device, - input_stream_params, - output_device, - output_stream_params, - latency, - data_callback, - state_callback, - user_ptr); - - if (r == CUBEB_ERROR_INVALID_FORMAT) { - LOG("Invalid format, %p %p %d %d", - output_stream_params, input_stream_params, - output_stream_params && output_stream_params->format, - input_stream_params && input_stream_params->format); - } - - return r; + return context->ops->stream_init(context, stream, stream_name, + input_device, + input_stream_params, + output_device, + output_stream_params, + latency, + data_callback, + state_callback, + user_ptr); } void @@ -362,20 +297,6 @@ cubeb_stream_stop(cubeb_stream * stream) return stream->context->ops->stream_stop(stream); } -int -cubeb_stream_reset_default_device(cubeb_stream * stream) -{ - if (!stream) { - return CUBEB_ERROR_INVALID_PARAMETER; - } - - if (!stream->context->ops->stream_reset_default_device) { - return CUBEB_ERROR_NOT_SUPPORTED; - } - - return stream->context->ops->stream_reset_default_device(stream); -} - int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position) { @@ -414,6 +335,19 @@ cubeb_stream_set_volume(cubeb_stream * stream, float volume) return stream->context->ops->stream_set_volume(stream, volume); } +int cubeb_stream_set_panning(cubeb_stream * stream, float panning) +{ + if (!stream || panning < -1.0 || panning > 1.0) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (!stream->context->ops->stream_set_panning) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return stream->context->ops->stream_set_panning(stream, panning); +} + int cubeb_stream_get_current_device(cubeb_stream * stream, cubeb_device ** const device) { @@ -456,15 +390,6 @@ int cubeb_stream_register_device_changed_callback(cubeb_stream * stream, return stream->context->ops->stream_register_device_changed_callback(stream, device_changed_callback); } -void * cubeb_stream_user_ptr(cubeb_stream * stream) -{ - if (!stream) { - return NULL; - } - - return stream->user_ptr; -} - static void log_device(cubeb_device_info * device_info) { @@ -554,7 +479,7 @@ void log_device(cubeb_device_info * device_info) int cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype, - cubeb_device_collection * collection) + cubeb_device_collection ** collection) { int rv; if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0) @@ -566,36 +491,42 @@ int cubeb_enumerate_devices(cubeb * context, rv = context->ops->enumerate_devices(context, devtype, collection); - if (g_cubeb_log_callback) { - for (size_t i = 0; i < collection->count; i++) { - log_device(&collection->device[i]); + if (g_log_callback) { + for (uint32_t i = 0; i < (*collection)->count; i++) { + log_device((*collection)->device[i]); } } return rv; } -int cubeb_device_collection_destroy(cubeb * context, - cubeb_device_collection * collection) +int cubeb_device_collection_destroy(cubeb_device_collection * collection) { - int r; + uint32_t i; - if (context == NULL || collection == NULL) + if (collection == NULL) return CUBEB_ERROR_INVALID_PARAMETER; - if (!context->ops->device_collection_destroy) - return CUBEB_ERROR_NOT_SUPPORTED; + for (i = 0; i < collection->count; i++) + cubeb_device_info_destroy(collection->device[i]); - if (!collection->device) - return CUBEB_OK; + free(collection); + return CUBEB_OK; +} - r = context->ops->device_collection_destroy(context, collection); - if (r == CUBEB_OK) { - collection->device = NULL; - collection->count = 0; +int cubeb_device_info_destroy(cubeb_device_info * info) +{ + if (info == NULL) { + return CUBEB_ERROR_INVALID_PARAMETER; } - return r; + free(info->device_id); + free(info->friendly_name); + free(info->group_id); + free(info->vendor_name); + + free(info); + return CUBEB_OK; } int cubeb_register_device_collection_changed(cubeb * context, @@ -624,22 +555,20 @@ int cubeb_set_log_callback(cubeb_log_level log_level, return CUBEB_ERROR_INVALID_PARAMETER; } - if (g_cubeb_log_callback && log_callback) { + if (g_log_callback && log_callback) { return CUBEB_ERROR_NOT_SUPPORTED; } - g_cubeb_log_callback = log_callback; - g_cubeb_log_level = log_level; - - // Logging a message here allows to initialize the asynchronous logger from a - // thread that is not the audio rendering thread, and especially to not - // initialize it the first time we find a verbose log, which is often in the - // audio rendering callback, that runs from the audio rendering thread, and - // that is high priority, and that we don't want to block. - if (log_level >= CUBEB_LOG_VERBOSE) { - ALOGV("Starting cubeb log"); - } + g_log_callback = log_callback; + g_log_level = log_level; return CUBEB_OK; } +void +cubeb_crash() +{ + *((volatile int *) NULL) = 0; + abort(); +} + diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c index a564fbfc6..72a6acfb1 100644 --- a/media/libcubeb/src/cubeb_alsa.c +++ b/media/libcubeb/src/cubeb_alsa.c @@ -14,58 +14,10 @@ #include #include #include -#include #include #include "cubeb/cubeb.h" #include "cubeb-internal.h" -#ifdef DISABLE_LIBASOUND_DLOPEN -#define WRAP(x) x -#else -#define WRAP(x) cubeb_##x -#define LIBASOUND_API_VISIT(X) \ - X(snd_config) \ - X(snd_config_add) \ - X(snd_config_copy) \ - X(snd_config_delete) \ - X(snd_config_get_id) \ - X(snd_config_get_string) \ - X(snd_config_imake_integer) \ - X(snd_config_search) \ - X(snd_config_search_definition) \ - X(snd_lib_error_set_handler) \ - X(snd_pcm_avail_update) \ - X(snd_pcm_close) \ - X(snd_pcm_delay) \ - X(snd_pcm_drain) \ - X(snd_pcm_frames_to_bytes) \ - X(snd_pcm_get_params) \ - X(snd_pcm_hw_params_any) \ - X(snd_pcm_hw_params_get_channels_max) \ - X(snd_pcm_hw_params_get_rate) \ - X(snd_pcm_hw_params_set_rate_near) \ - X(snd_pcm_hw_params_sizeof) \ - X(snd_pcm_nonblock) \ - X(snd_pcm_open) \ - X(snd_pcm_open_lconf) \ - X(snd_pcm_pause) \ - X(snd_pcm_poll_descriptors) \ - X(snd_pcm_poll_descriptors_count) \ - X(snd_pcm_poll_descriptors_revents) \ - X(snd_pcm_readi) \ - X(snd_pcm_recover) \ - X(snd_pcm_set_params) \ - X(snd_pcm_start) \ - X(snd_pcm_state) \ - X(snd_pcm_writei) \ - -#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; -LIBASOUND_API_VISIT(MAKE_TYPEDEF); -#undef MAKE_TYPEDEF -/* snd_pcm_hw_params_alloca is actually a macro */ -#define snd_pcm_hw_params_sizeof cubeb_snd_pcm_hw_params_sizeof -#endif - #define CUBEB_STREAM_MAX 16 #define CUBEB_WATCHDOG_MS 10000 @@ -84,7 +36,6 @@ static struct cubeb_ops const alsa_ops; struct cubeb { struct cubeb_ops const * ops; - void * libasound; pthread_t thread; @@ -125,15 +76,13 @@ enum stream_state { }; struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; - void * user_ptr; - /**/ pthread_mutex_t mutex; snd_pcm_t * pcm; cubeb_data_callback data_callback; cubeb_state_callback state_callback; - snd_pcm_uframes_t stream_position; + void * user_ptr; + snd_pcm_uframes_t write_position; snd_pcm_uframes_t last_position; snd_pcm_uframes_t buffer_size; cubeb_stream_params params; @@ -158,12 +107,6 @@ struct cubeb_stream { being logically active and playing. */ struct timeval last_activity; float volume; - - char * buffer; - snd_pcm_uframes_t bufframes; - snd_pcm_stream_t stream_type; - - struct cubeb_stream * other_stream; }; static int @@ -291,14 +234,6 @@ set_timeout(struct timeval * timeout, unsigned int ms) timeout->tv_usec += (ms % 1000) * 1000; } -static void -stream_buffer_decrement(cubeb_stream * stm, long count) -{ - char * bufremains = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, count); - memmove(stm->buffer, bufremains, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes - count)); - stm->bufframes -= count; -} - static void alsa_set_stream_state(cubeb_stream * stm, enum stream_state state) { @@ -314,173 +249,97 @@ alsa_set_stream_state(cubeb_stream * stm, enum stream_state state) } static enum stream_state -alsa_process_stream(cubeb_stream * stm) +alsa_refill_stream(cubeb_stream * stm) { - unsigned short revents; snd_pcm_sframes_t avail; + long got; + void * p; int draining; draining = 0; pthread_mutex_lock(&stm->mutex); - /* Call _poll_descriptors_revents() even if we don't use it - to let underlying plugins clear null events. Otherwise poll() - may wake up again and again, producing unnecessary CPU usage. */ - WRAP(snd_pcm_poll_descriptors_revents)(stm->pcm, stm->fds, stm->nfds, &revents); - - avail = WRAP(snd_pcm_avail_update)(stm->pcm); + avail = snd_pcm_avail_update(stm->pcm); + if (avail < 0) { + snd_pcm_recover(stm->pcm, avail, 1); + avail = snd_pcm_avail_update(stm->pcm); + } - /* Got null event? Bail and wait for another wakeup. */ - if (avail == 0) { + /* Failed to recover from an xrun, this stream must be broken. */ + if (avail < 0) { pthread_mutex_unlock(&stm->mutex); - return RUNNING; + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + return ERROR; } - /* This could happen if we were suspended with SIGSTOP/Ctrl+Z for a long time. */ + /* This should never happen. */ if ((unsigned int) avail > stm->buffer_size) { avail = stm->buffer_size; } - /* Capture: Read available frames */ - if (stm->stream_type == SND_PCM_STREAM_CAPTURE && avail > 0) { - snd_pcm_sframes_t got; - - if (avail + stm->bufframes > stm->buffer_size) { - /* Buffer overflow. Skip and overwrite with new data. */ - stm->bufframes = 0; - // TODO: should it be marked as DRAINING? - } - - got = WRAP(snd_pcm_readi)(stm->pcm, stm->buffer+stm->bufframes, avail); - - if (got < 0) { - avail = got; // the error handler below will recover us - } else { - stm->bufframes += got; - stm->stream_position += got; - - gettimeofday(&stm->last_activity, NULL); - } - } - - /* Capture: Pass read frames to callback function */ - if (stm->stream_type == SND_PCM_STREAM_CAPTURE && stm->bufframes > 0 && - (!stm->other_stream || stm->other_stream->bufframes < stm->other_stream->buffer_size)) { - snd_pcm_sframes_t wrote = stm->bufframes; - struct cubeb_stream * mainstm = stm->other_stream ? stm->other_stream : stm; - void * other_buffer = stm->other_stream ? stm->other_stream->buffer + stm->other_stream->bufframes : NULL; - - /* Correct write size to the other stream available space */ - if (stm->other_stream && wrote > (snd_pcm_sframes_t) (stm->other_stream->buffer_size - stm->other_stream->bufframes)) { - wrote = stm->other_stream->buffer_size - stm->other_stream->bufframes; - } - + /* poll(2) claims this stream is active, so there should be some space + available to write. If avail is still zero here, the stream must be in + a funky state, bail and wait for another wakeup. */ + if (avail == 0) { pthread_mutex_unlock(&stm->mutex); - wrote = stm->data_callback(mainstm, stm->user_ptr, stm->buffer, other_buffer, wrote); - pthread_mutex_lock(&stm->mutex); - - if (wrote < 0) { - avail = wrote; // the error handler below will recover us - } else { - stream_buffer_decrement(stm, wrote); - - if (stm->other_stream) { - stm->other_stream->bufframes += wrote; - } - } + return RUNNING; } - /* Playback: Don't have enough data? Let's ask for more. */ - if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > (snd_pcm_sframes_t) stm->bufframes && - (!stm->other_stream || stm->other_stream->bufframes > 0)) { - long got = avail - stm->bufframes; - void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL; - char * buftail = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes); - - /* Correct read size to the other stream available frames */ - if (stm->other_stream && got > (snd_pcm_sframes_t) stm->other_stream->bufframes) { - got = stm->other_stream->bufframes; - } + p = calloc(1, snd_pcm_frames_to_bytes(stm->pcm, avail)); + assert(p); + pthread_mutex_unlock(&stm->mutex); + got = stm->data_callback(stm, stm->user_ptr, NULL, p, avail); + pthread_mutex_lock(&stm->mutex); + if (got < 0) { pthread_mutex_unlock(&stm->mutex); - got = stm->data_callback(stm, stm->user_ptr, other_buffer, buftail, got); - pthread_mutex_lock(&stm->mutex); - - if (got < 0) { - avail = got; // the error handler below will recover us - } else { - stm->bufframes += got; - - if (stm->other_stream) { - stream_buffer_decrement(stm->other_stream, got); - } - } - } - - /* Playback: Still don't have enough data? Add some silence. */ - if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > (snd_pcm_sframes_t) stm->bufframes) { - long drain_frames = avail - stm->bufframes; - double drain_time = (double) drain_frames / stm->params.rate; - - char * buftail = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes); - memset(buftail, 0, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, drain_frames)); - stm->bufframes = avail; - - /* Mark as draining, unless we're waiting for capture */ - if (!stm->other_stream || stm->other_stream->bufframes > 0) { - set_timeout(&stm->drain_timeout, drain_time * 1000); - - draining = 1; - } + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + free(p); + return ERROR; } - - /* Playback: Have enough data and no errors. Let's write it out. */ - if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > 0) { + if (got > 0) { snd_pcm_sframes_t wrote; if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) { - float * b = (float *) stm->buffer; - for (uint32_t i = 0; i < avail * stm->params.channels; i++) { + float * b = (float *) p; + for (uint32_t i = 0; i < got * stm->params.channels; i++) { b[i] *= stm->volume; } } else { - short * b = (short *) stm->buffer; - for (uint32_t i = 0; i < avail * stm->params.channels; i++) { + short * b = (short *) p; + for (uint32_t i = 0; i < got * stm->params.channels; i++) { b[i] *= stm->volume; } } - - wrote = WRAP(snd_pcm_writei)(stm->pcm, stm->buffer, avail); + wrote = snd_pcm_writei(stm->pcm, p, got); if (wrote < 0) { - avail = wrote; // the error handler below will recover us - } else { - stream_buffer_decrement(stm, wrote); - - stm->stream_position += wrote; - gettimeofday(&stm->last_activity, NULL); + snd_pcm_recover(stm->pcm, wrote, 1); + wrote = snd_pcm_writei(stm->pcm, p, got); } + if (wrote < 0 || wrote != got) { + /* Recovery failed, somehow. */ + pthread_mutex_unlock(&stm->mutex); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + return ERROR; + } + stm->write_position += wrote; + gettimeofday(&stm->last_activity, NULL); } + if (got != avail) { + long buffer_fill = stm->buffer_size - (avail - got); + double buffer_time = (double) buffer_fill / stm->params.rate; - /* Got some error? Let's try to recover the stream. */ - if (avail < 0) { - avail = WRAP(snd_pcm_recover)(stm->pcm, avail, 0); + /* Fill the remaining buffer with silence to guarantee one full period + has been written. */ + snd_pcm_writei(stm->pcm, (char *) p + got, avail - got); - /* Capture pcm must be started after initial setup/recover */ - if (avail >= 0 && - stm->stream_type == SND_PCM_STREAM_CAPTURE && - WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) { - avail = WRAP(snd_pcm_start)(stm->pcm); - } - } + set_timeout(&stm->drain_timeout, buffer_time * 1000); - /* Failed to recover, this stream must be broken. */ - if (avail < 0) { - pthread_mutex_unlock(&stm->mutex); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return ERROR; + draining = 1; } + free(p); pthread_mutex_unlock(&stm->mutex); return draining ? DRAINING : RUNNING; } @@ -536,7 +395,7 @@ alsa_run(cubeb * ctx) if (stm && stm->state == RUNNING && stm->fds && any_revents(stm->fds, stm->nfds)) { alsa_set_stream_state(stm, PROCESSING); pthread_mutex_unlock(&ctx->mutex); - state = alsa_process_stream(stm); + state = alsa_refill_stream(stm); pthread_mutex_lock(&ctx->mutex); alsa_set_stream_state(stm, state); } @@ -586,26 +445,26 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm) slave_def = NULL; - r = WRAP(snd_config_search)(root_pcm, "slave", &slave_pcm); + r = snd_config_search(root_pcm, "slave", &slave_pcm); if (r < 0) { return NULL; } - r = WRAP(snd_config_get_string)(slave_pcm, &string); + r = snd_config_get_string(slave_pcm, &string); if (r >= 0) { - r = WRAP(snd_config_search_definition)(lconf, "pcm_slave", string, &slave_def); + r = snd_config_search_definition(lconf, "pcm_slave", string, &slave_def); if (r < 0) { return NULL; } } do { - r = WRAP(snd_config_search)(slave_def ? slave_def : slave_pcm, "pcm", &pcm); + r = snd_config_search(slave_def ? slave_def : slave_pcm, "pcm", &pcm); if (r < 0) { break; } - r = WRAP(snd_config_get_string)(slave_def ? slave_def : slave_pcm, &string); + r = snd_config_get_string(slave_def ? slave_def : slave_pcm, &string); if (r < 0) { break; } @@ -614,7 +473,7 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm) if (r < 0 || r > (int) sizeof(node_name)) { break; } - r = WRAP(snd_config_search)(lconf, node_name, &pcm); + r = snd_config_search(lconf, node_name, &pcm); if (r < 0) { break; } @@ -623,7 +482,7 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm) } while (0); if (slave_def) { - WRAP(snd_config_delete)(slave_def); + snd_config_delete(slave_def); } return NULL; @@ -646,22 +505,22 @@ init_local_config_with_workaround(char const * pcm_name) lconf = NULL; - if (*WRAP(snd_config) == NULL) { + if (snd_config == NULL) { return NULL; } - r = WRAP(snd_config_copy)(&lconf, *WRAP(snd_config)); + r = snd_config_copy(&lconf, snd_config); if (r < 0) { return NULL; } do { - r = WRAP(snd_config_search_definition)(lconf, "pcm", pcm_name, &pcm_node); + r = snd_config_search_definition(lconf, "pcm", pcm_name, &pcm_node); if (r < 0) { break; } - r = WRAP(snd_config_get_id)(pcm_node, &string); + r = snd_config_get_id(pcm_node, &string); if (r < 0) { break; } @@ -670,7 +529,7 @@ init_local_config_with_workaround(char const * pcm_name) if (r < 0 || r > (int) sizeof(node_name)) { break; } - r = WRAP(snd_config_search)(lconf, node_name, &pcm_node); + r = snd_config_search(lconf, node_name, &pcm_node); if (r < 0) { break; } @@ -681,12 +540,12 @@ init_local_config_with_workaround(char const * pcm_name) } /* Fetch the PCM node's type, and bail out if it's not the PulseAudio plugin. */ - r = WRAP(snd_config_search)(pcm_node, "type", &node); + r = snd_config_search(pcm_node, "type", &node); if (r < 0) { break; } - r = WRAP(snd_config_get_string)(node, &string); + r = snd_config_get_string(node, &string); if (r < 0) { break; } @@ -697,18 +556,18 @@ init_local_config_with_workaround(char const * pcm_name) /* Don't clobber an explicit existing handle_underrun value, set it only if it doesn't already exist. */ - r = WRAP(snd_config_search)(pcm_node, "handle_underrun", &node); + r = snd_config_search(pcm_node, "handle_underrun", &node); if (r != -ENOENT) { break; } /* Disable pcm_pulse's asynchronous underrun handling. */ - r = WRAP(snd_config_imake_integer)(&node, "handle_underrun", 0); + r = snd_config_imake_integer(&node, "handle_underrun", 0); if (r < 0) { break; } - r = WRAP(snd_config_add)(pcm_node, node); + r = snd_config_add(pcm_node, node); if (r < 0) { break; } @@ -716,21 +575,21 @@ init_local_config_with_workaround(char const * pcm_name) return lconf; } while (0); - WRAP(snd_config_delete)(lconf); + snd_config_delete(lconf); return NULL; } static int -alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name, snd_pcm_stream_t stream, snd_config_t * local_config) +alsa_locked_pcm_open(snd_pcm_t ** pcm, snd_pcm_stream_t stream, snd_config_t * local_config) { int r; pthread_mutex_lock(&cubeb_alsa_mutex); if (local_config) { - r = WRAP(snd_pcm_open_lconf)(pcm, pcm_name, stream, SND_PCM_NONBLOCK, local_config); + r = snd_pcm_open_lconf(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK, local_config); } else { - r = WRAP(snd_pcm_open)(pcm, pcm_name, stream, SND_PCM_NONBLOCK); + r = snd_pcm_open(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK); } pthread_mutex_unlock(&cubeb_alsa_mutex); @@ -743,7 +602,7 @@ alsa_locked_pcm_close(snd_pcm_t * pcm) int r; pthread_mutex_lock(&cubeb_alsa_mutex); - r = WRAP(snd_pcm_close)(pcm); + r = snd_pcm_close(pcm); pthread_mutex_unlock(&cubeb_alsa_mutex); return r; @@ -799,7 +658,6 @@ silent_error_handler(char const * file, int line, char const * function, alsa_init(cubeb ** context, char const * context_name) { (void)context_name; - void * libasound = NULL; cubeb * ctx; int r; int i; @@ -810,30 +668,9 @@ alsa_init(cubeb ** context, char const * context_name) assert(context); *context = NULL; -#ifndef DISABLE_LIBASOUND_DLOPEN - libasound = dlopen("libasound.so.2", RTLD_LAZY); - if (!libasound) { - libasound = dlopen("libasound.so", RTLD_LAZY); - if (!libasound) { - return CUBEB_ERROR; - } - } - -#define LOAD(x) { \ - cubeb_##x = dlsym(libasound, #x); \ - if (!cubeb_##x) { \ - dlclose(libasound); \ - return CUBEB_ERROR; \ - } \ - } - - LIBASOUND_API_VISIT(LOAD); -#undef LOAD -#endif - pthread_mutex_lock(&cubeb_alsa_mutex); if (!cubeb_alsa_error_handler_set) { - WRAP(snd_lib_error_set_handler)(silent_error_handler); + snd_lib_error_set_handler(silent_error_handler); cubeb_alsa_error_handler_set = 1; } pthread_mutex_unlock(&cubeb_alsa_mutex); @@ -842,7 +679,6 @@ alsa_init(cubeb ** context, char const * context_name) assert(ctx); ctx->ops = &alsa_ops; - ctx->libasound = libasound; r = pthread_mutex_init(&ctx->mutex, NULL); assert(r == 0); @@ -876,7 +712,7 @@ alsa_init(cubeb ** context, char const * context_name) /* Open a dummy PCM to force the configuration space to be evaluated so that init_local_config_with_workaround can find and modify the default node. */ - r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, NULL); + r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, NULL); if (r >= 0) { alsa_locked_pcm_close(dummy); } @@ -886,12 +722,12 @@ alsa_init(cubeb ** context, char const * context_name) pthread_mutex_unlock(&cubeb_alsa_mutex); if (ctx->local_config) { ctx->is_pa = 1; - r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, ctx->local_config); + r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, ctx->local_config); /* If we got a local_config, we found a PA PCM. If opening a PCM with that config fails with EINVAL, the PA PCM is too old for this workaround. */ if (r == -EINVAL) { pthread_mutex_lock(&cubeb_alsa_mutex); - WRAP(snd_config_delete)(ctx->local_config); + snd_config_delete(ctx->local_config); pthread_mutex_unlock(&cubeb_alsa_mutex); ctx->local_config = NULL; } else if (r >= 0) { @@ -933,28 +769,24 @@ alsa_destroy(cubeb * ctx) if (ctx->local_config) { pthread_mutex_lock(&cubeb_alsa_mutex); - WRAP(snd_config_delete)(ctx->local_config); + snd_config_delete(ctx->local_config); pthread_mutex_unlock(&cubeb_alsa_mutex); } - if (ctx->libasound) { - dlclose(ctx->libasound); - } - free(ctx); } static void alsa_stream_destroy(cubeb_stream * stm); static int -alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, - snd_pcm_stream_t stream_type, - cubeb_devid deviceid, - cubeb_stream_params * stream_params, - unsigned int latency_frames, - cubeb_data_callback data_callback, - cubeb_state_callback state_callback, - void * user_ptr) +alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, cubeb_state_callback state_callback, + void * user_ptr) { (void)stream_name; cubeb_stream * stm; @@ -962,17 +794,23 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream snd_pcm_format_t format; snd_pcm_uframes_t period_size; int latency_us = 0; - char const * pcm_name = deviceid ? (char const *) deviceid : CUBEB_ALSA_PCM_NAME; - assert(ctx && stream); - *stream = NULL; + assert(ctx && stream); - if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { + if (input_stream_params) { + /* Capture support not yet implemented. */ return CUBEB_ERROR_NOT_SUPPORTED; } - switch (stream_params->format) { + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } + + *stream = NULL; + + switch (output_stream_params->format) { case CUBEB_SAMPLE_S16LE: format = SND_PCM_FORMAT_S16_LE; break; @@ -1004,27 +842,20 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; - stm->params = *stream_params; + stm->params = *output_stream_params; stm->state = INACTIVE; stm->volume = 1.0; - stm->buffer = NULL; - stm->bufframes = 0; - stm->stream_type = stream_type; - stm->other_stream = NULL; r = pthread_mutex_init(&stm->mutex, NULL); assert(r == 0); - r = pthread_cond_init(&stm->cond, NULL); - assert(r == 0); - - r = alsa_locked_pcm_open(&stm->pcm, pcm_name, stm->stream_type, ctx->local_config); + r = alsa_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK, ctx->local_config); if (r < 0) { alsa_stream_destroy(stm); return CUBEB_ERROR; } - r = WRAP(snd_pcm_nonblock)(stm->pcm, 1); + r = snd_pcm_nonblock(stm->pcm, 1); assert(r == 0); latency_us = latency_frames * 1e6 / stm->params.rate; @@ -1037,7 +868,7 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream latency_us = latency_us < min_latency ? min_latency: latency_us; } - r = WRAP(snd_pcm_set_params)(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED, + r = snd_pcm_set_params(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED, stm->params.channels, stm->params.rate, 1, latency_us); if (r < 0) { @@ -1045,22 +876,20 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream return CUBEB_ERROR_INVALID_FORMAT; } - r = WRAP(snd_pcm_get_params)(stm->pcm, &stm->buffer_size, &period_size); + r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &period_size); assert(r == 0); - /* Double internal buffer size to have enough space when waiting for the other side of duplex connection */ - stm->buffer_size *= 2; - stm->buffer = calloc(1, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->buffer_size)); - assert(stm->buffer); - - stm->nfds = WRAP(snd_pcm_poll_descriptors_count)(stm->pcm); + stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm); assert(stm->nfds > 0); stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd)); assert(stm->saved_fds); - r = WRAP(snd_pcm_poll_descriptors)(stm->pcm, stm->saved_fds, stm->nfds); + r = snd_pcm_poll_descriptors(stm->pcm, stm->saved_fds, stm->nfds); assert((nfds_t) r == stm->nfds); + r = pthread_cond_init(&stm->cond, NULL); + assert(r == 0); + if (alsa_register_stream(ctx, stm) != 0) { alsa_stream_destroy(stm); return CUBEB_ERROR; @@ -1071,45 +900,6 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream return CUBEB_OK; } -static int -alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, - cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency_frames, - cubeb_data_callback data_callback, cubeb_state_callback state_callback, - void * user_ptr) -{ - int result = CUBEB_OK; - cubeb_stream * instm = NULL, * outstm = NULL; - - if (result == CUBEB_OK && input_stream_params) { - result = alsa_stream_init_single(ctx, &instm, stream_name, SND_PCM_STREAM_CAPTURE, - input_device, input_stream_params, latency_frames, - data_callback, state_callback, user_ptr); - } - - if (result == CUBEB_OK && output_stream_params) { - result = alsa_stream_init_single(ctx, &outstm, stream_name, SND_PCM_STREAM_PLAYBACK, - output_device, output_stream_params, latency_frames, - data_callback, state_callback, user_ptr); - } - - if (result == CUBEB_OK && input_stream_params && output_stream_params) { - instm->other_stream = outstm; - outstm->other_stream = instm; - } - - if (result != CUBEB_OK && instm) { - alsa_stream_destroy(instm); - } - - *stream = outstm ? outstm : instm; - - return result; -} - static void alsa_stream_destroy(cubeb_stream * stm) { @@ -1122,15 +912,10 @@ alsa_stream_destroy(cubeb_stream * stm) ctx = stm->context; - if (stm->other_stream) { - stm->other_stream->other_stream = NULL; // to stop infinite recursion - alsa_stream_destroy(stm->other_stream); - } - pthread_mutex_lock(&stm->mutex); if (stm->pcm) { if (stm->state == DRAINING) { - WRAP(snd_pcm_drain)(stm->pcm); + snd_pcm_drain(stm->pcm); } alsa_locked_pcm_close(stm->pcm); stm->pcm = NULL; @@ -1149,8 +934,6 @@ alsa_stream_destroy(cubeb_stream * stm) ctx->active_streams -= 1; pthread_mutex_unlock(&ctx->mutex); - free(stm->buffer); - free(stm); } @@ -1174,14 +957,12 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) return CUBEB_ERROR; } - assert(stm); - - r = WRAP(snd_pcm_hw_params_any)(stm->pcm, hw_params); + r = snd_pcm_hw_params_any(stm->pcm, hw_params); if (r < 0) { return CUBEB_ERROR; } - r = WRAP(snd_pcm_hw_params_get_channels_max)(hw_params, max_channels); + r = snd_pcm_hw_params_get_channels_max(hw_params, max_channels); if (r < 0) { return CUBEB_ERROR; } @@ -1202,34 +983,34 @@ alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) { /* get a pcm, disabling resampling, so we get a rate the * hardware/dmix/pulse/etc. supports. */ - r = WRAP(snd_pcm_open)(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE); + r = snd_pcm_open(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE); if (r < 0) { return CUBEB_ERROR; } - r = WRAP(snd_pcm_hw_params_any)(pcm, hw_params); + r = snd_pcm_hw_params_any(pcm, hw_params); if (r < 0) { - WRAP(snd_pcm_close)(pcm); + snd_pcm_close(pcm); return CUBEB_ERROR; } - r = WRAP(snd_pcm_hw_params_get_rate)(hw_params, rate, &dir); + r = snd_pcm_hw_params_get_rate(hw_params, rate, &dir); if (r >= 0) { /* There is a default rate: use it. */ - WRAP(snd_pcm_close)(pcm); + snd_pcm_close(pcm); return CUBEB_OK; } /* Use a common rate, alsa may adjust it based on hw/etc. capabilities. */ *rate = 44100; - r = WRAP(snd_pcm_hw_params_set_rate_near)(pcm, hw_params, rate, NULL); + r = snd_pcm_hw_params_set_rate_near(pcm, hw_params, rate, NULL); if (r < 0) { - WRAP(snd_pcm_close)(pcm); + snd_pcm_close(pcm); return CUBEB_ERROR; } - WRAP(snd_pcm_close)(pcm); + snd_pcm_close(pcm); return CUBEB_OK; } @@ -1253,19 +1034,8 @@ alsa_stream_start(cubeb_stream * stm) assert(stm); ctx = stm->context; - if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) { - int r = alsa_stream_start(stm->other_stream); - if (r != CUBEB_OK) - return r; - } - pthread_mutex_lock(&stm->mutex); - /* Capture pcm must be started after initial setup/recover */ - if (stm->stream_type == SND_PCM_STREAM_CAPTURE && - WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) { - WRAP(snd_pcm_start)(stm->pcm); - } - WRAP(snd_pcm_pause)(stm->pcm, 0); + snd_pcm_pause(stm->pcm, 0); gettimeofday(&stm->last_activity, NULL); pthread_mutex_unlock(&stm->mutex); @@ -1289,12 +1059,6 @@ alsa_stream_stop(cubeb_stream * stm) assert(stm); ctx = stm->context; - if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) { - int r = alsa_stream_stop(stm->other_stream); - if (r != CUBEB_OK) - return r; - } - pthread_mutex_lock(&ctx->mutex); while (stm->state == PROCESSING) { r = pthread_cond_wait(&stm->cond, &ctx->mutex); @@ -1305,7 +1069,7 @@ alsa_stream_stop(cubeb_stream * stm) pthread_mutex_unlock(&ctx->mutex); pthread_mutex_lock(&stm->mutex); - WRAP(snd_pcm_pause)(stm->pcm, 1); + snd_pcm_pause(stm->pcm, 1); pthread_mutex_unlock(&stm->mutex); return CUBEB_OK; @@ -1321,8 +1085,8 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position) pthread_mutex_lock(&stm->mutex); delay = -1; - if (WRAP(snd_pcm_state)(stm->pcm) != SND_PCM_STATE_RUNNING || - WRAP(snd_pcm_delay)(stm->pcm, &delay) != 0) { + if (snd_pcm_state(stm->pcm) != SND_PCM_STATE_RUNNING || + snd_pcm_delay(stm->pcm, &delay) != 0) { *position = stm->last_position; pthread_mutex_unlock(&stm->mutex); return CUBEB_OK; @@ -1331,8 +1095,8 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position) assert(delay >= 0); *position = 0; - if (stm->stream_position >= (snd_pcm_uframes_t) delay) { - *position = stm->stream_position - delay; + if (stm->write_position >= (snd_pcm_uframes_t) delay) { + *position = stm->write_position - delay; } stm->last_position = *position; @@ -1347,7 +1111,7 @@ alsa_stream_get_latency(cubeb_stream * stm, uint32_t * latency) snd_pcm_sframes_t delay; /* This function returns the delay in frames until a frame written using snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways. */ - if (WRAP(snd_pcm_delay)(stm->pcm, &delay)) { + if (snd_pcm_delay(stm->pcm, &delay)) { return CUBEB_ERROR; } @@ -1367,84 +1131,22 @@ alsa_stream_set_volume(cubeb_stream * stm, float volume) return CUBEB_OK; } -static int -alsa_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection * collection) -{ - cubeb_device_info* device = NULL; - - if (!context) - return CUBEB_ERROR; - - uint32_t rate, max_channels; - int r; - - r = alsa_get_preferred_sample_rate(context, &rate); - if (r != CUBEB_OK) { - return CUBEB_ERROR; - } - - r = alsa_get_max_channel_count(context, &max_channels); - if (r != CUBEB_OK) { - return CUBEB_ERROR; - } - - char const * a_name = "default"; - device = (cubeb_device_info *) calloc(1, sizeof(cubeb_device_info)); - assert(device); - if (!device) - return CUBEB_ERROR; - - device->device_id = a_name; - device->devid = (cubeb_devid) device->device_id; - device->friendly_name = a_name; - device->group_id = a_name; - device->vendor_name = a_name; - device->type = type; - device->state = CUBEB_DEVICE_STATE_ENABLED; - device->preferred = CUBEB_DEVICE_PREF_ALL; - device->format = CUBEB_DEVICE_FMT_S16NE; - device->default_format = CUBEB_DEVICE_FMT_S16NE; - device->max_channels = max_channels; - device->min_rate = rate; - device->max_rate = rate; - device->default_rate = rate; - device->latency_lo = 0; - device->latency_hi = 0; - - collection->device = device; - collection->count = 1; - - return CUBEB_OK; -} - -static int -alsa_device_collection_destroy(cubeb * context, - cubeb_device_collection * collection) -{ - assert(collection->count == 1); - (void) context; - free(collection->device); - return CUBEB_OK; -} - static struct cubeb_ops const alsa_ops = { .init = alsa_init, .get_backend_id = alsa_get_backend_id, .get_max_channel_count = alsa_get_max_channel_count, .get_min_latency = alsa_get_min_latency, .get_preferred_sample_rate = alsa_get_preferred_sample_rate, - .enumerate_devices = alsa_enumerate_devices, - .device_collection_destroy = alsa_device_collection_destroy, + .enumerate_devices = NULL, .destroy = alsa_destroy, .stream_init = alsa_stream_init, .stream_destroy = alsa_stream_destroy, .stream_start = alsa_stream_start, .stream_stop = alsa_stream_stop, - .stream_reset_default_device = NULL, .stream_get_position = alsa_stream_get_position, .stream_get_latency = alsa_stream_get_latency, .stream_set_volume = alsa_stream_set_volume, + .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, diff --git a/media/libcubeb/src/cubeb_array_queue.h b/media/libcubeb/src/cubeb_array_queue.h deleted file mode 100644 index a8ea4cd17..000000000 --- a/media/libcubeb/src/cubeb_array_queue.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright © 2016 Mozilla Foundation - * - * This program is made available under an ISC-style license. See the - * accompanying file LICENSE for details. - */ - -#ifndef CUBEB_ARRAY_QUEUE_H -#define CUBEB_ARRAY_QUEUE_H - -#include -#include -#include - -#if defined(__cplusplus) -extern "C" { -#endif - -typedef struct -{ - void ** buf; - size_t num; - size_t writePos; - size_t readPos; - pthread_mutex_t mutex; -} array_queue; - -array_queue * array_queue_create(size_t num) -{ - assert(num != 0); - array_queue * new_queue = (array_queue*)calloc(1, sizeof(array_queue)); - new_queue->buf = (void **)calloc(1, sizeof(void *) * num); - new_queue->readPos = 0; - new_queue->writePos = 0; - new_queue->num = num; - - pthread_mutex_init(&new_queue->mutex, NULL); - - return new_queue; -} - -void array_queue_destroy(array_queue * aq) -{ - assert(aq); - - free(aq->buf); - pthread_mutex_destroy(&aq->mutex); - free(aq); -} - -int array_queue_push(array_queue * aq, void * item) -{ - assert(item); - - pthread_mutex_lock(&aq->mutex); - int ret = -1; - if(aq->buf[aq->writePos % aq->num] == NULL) - { - aq->buf[aq->writePos % aq->num] = item; - aq->writePos = (aq->writePos + 1) % aq->num; - ret = 0; - } - // else queue is full - pthread_mutex_unlock(&aq->mutex); - return ret; -} - -void* array_queue_pop(array_queue * aq) -{ - pthread_mutex_lock(&aq->mutex); - void * value = aq->buf[aq->readPos % aq->num]; - if(value) - { - aq->buf[aq->readPos % aq->num] = NULL; - aq->readPos = (aq->readPos + 1) % aq->num; - } - pthread_mutex_unlock(&aq->mutex); - return value; -} - -size_t array_queue_get_size(array_queue * aq) -{ - pthread_mutex_lock(&aq->mutex); - ssize_t r = aq->writePos - aq->readPos; - if (r < 0) { - r = aq->num + r; - assert(r >= 0); - } - pthread_mutex_unlock(&aq->mutex); - return (size_t)r; -} - -#if defined(__cplusplus) -} -#endif - -#endif //CUBE_ARRAY_QUEUE_H diff --git a/media/libcubeb/src/cubeb_assert.h b/media/libcubeb/src/cubeb_assert.h deleted file mode 100644 index 00d48d8ec..000000000 --- a/media/libcubeb/src/cubeb_assert.h +++ /dev/null @@ -1,17 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* 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 CUBEB_ASSERT -#define CUBEB_ASSERT - -#include -#include -#include - -/* Forward fatal asserts to MOZ_RELEASE_ASSERT when built inside Gecko. */ -#define XASSERT(expr) MOZ_RELEASE_ASSERT(expr) - -#endif diff --git a/media/libcubeb/src/cubeb_audiotrack.c b/media/libcubeb/src/cubeb_audiotrack.c index 22f1fe0bc..fe2603405 100644 --- a/media/libcubeb/src/cubeb_audiotrack.c +++ b/media/libcubeb/src/cubeb_audiotrack.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include "android/log.h" #include "cubeb/cubeb.h" #include "cubeb-internal.h" @@ -75,14 +75,12 @@ struct cubeb { }; struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; - void * user_ptr; - /**/ cubeb_stream_params params; cubeb_data_callback data_callback; cubeb_state_callback state_callback; void * instance; + void * user_ptr; /* Number of frames that have been passed to the AudioTrack callback */ long unsigned written; int draining; @@ -147,9 +145,9 @@ audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * status_t status; /* Recent Android have a getMinFrameCount method. */ if (!audiotrack_version_is_gingerbread(ctx)) { - status = ctx->klass.get_min_frame_count(min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate); + status = ctx->klass.get_min_frame_count(min_frame_count, params->stream_type, params->rate); } else { - status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate); + status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, params->stream_type, params->rate); } if (status != 0) { ALOG("error getting the min frame count"); @@ -327,7 +325,7 @@ audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_ channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS : AUDIO_CHANNEL_OUT_MONO_ICS; } - ctx->klass.ctor(stm->instance, AUDIO_STREAM_TYPE_MUSIC, stm->params.rate, + ctx->klass.ctor(stm->instance, stm->params.stream_type, stm->params.rate, AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0, audiotrack_refill, stm, 0, 0); @@ -424,16 +422,15 @@ static struct cubeb_ops const audiotrack_ops = { .get_min_latency = audiotrack_get_min_latency, .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate, .enumerate_devices = NULL, - .device_collection_destroy = NULL, .destroy = audiotrack_destroy, .stream_init = audiotrack_stream_init, .stream_destroy = audiotrack_stream_destroy, .stream_start = audiotrack_stream_start, .stream_stop = audiotrack_stream_stop, - .stream_reset_default_device = NULL, .stream_get_position = audiotrack_stream_get_position, .stream_get_latency = audiotrack_stream_get_latency, .stream_set_volume = audiotrack_stream_set_volume, + .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp index e0c8fc696..9483c2795 100644 --- a/media/libcubeb/src/cubeb_audiounit.cpp +++ b/media/libcubeb/src/cubeb_audiounit.cpp @@ -22,243 +22,200 @@ #include #include "cubeb/cubeb.h" #include "cubeb-internal.h" -#include "cubeb_mixer.h" +#include "cubeb_panner.h" #if !TARGET_OS_IPHONE #include "cubeb_osx_run_loop.h" #endif #include "cubeb_resampler.h" #include "cubeb_ring_array.h" +#include "cubeb_utils.h" #include #include -#include -#include -#include -#include -using namespace std; +#if !defined(kCFCoreFoundationVersionNumber10_7) +/* From CoreFoundation CFBase.h */ +#define kCFCoreFoundationVersionNumber10_7 635.00 +#endif + +#if !TARGET_OS_IPHONE && MAC_OS_X_VERSION_MIN_REQUIRED < 1060 +#define AudioComponent Component +#define AudioComponentDescription ComponentDescription +#define AudioComponentFindNext FindNextComponent +#define AudioComponentInstanceNew OpenAComponent +#define AudioComponentInstanceDispose CloseComponent +#endif #if MAC_OS_X_VERSION_MIN_REQUIRED < 101000 -typedef UInt32 AudioFormatFlags; +typedef UInt32 AudioFormatFlags; #endif +#define CUBEB_STREAM_MAX 8 + #define AU_OUT_BUS 0 #define AU_IN_BUS 1 -const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb"; -const char * PRIVATE_AGGREGATE_DEVICE_NAME = "CubebAggregateDevice"; - -#ifdef ALOGV -#undef ALOGV -#endif -#define ALOGV(msg, ...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{LOGV(msg, ##__VA_ARGS__);}) +#define PRINT_ERROR_CODE(str, r) do { \ + LOG("System call failed: %s (rv: %d)", str, r); \ +} while(0) -#ifdef ALOG -#undef ALOG -#endif -#define ALOG(msg, ...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{LOG(msg, ##__VA_ARGS__);}) +const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb"; /* Testing empirically, some headsets report a minimal latency that is very * low, but this does not work in practice. Lie and say the minimum is 256 * frames. */ -const uint32_t SAFE_MIN_LATENCY_FRAMES = 128; +const uint32_t SAFE_MIN_LATENCY_FRAMES = 256; const uint32_t SAFE_MAX_LATENCY_FRAMES = 512; -const AudioObjectPropertyAddress DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS = { - kAudioHardwarePropertyDefaultInputDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster -}; - -const AudioObjectPropertyAddress DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS = { - kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster -}; - -const AudioObjectPropertyAddress DEVICE_IS_ALIVE_PROPERTY_ADDRESS = { - kAudioDevicePropertyDeviceIsAlive, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster -}; - -const AudioObjectPropertyAddress DEVICES_PROPERTY_ADDRESS = { - kAudioHardwarePropertyDevices, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster -}; - -const AudioObjectPropertyAddress INPUT_DATA_SOURCE_PROPERTY_ADDRESS = { - kAudioDevicePropertyDataSource, - kAudioDevicePropertyScopeInput, - kAudioObjectPropertyElementMaster -}; - -const AudioObjectPropertyAddress OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS = { - kAudioDevicePropertyDataSource, - kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMaster -}; - -typedef uint32_t device_flags_value; - -enum device_flags { - DEV_UNKNOWN = 0x00, /* Unknown */ - DEV_INPUT = 0x01, /* Record device like mic */ - DEV_OUTPUT = 0x02, /* Playback device like speakers */ - DEV_SYSTEM_DEFAULT = 0x04, /* System default device */ - DEV_SELECTED_DEFAULT = 0x08, /* User selected to use the system default device */ -}; - void audiounit_stream_stop_internal(cubeb_stream * stm); -static int audiounit_stream_start_internal(cubeb_stream * stm); +void audiounit_stream_start_internal(cubeb_stream * stm); static void audiounit_close_stream(cubeb_stream *stm); static int audiounit_setup_stream(cubeb_stream *stm); -static vector -audiounit_get_devices_of_type(cubeb_device_type devtype); -static UInt32 audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectPropertyScope scope); - -#if !TARGET_OS_IPHONE -static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type); -static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm); -static int audiounit_uninstall_system_changed_callback(cubeb_stream * stm); -static void audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags); -#endif extern cubeb_ops const audiounit_ops; struct cubeb { - cubeb_ops const * ops = &audiounit_ops; + cubeb_ops const * ops; owned_critical_section mutex; - int active_streams = 0; + std::atomic active_streams; uint32_t global_latency_frames = 0; - cubeb_device_collection_changed_callback input_collection_changed_callback = nullptr; - void * input_collection_changed_user_ptr = nullptr; - cubeb_device_collection_changed_callback output_collection_changed_callback = nullptr; - void * output_collection_changed_user_ptr = nullptr; - // Store list of devices to detect changes - vector input_device_array; - vector output_device_array; - // The queue should be released when it’s no longer needed. + int limit_streams; + cubeb_device_collection_changed_callback collection_changed_callback; + void * collection_changed_user_ptr; + /* Differentiate input from output devices. */ + cubeb_device_type collection_changed_devtype; + uint32_t devtype_device_count; + AudioObjectID * devtype_device_array; + // The queue is asynchronously deallocated once all references to it are released dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL); - // Current used channel layout - atomic layout{ CUBEB_LAYOUT_UNDEFINED }; - uint32_t channels = 0; }; -static unique_ptr -make_sized_audio_channel_layout(size_t sz) +class auto_array_wrapper { - assert(sz >= sizeof(AudioChannelLayout)); - AudioChannelLayout * acl = reinterpret_cast(calloc(1, sz)); - assert(acl); // Assert the allocation works. - return unique_ptr(acl, free); -} +public: + explicit auto_array_wrapper(auto_array * ar) + : float_ar(ar) + , short_ar(nullptr) + {assert((float_ar && !short_ar) || (!float_ar && short_ar));} -enum class io_side { - INPUT, - OUTPUT, -}; + explicit auto_array_wrapper(auto_array * ar) + : float_ar(nullptr) + , short_ar(ar) + {assert((float_ar && !short_ar) || (!float_ar && short_ar));} -static char const * -to_string(io_side side) -{ - switch (side) { - case io_side::INPUT: - return "input"; - case io_side::OUTPUT: - return "output"; + ~auto_array_wrapper() { + auto_lock l(lock); + assert((float_ar && !short_ar) || (!float_ar && short_ar)); + delete float_ar; + delete short_ar; } -} -struct device_info { - AudioDeviceID id = kAudioObjectUnknown; - device_flags_value flags = DEV_UNKNOWN; -}; + void push(void * elements, size_t length){ + assert((float_ar && !short_ar) || (!float_ar && short_ar)); + auto_lock l(lock); + if (float_ar) + return float_ar->push(static_cast(elements), length); + return short_ar->push(static_cast(elements), length); + } + + size_t length() { + assert((float_ar && !short_ar) || (!float_ar && short_ar)); + auto_lock l(lock); + if (float_ar) + return float_ar->length(); + return short_ar->length(); + } + + void push_silence(size_t length) { + assert((float_ar && !short_ar) || (!float_ar && short_ar)); + auto_lock l(lock); + if (float_ar) + return float_ar->push_silence(length); + return short_ar->push_silence(length); + } + + bool pop(void * elements, size_t length) { + assert((float_ar && !short_ar) || (!float_ar && short_ar)); + auto_lock l(lock); + if (float_ar) + return float_ar->pop(static_cast(elements), length); + return short_ar->pop(static_cast(elements), length); + } + + void * data() { + assert((float_ar && !short_ar) || (!float_ar && short_ar)); + auto_lock l(lock); + if (float_ar) + return float_ar->data(); + return short_ar->data(); + } -struct property_listener { - AudioDeviceID device_id; - const AudioObjectPropertyAddress * property_address; - AudioObjectPropertyListenerProc callback; - cubeb_stream * stream; - - property_listener(AudioDeviceID id, - const AudioObjectPropertyAddress * address, - AudioObjectPropertyListenerProc proc, - cubeb_stream * stm) - : device_id(id) - , property_address(address) - , callback(proc) - , stream(stm) - {} + void clear() { + assert((float_ar && !short_ar) || (!float_ar && short_ar)); + auto_lock l(lock); + if (float_ar) { + float_ar->clear(); + } else { + short_ar->clear(); + } + } + +private: + auto_array * float_ar; + auto_array * short_ar; + owned_critical_section lock; }; struct cubeb_stream { - explicit cubeb_stream(cubeb * context); - - /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; - void * user_ptr = nullptr; - /**/ - - cubeb_data_callback data_callback = nullptr; - cubeb_state_callback state_callback = nullptr; - cubeb_device_changed_callback device_changed_callback = nullptr; - owned_critical_section device_changed_callback_lock; + cubeb_data_callback data_callback; + cubeb_state_callback state_callback; + cubeb_device_changed_callback device_changed_callback; /* Stream creation parameters */ - cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; - cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; - device_info input_device; - device_info output_device; + cubeb_stream_params input_stream_params; + cubeb_stream_params output_stream_params; + cubeb_devid input_device; + bool is_default_input; + cubeb_devid output_device; + /* User pointer of data_callback */ + void * user_ptr; /* Format descriptions */ AudioStreamBasicDescription input_desc; AudioStreamBasicDescription output_desc; /* I/O AudioUnits */ - AudioUnit input_unit = nullptr; - AudioUnit output_unit = nullptr; + AudioUnit input_unit; + AudioUnit output_unit; /* I/O device sample rate */ - Float64 input_hw_rate = 0; - Float64 output_hw_rate = 0; + Float64 input_hw_rate; + Float64 output_hw_rate; /* Expected I/O thread interleave, * calculated from I/O hw rate. */ - int expected_output_callbacks_in_a_row = 0; + int expected_output_callbacks_in_a_row; owned_critical_section mutex; - // Hold the input samples in every input callback iteration. - // Only accessed on input/output callback thread and during initial configure. - unique_ptr input_linear_buffer; + /* Hold the input samples in every + * input callback iteration */ + auto_array_wrapper * input_linear_buffer; + /* Frames on input buffer */ + std::atomic input_buffer_frames; /* Frame counters */ - atomic frames_played{ 0 }; - uint64_t frames_queued = 0; - // How many frames got read from the input since the stream started (includes - // padded silence) - atomic frames_read{ 0 }; - // How many frames got written to the output device since the stream started - atomic frames_written{ 0 }; - atomic shutdown{ true }; - atomic draining{ false }; - atomic reinit_pending { false }; - atomic destroy_pending{ false }; + uint64_t frames_played; + uint64_t frames_queued; + std::atomic frames_read; + std::atomic shutdown; + std::atomic draining; /* Latency requested by the user. */ - uint32_t latency_frames = 0; - atomic current_latency_frames{ 0 }; - atomic total_output_latency_frames { 0 }; - unique_ptr resampler; + uint32_t latency_frames; + std::atomic current_latency_frames; + uint64_t hw_latency_frames; + std::atomic panning; + cubeb_resampler * resampler; + /* This is the number of output callback we got in a row. This is usually one, + * but can be two when the input and output rate are different, and more when + * a device has been plugged or unplugged, as there can be some time before + * the device is ready. */ + std::atomic output_callback_in_a_row; /* This is true if a device change callback is currently running. */ - atomic switching_device{ false }; - atomic buffer_size_change_state{ false }; - AudioDeviceID aggregate_device_id = kAudioObjectUnknown; // the aggregate device id - AudioObjectID plugin_id = kAudioObjectUnknown; // used to create aggregate device - /* Mixer interface */ - unique_ptr mixer; - /* Buffer where remixing/resampling will occur when upmixing is required */ - /* Only accessed from callback thread */ - unique_ptr temp_buffer; - size_t temp_buffer_size = 0; // size in bytes. - /* Listeners indicating what system events are monitored. */ - unique_ptr default_input_listener; - unique_ptr default_output_listener; - unique_ptr input_alive_listener; - unique_ptr input_source_listener; - unique_ptr output_source_listener; + std::atomic switching_device; + std::atomic buffer_size_change_state{ false }; }; bool has_input(cubeb_stream * stm) @@ -271,106 +228,14 @@ bool has_output(cubeb_stream * stm) return stm->output_stream_params.rate != 0; } -cubeb_channel -channel_label_to_cubeb_channel(UInt32 label) -{ - switch (label) { - case kAudioChannelLabel_Left: - return CHANNEL_FRONT_LEFT; - case kAudioChannelLabel_Right: - return CHANNEL_FRONT_RIGHT; - case kAudioChannelLabel_Center: - return CHANNEL_FRONT_CENTER; - case kAudioChannelLabel_LFEScreen: - return CHANNEL_LOW_FREQUENCY; - case kAudioChannelLabel_LeftSurround: - return CHANNEL_BACK_LEFT; - case kAudioChannelLabel_RightSurround: - return CHANNEL_BACK_RIGHT; - case kAudioChannelLabel_LeftCenter: - return CHANNEL_FRONT_LEFT_OF_CENTER; - case kAudioChannelLabel_RightCenter: - return CHANNEL_FRONT_RIGHT_OF_CENTER; - case kAudioChannelLabel_CenterSurround: - return CHANNEL_BACK_CENTER; - case kAudioChannelLabel_LeftSurroundDirect: - return CHANNEL_SIDE_LEFT; - case kAudioChannelLabel_RightSurroundDirect: - return CHANNEL_SIDE_RIGHT; - case kAudioChannelLabel_TopCenterSurround: - return CHANNEL_TOP_CENTER; - case kAudioChannelLabel_VerticalHeightLeft: - return CHANNEL_TOP_FRONT_LEFT; - case kAudioChannelLabel_VerticalHeightCenter: - return CHANNEL_TOP_FRONT_CENTER; - case kAudioChannelLabel_VerticalHeightRight: - return CHANNEL_TOP_FRONT_RIGHT; - case kAudioChannelLabel_TopBackLeft: - return CHANNEL_TOP_BACK_LEFT; - case kAudioChannelLabel_TopBackCenter: - return CHANNEL_TOP_BACK_CENTER; - case kAudioChannelLabel_TopBackRight: - return CHANNEL_TOP_BACK_RIGHT; - default: - return CHANNEL_UNKNOWN; - } -} - -AudioChannelLabel -cubeb_channel_to_channel_label(cubeb_channel channel) -{ - switch (channel) { - case CHANNEL_FRONT_LEFT: - return kAudioChannelLabel_Left; - case CHANNEL_FRONT_RIGHT: - return kAudioChannelLabel_Right; - case CHANNEL_FRONT_CENTER: - return kAudioChannelLabel_Center; - case CHANNEL_LOW_FREQUENCY: - return kAudioChannelLabel_LFEScreen; - case CHANNEL_BACK_LEFT: - return kAudioChannelLabel_LeftSurround; - case CHANNEL_BACK_RIGHT: - return kAudioChannelLabel_RightSurround; - case CHANNEL_FRONT_LEFT_OF_CENTER: - return kAudioChannelLabel_LeftCenter; - case CHANNEL_FRONT_RIGHT_OF_CENTER: - return kAudioChannelLabel_RightCenter; - case CHANNEL_BACK_CENTER: - return kAudioChannelLabel_CenterSurround; - case CHANNEL_SIDE_LEFT: - return kAudioChannelLabel_LeftSurroundDirect; - case CHANNEL_SIDE_RIGHT: - return kAudioChannelLabel_RightSurroundDirect; - case CHANNEL_TOP_CENTER: - return kAudioChannelLabel_TopCenterSurround; - case CHANNEL_TOP_FRONT_LEFT: - return kAudioChannelLabel_VerticalHeightLeft; - case CHANNEL_TOP_FRONT_CENTER: - return kAudioChannelLabel_VerticalHeightCenter; - case CHANNEL_TOP_FRONT_RIGHT: - return kAudioChannelLabel_VerticalHeightRight; - case CHANNEL_TOP_BACK_LEFT: - return kAudioChannelLabel_TopBackLeft; - case CHANNEL_TOP_BACK_CENTER: - return kAudioChannelLabel_TopBackCenter; - case CHANNEL_TOP_BACK_RIGHT: - return kAudioChannelLabel_TopBackRight; - default: - return kAudioChannelLabel_Unknown; - } -} - #if TARGET_OS_IPHONE typedef UInt32 AudioDeviceID; typedef UInt32 AudioObjectID; #define AudioGetCurrentHostTime mach_absolute_time -#endif - uint64_t -ConvertHostTimeToNanos(uint64_t host_time) +AudioConvertHostTimeToNanos(uint64_t host_time) { static struct mach_timebase_info timebase_info; static bool initialized = false; @@ -386,34 +251,27 @@ ConvertHostTimeToNanos(uint64_t host_time) } return (uint64_t)answer; } +#endif -static void -audiounit_increment_active_streams(cubeb * ctx) +static int64_t +audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream) { - ctx->mutex.assert_current_thread_owns(); - ctx->active_streams += 1; -} + if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) { + return 0; + } -static void -audiounit_decrement_active_streams(cubeb * ctx) -{ - ctx->mutex.assert_current_thread_owns(); - ctx->active_streams -= 1; -} + uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime); + uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); -static int -audiounit_active_streams(cubeb * ctx) -{ - ctx->mutex.assert_current_thread_owns(); - return ctx->active_streams; + return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL; } static void -audiounit_set_global_latency(cubeb * ctx, uint32_t latency_frames) +audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames) { - ctx->mutex.assert_current_thread_owns(); - assert(audiounit_active_streams(ctx) == 1); - ctx->global_latency_frames = latency_frames; + stm->mutex.assert_current_thread_owns(); + assert(stm->context->active_streams == 1); + stm->context->global_latency_frames = latency_frames; } static void @@ -448,38 +306,24 @@ audiounit_render_input(cubeb_stream * stm, &input_buffer_list); if (r != noErr) { - LOG("AudioUnitRender rv=%d", r); - if (r != kAudioUnitErr_CannotDoInCurrentContext) { - return r; - } - if (stm->output_unit) { - // kAudioUnitErr_CannotDoInCurrentContext is returned when using a BT - // headset and the profile is changed from A2DP to HFP/HSP. The previous - // output device is no longer valid and must be reset. - audiounit_reinit_stream_async(stm, DEV_INPUT | DEV_OUTPUT); - } - // For now state that no error occurred and feed silence, stream will be - // resumed once reinit has completed. - ALOGV("(%p) input: reinit pending feeding silence instead", stm); - stm->input_linear_buffer->push_silence(input_frames * stm->input_desc.mChannelsPerFrame); - } else { - /* Copy input data in linear buffer. */ - stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData, - input_frames * stm->input_desc.mChannelsPerFrame); + PRINT_ERROR_CODE("AudioUnitRender", r); + return r; } + /* Copy input data in linear buffer. */ + stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData, + input_frames * stm->input_desc.mChannelsPerFrame); + + LOGV("(%p) input: buffers %d, size %d, channels %d, frames %d.", + stm, input_buffer_list.mNumberBuffers, + input_buffer_list.mBuffers[0].mDataByteSize, + input_buffer_list.mBuffers[0].mNumberChannels, + input_frames); + /* Advance input frame counter. */ assert(input_frames > 0); stm->frames_read += input_frames; - ALOGV("(%p) input: buffers %u, size %u, channels %u, rendered frames %d, total frames %lu.", - stm, - (unsigned int) input_buffer_list.mNumberBuffers, - (unsigned int) input_buffer_list.mBuffers[0].mDataByteSize, - (unsigned int) input_buffer_list.mBuffers[0].mNumberChannels, - (unsigned int) input_frames, - stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame); - return noErr; } @@ -492,15 +336,26 @@ audiounit_input_callback(void * user_ptr, AudioBufferList * /* bufs */) { cubeb_stream * stm = static_cast(user_ptr); + long outframes; assert(stm->input_unit != NULL); assert(AU_IN_BUS == bus); if (stm->shutdown) { - ALOG("(%p) input shutdown", stm); + LOG("(%p) input shutdown", stm); return noErr; } + // This happens when we're finally getting a new input callback after having + // switched device, we can clear the input buffer now, only keeping the data + // we just got. + if (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row) { + stm->input_linear_buffer->pop( + nullptr, + stm->input_linear_buffer->length() - + input_frames * stm->input_stream_params.channels); + } + OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames); if (r != noErr) { return r; @@ -508,6 +363,7 @@ audiounit_input_callback(void * user_ptr, // Full Duplex. We'll call data_callback in the AudioUnit output callback. if (stm->output_unit != NULL) { + stm->output_callback_in_a_row = 0; return noErr; } @@ -515,59 +371,41 @@ audiounit_input_callback(void * user_ptr, Resampler will deliver input buffer in the correct rate. */ assert(input_frames <= stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame); long total_input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame; - long outframes = cubeb_resampler_fill(stm->resampler.get(), - stm->input_linear_buffer->data(), - &total_input_frames, - NULL, - 0); - if (outframes < total_input_frames) { - OSStatus r = AudioOutputUnitStop(stm->input_unit); - assert(r == 0); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); - return noErr; - } - + outframes = cubeb_resampler_fill(stm->resampler, + stm->input_linear_buffer->data(), + &total_input_frames, + NULL, + 0); // Reset input buffer stm->input_linear_buffer->clear(); + if (outframes < 0 || outframes != input_frames) { + stm->shutdown = true; + return noErr; + } + return noErr; } -static void -audiounit_mix_output_buffer(cubeb_stream * stm, - size_t output_frames, - void * input_buffer, - size_t input_buffer_size, - void * output_buffer, - size_t output_buffer_size) -{ - assert(input_buffer_size >= - cubeb_sample_size(stm->output_stream_params.format) * - stm->output_stream_params.channels * output_frames); - assert(output_buffer_size >= stm->output_desc.mBytesPerFrame * output_frames); - - int r = cubeb_mixer_mix(stm->mixer.get(), - output_frames, - input_buffer, - input_buffer_size, - output_buffer, - output_buffer_size); - if (r != 0) { - LOG("Remix error = %d", r); - } -} - -// Return how many input frames (sampled at input_hw_rate) are needed to provide -// output_frames (sampled at output_stream_params.rate) -static int64_t -minimum_resampling_input_frames(cubeb_stream * stm, uint32_t output_frames) +static bool +is_extra_input_needed(cubeb_stream * stm) { - if (stm->input_hw_rate == stm->output_stream_params.rate) { - // Fast path. - return output_frames; - } - return ceil(stm->input_hw_rate * output_frames / - stm->output_stream_params.rate); + /* If the output callback came first and this is a duplex stream, we need to + * fill in some additional silence in the resampler. + * Otherwise, if we had more than expected callbacks in a row, or we're currently + * switching, we add some silence as well to compensate for the fact that + * we're lacking some input data. */ + + /* If resampling is taking place after every output callback + * the input buffer expected to be empty. Any frame left over + * from resampling is stored inside the resampler available to + * be used in next iteration as needed. + * BUT when noop_resampler is operating we have left over + * frames since it does not store anything internally. */ + return stm->frames_read == 0 || + (stm->input_linear_buffer->length() == 0 && + (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row || + stm->switching_device)); } static OSStatus @@ -583,32 +421,23 @@ audiounit_output_callback(void * user_ptr, cubeb_stream * stm = static_cast(user_ptr); - uint64_t now = ConvertHostTimeToNanos(mach_absolute_time()); - uint64_t audio_output_time = ConvertHostTimeToNanos(tstamp->mHostTime); - uint64_t output_latency_ns = audio_output_time - now; - - const int ns2s = 1e9; - // The total output latency is the timestamp difference + the stream latency + - // the hardware latency. - stm->total_output_latency_frames = output_latency_ns * stm->output_hw_rate / ns2s + stm->current_latency_frames; + stm->output_callback_in_a_row++; - ALOGV("(%p) output: buffers %u, size %u, channels %u, frames %u, total input frames %lu.", - stm, - (unsigned int) outBufferList->mNumberBuffers, - (unsigned int) outBufferList->mBuffers[0].mDataByteSize, - (unsigned int) outBufferList->mBuffers[0].mNumberChannels, - (unsigned int) output_frames, - has_input(stm) ? stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame : 0); + LOGV("(%p) output: buffers %d, size %d, channels %d, frames %d.", + stm, outBufferList->mNumberBuffers, + outBufferList->mBuffers[0].mDataByteSize, + outBufferList->mBuffers[0].mNumberChannels, output_frames); - long input_frames = 0; + long outframes = 0, input_frames = 0; void * output_buffer = NULL, * input_buffer = NULL; if (stm->shutdown) { - ALOG("(%p) output shutdown.", stm); + LOG("(%p) output shutdown.", stm); audiounit_make_silent(&outBufferList->mBuffers[0]); return noErr; } + stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm); if (stm->draining) { OSStatus r = AudioOutputUnitStop(stm->output_unit); assert(r == 0); @@ -620,98 +449,61 @@ audiounit_output_callback(void * user_ptr, audiounit_make_silent(&outBufferList->mBuffers[0]); return noErr; } - /* Get output buffer. */ - if (stm->mixer) { - // If remixing needs to occur, we can't directly work in our final - // destination buffer as data may be overwritten or too small to start with. - size_t size_needed = output_frames * stm->output_stream_params.channels * - cubeb_sample_size(stm->output_stream_params.format); - if (stm->temp_buffer_size < size_needed) { - stm->temp_buffer.reset(new uint8_t[size_needed]); - stm->temp_buffer_size = size_needed; - } - output_buffer = stm->temp_buffer.get(); - } else { - output_buffer = outBufferList->mBuffers[0].mData; - } - - stm->frames_written += output_frames; - + output_buffer = outBufferList->mBuffers[0].mData; /* If Full duplex get also input buffer */ if (stm->input_unit != NULL) { - /* If the output callback came first and this is a duplex stream, we need to - * fill in some additional silence in the resampler. - * Otherwise, if we had more than expected callbacks in a row, or we're - * currently switching, we add some silence as well to compensate for the - * fact that we're lacking some input data. */ - uint32_t input_frames_needed = - minimum_resampling_input_frames(stm, stm->frames_written); - long missing_frames = input_frames_needed - stm->frames_read; - if (missing_frames > 0) { - stm->input_linear_buffer->push_silence(missing_frames * stm->input_desc.mChannelsPerFrame); - stm->frames_read = input_frames_needed; - - ALOG("(%p) %s pushed %ld frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," : - stm->switching_device ? "Device switching," : "Drop out,", missing_frames); - } + if (is_extra_input_needed(stm)) { + uint32_t min_input_frames_required = ceilf(stm->input_hw_rate / stm->output_hw_rate * + stm->input_buffer_frames); + stm->input_linear_buffer->push_silence(min_input_frames_required * stm->input_desc.mChannelsPerFrame); + LOG("(%p) %s pushed %u frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," : + stm->switching_device ? "Device switching," : "Drop out,", min_input_frames_required); + } + // The input buffer input_buffer = stm->input_linear_buffer->data(); - // Number of input frames in the buffer. It will change to actually used frames - // inside fill + // Number of input frames in the buffer input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame; } /* Call user callback through resampler. */ - long outframes = cubeb_resampler_fill(stm->resampler.get(), - input_buffer, - input_buffer ? &input_frames : NULL, - output_buffer, - output_frames); + outframes = cubeb_resampler_fill(stm->resampler, + input_buffer, + input_buffer ? &input_frames : NULL, + output_buffer, + output_frames); if (input_buffer) { - // Pop from the buffer the frames used by the the resampler. - stm->input_linear_buffer->pop(input_frames * stm->input_desc.mChannelsPerFrame); + stm->input_linear_buffer->pop(nullptr, input_frames * stm->input_desc.mChannelsPerFrame); } - if (outframes < 0 || outframes > output_frames) { + if (outframes < 0) { stm->shutdown = true; - OSStatus r = AudioOutputUnitStop(stm->output_unit); - assert(r == 0); - if (stm->input_unit) { - r = AudioOutputUnitStop(stm->input_unit); - assert(r == 0); - } - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - audiounit_make_silent(&outBufferList->mBuffers[0]); return noErr; } - stm->draining = (UInt32) outframes < output_frames; + size_t outbpf = stm->output_desc.mBytesPerFrame; + stm->draining = outframes < output_frames; stm->frames_played = stm->frames_queued; stm->frames_queued += outframes; + AudioFormatFlags outaff = stm->output_desc.mFormatFlags; + float panning = (stm->output_desc.mChannelsPerFrame == 2) ? + stm->panning.load(std::memory_order_relaxed) : 0.0f; + /* Post process output samples. */ if (stm->draining) { /* Clear missing frames (silence) */ - size_t channels = stm->output_stream_params.channels; - size_t missing_samples = (output_frames - outframes) * channels; - size_t size_sample = cubeb_sample_size(stm->output_stream_params.format); - /* number of bytes that have been filled with valid audio by the callback. */ - size_t audio_byte_count = outframes * channels * size_sample; - PodZero((uint8_t*)output_buffer + audio_byte_count, - missing_samples * size_sample); + memset((uint8_t*)output_buffer + outframes * outbpf, 0, (output_frames - outframes) * outbpf); } - - /* Mixing */ - if (stm->mixer) { - audiounit_mix_output_buffer(stm, - output_frames, - output_buffer, - stm->temp_buffer_size, - outBufferList->mBuffers[0].mData, - outBufferList->mBuffers[0].mDataByteSize); + /* Pan stereo. */ + if (panning != 0.0f) { + if (outaff & kAudioFormatFlagIsFloat) { + cubeb_pan_stereo_buffer_float((float*)output_buffer, outframes, panning); + } else if (outaff & kAudioFormatFlagIsSignedInteger) { + cubeb_pan_stereo_buffer_int((short*)output_buffer, outframes, panning); + } } - return noErr; } @@ -719,11 +511,25 @@ extern "C" { int audiounit_init(cubeb ** context, char const * /* context_name */) { + cubeb * ctx; + + *context = NULL; + + ctx = (cubeb *)calloc(1, sizeof(cubeb)); + assert(ctx); + // Placement new to call the ctors of cubeb members. + new (ctx) cubeb(); + + ctx->ops = &audiounit_ops; + + ctx->active_streams = 0; + + ctx->limit_streams = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber10_7; #if !TARGET_OS_IPHONE cubeb_set_coreaudio_notification_runloop(); #endif - *context = new cubeb; + *context = ctx; return CUBEB_OK; } @@ -736,233 +542,146 @@ audiounit_get_backend_id(cubeb * /* ctx */) } #if !TARGET_OS_IPHONE - -static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); -static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); - static int -audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side) +audiounit_get_output_device_id(AudioDeviceID * device_id) { - assert(stm); + UInt32 size; + OSStatus r; + AudioObjectPropertyAddress output_device_address = { + kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; - device_info * info = nullptr; - cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN; + size = sizeof(*device_id); - if (side == io_side::INPUT) { - info = &stm->input_device; - type = CUBEB_DEVICE_TYPE_INPUT; - } else if (side == io_side::OUTPUT) { - info = &stm->output_device; - type = CUBEB_DEVICE_TYPE_OUTPUT; + r = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &output_device_address, + 0, + NULL, + &size, + device_id); + if (r != noErr) { + PRINT_ERROR_CODE("output_device_id", r); + return CUBEB_ERROR; } - memset(info, 0, sizeof(device_info)); - info->id = id; - if (side == io_side::INPUT) { - info->flags |= DEV_INPUT; - } else if (side == io_side::OUTPUT) { - info->flags |= DEV_OUTPUT; - } + return CUBEB_OK; +} - AudioDeviceID default_device_id = audiounit_get_default_device_id(type); - if (default_device_id == kAudioObjectUnknown) { - return CUBEB_ERROR; - } - if (id == kAudioObjectUnknown) { - info->id = default_device_id; - info->flags |= DEV_SELECTED_DEFAULT; - } +static int +audiounit_get_input_device_id(AudioDeviceID * device_id) +{ + UInt32 size; + OSStatus r; + AudioObjectPropertyAddress input_device_address = { + kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; - if (info->id == default_device_id) { - info->flags |= DEV_SYSTEM_DEFAULT; - } + size = sizeof(*device_id); - assert(info->id); - assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) || - !(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT); + r = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &input_device_address, + 0, + NULL, + &size, + device_id); + if (r != noErr) { + return CUBEB_ERROR; + } return CUBEB_OK; } +static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); +static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); +static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm); static int -audiounit_reinit_stream(cubeb_stream * stm, device_flags_value flags) +audiounit_reinit_stream(cubeb_stream * stm) { auto_lock context_lock(stm->context->mutex); - assert((flags & DEV_INPUT && stm->input_unit) || - (flags & DEV_OUTPUT && stm->output_unit)); if (!stm->shutdown) { audiounit_stream_stop_internal(stm); } int r = audiounit_uninstall_device_changed_callback(stm); if (r != CUBEB_OK) { - LOG("(%p) Could not uninstall all device change listeners.", stm); + LOG("(%p) Could not uninstall the device changed callback", stm); } { auto_lock lock(stm->mutex); float volume = 0.0; - int vol_rv = CUBEB_ERROR; - if (stm->output_unit) { - vol_rv = audiounit_stream_get_volume(stm, &volume); - } + int vol_rv = audiounit_stream_get_volume(stm, &volume); audiounit_close_stream(stm); - /* Reinit occurs in one of the following case: - * - When the device is not alive any more - * - When the default system device change. - * - The bluetooth device changed from A2DP to/from HFP/HSP profile - * We first attempt to re-use the same device id, should that fail we will - * default to the (potentially new) default device. */ - AudioDeviceID input_device = flags & DEV_INPUT ? stm->input_device.id : kAudioObjectUnknown; - if (flags & DEV_INPUT) { - r = audiounit_set_device_info(stm, input_device, io_side::INPUT); - if (r != CUBEB_OK) { - LOG("(%p) Set input device info failed. This can happen when last media device is unplugged", stm); - return CUBEB_ERROR; - } - } - - /* Always use the default output on reinit. This is not correct in every - * case but it is sufficient for Firefox and prevent reinit from reporting - * failures. It will change soon when reinit mechanism will be updated. */ - r = audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::OUTPUT); - if (r != CUBEB_OK) { - LOG("(%p) Set output device info failed. This can happen when last media device is unplugged", stm); - return CUBEB_ERROR; - } - if (audiounit_setup_stream(stm) != CUBEB_OK) { LOG("(%p) Stream reinit failed.", stm); - if (flags & DEV_INPUT && input_device != kAudioObjectUnknown) { - // Attempt to re-use the same device-id failed, so attempt again with - // default input device. - audiounit_close_stream(stm); - if (audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::INPUT) != CUBEB_OK || - audiounit_setup_stream(stm) != CUBEB_OK) { - LOG("(%p) Second stream reinit failed.", stm); - return CUBEB_ERROR; - } - } + return CUBEB_ERROR; } if (vol_rv == CUBEB_OK) { audiounit_stream_set_volume(stm, volume); } + // Reset input frames to force new stream pre-buffer + // silence if needed, check `is_extra_input_needed()` + stm->frames_read = 0; + // If the stream was running, start it again. if (!stm->shutdown) { - r = audiounit_stream_start_internal(stm); - if (r != CUBEB_OK) { - return CUBEB_ERROR; - } + audiounit_stream_start_internal(stm); } } return CUBEB_OK; } -static void -audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags) -{ - if (std::atomic_exchange(&stm->reinit_pending, true)) { - // A reinit task is already pending, nothing more to do. - ALOG("(%p) re-init stream task already pending, cancelling request", stm); - return; - } - - // Use a new thread, through the queue, to avoid deadlock when calling - // Get/SetProperties method from inside notify callback - dispatch_async(stm->context->serial_queue, ^() { - if (stm->destroy_pending) { - ALOG("(%p) stream pending destroy, cancelling reinit task", stm); - return; - } - - if (audiounit_reinit_stream(stm, flags) != CUBEB_OK) { - if (audiounit_uninstall_system_changed_callback(stm) != CUBEB_OK) { - LOG("(%p) Could not uninstall system changed callback", stm); - } - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - LOG("(%p) Could not reopen the stream after switching.", stm); - } - stm->switching_device = false; - stm->reinit_pending = false; - }); -} - -static char const * -event_addr_to_string(AudioObjectPropertySelector selector) -{ - switch(selector) { - case kAudioHardwarePropertyDefaultOutputDevice: - return "kAudioHardwarePropertyDefaultOutputDevice"; - case kAudioHardwarePropertyDefaultInputDevice: - return "kAudioHardwarePropertyDefaultInputDevice"; - case kAudioDevicePropertyDeviceIsAlive: - return "kAudioDevicePropertyDeviceIsAlive"; - case kAudioDevicePropertyDataSource: - return "kAudioDevicePropertyDataSource"; - default: - return "Unknown"; - } -} - static OSStatus -audiounit_property_listener_callback(AudioObjectID id, UInt32 address_count, +audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count, const AudioObjectPropertyAddress * addresses, void * user) { cubeb_stream * stm = (cubeb_stream*) user; - if (stm->switching_device) { - LOG("Switching is already taking place. Skip Event %s for id=%d", event_addr_to_string(addresses[0].mSelector), id); - return noErr; - } stm->switching_device = true; - LOG("(%p) Audio device changed, %u events.", stm, (unsigned int) address_count); + LOG("(%p) Audio device changed, %d events.", stm, address_count); for (UInt32 i = 0; i < address_count; i++) { switch(addresses[i].mSelector) { case kAudioHardwarePropertyDefaultOutputDevice: { - LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice for id=%d", (unsigned int) i, id); + LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i); + // Allow restart to choose the new default + stm->output_device = nullptr; } break; case kAudioHardwarePropertyDefaultInputDevice: { - LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice for id=%d", (unsigned int) i, id); + LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultInputDevice", i); + // Allow restart to choose the new default + stm->input_device = nullptr; } break; case kAudioDevicePropertyDeviceIsAlive: { - LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive for id=%d", (unsigned int) i, id); + LOG("Event[%d] - mSelector == kAudioDevicePropertyDeviceIsAlive", i); // If this is the default input device ignore the event, // kAudioHardwarePropertyDefaultInputDevice will take care of the switch - if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) { + if (stm->is_default_input) { LOG("It's the default input device, ignore the event"); - stm->switching_device = false; return noErr; } + // Allow restart to choose the new default. Event register only for input. + stm->input_device = nullptr; } break; case kAudioDevicePropertyDataSource: { - LOG("Event[%u] - mSelector == kAudioDevicePropertyDataSource for id=%d", (unsigned int) i, id); + LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i); + return noErr; } - break; - default: - LOG("Event[%u] - mSelector == Unexpected Event id %d, return", (unsigned int) i, addresses[i].mSelector); - stm->switching_device = false; - return noErr; } } - // Allow restart to choose the new default - device_flags_value switch_side = DEV_UNKNOWN; - if (has_input(stm)) { - switch_side |= DEV_INPUT; - } - if (has_output(stm)) { - switch_side |= DEV_OUTPUT; - } - for (UInt32 i = 0; i < address_count; i++) { switch(addresses[i].mSelector) { case kAudioHardwarePropertyDefaultOutputDevice: @@ -970,7 +689,7 @@ audiounit_property_listener_callback(AudioObjectID id, UInt32 address_count, case kAudioDevicePropertyDeviceIsAlive: /* fall through */ case kAudioDevicePropertyDataSource: { - auto_lock dev_cb_lock(stm->device_changed_callback_lock); + auto_lock lock(stm->mutex); if (stm->device_changed_callback) { stm->device_changed_callback(stm->user_ptr); } @@ -979,77 +698,99 @@ audiounit_property_listener_callback(AudioObjectID id, UInt32 address_count, } } - audiounit_reinit_stream_async(stm, switch_side); + // Use a new thread, through the queue, to avoid deadlock when calling + // Get/SetProperties method from inside notify callback + dispatch_async(stm->context->serial_queue, ^() { + if (audiounit_reinit_stream(stm) != CUBEB_OK) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + LOG("(%p) Could not reopen the stream after switching.", stm); + } + stm->switching_device = false; + }); return noErr; } OSStatus -audiounit_add_listener(const property_listener * listener) +audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector, + AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener) { - assert(listener); - return AudioObjectAddPropertyListener(listener->device_id, - listener->property_address, - listener->callback, - listener->stream); + AudioObjectPropertyAddress address = { + selector, + scope, + kAudioObjectPropertyElementMaster + }; + + return AudioObjectAddPropertyListener(id, &address, listener, stm); } OSStatus -audiounit_remove_listener(const property_listener * listener) -{ - assert(listener); - return AudioObjectRemovePropertyListener(listener->device_id, - listener->property_address, - listener->callback, - listener->stream); +audiounit_remove_listener(cubeb_stream * stm, AudioDeviceID id, + AudioObjectPropertySelector selector, + AudioObjectPropertyScope scope, + AudioObjectPropertyListenerProc listener) +{ + AudioObjectPropertyAddress address = { + selector, + scope, + kAudioObjectPropertyElementMaster + }; + + return AudioObjectRemovePropertyListener(id, &address, listener, stm); } +static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type); + static int audiounit_install_device_changed_callback(cubeb_stream * stm) { - OSStatus rv; - int r = CUBEB_OK; + OSStatus r; if (stm->output_unit) { /* This event will notify us when the data source on the same device changes, * for example when the user plugs in a normal (non-usb) headset in the * headphone jack. */ - stm->output_source_listener.reset(new property_listener( - stm->output_device.id, &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS, - &audiounit_property_listener_callback, stm)); - rv = audiounit_add_listener(stm->output_source_listener.get()); - if (rv != noErr) { - stm->output_source_listener.reset(); - LOG("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->output_device.id); - r = CUBEB_ERROR; + AudioDeviceID output_dev_id; + r = audiounit_get_output_device_id(&output_dev_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); + if (r != noErr) { + PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource", r); + return CUBEB_ERROR; } } if (stm->input_unit) { /* This event will notify us when the data source on the input device changes. */ - stm->input_source_listener.reset(new property_listener( - stm->input_device.id, &INPUT_DATA_SOURCE_PROPERTY_ADDRESS, - &audiounit_property_listener_callback, stm)); - rv = audiounit_add_listener(stm->input_source_listener.get()); - if (rv != noErr) { - stm->input_source_listener.reset(); - LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->input_device.id); - r = CUBEB_ERROR; + AudioDeviceID input_dev_id; + r = audiounit_get_input_device_id(&input_dev_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); + if (r != noErr) { + PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource", r); + return CUBEB_ERROR; } /* Event to notify when the input is going away. */ - stm->input_alive_listener.reset(new property_listener( - stm->input_device.id, &DEVICE_IS_ALIVE_PROPERTY_ADDRESS, - &audiounit_property_listener_callback, stm)); - rv = audiounit_add_listener(stm->input_alive_listener.get()); - if (rv != noErr) { - stm->input_alive_listener.reset(); - LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d, device id =%d", rv, stm->input_device.id); - r = CUBEB_ERROR; + AudioDeviceID dev = stm->input_device ? reinterpret_cast(stm->input_device) : + audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); + r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + if (r != noErr) { + PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive", r); + return CUBEB_ERROR; } } - return r; + return CUBEB_OK; } static int @@ -1062,12 +803,9 @@ audiounit_install_system_changed_callback(cubeb_stream * stm) * for example when the user plugs in a USB headset and the system chooses it * automatically as the default, or when another device is chosen in the * dropdown list. */ - stm->default_output_listener.reset(new property_listener( - kAudioObjectSystemObject, &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS, - &audiounit_property_listener_callback, stm)); - r = audiounit_add_listener(stm->default_output_listener.get()); + r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { - stm->default_output_listener.reset(); LOG("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv=%d", r); return CUBEB_ERROR; } @@ -1075,12 +813,9 @@ audiounit_install_system_changed_callback(cubeb_stream * stm) if (stm->input_unit) { /* This event will notify us when the default input device changes. */ - stm->default_input_listener.reset(new property_listener( - kAudioObjectSystemObject, &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS, - &audiounit_property_listener_callback, stm)); - r = audiounit_add_listener(stm->default_input_listener.get()); + r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { - stm->default_input_listener.reset(); LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r); return CUBEB_ERROR; } @@ -1092,38 +827,36 @@ audiounit_install_system_changed_callback(cubeb_stream * stm) static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm) { - OSStatus rv; - // Failing to uninstall listeners is not a fatal error. - int r = CUBEB_OK; + OSStatus r; - if (stm->output_source_listener) { - rv = audiounit_remove_listener(stm->output_source_listener.get()); - if (rv != noErr) { - LOG("AudioObjectRemovePropertyListener/output/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->output_device.id); - r = CUBEB_ERROR; + if (stm->output_unit) { + AudioDeviceID output_dev_id; + r = audiounit_get_output_device_id(&output_dev_id); + if (r != noErr) { + return CUBEB_ERROR; } - stm->output_source_listener.reset(); - } - if (stm->input_source_listener) { - rv = audiounit_remove_listener(stm->input_source_listener.get()); - if (rv != noErr) { - LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->input_device.id); - r = CUBEB_ERROR; + r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); + if (r != noErr) { + return CUBEB_ERROR; } - stm->input_source_listener.reset(); } - if (stm->input_alive_listener) { - rv = audiounit_remove_listener(stm->input_alive_listener.get()); - if (rv != noErr) { - LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d, device id=%d", rv, stm->input_device.id); - r = CUBEB_ERROR; + if (stm->input_unit) { + AudioDeviceID input_dev_id; + r = audiounit_get_input_device_id(&input_dev_id); + if (r != noErr) { + return CUBEB_ERROR; } - stm->input_alive_listener.reset(); - } - return r; + r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); + if (r != noErr) { + return CUBEB_ERROR; + } + } + return CUBEB_OK; } static int @@ -1131,20 +864,20 @@ audiounit_uninstall_system_changed_callback(cubeb_stream * stm) { OSStatus r; - if (stm->default_output_listener) { - r = audiounit_remove_listener(stm->default_output_listener.get()); + if (stm->output_unit) { + r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { return CUBEB_ERROR; } - stm->default_output_listener.reset(); } - if (stm->default_input_listener) { - r = audiounit_remove_listener(stm->default_input_listener.get()); + if (stm->input_unit) { + r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { return CUBEB_ERROR; } - stm->default_input_listener.reset(); } return CUBEB_OK; } @@ -1162,8 +895,7 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) kAudioObjectPropertyElementMaster }; - output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); - if (output_device_id == kAudioObjectUnknown) { + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { LOG("Could not get default output device id."); return CUBEB_ERROR; } @@ -1178,7 +910,7 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) &size, latency_range); if (r != noErr) { - LOG("AudioObjectGetPropertyData/buffer size range rv=%d", r); + PRINT_ERROR_CODE("AudioObjectGetPropertyData/buffer size range", r); return CUBEB_ERROR; } @@ -1189,19 +921,20 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type) { - const AudioObjectPropertyAddress * adr; + AudioObjectPropertyAddress adr = { 0, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + AudioDeviceID devid; + UInt32 size; + if (type == CUBEB_DEVICE_TYPE_OUTPUT) { - adr = &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS; + adr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; } else if (type == CUBEB_DEVICE_TYPE_INPUT) { - adr = &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS; + adr.mSelector = kAudioHardwarePropertyDefaultInputDevice; } else { return kAudioObjectUnknown; } - AudioDeviceID devid; - UInt32 size = sizeof(AudioDeviceID); - if (AudioObjectGetPropertyData(kAudioObjectSystemObject, - adr, 0, NULL, &size, &devid) != noErr) { + size = sizeof(AudioDeviceID); + if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devid) != noErr) { return kAudioObjectUnknown; } @@ -1220,790 +953,181 @@ audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) AudioDeviceID output_device_id; AudioStreamBasicDescription stream_format; AudioObjectPropertyAddress stream_format_address = { - kAudioDevicePropertyStreamFormat, - kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMaster - }; - - assert(ctx && max_channels); - - output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); - if (output_device_id == kAudioObjectUnknown) { - return CUBEB_ERROR; - } - - size = sizeof(stream_format); - - r = AudioObjectGetPropertyData(output_device_id, - &stream_format_address, - 0, - NULL, - &size, - &stream_format); - if (r != noErr) { - LOG("AudioObjectPropertyAddress/StreamFormat rv=%d", r); - return CUBEB_ERROR; - } - - *max_channels = stream_format.mChannelsPerFrame; -#endif - return CUBEB_OK; -} - -static int -audiounit_get_min_latency(cubeb * /* ctx */, - cubeb_stream_params /* params */, - uint32_t * latency_frames) -{ -#if TARGET_OS_IPHONE - //TODO: [[AVAudioSession sharedInstance] inputLatency] - return CUBEB_ERROR_NOT_SUPPORTED; -#else - AudioValueRange latency_range; - if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) { - LOG("Could not get acceptable latency range."); - return CUBEB_ERROR; - } - - *latency_frames = max(latency_range.mMinimum, - SAFE_MIN_LATENCY_FRAMES); -#endif - - return CUBEB_OK; -} - -static int -audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate) -{ -#if TARGET_OS_IPHONE - //TODO - return CUBEB_ERROR_NOT_SUPPORTED; -#else - UInt32 size; - OSStatus r; - Float64 fsamplerate; - AudioDeviceID output_device_id; - AudioObjectPropertyAddress samplerate_address = { - kAudioDevicePropertyNominalSampleRate, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster - }; - - output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); - if (output_device_id == kAudioObjectUnknown) { - return CUBEB_ERROR; - } - - size = sizeof(fsamplerate); - r = AudioObjectGetPropertyData(output_device_id, - &samplerate_address, - 0, - NULL, - &size, - &fsamplerate); - - if (r != noErr) { - return CUBEB_ERROR; - } - - *rate = static_cast(fsamplerate); -#endif - return CUBEB_OK; -} - -static cubeb_channel_layout -audiounit_convert_channel_layout(AudioChannelLayout * layout) -{ - // When having one or two channel, force mono or stereo. Some devices (namely, - // Bose QC35, mark 1 and 2), expose a single channel mapped to the right for - // some reason. - if (layout->mNumberChannelDescriptions == 1) { - return CUBEB_LAYOUT_MONO; - } else if (layout->mNumberChannelDescriptions == 2) { - return CUBEB_LAYOUT_STEREO; - } - - if (layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions) { - // kAudioChannelLayoutTag_UseChannelBitmap - // kAudioChannelLayoutTag_Mono - // kAudioChannelLayoutTag_Stereo - // .... - LOG("Only handle UseChannelDescriptions for now.\n"); - return CUBEB_LAYOUT_UNDEFINED; - } - - cubeb_channel_layout cl = 0; - for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; ++i) { - cubeb_channel cc = channel_label_to_cubeb_channel( - layout->mChannelDescriptions[i].mChannelLabel); - if (cc == CHANNEL_UNKNOWN) { - return CUBEB_LAYOUT_UNDEFINED; - } - cl |= cc; - } - - return cl; -} - -static cubeb_channel_layout -audiounit_get_preferred_channel_layout(AudioUnit output_unit) -{ - OSStatus rv = noErr; - UInt32 size = 0; - rv = AudioUnitGetPropertyInfo(output_unit, - kAudioDevicePropertyPreferredChannelLayout, - kAudioUnitScope_Output, - AU_OUT_BUS, - &size, - nullptr); - if (rv != noErr) { - LOG("AudioUnitGetPropertyInfo/kAudioDevicePropertyPreferredChannelLayout rv=%d", rv); - return CUBEB_LAYOUT_UNDEFINED; - } - assert(size > 0); - - auto layout = make_sized_audio_channel_layout(size); - rv = AudioUnitGetProperty(output_unit, - kAudioDevicePropertyPreferredChannelLayout, - kAudioUnitScope_Output, - AU_OUT_BUS, - layout.get(), - &size); - if (rv != noErr) { - LOG("AudioUnitGetProperty/kAudioDevicePropertyPreferredChannelLayout rv=%d", rv); - return CUBEB_LAYOUT_UNDEFINED; - } - - return audiounit_convert_channel_layout(layout.get()); -} - -static cubeb_channel_layout -audiounit_get_current_channel_layout(AudioUnit output_unit) -{ - OSStatus rv = noErr; - UInt32 size = 0; - rv = AudioUnitGetPropertyInfo(output_unit, - kAudioUnitProperty_AudioChannelLayout, - kAudioUnitScope_Output, - AU_OUT_BUS, - &size, - nullptr); - if (rv != noErr) { - LOG("AudioUnitGetPropertyInfo/kAudioUnitProperty_AudioChannelLayout rv=%d", rv); - // This property isn't known before macOS 10.12, attempt another method. - return audiounit_get_preferred_channel_layout(output_unit); - } - assert(size > 0); - - auto layout = make_sized_audio_channel_layout(size); - rv = AudioUnitGetProperty(output_unit, - kAudioUnitProperty_AudioChannelLayout, - kAudioUnitScope_Output, - AU_OUT_BUS, - layout.get(), - &size); - if (rv != noErr) { - LOG("AudioUnitGetProperty/kAudioUnitProperty_AudioChannelLayout rv=%d", rv); - return CUBEB_LAYOUT_UNDEFINED; - } - - return audiounit_convert_channel_layout(layout.get()); -} - -static int audiounit_create_unit(AudioUnit * unit, device_info * device); - -static OSStatus audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype); - -static void -audiounit_destroy(cubeb * ctx) -{ - { - auto_lock lock(ctx->mutex); - - // Disabling this assert for bug 1083664 -- we seem to leak a stream - // assert(ctx->active_streams == 0); - if (audiounit_active_streams(ctx) > 0) { - LOG("(%p) API misuse, %d streams active when context destroyed!", ctx, audiounit_active_streams(ctx)); - } - - /* Unregister the callback if necessary. */ - if (ctx->input_collection_changed_callback) { - audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_INPUT); - } - if (ctx->output_collection_changed_callback) { - audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_OUTPUT); - } - } - - dispatch_release(ctx->serial_queue); - - delete ctx; -} - -static void audiounit_stream_destroy(cubeb_stream * stm); - -static int -audio_stream_desc_init(AudioStreamBasicDescription * ss, - const cubeb_stream_params * stream_params) -{ - switch (stream_params->format) { - case CUBEB_SAMPLE_S16LE: - ss->mBitsPerChannel = 16; - ss->mFormatFlags = kAudioFormatFlagIsSignedInteger; - break; - case CUBEB_SAMPLE_S16BE: - ss->mBitsPerChannel = 16; - ss->mFormatFlags = kAudioFormatFlagIsSignedInteger | - kAudioFormatFlagIsBigEndian; - break; - case CUBEB_SAMPLE_FLOAT32LE: - ss->mBitsPerChannel = 32; - ss->mFormatFlags = kAudioFormatFlagIsFloat; - break; - case CUBEB_SAMPLE_FLOAT32BE: - ss->mBitsPerChannel = 32; - ss->mFormatFlags = kAudioFormatFlagIsFloat | - kAudioFormatFlagIsBigEndian; - break; - default: - return CUBEB_ERROR_INVALID_FORMAT; - } - - ss->mFormatID = kAudioFormatLinearPCM; - ss->mFormatFlags |= kLinearPCMFormatFlagIsPacked; - ss->mSampleRate = stream_params->rate; - ss->mChannelsPerFrame = stream_params->channels; - - ss->mBytesPerFrame = (ss->mBitsPerChannel / 8) * ss->mChannelsPerFrame; - ss->mFramesPerPacket = 1; - ss->mBytesPerPacket = ss->mBytesPerFrame * ss->mFramesPerPacket; - - ss->mReserved = 0; - - return CUBEB_OK; -} - -void -audiounit_init_mixer(cubeb_stream * stm) -{ - // We can't rely on macOS' AudioUnit to properly downmix (or upmix) the audio - // data, it silently drop the channels so we need to remix the - // audio data by ourselves to keep all the information. - stm->mixer.reset(cubeb_mixer_create(stm->output_stream_params.format, - stm->output_stream_params.channels, - stm->output_stream_params.layout, - stm->context->channels, - stm->context->layout)); - assert(stm->mixer); -} - -static int -audiounit_set_channel_layout(AudioUnit unit, - io_side side, - cubeb_channel_layout layout) -{ - if (side != io_side::OUTPUT) { - return CUBEB_ERROR; - } - - if (layout == CUBEB_LAYOUT_UNDEFINED) { - // We leave everything as-is... - return CUBEB_OK; - } - - - OSStatus r; - uint32_t nb_channels = cubeb_channel_layout_nb_channels(layout); - - // We do not use CoreAudio standard layout for lack of documentation on what - // the actual channel orders are. So we set a custom layout. - size_t size = offsetof(AudioChannelLayout, mChannelDescriptions[nb_channels]); - auto au_layout = make_sized_audio_channel_layout(size); - au_layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions; - au_layout->mNumberChannelDescriptions = nb_channels; - - uint32_t channels = 0; - cubeb_channel_layout channelMap = layout; - for (uint32_t i = 0; channelMap != 0; ++i) { - XASSERT(channels < nb_channels); - uint32_t channel = (channelMap & 1) << i; - if (channel != 0) { - au_layout->mChannelDescriptions[channels].mChannelLabel = - cubeb_channel_to_channel_label(static_cast(channel)); - au_layout->mChannelDescriptions[channels].mChannelFlags = kAudioChannelFlags_AllOff; - channels++; - } - channelMap = channelMap >> 1; - } - - r = AudioUnitSetProperty(unit, - kAudioUnitProperty_AudioChannelLayout, - kAudioUnitScope_Input, - AU_OUT_BUS, - au_layout.get(), - size); - if (r != noErr) { - LOG("AudioUnitSetProperty/%s/kAudioUnitProperty_AudioChannelLayout rv=%d", to_string(side), r); - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -void -audiounit_layout_init(cubeb_stream * stm, io_side side) -{ - // We currently don't support the input layout setting. - if (side == io_side::INPUT) { - return; - } - - stm->context->layout = audiounit_get_current_channel_layout(stm->output_unit); - - audiounit_set_channel_layout(stm->output_unit, io_side::OUTPUT, stm->context->layout); -} - -static vector -audiounit_get_sub_devices(AudioDeviceID device_id) -{ - vector sub_devices; - AudioObjectPropertyAddress property_address = { kAudioAggregateDevicePropertyActiveSubDeviceList, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - UInt32 size = 0; - OSStatus rv = AudioObjectGetPropertyDataSize(device_id, - &property_address, - 0, - nullptr, - &size); - - if (rv != noErr) { - sub_devices.push_back(device_id); - return sub_devices; - } - - uint32_t count = static_cast(size / sizeof(AudioObjectID)); - sub_devices.resize(count); - rv = AudioObjectGetPropertyData(device_id, - &property_address, - 0, - nullptr, - &size, - sub_devices.data()); - if (rv != noErr) { - sub_devices.clear(); - sub_devices.push_back(device_id); - } else { - LOG("Found %u sub-devices", count); - } - return sub_devices; -} - -static int -audiounit_create_blank_aggregate_device(AudioObjectID * plugin_id, AudioDeviceID * aggregate_device_id) -{ - AudioObjectPropertyAddress address_plugin_bundle_id = { kAudioHardwarePropertyPlugInForBundleID, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - UInt32 size = 0; - OSStatus r = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, - &address_plugin_bundle_id, - 0, NULL, - &size); - if (r != noErr) { - LOG("AudioObjectGetPropertyDataSize/kAudioHardwarePropertyPlugInForBundleID, rv=%d", r); - return CUBEB_ERROR; - } + kAudioDevicePropertyStreamFormat, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; - AudioValueTranslation translation_value; - CFStringRef in_bundle_ref = CFSTR("com.apple.audio.CoreAudio"); - translation_value.mInputData = &in_bundle_ref; - translation_value.mInputDataSize = sizeof(in_bundle_ref); - translation_value.mOutputData = plugin_id; - translation_value.mOutputDataSize = sizeof(*plugin_id); + assert(ctx && max_channels); - r = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &address_plugin_bundle_id, - 0, - nullptr, - &size, - &translation_value); - if (r != noErr) { - LOG("AudioObjectGetPropertyData/kAudioHardwarePropertyPlugInForBundleID, rv=%d", r); + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { return CUBEB_ERROR; } - AudioObjectPropertyAddress create_aggregate_device_address = { kAudioPlugInCreateAggregateDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - r = AudioObjectGetPropertyDataSize(*plugin_id, - &create_aggregate_device_address, - 0, - nullptr, - &size); - if (r != noErr) { - LOG("AudioObjectGetPropertyDataSize/kAudioPlugInCreateAggregateDevice, rv=%d", r); - return CUBEB_ERROR; - } + size = sizeof(stream_format); - CFMutableDictionaryRef aggregate_device_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - struct timeval timestamp; - gettimeofday(×tamp, NULL); - long long int time_id = timestamp.tv_sec * 1000000LL + timestamp.tv_usec; - CFStringRef aggregate_device_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s_%llx"), PRIVATE_AGGREGATE_DEVICE_NAME, time_id); - CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceNameKey), aggregate_device_name); - CFRelease(aggregate_device_name); - - CFStringRef aggregate_device_UID = CFStringCreateWithFormat(NULL, NULL, CFSTR("org.mozilla.%s_%llx"), PRIVATE_AGGREGATE_DEVICE_NAME, time_id); - CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceUIDKey), aggregate_device_UID); - CFRelease(aggregate_device_UID); - - int private_value = 1; - CFNumberRef aggregate_device_private_key = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &private_value); - CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceIsPrivateKey), aggregate_device_private_key); - CFRelease(aggregate_device_private_key); - - int stacked_value = 0; - CFNumberRef aggregate_device_stacked_key = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &stacked_value); - CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceIsStackedKey), aggregate_device_stacked_key); - CFRelease(aggregate_device_stacked_key); - - r = AudioObjectGetPropertyData(*plugin_id, - &create_aggregate_device_address, - sizeof(aggregate_device_dict), - &aggregate_device_dict, + r = AudioObjectGetPropertyData(output_device_id, + &stream_format_address, + 0, + NULL, &size, - aggregate_device_id); - CFRelease(aggregate_device_dict); + &stream_format); if (r != noErr) { - LOG("AudioObjectGetPropertyData/kAudioPlugInCreateAggregateDevice, rv=%d", r); + PRINT_ERROR_CODE("AudioObjectPropertyAddress/StreamFormat", r); return CUBEB_ERROR; } - LOG("New aggregate device %u", *aggregate_device_id); + *max_channels = stream_format.mChannelsPerFrame; +#endif return CUBEB_OK; } -// The returned CFStringRef object needs to be released (via CFRelease) -// if it's not NULL, since the reference count of the returned CFStringRef -// object is increased. -static CFStringRef -get_device_name(AudioDeviceID id) -{ - UInt32 size = sizeof(CFStringRef); - CFStringRef UIname = nullptr; - AudioObjectPropertyAddress address_uuid = { kAudioDevicePropertyDeviceUID, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - OSStatus err = AudioObjectGetPropertyData(id, &address_uuid, 0, nullptr, &size, &UIname); - return (err == noErr) ? UIname : NULL; -} - static int -audiounit_set_aggregate_sub_device_list(AudioDeviceID aggregate_device_id, - AudioDeviceID input_device_id, - AudioDeviceID output_device_id) -{ - LOG("Add devices input %u and output %u into aggregate device %u", - input_device_id, output_device_id, aggregate_device_id); - const vector output_sub_devices = audiounit_get_sub_devices(output_device_id); - const vector input_sub_devices = audiounit_get_sub_devices(input_device_id); - - CFMutableArrayRef aggregate_sub_devices_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); - /* The order of the items in the array is significant and is used to determine the order of the streams - of the AudioAggregateDevice. */ - for (UInt32 i = 0; i < output_sub_devices.size(); i++) { - CFStringRef ref = get_device_name(output_sub_devices[i]); - if (ref == NULL) { - CFRelease(aggregate_sub_devices_array); - return CUBEB_ERROR; - } - CFArrayAppendValue(aggregate_sub_devices_array, ref); - CFRelease(ref); - } - for (UInt32 i = 0; i < input_sub_devices.size(); i++) { - CFStringRef ref = get_device_name(input_sub_devices[i]); - if (ref == NULL) { - CFRelease(aggregate_sub_devices_array); - return CUBEB_ERROR; - } - CFArrayAppendValue(aggregate_sub_devices_array, ref); - CFRelease(ref); - } - - AudioObjectPropertyAddress aggregate_sub_device_list = { kAudioAggregateDevicePropertyFullSubDeviceList, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - UInt32 size = sizeof(CFMutableArrayRef); - OSStatus rv = AudioObjectSetPropertyData(aggregate_device_id, - &aggregate_sub_device_list, - 0, - nullptr, - size, - &aggregate_sub_devices_array); - CFRelease(aggregate_sub_devices_array); - if (rv != noErr) { - LOG("AudioObjectSetPropertyData/kAudioAggregateDevicePropertyFullSubDeviceList, rv=%d", rv); +audiounit_get_min_latency(cubeb * /* ctx */, + cubeb_stream_params /* params */, + uint32_t * latency_frames) +{ +#if TARGET_OS_IPHONE + //TODO: [[AVAudioSession sharedInstance] inputLatency] + return CUBEB_ERROR_NOT_SUPPORTED; +#else + AudioValueRange latency_range; + if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) { + LOG("Could not get acceptable latency range."); return CUBEB_ERROR; } - return CUBEB_OK; -} - -static int -audiounit_set_master_aggregate_device(const AudioDeviceID aggregate_device_id) -{ - assert(aggregate_device_id != kAudioObjectUnknown); - AudioObjectPropertyAddress master_aggregate_sub_device = { kAudioAggregateDevicePropertyMasterSubDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - - // Master become the 1st output sub device - AudioDeviceID output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); - const vector output_sub_devices = audiounit_get_sub_devices(output_device_id); - CFStringRef master_sub_device = get_device_name(output_sub_devices[0]); - - UInt32 size = sizeof(CFStringRef); - OSStatus rv = AudioObjectSetPropertyData(aggregate_device_id, - &master_aggregate_sub_device, - 0, - NULL, - size, - &master_sub_device); - if (master_sub_device) { - CFRelease(master_sub_device); - } - if (rv != noErr) { - LOG("AudioObjectSetPropertyData/kAudioAggregateDevicePropertyMasterSubDevice, rv=%d", rv); - return CUBEB_ERROR; - } + *latency_frames = std::max(latency_range.mMinimum, + SAFE_MIN_LATENCY_FRAMES); +#endif return CUBEB_OK; } static int -audiounit_activate_clock_drift_compensation(const AudioDeviceID aggregate_device_id) +audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate) { - assert(aggregate_device_id != kAudioObjectUnknown); - AudioObjectPropertyAddress address_owned = { kAudioObjectPropertyOwnedObjects, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; +#if TARGET_OS_IPHONE + //TODO + return CUBEB_ERROR_NOT_SUPPORTED; +#else + UInt32 size; + OSStatus r; + Float64 fsamplerate; + AudioDeviceID output_device_id; + AudioObjectPropertyAddress samplerate_address = { + kAudioDevicePropertyNominalSampleRate, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; - UInt32 qualifier_data_size = sizeof(AudioObjectID); - AudioClassID class_id = kAudioSubDeviceClassID; - void * qualifier_data = &class_id; - UInt32 size = 0; - OSStatus rv = AudioObjectGetPropertyDataSize(aggregate_device_id, - &address_owned, - qualifier_data_size, - qualifier_data, - &size); - if (rv != noErr) { - LOG("AudioObjectGetPropertyDataSize/kAudioObjectPropertyOwnedObjects, rv=%d", rv); + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { return CUBEB_ERROR; } - UInt32 subdevices_num = 0; - subdevices_num = size / sizeof(AudioObjectID); - AudioObjectID sub_devices[subdevices_num]; - size = sizeof(sub_devices); + size = sizeof(fsamplerate); + r = AudioObjectGetPropertyData(output_device_id, + &samplerate_address, + 0, + NULL, + &size, + &fsamplerate); - rv = AudioObjectGetPropertyData(aggregate_device_id, - &address_owned, - qualifier_data_size, - qualifier_data, - &size, - sub_devices); - if (rv != noErr) { - LOG("AudioObjectGetPropertyData/kAudioObjectPropertyOwnedObjects, rv=%d", rv); + if (r != noErr) { return CUBEB_ERROR; } - AudioObjectPropertyAddress address_drift = { kAudioSubDevicePropertyDriftCompensation, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - - // Start from the second device since the first is the master clock - for (UInt32 i = 1; i < subdevices_num; ++i) { - UInt32 drift_compensation_value = 1; - rv = AudioObjectSetPropertyData(sub_devices[i], - &address_drift, - 0, - nullptr, - sizeof(UInt32), - &drift_compensation_value); - if (rv != noErr) { - LOG("AudioObjectSetPropertyData/kAudioSubDevicePropertyDriftCompensation, rv=%d", rv); - return CUBEB_OK; - } - } + *rate = static_cast(fsamplerate); +#endif return CUBEB_OK; } -static int audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id); -static void audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope scope, - uint32_t * min, uint32_t * max, uint32_t * def); -static int -audiounit_create_device_from_hwdev(cubeb_device_info * dev_info, AudioObjectID devid, cubeb_device_type type); -static void audiounit_device_destroy(cubeb_device_info * device); +static OSStatus audiounit_remove_device_listener(cubeb * context); static void -audiounit_workaround_for_airpod(cubeb_stream * stm) -{ - cubeb_device_info input_device_info; - audiounit_create_device_from_hwdev(&input_device_info, stm->input_device.id, CUBEB_DEVICE_TYPE_INPUT); - - cubeb_device_info output_device_info; - audiounit_create_device_from_hwdev(&output_device_info, stm->output_device.id, CUBEB_DEVICE_TYPE_OUTPUT); - - std::string input_name_str(input_device_info.friendly_name); - std::string output_name_str(output_device_info.friendly_name); - - if(input_name_str.find("AirPods") != std::string::npos && - output_name_str.find("AirPods") != std::string::npos) { - uint32_t input_min_rate = 0; - uint32_t input_max_rate = 0; - uint32_t input_nominal_rate = 0; - audiounit_get_available_samplerate(stm->input_device.id, kAudioObjectPropertyScopeGlobal, - &input_min_rate, &input_max_rate, &input_nominal_rate); - LOG("(%p) Input device %u, name: %s, min: %u, max: %u, nominal rate: %u", stm, stm->input_device.id - , input_device_info.friendly_name, input_min_rate, input_max_rate, input_nominal_rate); - uint32_t output_min_rate = 0; - uint32_t output_max_rate = 0; - uint32_t output_nominal_rate = 0; - audiounit_get_available_samplerate(stm->output_device.id, kAudioObjectPropertyScopeGlobal, - &output_min_rate, &output_max_rate, &output_nominal_rate); - LOG("(%p) Output device %u, name: %s, min: %u, max: %u, nominal rate: %u", stm, stm->output_device.id - , output_device_info.friendly_name, output_min_rate, output_max_rate, output_nominal_rate); - - Float64 rate = input_nominal_rate; - AudioObjectPropertyAddress addr = {kAudioDevicePropertyNominalSampleRate, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - - OSStatus rv = AudioObjectSetPropertyData(stm->aggregate_device_id, - &addr, - 0, - nullptr, - sizeof(Float64), - &rate); - if (rv != noErr) { - LOG("Non fatal error, AudioObjectSetPropertyData/kAudioDevicePropertyNominalSampleRate, rv=%d", rv); +audiounit_destroy(cubeb * ctx) +{ + // Disabling this assert for bug 1083664 -- we seem to leak a stream + // assert(ctx->active_streams == 0); + + { + auto_lock lock(ctx->mutex); + /* Unregister the callback if necessary. */ + if(ctx->collection_changed_callback) { + audiounit_remove_device_listener(ctx); } } - audiounit_device_destroy(&input_device_info); - audiounit_device_destroy(&output_device_info); + + ctx->~cubeb(); + free(ctx); } -/* - * Aggregate Device is a virtual audio interface which utilizes inputs and outputs - * of one or more physical audio interfaces. It is possible to use the clock of - * one of the devices as a master clock for all the combined devices and enable - * drift compensation for the devices that are not designated clock master. - * - * Creating a new aggregate device programmatically requires [0][1]: - * 1. Locate the base plug-in ("com.apple.audio.CoreAudio") - * 2. Create a dictionary that describes the aggregate device - * (don't add sub-devices in that step, prone to fail [0]) - * 3. Ask the base plug-in to create the aggregate device (blank) - * 4. Add the array of sub-devices. - * 5. Set the master device (1st output device in our case) - * 6. Enable drift compensation for the non-master devices - * - * [0] https://lists.apple.com/archives/coreaudio-api/2006/Apr/msg00092.html - * [1] https://lists.apple.com/archives/coreaudio-api/2005/Jul/msg00150.html - * [2] CoreAudio.framework/Headers/AudioHardware.h - * */ +static void audiounit_stream_destroy(cubeb_stream * stm); + static int -audiounit_create_aggregate_device(cubeb_stream * stm) +audio_stream_desc_init(AudioStreamBasicDescription * ss, + const cubeb_stream_params * stream_params) { - int r = audiounit_create_blank_aggregate_device(&stm->plugin_id, &stm->aggregate_device_id); - if (r != CUBEB_OK) { - LOG("(%p) Failed to create blank aggregate device", stm); - return CUBEB_ERROR; - } - - r = audiounit_set_aggregate_sub_device_list(stm->aggregate_device_id, stm->input_device.id, stm->output_device.id); - if (r != CUBEB_OK) { - LOG("(%p) Failed to set aggregate sub-device list", stm); - audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); - return CUBEB_ERROR; - } - - r = audiounit_set_master_aggregate_device(stm->aggregate_device_id); - if (r != CUBEB_OK) { - LOG("(%p) Failed to set master sub-device for aggregate device", stm); - audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); - return CUBEB_ERROR; - } - - r = audiounit_activate_clock_drift_compensation(stm->aggregate_device_id); - if (r != CUBEB_OK) { - LOG("(%p) Failed to activate clock drift compensation for aggregate device", stm); - audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); - return CUBEB_ERROR; + switch (stream_params->format) { + case CUBEB_SAMPLE_S16LE: + ss->mBitsPerChannel = 16; + ss->mFormatFlags = kAudioFormatFlagIsSignedInteger; + break; + case CUBEB_SAMPLE_S16BE: + ss->mBitsPerChannel = 16; + ss->mFormatFlags = kAudioFormatFlagIsSignedInteger | + kAudioFormatFlagIsBigEndian; + break; + case CUBEB_SAMPLE_FLOAT32LE: + ss->mBitsPerChannel = 32; + ss->mFormatFlags = kAudioFormatFlagIsFloat; + break; + case CUBEB_SAMPLE_FLOAT32BE: + ss->mBitsPerChannel = 32; + ss->mFormatFlags = kAudioFormatFlagIsFloat | + kAudioFormatFlagIsBigEndian; + break; + default: + return CUBEB_ERROR_INVALID_FORMAT; } - audiounit_workaround_for_airpod(stm); - - return CUBEB_OK; -} + ss->mFormatID = kAudioFormatLinearPCM; + ss->mFormatFlags |= kLinearPCMFormatFlagIsPacked; + ss->mSampleRate = stream_params->rate; + ss->mChannelsPerFrame = stream_params->channels; -static int -audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id) -{ - assert(aggregate_device_id && - *aggregate_device_id != kAudioDeviceUnknown && - plugin_id != kAudioObjectUnknown); - AudioObjectPropertyAddress destroy_aggregate_device_addr = { kAudioPlugInDestroyAggregateDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - UInt32 size; - OSStatus rv = AudioObjectGetPropertyDataSize(plugin_id, - &destroy_aggregate_device_addr, - 0, - NULL, - &size); - if (rv != noErr) { - LOG("AudioObjectGetPropertyDataSize/kAudioPlugInDestroyAggregateDevice, rv=%d", rv); - return CUBEB_ERROR; - } + ss->mBytesPerFrame = (ss->mBitsPerChannel / 8) * ss->mChannelsPerFrame; + ss->mFramesPerPacket = 1; + ss->mBytesPerPacket = ss->mBytesPerFrame * ss->mFramesPerPacket; - rv = AudioObjectGetPropertyData(plugin_id, - &destroy_aggregate_device_addr, - 0, - NULL, - &size, - aggregate_device_id); - if (rv != noErr) { - LOG("AudioObjectGetPropertyData/kAudioPlugInDestroyAggregateDevice, rv=%d", rv); - return CUBEB_ERROR; - } + ss->mReserved = 0; - LOG("Destroyed aggregate device %d", *aggregate_device_id); - *aggregate_device_id = kAudioObjectUnknown; return CUBEB_OK; } static int -audiounit_new_unit_instance(AudioUnit * unit, device_info * device) +audiounit_create_unit(AudioUnit * unit, + bool is_input, + const cubeb_stream_params * /* stream_params */, + cubeb_devid device) { AudioComponentDescription desc; AudioComponent comp; + UInt32 enable; + AudioDeviceID devid; OSStatus rv; desc.componentType = kAudioUnitType_Output; #if TARGET_OS_IPHONE + bool use_default_output = false; desc.componentSubType = kAudioUnitSubType_RemoteIO; #else // Use the DefaultOutputUnit for output when no device is specified // so we retain automatic output device switching when the default // changes. Once we have complete support for device notifications // and switching, we can use the AUHAL for everything. - if ((device->flags & DEV_SYSTEM_DEFAULT) && - (device->flags & DEV_OUTPUT)) { + bool use_default_output = device == NULL && !is_input; + if (use_default_output) { desc.componentSubType = kAudioUnitSubType_DefaultOutput; } else { desc.componentSubType = kAudioUnitSubType_HALOutput; @@ -2020,118 +1144,96 @@ audiounit_new_unit_instance(AudioUnit * unit, device_info * device) rv = AudioComponentInstanceNew(comp, unit); if (rv != noErr) { - LOG("AudioComponentInstanceNew rv=%d", rv); + PRINT_ERROR_CODE("AudioComponentInstanceNew", rv); return CUBEB_ERROR; } - return CUBEB_OK; -} -enum enable_state { - DISABLE, - ENABLE, -}; + if (!use_default_output) { + enable = 1; + rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO, + is_input ? kAudioUnitScope_Input : kAudioUnitScope_Output, + is_input ? AU_IN_BUS : AU_OUT_BUS, &enable, sizeof(UInt32)); + if (rv != noErr) { + PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO", rv); + return CUBEB_ERROR; + } -static int -audiounit_enable_unit_scope(AudioUnit * unit, io_side side, enable_state state) -{ - OSStatus rv; - UInt32 enable = state; - rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO, - (side == io_side::INPUT) ? kAudioUnitScope_Input : kAudioUnitScope_Output, - (side == io_side::INPUT) ? AU_IN_BUS : AU_OUT_BUS, - &enable, - sizeof(UInt32)); - if (rv != noErr) { - LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO rv=%d", rv); - return CUBEB_ERROR; + enable = 0; + rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO, + is_input ? kAudioUnitScope_Output : kAudioUnitScope_Input, + is_input ? AU_OUT_BUS : AU_IN_BUS, &enable, sizeof(UInt32)); + if (rv != noErr) { + PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO", rv); + return CUBEB_ERROR; + } + + if (device == NULL) { + assert(is_input); + devid = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); + } else { + devid = reinterpret_cast(device); + } + rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + is_input ? AU_IN_BUS : AU_OUT_BUS, + &devid, sizeof(AudioDeviceID)); + if (rv != noErr) { + PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice", rv); + return CUBEB_ERROR; + } } + return CUBEB_OK; } static int -audiounit_create_unit(AudioUnit * unit, device_info * device) +audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity) { - assert(*unit == nullptr); - assert(device); - - OSStatus rv; - int r; - - r = audiounit_new_unit_instance(unit, device); - if (r != CUBEB_OK) { - return r; + if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) { + stream->input_linear_buffer = new auto_array_wrapper( + new auto_array(capacity * + stream->input_buffer_frames * + stream->input_desc.mChannelsPerFrame) ); + } else { + stream->input_linear_buffer = new auto_array_wrapper( + new auto_array(capacity * + stream->input_buffer_frames * + stream->input_desc.mChannelsPerFrame) ); } - assert(*unit); - if ((device->flags & DEV_SYSTEM_DEFAULT) && - (device->flags & DEV_OUTPUT)) { - return CUBEB_OK; + if (!stream->input_linear_buffer) { + return CUBEB_ERROR; } + assert(stream->input_linear_buffer->length() == 0); - if (device->flags & DEV_INPUT) { - r = audiounit_enable_unit_scope(unit, io_side::INPUT, ENABLE); - if (r != CUBEB_OK) { - LOG("Failed to enable audiounit input scope"); - return r; - } - r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, DISABLE); - if (r != CUBEB_OK) { - LOG("Failed to disable audiounit output scope"); - return r; - } - } else if (device->flags & DEV_OUTPUT) { - r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, ENABLE); - if (r != CUBEB_OK) { - LOG("Failed to enable audiounit output scope"); - return r; - } - r = audiounit_enable_unit_scope(unit, io_side::INPUT, DISABLE); - if (r != CUBEB_OK) { - LOG("Failed to disable audiounit input scope"); - return r; - } - } else { - assert(false); - } + // Pre-buffer silence if needed + if (capacity != 1) { + size_t silence_size = stream->input_buffer_frames * + stream->input_desc.mChannelsPerFrame; + stream->input_linear_buffer->push_silence(silence_size); - rv = AudioUnitSetProperty(*unit, - kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, - 0, - &device->id, sizeof(AudioDeviceID)); - if (rv != noErr) { - LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice rv=%d", rv); - return CUBEB_ERROR; + assert(stream->input_linear_buffer->length() == silence_size); } return CUBEB_OK; } -static int -audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity) +static void +audiounit_destroy_input_linear_buffer(cubeb_stream * stream) { - uint32_t size = capacity * stream->latency_frames * stream->input_desc.mChannelsPerFrame; - if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) { - stream->input_linear_buffer.reset(new auto_array_wrapper_impl(size)); - } else { - stream->input_linear_buffer.reset(new auto_array_wrapper_impl(size)); - } - assert(stream->input_linear_buffer->length() == 0); - - return CUBEB_OK; + delete stream->input_linear_buffer; } static uint32_t audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) { // For the 1st stream set anything within safe min-max - assert(audiounit_active_streams(stm->context) > 0); - if (audiounit_active_streams(stm->context) == 1) { - return max(min(latency_frames, SAFE_MAX_LATENCY_FRAMES), + assert(stm->context->active_streams > 0); + if (stm->context->active_streams == 1) { + return std::max(std::min(latency_frames, SAFE_MAX_LATENCY_FRAMES), SAFE_MIN_LATENCY_FRAMES); } - assert(stm->output_unit); // If more than one stream operates in parallel // allow only lower values of latency @@ -2146,11 +1248,11 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) &output_buffer_size, &size); if (r != noErr) { - LOG("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize rv=%d", r); + PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r); return 0; } - output_buffer_size = max(min(output_buffer_size, SAFE_MAX_LATENCY_FRAMES), + output_buffer_size = std::max(std::min(output_buffer_size, SAFE_MAX_LATENCY_FRAMES), SAFE_MIN_LATENCY_FRAMES); } @@ -2163,18 +1265,18 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) &input_buffer_size, &size); if (r != noErr) { - LOG("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize rv=%d", r); + PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r); return 0; } - input_buffer_size = max(min(input_buffer_size, SAFE_MAX_LATENCY_FRAMES), + input_buffer_size = std::max(std::min(input_buffer_size, SAFE_MAX_LATENCY_FRAMES), SAFE_MIN_LATENCY_FRAMES); } // Every following active streams can only set smaller latency UInt32 upper_latency_limit = 0; if (input_buffer_size != 0 && output_buffer_size != 0) { - upper_latency_limit = min(input_buffer_size, output_buffer_size); + upper_latency_limit = std::min(input_buffer_size, output_buffer_size); } else if (input_buffer_size != 0) { upper_latency_limit = input_buffer_size; } else if (output_buffer_size != 0) { @@ -2183,7 +1285,7 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) upper_latency_limit = SAFE_MAX_LATENCY_FRAMES; } - return max(min(latency_frames, upper_latency_limit), + return std::max(std::min(latency_frames, upper_latency_limit), SAFE_MIN_LATENCY_FRAMES); } @@ -2198,18 +1300,18 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) static void buffer_size_changed_callback(void * inClientData, AudioUnit inUnit, - AudioUnitPropertyID inPropertyID, - AudioUnitScope inScope, - AudioUnitElement inElement) + AudioUnitPropertyID inPropertyID, + AudioUnitScope inScope, + AudioUnitElement inElement) { cubeb_stream * stm = (cubeb_stream *)inClientData; AudioUnit au = inUnit; AudioUnitScope au_scope = kAudioUnitScope_Input; AudioUnitElement au_element = inElement; - char const * au_type = "output"; + const char * au_type = "output"; - if (AU_IN_BUS == inElement) { + if (au == stm->input_unit) { au_scope = kAudioUnitScope_Output; au_type = "input"; } @@ -2240,17 +1342,24 @@ buffer_size_changed_callback(void * inClientData, } } +enum set_buffer_size_side { + INPUT, + OUTPUT, +}; + static int -audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, io_side side) +audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side) { AudioUnit au = stm->output_unit; AudioUnitScope au_scope = kAudioUnitScope_Input; AudioUnitElement au_element = AU_OUT_BUS; + const char * au_type = "output"; - if (side == io_side::INPUT) { + if (set_side == INPUT) { au = stm->input_unit; au_scope = kAudioUnitScope_Output; au_element = AU_IN_BUS; + au_type = "input"; } uint32_t buffer_frames = 0; @@ -2262,12 +1371,16 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, io_side &buffer_frames, &size); if (r != noErr) { - LOG("AudioUnitGetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r); + if (set_side == INPUT) { + PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r); + } else { + PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r); + } return CUBEB_ERROR; } if (new_size_frames == buffer_frames) { - LOG("(%p) No need to update %s buffer size already %u frames", stm, to_string(side), buffer_frames); + LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames); return CUBEB_OK; } @@ -2276,7 +1389,11 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, io_side buffer_size_changed_callback, stm); if (r != noErr) { - LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r); + if (set_side == INPUT) { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); + } else { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); + } return CUBEB_ERROR; } @@ -2289,14 +1406,22 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, io_side &new_size_frames, sizeof(new_size_frames)); if (r != noErr) { - LOG("AudioUnitSetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r); + if (set_side == INPUT) { + PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r); + } else { + PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r); + } r = AudioUnitRemovePropertyListenerWithUserData(au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback, stm); if (r != noErr) { - LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r); + if (set_side == INPUT) { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); + } else { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); + } } return CUBEB_ERROR; @@ -2318,7 +1443,11 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, io_side buffer_size_changed_callback, stm); if (r != noErr) { - LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize rv=%d", to_string(side), r); + if (set_side == INPUT) { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); + } else { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); + } return CUBEB_ERROR; } @@ -2327,15 +1456,13 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, io_side return CUBEB_ERROR; } - LOG("(%p) %s buffer size changed to %u frames.", stm, to_string(side), new_size_frames); + LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames); return CUBEB_OK; } static int audiounit_configure_input(cubeb_stream * stm) { - assert(stm && stm->input_unit); - int r = 0; UInt32 size; AURenderCallbackStruct aurcbs_in; @@ -2354,7 +1481,7 @@ audiounit_configure_input(cubeb_stream * stm) &input_hw_desc, &size); if (r != noErr) { - LOG("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r); + PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r); return CUBEB_ERROR; } stm->input_hw_rate = input_hw_desc.mSampleRate; @@ -2368,7 +1495,8 @@ audiounit_configure_input(cubeb_stream * stm) } // Use latency to set buffer size - r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::INPUT); + stm->input_buffer_frames = stm->latency_frames; + r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT); if (r != CUBEB_OK) { LOG("(%p) Error in change input buffer size.", stm); return CUBEB_ERROR; @@ -2386,7 +1514,7 @@ audiounit_configure_input(cubeb_stream * stm) &src_desc, sizeof(AudioStreamBasicDescription)); if (r != noErr) { - LOG("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r); + PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r); return CUBEB_ERROR; } @@ -2395,10 +1523,10 @@ audiounit_configure_input(cubeb_stream * stm) kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, AU_IN_BUS, - &stm->latency_frames, + &stm->input_buffer_frames, sizeof(UInt32)); if (r != noErr) { - LOG("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice rv=%d", r); + PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r); return CUBEB_ERROR; } @@ -2412,6 +1540,7 @@ audiounit_configure_input(cubeb_stream * stm) return CUBEB_ERROR; } + assert(stm->input_unit != NULL); aurcbs_in.inputProc = audiounit_input_callback; aurcbs_in.inputProcRefCon = stm; @@ -2422,12 +1551,9 @@ audiounit_configure_input(cubeb_stream * stm) &aurcbs_in, sizeof(aurcbs_in)); if (r != noErr) { - LOG("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback rv=%d", r); + PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r); return CUBEB_ERROR; } - - stm->frames_read = 0; - LOG("(%p) Input audiounit init successfully.", stm); return CUBEB_OK; @@ -2436,8 +1562,6 @@ audiounit_configure_input(cubeb_stream * stm) static int audiounit_configure_output(cubeb_stream * stm) { - assert(stm && stm->output_unit); - int r; AURenderCallbackStruct aurcbs_out; UInt32 size; @@ -2464,30 +1588,11 @@ audiounit_configure_output(cubeb_stream * stm) &output_hw_desc, &size); if (r != noErr) { - LOG("AudioUnitGetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r); + PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r); return CUBEB_ERROR; } stm->output_hw_rate = output_hw_desc.mSampleRate; LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate); - stm->context->channels = output_hw_desc.mChannelsPerFrame; - - // Set the input layout to match the output device layout. - audiounit_layout_init(stm, io_side::OUTPUT); - if (stm->context->channels != stm->output_stream_params.channels || - stm->context->layout != stm->output_stream_params.layout) { - LOG("Incompatible channel layouts detected, setting up remixer"); - audiounit_init_mixer(stm); - // We will be remixing the data before it reaches the output device. - // We need to adjust the number of channels and other - // AudioStreamDescription details. - stm->output_desc.mChannelsPerFrame = stm->context->channels; - stm->output_desc.mBytesPerFrame = (stm->output_desc.mBitsPerChannel / 8) * - stm->output_desc.mChannelsPerFrame; - stm->output_desc.mBytesPerPacket = - stm->output_desc.mBytesPerFrame * stm->output_desc.mFramesPerPacket; - } else { - stm->mixer = nullptr; - } r = AudioUnitSetProperty(stm->output_unit, kAudioUnitProperty_StreamFormat, @@ -2496,11 +1601,11 @@ audiounit_configure_output(cubeb_stream * stm) &stm->output_desc, sizeof(AudioStreamBasicDescription)); if (r != noErr) { - LOG("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r); + PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r); return CUBEB_ERROR; } - r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::OUTPUT); + r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT); if (r != CUBEB_OK) { LOG("(%p) Error in change output buffer size.", stm); return CUBEB_ERROR; @@ -2514,10 +1619,11 @@ audiounit_configure_output(cubeb_stream * stm) &stm->latency_frames, sizeof(UInt32)); if (r != noErr) { - LOG("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice rv=%d", r); + PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r); return CUBEB_ERROR; } + assert(stm->output_unit != NULL); aurcbs_out.inputProc = audiounit_output_callback; aurcbs_out.inputProcRefCon = stm; r = AudioUnitSetProperty(stm->output_unit, @@ -2527,12 +1633,10 @@ audiounit_configure_output(cubeb_stream * stm) &aurcbs_out, sizeof(aurcbs_out)); if (r != noErr) { - LOG("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback rv=%d", r); + PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r); return CUBEB_ERROR; } - stm->frames_written = 0; - LOG("(%p) Output audiounit init successfully.", stm); return CUBEB_OK; } @@ -2542,37 +1646,11 @@ audiounit_setup_stream(cubeb_stream * stm) { stm->mutex.assert_current_thread_owns(); - if ((stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) || - (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK)) { - LOG("(%p) Loopback not supported for audiounit.", stm); - return CUBEB_ERROR_NOT_SUPPORTED; - } - int r = 0; - - device_info in_dev_info = stm->input_device; - device_info out_dev_info = stm->output_device; - - if (has_input(stm) && has_output(stm) && - stm->input_device.id != stm->output_device.id) { - r = audiounit_create_aggregate_device(stm); - if (r != CUBEB_OK) { - stm->aggregate_device_id = kAudioObjectUnknown; - LOG("(%p) Create aggregate devices failed.", stm); - // !!!NOTE: It is not necessary to return here. If it does not - // return it will fallback to the old implementation. The intention - // is to investigate how often it fails. I plan to remove - // it after a couple of weeks. - return r; - } else { - in_dev_info.id = out_dev_info.id = stm->aggregate_device_id; - in_dev_info.flags = DEV_INPUT; - out_dev_info.flags = DEV_OUTPUT; - } - } - if (has_input(stm)) { - r = audiounit_create_unit(&stm->input_unit, &in_dev_info); + r = audiounit_create_unit(&stm->input_unit, true, + &stm->input_stream_params, + stm->input_device); if (r != CUBEB_OK) { LOG("(%p) AudioUnit creation for input failed.", stm); return r; @@ -2580,7 +1658,9 @@ audiounit_setup_stream(cubeb_stream * stm) } if (has_output(stm)) { - r = audiounit_create_unit(&stm->output_unit, &out_dev_info); + r = audiounit_create_unit(&stm->output_unit, false, + &stm->output_stream_params, + stm->output_device); if (r != CUBEB_OK) { LOG("(%p) AudioUnit creation for output failed.", stm); return r; @@ -2588,20 +1668,20 @@ audiounit_setup_stream(cubeb_stream * stm) } /* Latency cannot change if another stream is operating in parallel. In this case - * latency is set to the other stream value. */ - if (audiounit_active_streams(stm->context) > 1) { + * latecy is set to the other stream value. */ + if (stm->context->active_streams > 1) { LOG("(%p) More than one active stream, use global latency.", stm); stm->latency_frames = stm->context->global_latency_frames; } else { /* Silently clamp the latency down to the platform default, because we - * synthetize the clock from the callbacks, and we want the clock to update - * often. */ + * synthetize the clock from the callbacks, and we want the clock to update + * often. */ stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames); - assert(stm->latency_frames); // Ugly error check - audiounit_set_global_latency(stm->context, stm->latency_frames); + assert(stm->latency_frames); // Ungly error check + audiounit_set_global_latency(stm, stm->latency_frames); } - /* Configure I/O stream */ + /* Setup Input Stream! */ if (has_input(stm)) { r = audiounit_configure_input(stm); if (r != CUBEB_OK) { @@ -2610,6 +1690,7 @@ audiounit_setup_stream(cubeb_stream * stm) } } + /* Setup Output Stream! */ if (has_output(stm)) { r = audiounit_configure_output(stm); if (r != CUBEB_OK) { @@ -2681,13 +1762,13 @@ audiounit_setup_stream(cubeb_stream * stm) /* Create resampler. Output params are unchanged * because we do not need conversion on the output. */ - stm->resampler.reset(cubeb_resampler_create(stm, - has_input(stm) ? &input_unconverted_params : NULL, - has_output(stm) ? &stm->output_stream_params : NULL, - target_sample_rate, - stm->data_callback, - stm->user_ptr, - CUBEB_RESAMPLER_QUALITY_DESKTOP)); + stm->resampler = cubeb_resampler_create(stm, + has_input(stm) ? &input_unconverted_params : NULL, + has_output(stm) ? &stm->output_stream_params : NULL, + target_sample_rate, + stm->data_callback, + stm->user_ptr, + CUBEB_RESAMPLER_QUALITY_DESKTOP); if (!stm->resampler) { LOG("(%p) Could not create resampler.", stm); return CUBEB_ERROR; @@ -2696,7 +1777,7 @@ audiounit_setup_stream(cubeb_stream * stm) if (stm->input_unit != NULL) { r = AudioUnitInitialize(stm->input_unit); if (r != noErr) { - LOG("AudioUnitInitialize/input rv=%d", r); + PRINT_ERROR_CODE("AudioUnitInitialize/input", r); return CUBEB_ERROR; } } @@ -2704,17 +1785,9 @@ audiounit_setup_stream(cubeb_stream * stm) if (stm->output_unit != NULL) { r = AudioUnitInitialize(stm->output_unit); if (r != noErr) { - LOG("AudioUnitInitialize/output rv=%d", r); + PRINT_ERROR_CODE("AudioUnitInitialize/output", r); return CUBEB_ERROR; } - - stm->current_latency_frames = audiounit_get_device_presentation_latency(stm->output_device.id, kAudioDevicePropertyScopeOutput); - - Float64 unit_s; - UInt32 size = sizeof(unit_s); - if (AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &unit_s, &size) == noErr) { - stm->current_latency_frames += static_cast(unit_s * stm->output_desc.mSampleRate); - } } if (stm->input_unit && stm->output_unit) { @@ -2726,23 +1799,12 @@ audiounit_setup_stream(cubeb_stream * stm) r = audiounit_install_device_changed_callback(stm); if (r != CUBEB_OK) { - LOG("(%p) Could not install all device change callback.", stm); + LOG("(%p) Could not install the device change callback.", stm); + return r; } - - return CUBEB_OK; -} - -cubeb_stream::cubeb_stream(cubeb * context) - : context(context) - , resampler(nullptr, cubeb_resampler_destroy) - , mixer(nullptr, cubeb_mixer_destroy) -{ - PodZero(&input_desc, 1); - PodZero(&output_desc, 1); -} - -static void audiounit_stream_destroy_internal(cubeb_stream * stm); + return CUBEB_OK; +} static int audiounit_stream_init(cubeb * context, @@ -2757,64 +1819,72 @@ audiounit_stream_init(cubeb * context, cubeb_state_callback state_callback, void * user_ptr) { - assert(context); - auto_lock context_lock(context->mutex); - audiounit_increment_active_streams(context); - unique_ptr stm(new cubeb_stream(context), - audiounit_stream_destroy_internal); + cubeb_stream * stm; int r; + + assert(context); *stream = NULL; + assert(latency_frames > 0); + if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) { + LOG("Reached the stream limit of %d", CUBEB_STREAM_MAX); + return CUBEB_ERROR; + } + + stm = (cubeb_stream *) calloc(1, sizeof(cubeb_stream)); + assert(stm); + // Placement new to call the ctors of cubeb_stream members. + new (stm) cubeb_stream(); /* These could be different in the future if we have both * full-duplex stream and different devices for input vs output. */ + stm->context = context; stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; stm->latency_frames = latency_frames; - - if ((input_device && !input_stream_params) || - (output_device && !output_stream_params)) { - return CUBEB_ERROR_INVALID_PARAMETER; - } + stm->device_changed_callback = NULL; if (input_stream_params) { stm->input_stream_params = *input_stream_params; - r = audiounit_set_device_info(stm.get(), reinterpret_cast(input_device), io_side::INPUT); - if (r != CUBEB_OK) { - LOG("(%p) Fail to set device info for input.", stm.get()); - return r; - } + stm->input_device = input_device; + stm->is_default_input = input_device == nullptr || + (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) == + reinterpret_cast(input_device)); } if (output_stream_params) { stm->output_stream_params = *output_stream_params; - r = audiounit_set_device_info(stm.get(), reinterpret_cast(output_device), io_side::OUTPUT); - if (r != CUBEB_OK) { - LOG("(%p) Fail to set device info for output.", stm.get()); - return r; - } + stm->output_device = output_device; } + /* Init data members where necessary */ + stm->hw_latency_frames = UINT64_MAX; + + stm->switching_device = false; + + auto_lock context_lock(context->mutex); { // It's not critical to lock here, because no other thread has been started // yet, but it allows to assert that the lock has been taken in // `audiounit_setup_stream`. + context->active_streams += 1; auto_lock lock(stm->mutex); - r = audiounit_setup_stream(stm.get()); + r = audiounit_setup_stream(stm); } if (r != CUBEB_OK) { - LOG("(%p) Could not setup the audiounit stream.", stm.get()); + LOG("(%p) Could not setup the audiounit stream.", stm); + audiounit_stream_destroy(stm); return r; } - r = audiounit_install_system_changed_callback(stm.get()); + r = audiounit_install_system_changed_callback(stm); if (r != CUBEB_OK) { - LOG("(%p) Could not install the device change callback.", stm.get()); + LOG("(%p) Could not install the device change callback.", stm); return r; } - *stream = stm.release(); - LOG("(%p) Cubeb stream init successful.", *stream); + *stream = stm; + LOG("Cubeb stream (%p) init successful.", stm); return CUBEB_OK; } @@ -2826,86 +1896,64 @@ audiounit_close_stream(cubeb_stream *stm) if (stm->input_unit) { AudioUnitUninitialize(stm->input_unit); AudioComponentInstanceDispose(stm->input_unit); - stm->input_unit = nullptr; } - stm->input_linear_buffer.reset(); + audiounit_destroy_input_linear_buffer(stm); if (stm->output_unit) { AudioUnitUninitialize(stm->output_unit); AudioComponentInstanceDispose(stm->output_unit); - stm->output_unit = nullptr; } - stm->resampler.reset(); - stm->mixer.reset(); - - if (stm->aggregate_device_id != kAudioObjectUnknown) { - audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); - stm->aggregate_device_id = kAudioObjectUnknown; - } + cubeb_resampler_destroy(stm->resampler); } static void -audiounit_stream_destroy_internal(cubeb_stream *stm) +audiounit_stream_destroy(cubeb_stream * stm) { - stm->context->mutex.assert_current_thread_owns(); + stm->shutdown = true; int r = audiounit_uninstall_system_changed_callback(stm); if (r != CUBEB_OK) { LOG("(%p) Could not uninstall the device changed callback", stm); } + r = audiounit_uninstall_device_changed_callback(stm); if (r != CUBEB_OK) { - LOG("(%p) Could not uninstall all device change listeners", stm); + LOG("(%p) Could not uninstall the device changed callback", stm); } - auto_lock lock(stm->mutex); - audiounit_close_stream(stm); - assert(audiounit_active_streams(stm->context) >= 1); - audiounit_decrement_active_streams(stm->context); -} - -static void -audiounit_stream_destroy(cubeb_stream * stm) -{ - if (!stm->shutdown.load()){ - auto_lock context_lock(stm->context->mutex); - audiounit_stream_stop_internal(stm); - stm->shutdown = true; - } + auto_lock context_lock(stm->context->mutex); + audiounit_stream_stop_internal(stm); - stm->destroy_pending = true; // Execute close in serial queue to avoid collision // with reinit when un/plug devices dispatch_sync(stm->context->serial_queue, ^() { - auto_lock context_lock(stm->context->mutex); - audiounit_stream_destroy_internal(stm); + auto_lock lock(stm->mutex); + audiounit_close_stream(stm); }); + assert(stm->context->active_streams >= 1); + stm->context->active_streams -= 1; + LOG("Cubeb stream (%p) destroyed successful.", stm); - delete stm; + + stm->~cubeb_stream(); + free(stm); } -static int +void audiounit_stream_start_internal(cubeb_stream * stm) { OSStatus r; if (stm->input_unit != NULL) { r = AudioOutputUnitStart(stm->input_unit); - if (r != noErr) { - LOG("AudioOutputUnitStart (input) rv=%d", r); - return CUBEB_ERROR; - } + assert(r == 0); } if (stm->output_unit != NULL) { r = AudioOutputUnitStart(stm->output_unit); - if (r != noErr) { - LOG("AudioOutputUnitStart (output) rv=%d", r); - return CUBEB_ERROR; - } + assert(r == 0); } - return CUBEB_OK; } static int @@ -2915,10 +1963,7 @@ audiounit_stream_start(cubeb_stream * stm) stm->shutdown = false; stm->draining = false; - int r = audiounit_stream_start_internal(stm); - if (r != CUBEB_OK) { - return r; - } + audiounit_stream_start_internal(stm); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); @@ -2957,12 +2002,9 @@ audiounit_stream_stop(cubeb_stream * stm) static int audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position) { - assert(stm); - if (stm->current_latency_frames > stm->frames_played) { - *position = 0; - } else { - *position = stm->frames_played - stm->current_latency_frames; - } + auto_lock lock(stm->mutex); + + *position = stm->frames_played; return CUBEB_OK; } @@ -2973,7 +2015,74 @@ audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency) //TODO return CUBEB_ERROR_NOT_SUPPORTED; #else - *latency = stm->total_output_latency_frames; + auto_lock lock(stm->mutex); + if (stm->hw_latency_frames == UINT64_MAX) { + UInt32 size; + uint32_t device_latency_frames, device_safety_offset; + double unit_latency_sec; + AudioDeviceID output_device_id; + OSStatus r; + AudioObjectPropertyAddress latency_address = { + kAudioDevicePropertyLatency, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + AudioObjectPropertyAddress safety_offset_address = { + kAudioDevicePropertySafetyOffset, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + + r = audiounit_get_output_device_id(&output_device_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + size = sizeof(unit_latency_sec); + r = AudioUnitGetProperty(stm->output_unit, + kAudioUnitProperty_Latency, + kAudioUnitScope_Global, + 0, + &unit_latency_sec, + &size); + if (r != noErr) { + PRINT_ERROR_CODE("AudioUnitGetProperty/kAudioUnitProperty_Latency", r); + return CUBEB_ERROR; + } + + size = sizeof(device_latency_frames); + r = AudioObjectGetPropertyData(output_device_id, + &latency_address, + 0, + NULL, + &size, + &device_latency_frames); + if (r != noErr) { + PRINT_ERROR_CODE("AudioUnitGetPropertyData/latency_frames", r); + return CUBEB_ERROR; + } + + size = sizeof(device_safety_offset); + r = AudioObjectGetPropertyData(output_device_id, + &safety_offset_address, + 0, + NULL, + &size, + &device_safety_offset); + if (r != noErr) { + PRINT_ERROR_CODE("AudioUnitGetPropertyData/safety_offset", r); + return CUBEB_ERROR; + } + + /* This part is fixed and depend on the stream parameter and the hardware. */ + stm->hw_latency_frames = + static_cast(unit_latency_sec * stm->output_desc.mSampleRate) + + device_latency_frames + + device_safety_offset; + } + + *latency = stm->hw_latency_frames + stm->current_latency_frames; + return CUBEB_OK; #endif } @@ -2993,111 +2102,120 @@ audiounit_stream_get_volume(cubeb_stream * stm, float * volume) return CUBEB_OK; } -static int -audiounit_stream_set_volume(cubeb_stream * stm, float volume) +int audiounit_stream_set_volume(cubeb_stream * stm, float volume) { - assert(stm->output_unit); OSStatus r; + r = AudioUnitSetParameter(stm->output_unit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, volume, 0); if (r != noErr) { - LOG("AudioUnitSetParameter/kHALOutputParam_Volume rv=%d", r); + PRINT_ERROR_CODE("AudioUnitSetParameter/kHALOutputParam_Volume", r); return CUBEB_ERROR; } return CUBEB_OK; } -unique_ptr convert_uint32_into_string(UInt32 data) -{ - // Simply create an empty string if no data. - size_t size = data == 0 ? 0 : 4; // 4 bytes for uint32. - auto str = unique_ptr { new char[size + 1] }; // + 1 for '\0'. - str[size] = '\0'; - if (size < 4) { - return str; - } - - // Reverse 0xWXYZ into 0xZYXW. - str[0] = (char)(data >> 24); - str[1] = (char)(data >> 16); - str[2] = (char)(data >> 8); - str[3] = (char)(data); - return str; -} - -int audiounit_get_default_device_datasource(cubeb_device_type type, - UInt32 * data) +int audiounit_stream_set_panning(cubeb_stream * stm, float panning) { - AudioDeviceID id = audiounit_get_default_device_id(type); - if (id == kAudioObjectUnknown) { - return CUBEB_ERROR; - } - - UInt32 size = sizeof(*data); - /* This fails with some USB headsets (e.g., Plantronic .Audio 628). */ - OSStatus r = AudioObjectGetPropertyData(id, - type == CUBEB_DEVICE_TYPE_INPUT ? - &INPUT_DATA_SOURCE_PROPERTY_ADDRESS : - &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS, - 0, NULL, &size, data); - if (r != noErr) { - *data = 0; + if (stm->output_desc.mChannelsPerFrame > 2) { + return CUBEB_ERROR_INVALID_PARAMETER; } + stm->panning.store(panning, std::memory_order_relaxed); return CUBEB_OK; } -int audiounit_get_default_device_name(cubeb_stream * stm, - cubeb_device * const device, - cubeb_device_type type) -{ - assert(stm); - assert(device); - - UInt32 data; - int r = audiounit_get_default_device_datasource(type, &data); - if (r != CUBEB_OK) { - return r; - } - char ** name = type == CUBEB_DEVICE_TYPE_INPUT ? - &device->input_name : &device->output_name; - *name = convert_uint32_into_string(data).release(); - if (!strlen(*name)) { // empty string. - LOG("(%p) name of %s device is empty!", stm, - type == CUBEB_DEVICE_TYPE_INPUT ? "input" : "output"); - } - return CUBEB_OK; -} - - int audiounit_stream_get_current_device(cubeb_stream * stm, - cubeb_device ** const device) + cubeb_device ** const device) { #if TARGET_OS_IPHONE //TODO return CUBEB_ERROR_NOT_SUPPORTED; #else + OSStatus r; + UInt32 size; + UInt32 data; + char strdata[4]; + AudioDeviceID output_device_id; + AudioDeviceID input_device_id; + + AudioObjectPropertyAddress datasource_address = { + kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + + AudioObjectPropertyAddress datasource_address_input = { + kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeInput, + kAudioObjectPropertyElementMaster + }; + + *device = NULL; + + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + return CUBEB_ERROR; + } + *device = new cubeb_device; if (!*device) { return CUBEB_ERROR; } PodZero(*device, 1); - int r = audiounit_get_default_device_name(stm, *device, - CUBEB_DEVICE_TYPE_OUTPUT); - if (r != CUBEB_OK) { - return r; + size = sizeof(UInt32); + /* This fails with some USB headset, so simply return an empty string. */ + r = AudioObjectGetPropertyData(output_device_id, + &datasource_address, + 0, NULL, &size, &data); + if (r != noErr) { + size = 0; + data = 0; } - r = audiounit_get_default_device_name(stm, *device, - CUBEB_DEVICE_TYPE_INPUT); - if (r != CUBEB_OK) { - return r; + (*device)->output_name = new char[size + 1]; + if (!(*device)->output_name) { + return CUBEB_ERROR; + } + + // Turn the four chars packed into a uint32 into a string + strdata[0] = (char)(data >> 24); + strdata[1] = (char)(data >> 16); + strdata[2] = (char)(data >> 8); + strdata[3] = (char)(data); + + memcpy((*device)->output_name, strdata, size); + (*device)->output_name[size] = '\0'; + + if (audiounit_get_input_device_id(&input_device_id) != CUBEB_OK) { + return CUBEB_ERROR; + } + + size = sizeof(UInt32); + r = AudioObjectGetPropertyData(input_device_id, &datasource_address_input, 0, NULL, &size, &data); + if (r != noErr) { + LOG("(%p) Error when getting device !", stm); + size = 0; + data = 0; + } + + (*device)->input_name = new char[size + 1]; + if (!(*device)->input_name) { + return CUBEB_ERROR; } + // Turn the four chars packed into a uint32 into a string + strdata[0] = (char)(data >> 24); + strdata[1] = (char)(data >> 16); + strdata[2] = (char)(data >> 8); + strdata[3] = (char)(data); + + memcpy((*device)->input_name, strdata, size); + (*device)->input_name[size] = '\0'; + return CUBEB_OK; #endif } @@ -3114,14 +2232,52 @@ int audiounit_stream_device_destroy(cubeb_stream * /* stream */, int audiounit_stream_register_device_changed_callback(cubeb_stream * stream, cubeb_device_changed_callback device_changed_callback) { - auto_lock dev_cb_lock(stream->device_changed_callback_lock); /* Note: second register without unregister first causes 'nope' error. * Current implementation requires unregister before register a new cb. */ - assert(!device_changed_callback || !stream->device_changed_callback); + assert(!stream->device_changed_callback); + + auto_lock lock(stream->mutex); + stream->device_changed_callback = device_changed_callback; + return CUBEB_OK; } +static OSStatus +audiounit_get_devices(AudioObjectID ** devices, uint32_t * count) +{ + OSStatus ret; + UInt32 size = 0; + AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size); + if (ret != noErr) { + return ret; + } + + *count = static_cast(size / sizeof(AudioObjectID)); + if (size >= sizeof(AudioObjectID)) { + if (*devices != NULL) { + delete [] (*devices); + } + *devices = new AudioObjectID[*count]; + PodZero(*devices, *count); + + ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, (void *)*devices); + if (ret != noErr) { + delete [] (*devices); + *devices = NULL; + } + } else { + *devices = NULL; + ret = -1; + } + + return ret; +} + static char * audiounit_strref_to_cstr_utf8(CFStringRef strref) { @@ -3132,12 +2288,11 @@ audiounit_strref_to_cstr_utf8(CFStringRef strref) } len = CFStringGetLength(strref); - // Add 1 to size to allow for '\0' termination character. - size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1; - ret = new char[size]; + size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8); + ret = static_cast(malloc(size)); if (!CFStringGetCString(strref, ret, size, kCFStringEncodingUTF8)) { - delete [] ret; + free(ret); ret = NULL; } @@ -3184,18 +2339,19 @@ audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope AudioValueRange range; if (AudioObjectHasProperty(devid, &adr) && AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr) { - uint32_t count = size / sizeof(AudioValueRange); - vector ranges(count); + uint32_t i, count = size / sizeof(AudioValueRange); + AudioValueRange * ranges = new AudioValueRange[count]; range.mMinimum = 9999999999.0; range.mMaximum = 0.0; - if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, ranges.data()) == noErr) { - for (uint32_t i = 0; i < count; i++) { + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, ranges) == noErr) { + for (i = 0; i < count; i++) { if (ranges[i].mMaximum > range.mMaximum) range.mMaximum = ranges[i].mMaximum; if (ranges[i].mMinimum < range.mMinimum) range.mMinimum = ranges[i].mMinimum; } } + delete [] ranges; *max = static_cast(range.mMaximum); *min = static_cast(range.mMinimum); } else { @@ -3208,7 +2364,7 @@ static UInt32 audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectPropertyScope scope) { AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster }; - UInt32 size, dev, stream = 0; + UInt32 size, dev, stream = 0, offset; AudioStreamID sid[1]; adr.mSelector = kAudioDevicePropertyLatency; @@ -3225,249 +2381,216 @@ audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectProper AudioObjectGetPropertyData(sid[0], &adr, 0, NULL, &size, &stream); } - return dev + stream; + adr.mSelector = kAudioDevicePropertySafetyOffset; + size = sizeof(UInt32); + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &offset) != noErr) { + offset = 0; + } + + return dev + stream + offset; } -static int -audiounit_create_device_from_hwdev(cubeb_device_info * dev_info, AudioObjectID devid, cubeb_device_type type) +static cubeb_device_info * +audiounit_create_device_from_hwdev(AudioObjectID devid, cubeb_device_type type) { AudioObjectPropertyAddress adr = { 0, 0, kAudioObjectPropertyElementMaster }; - UInt32 size; + UInt32 size, ch, latency; + cubeb_device_info * ret; + CFStringRef str = NULL; + AudioValueRange range; if (type == CUBEB_DEVICE_TYPE_OUTPUT) { adr.mScope = kAudioDevicePropertyScopeOutput; } else if (type == CUBEB_DEVICE_TYPE_INPUT) { adr.mScope = kAudioDevicePropertyScopeInput; } else { - return CUBEB_ERROR; + return NULL; } - UInt32 ch = audiounit_get_channel_count(devid, adr.mScope); + ch = audiounit_get_channel_count(devid, adr.mScope); if (ch == 0) { - return CUBEB_ERROR; + return NULL; } - PodZero(dev_info, 1); + ret = new cubeb_device_info; + PodZero(ret, 1); - CFStringRef device_id_str = nullptr; size = sizeof(CFStringRef); adr.mSelector = kAudioDevicePropertyDeviceUID; - OSStatus ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &device_id_str); - if ( ret == noErr && device_id_str != NULL) { - dev_info->device_id = audiounit_strref_to_cstr_utf8(device_id_str); - static_assert(sizeof(cubeb_devid) >= sizeof(decltype(devid)), "cubeb_devid can't represent devid"); - dev_info->devid = reinterpret_cast(devid); - dev_info->group_id = dev_info->device_id; - CFRelease(device_id_str); - } - - CFStringRef friendly_name_str = nullptr; - UInt32 ds; - size = sizeof(UInt32); - adr.mSelector = kAudioDevicePropertyDataSource; - ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds); - if (ret == noErr) { - AudioValueTranslation trl = { &ds, sizeof(ds), &friendly_name_str, sizeof(CFStringRef) }; - adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString; - size = sizeof(AudioValueTranslation); - AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl); + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) { + ret->device_id = audiounit_strref_to_cstr_utf8(str); + ret->devid = (cubeb_devid)(size_t)devid; + ret->group_id = strdup(ret->device_id); + CFRelease(str); } - // If there is no datasource for this device, fall back to the - // device name. - if (!friendly_name_str) { - size = sizeof(CFStringRef); - adr.mSelector = kAudioObjectPropertyName; - AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &friendly_name_str); - } + size = sizeof(CFStringRef); + adr.mSelector = kAudioObjectPropertyName; + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) { + UInt32 ds; + size = sizeof(UInt32); + adr.mSelector = kAudioDevicePropertyDataSource; + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds) == noErr) { + CFStringRef dsname; + AudioValueTranslation trl = { &ds, sizeof(ds), &dsname, sizeof(dsname) }; + adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString; + size = sizeof(AudioValueTranslation); + // If there is a datasource for this device, use it instead of the device + // name. + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl) == noErr) { + CFRelease(str); + str = dsname; + } + } - if (friendly_name_str) { - dev_info->friendly_name = audiounit_strref_to_cstr_utf8(friendly_name_str); - CFRelease(friendly_name_str); - } else { - // Couldn't get a datasource name nor a device name, return a - // valid string of length 0. - char * fallback_name = new char[1]; - fallback_name[0] = '\0'; - dev_info->friendly_name = fallback_name; + ret->friendly_name = audiounit_strref_to_cstr_utf8(str); + CFRelease(str); } - CFStringRef vendor_name_str = nullptr; size = sizeof(CFStringRef); adr.mSelector = kAudioObjectPropertyManufacturer; - ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &vendor_name_str); - if (ret == noErr && vendor_name_str != NULL) { - dev_info->vendor_name = audiounit_strref_to_cstr_utf8(vendor_name_str); - CFRelease(vendor_name_str); + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) { + ret->vendor_name = audiounit_strref_to_cstr_utf8(str); + CFRelease(str); } - dev_info->type = type; - dev_info->state = CUBEB_DEVICE_STATE_ENABLED; - dev_info->preferred = (devid == audiounit_get_default_device_id(type)) ? + ret->type = type; + ret->state = CUBEB_DEVICE_STATE_ENABLED; + ret->preferred = (devid == audiounit_get_default_device_id(type)) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; - dev_info->max_channels = ch; - dev_info->format = (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */ + ret->max_channels = ch; + ret->format = (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */ /* kAudioFormatFlagsAudioUnitCanonical is deprecated, prefer floating point */ - dev_info->default_format = CUBEB_DEVICE_FMT_F32NE; + ret->default_format = CUBEB_DEVICE_FMT_F32NE; audiounit_get_available_samplerate(devid, adr.mScope, - &dev_info->min_rate, &dev_info->max_rate, &dev_info->default_rate); + &ret->min_rate, &ret->max_rate, &ret->default_rate); - UInt32 latency = audiounit_get_device_presentation_latency(devid, adr.mScope); + latency = audiounit_get_device_presentation_latency(devid, adr.mScope); - AudioValueRange range; adr.mSelector = kAudioDevicePropertyBufferFrameSizeRange; size = sizeof(AudioValueRange); - ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range); - if (ret == noErr) { - dev_info->latency_lo = latency + range.mMinimum; - dev_info->latency_hi = latency + range.mMaximum; + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range) == noErr) { + ret->latency_lo = latency + range.mMinimum; + ret->latency_hi = latency + range.mMaximum; } else { - dev_info->latency_lo = 10 * dev_info->default_rate / 1000; /* Default to 10ms */ - dev_info->latency_hi = 100 * dev_info->default_rate / 1000; /* Default to 100ms */ + ret->latency_lo = 10 * ret->default_rate / 1000; /* Default to 10ms */ + ret->latency_hi = 100 * ret->default_rate / 1000; /* Default to 100ms */ } - return CUBEB_OK; -} - -bool -is_aggregate_device(cubeb_device_info * device_info) -{ - assert(device_info->friendly_name); - return !strncmp(device_info->friendly_name, PRIVATE_AGGREGATE_DEVICE_NAME, - strlen(PRIVATE_AGGREGATE_DEVICE_NAME)); + return ret; } static int audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type, - cubeb_device_collection * collection) + cubeb_device_collection ** collection) { - vector input_devs; - vector output_devs; - - // Count number of input and output devices. This is not - // necessarily the same as the count of raw devices supported by the - // system since, for example, with Soundflower installed, some - // devices may report as being both input *and* output and cubeb - // separates those into two different devices. + AudioObjectID * hwdevs = NULL; + uint32_t i, hwdevcount = 0; + OSStatus err; - if (type & CUBEB_DEVICE_TYPE_OUTPUT) { - output_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT); + if ((err = audiounit_get_devices(&hwdevs, &hwdevcount)) != noErr) { + return CUBEB_ERROR; } - if (type & CUBEB_DEVICE_TYPE_INPUT) { - input_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT); - } + *collection = static_cast(malloc(sizeof(cubeb_device_collection) + + sizeof(cubeb_device_info*) * (hwdevcount > 0 ? hwdevcount - 1 : 0))); + (*collection)->count = 0; - auto devices = new cubeb_device_info[output_devs.size() + input_devs.size()]; - collection->count = 0; + if (hwdevcount > 0) { + cubeb_device_info * cur; - if (type & CUBEB_DEVICE_TYPE_OUTPUT) { - for (auto dev: output_devs) { - auto device = &devices[collection->count]; - auto err = audiounit_create_device_from_hwdev(device, dev, CUBEB_DEVICE_TYPE_OUTPUT); - if (err != CUBEB_OK || is_aggregate_device(device)) { - continue; + if (type & CUBEB_DEVICE_TYPE_OUTPUT) { + for (i = 0; i < hwdevcount; i++) { + if ((cur = audiounit_create_device_from_hwdev(hwdevs[i], CUBEB_DEVICE_TYPE_OUTPUT)) != NULL) + (*collection)->device[(*collection)->count++] = cur; } - collection->count += 1; } - } - if (type & CUBEB_DEVICE_TYPE_INPUT) { - for (auto dev: input_devs) { - auto device = &devices[collection->count]; - auto err = audiounit_create_device_from_hwdev(device, dev, CUBEB_DEVICE_TYPE_INPUT); - if (err != CUBEB_OK || is_aggregate_device(device)) { - continue; + if (type & CUBEB_DEVICE_TYPE_INPUT) { + for (i = 0; i < hwdevcount; i++) { + if ((cur = audiounit_create_device_from_hwdev(hwdevs[i], CUBEB_DEVICE_TYPE_INPUT)) != NULL) + (*collection)->device[(*collection)->count++] = cur; } - collection->count += 1; } } - if (collection->count > 0) { - collection->device = devices; - } else { - delete [] devices; - collection->device = NULL; - } + delete [] hwdevs; return CUBEB_OK; } -static void -audiounit_device_destroy(cubeb_device_info * device) +/* qsort compare method. */ +int compare_devid(const void * a, const void * b) { - delete [] device->device_id; - delete [] device->friendly_name; - delete [] device->vendor_name; + return (*(AudioObjectID*)a - *(AudioObjectID*)b); } -static int -audiounit_device_collection_destroy(cubeb * /* context */, - cubeb_device_collection * collection) +static uint32_t +audiounit_get_devices_of_type(cubeb_device_type devtype, AudioObjectID ** devid_array) { - for (size_t i = 0; i < collection->count; i++) { - audiounit_device_destroy(&collection->device[i]); - } - delete [] collection->device; - - return CUBEB_OK; -} + assert(devid_array == NULL || *devid_array == NULL); -static vector -audiounit_get_devices_of_type(cubeb_device_type devtype) -{ + AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; UInt32 size = 0; - OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, - &DEVICES_PROPERTY_ADDRESS, 0, - NULL, &size); - if (ret != noErr) { - return vector(); - } - vector devices(size / sizeof(AudioObjectID)); - ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &DEVICES_PROPERTY_ADDRESS, 0, NULL, &size, - devices.data()); + OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size); if (ret != noErr) { - return vector(); + return 0; } + /* Total number of input and output devices. */ + uint32_t count = (uint32_t)(size / sizeof(AudioObjectID)); - // Remove the aggregate device from the list of devices (if any). - for (auto it = devices.begin(); it != devices.end();) { - CFStringRef name = get_device_name(*it); - if (name && CFStringFind(name, CFSTR("CubebAggregateDevice"), 0).location != - kCFNotFound) { - it = devices.erase(it); - } else { - it++; - } - if (name) { - CFRelease(name); - } + AudioObjectID devices[count]; + ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devices); + if (ret != noErr) { + return 0; } - /* Expected sorted but did not find anything in the docs. */ - sort(devices.begin(), devices.end(), [](AudioObjectID a, AudioObjectID b) { - return a < b; - }); + qsort(devices, count, sizeof(AudioObjectID), compare_devid); if (devtype == (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) { - return devices; + if (devid_array) { + *devid_array = new AudioObjectID[count]; + assert(*devid_array); + memcpy(*devid_array, &devices, count * sizeof(AudioObjectID)); + } + return count; } AudioObjectPropertyScope scope = (devtype == CUBEB_DEVICE_TYPE_INPUT) ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; - vector devices_in_scope; - for (uint32_t i = 0; i < devices.size(); ++i) { + uint32_t dev_count = 0; + AudioObjectID devices_in_scope[count]; + for(uint32_t i = 0; i < count; ++i) { /* For device in the given scope channel must be > 0. */ if (audiounit_get_channel_count(devices[i], scope) > 0) { - devices_in_scope.push_back(devices[i]); + devices_in_scope[dev_count] = devices[i]; + ++dev_count; } } - return devices_in_scope; + if (devid_array && dev_count > 0) { + *devid_array = new AudioObjectID[dev_count]; + assert(*devid_array); + memcpy(*devid_array, &devices_in_scope, dev_count * sizeof(AudioObjectID)); + } + return dev_count; +} + +static uint32_t +audiounit_equal_arrays(AudioObjectID * left, AudioObjectID * right, uint32_t size) +{ + /* Expected sorted arrays. */ + for (uint32_t i = 0; i < size; ++i) { + if (left[i] != right[i]) { + return 0; + } + } + return 1; } static OSStatus @@ -3477,32 +2600,33 @@ audiounit_collection_changed_callback(AudioObjectID /* inObjectID */, void * inClientData) { cubeb * context = static_cast(inClientData); + auto_lock lock(context->mutex); - // This can be called from inside an AudioUnit function, dispatch to another queue. - dispatch_async(context->serial_queue, ^() { - auto_lock lock(context->mutex); - if (!context->input_collection_changed_callback && - !context->output_collection_changed_callback) { - /* Listener removed while waiting in mutex, abort. */ - return; - } - if (context->input_collection_changed_callback) { - vector devices = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT); - /* Elements in the vector expected sorted. */ - if (context->input_device_array != devices) { - context->input_device_array = devices; - context->input_collection_changed_callback(context, context->input_collection_changed_user_ptr); - } - } - if (context->output_collection_changed_callback) { - vector devices = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT); - /* Elements in the vector expected sorted. */ - if (context->output_device_array != devices) { - context->output_device_array = devices; - context->output_collection_changed_callback(context, context->output_collection_changed_user_ptr); - } + if (context->collection_changed_callback == NULL) { + /* Listener removed while waiting in mutex, abort. */ + return noErr; + } + + /* Differentiate input from output changes. */ + if (context->collection_changed_devtype == CUBEB_DEVICE_TYPE_INPUT || + context->collection_changed_devtype == CUBEB_DEVICE_TYPE_OUTPUT) { + AudioObjectID * devices = NULL; + uint32_t new_number_of_devices = audiounit_get_devices_of_type(context->collection_changed_devtype, &devices); + /* When count is the same examine the devid for the case of coalescing. */ + if (context->devtype_device_count == new_number_of_devices && + audiounit_equal_arrays(devices, context->devtype_device_array, new_number_of_devices)) { + /* Device changed for the other scope, ignore. */ + delete [] devices; + return noErr; } - }); + /* Device on desired scope changed, reset counter and array. */ + context->devtype_device_count = new_number_of_devices; + /* Free the old array before replace. */ + delete [] context->devtype_device_array; + context->devtype_device_array = devices; + } + + context->collection_changed_callback(context, context->collection_changed_user_ptr); return noErr; } @@ -3512,65 +2636,62 @@ audiounit_add_device_listener(cubeb * context, cubeb_device_collection_changed_callback collection_changed_callback, void * user_ptr) { - context->mutex.assert_current_thread_owns(); - assert(devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)); /* Note: second register without unregister first causes 'nope' error. * Current implementation requires unregister before register a new cb. */ - assert((devtype & CUBEB_DEVICE_TYPE_INPUT) && !context->input_collection_changed_callback || - (devtype & CUBEB_DEVICE_TYPE_OUTPUT) && !context->output_collection_changed_callback); - - if (!context->input_collection_changed_callback && - !context->output_collection_changed_callback) { - OSStatus ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject, - &DEVICES_PROPERTY_ADDRESS, - audiounit_collection_changed_callback, - context); - if (ret != noErr) { - return ret; - } - } - if (devtype & CUBEB_DEVICE_TYPE_INPUT) { - /* Expected empty after unregister. */ - assert(context->input_device_array.empty()); - context->input_device_array = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT); - context->input_collection_changed_callback = collection_changed_callback; - context->input_collection_changed_user_ptr = user_ptr; - } - if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { - /* Expected empty after unregister. */ - assert(context->output_device_array.empty()); - context->output_device_array = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT); - context->output_collection_changed_callback = collection_changed_callback; - context->output_collection_changed_user_ptr = user_ptr; + assert(context->collection_changed_callback == NULL); + + AudioObjectPropertyAddress devAddr; + devAddr.mSelector = kAudioHardwarePropertyDevices; + devAddr.mScope = kAudioObjectPropertyScopeGlobal; + devAddr.mElement = kAudioObjectPropertyElementMaster; + + OSStatus ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject, + &devAddr, + audiounit_collection_changed_callback, + context); + if (ret == noErr) { + /* Expected zero after unregister. */ + assert(context->devtype_device_count == 0); + assert(context->devtype_device_array == NULL); + /* Listener works for input and output. + * When requested one of them we need to differentiate. */ + if (devtype == CUBEB_DEVICE_TYPE_INPUT || + devtype == CUBEB_DEVICE_TYPE_OUTPUT) { + /* Used to differentiate input from output device changes. */ + context->devtype_device_count = audiounit_get_devices_of_type(devtype, &context->devtype_device_array); + } + context->collection_changed_devtype = devtype; + context->collection_changed_callback = collection_changed_callback; + context->collection_changed_user_ptr = user_ptr; } - return noErr; + return ret; } static OSStatus -audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype) +audiounit_remove_device_listener(cubeb * context) { - context->mutex.assert_current_thread_owns(); - - if (devtype & CUBEB_DEVICE_TYPE_INPUT) { - context->input_collection_changed_callback = nullptr; - context->input_collection_changed_user_ptr = nullptr; - context->input_device_array.clear(); - } - if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { - context->output_collection_changed_callback = nullptr; - context->output_collection_changed_user_ptr = nullptr; - context->output_device_array.clear(); - } + AudioObjectPropertyAddress devAddr; + devAddr.mSelector = kAudioHardwarePropertyDevices; + devAddr.mScope = kAudioObjectPropertyScopeGlobal; + devAddr.mElement = kAudioObjectPropertyElementMaster; - if (context->input_collection_changed_callback || - context->output_collection_changed_callback) { - return noErr; - } /* Note: unregister a non registered cb is not a problem, not checking. */ - return AudioObjectRemovePropertyListener(kAudioObjectSystemObject, - &DEVICES_PROPERTY_ADDRESS, - audiounit_collection_changed_callback, - context); + OSStatus ret = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, + &devAddr, + audiounit_collection_changed_callback, + context); + if (ret == noErr) { + /* Reset all values. */ + context->collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN; + context->collection_changed_callback = NULL; + context->collection_changed_user_ptr = NULL; + context->devtype_device_count = 0; + if (context->devtype_device_array) { + delete [] context->devtype_device_array; + context->devtype_device_array = NULL; + } + } + return ret; } int audiounit_register_device_collection_changed(cubeb * context, @@ -3578,18 +2699,14 @@ int audiounit_register_device_collection_changed(cubeb * context, cubeb_device_collection_changed_callback collection_changed_callback, void * user_ptr) { - if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) { - return CUBEB_ERROR_INVALID_PARAMETER; - } OSStatus ret; auto_lock lock(context->mutex); if (collection_changed_callback) { - ret = audiounit_add_device_listener(context, - devtype, + ret = audiounit_add_device_listener(context, devtype, collection_changed_callback, user_ptr); } else { - ret = audiounit_remove_device_listener(context, devtype); + ret = audiounit_remove_device_listener(context); } return (ret == noErr) ? CUBEB_OK : CUBEB_ERROR; } @@ -3601,16 +2718,15 @@ cubeb_ops const audiounit_ops = { /*.get_min_latency =*/ audiounit_get_min_latency, /*.get_preferred_sample_rate =*/ audiounit_get_preferred_sample_rate, /*.enumerate_devices =*/ audiounit_enumerate_devices, - /*.device_collection_destroy =*/ audiounit_device_collection_destroy, /*.destroy =*/ audiounit_destroy, /*.stream_init =*/ audiounit_stream_init, /*.stream_destroy =*/ audiounit_stream_destroy, /*.stream_start =*/ audiounit_stream_start, /*.stream_stop =*/ audiounit_stream_stop, - /*.stream_reset_default_device =*/ nullptr, /*.stream_get_position =*/ audiounit_stream_get_position, /*.stream_get_latency =*/ audiounit_stream_get_latency, /*.stream_set_volume =*/ audiounit_stream_set_volume, + /*.stream_set_panning =*/ audiounit_stream_set_panning, /*.stream_get_current_device =*/ audiounit_stream_get_current_device, /*.stream_device_destroy =*/ audiounit_stream_device_destroy, /*.stream_register_device_changed_callback =*/ audiounit_stream_register_device_changed_callback, diff --git a/media/libcubeb/src/cubeb_jack.cpp b/media/libcubeb/src/cubeb_jack.cpp index 1ab876c3f..8f995da66 100644 --- a/media/libcubeb/src/cubeb_jack.cpp +++ b/media/libcubeb/src/cubeb_jack.cpp @@ -8,20 +8,23 @@ */ #define _DEFAULT_SOURCE #define _BSD_SOURCE -#ifndef __FreeBSD__ #define _POSIX_SOURCE -#endif +#include #include +#include #include +#include +#include #include #include +#include +#include #include #include #include #include "cubeb/cubeb.h" #include "cubeb-internal.h" #include "cubeb_resampler.h" -#include "cubeb_utils.h" #include #include @@ -95,9 +98,7 @@ static int cbjack_stream_device_destroy(cubeb_stream * stream, cubeb_device * device); static int cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device); static int cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection * collection); -static int cbjack_device_collection_destroy(cubeb * context, - cubeb_device_collection * collection); + cubeb_device_collection ** collection); static int cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, cubeb_devid input_device, cubeb_stream_params * input_stream_params, @@ -120,16 +121,15 @@ static struct cubeb_ops const cbjack_ops = { .get_min_latency = cbjack_get_min_latency, .get_preferred_sample_rate = cbjack_get_preferred_sample_rate, .enumerate_devices = cbjack_enumerate_devices, - .device_collection_destroy = cbjack_device_collection_destroy, .destroy = cbjack_destroy, .stream_init = cbjack_stream_init, .stream_destroy = cbjack_stream_destroy, .stream_start = cbjack_stream_start, .stream_stop = cbjack_stream_stop, - .stream_reset_default_device = NULL, .stream_get_position = cbjack_stream_get_position, .stream_get_latency = cbjack_get_latency, .stream_set_volume = cbjack_stream_set_volume, + .stream_set_panning = NULL, .stream_get_current_device = cbjack_stream_get_current_device, .stream_device_destroy = cbjack_stream_device_destroy, .stream_register_device_changed_callback = NULL, @@ -137,10 +137,7 @@ static struct cubeb_ops const cbjack_ops = { }; struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; - void * user_ptr; - /**/ /**< Mutex for each stream */ pthread_mutex_t mutex; @@ -150,6 +147,7 @@ struct cubeb_stream { cubeb_data_callback data_callback; cubeb_state_callback state_callback; + void * user_ptr; cubeb_stream_params in_params; cubeb_stream_params out_params; @@ -185,6 +183,7 @@ struct cubeb { cubeb_stream streams[MAX_STREAMS]; unsigned int active_streams; + cubeb_device_info * devinfo[2]; cubeb_device_collection_changed_callback collection_changed_callback; bool active; @@ -211,9 +210,6 @@ load_jack_lib(cubeb * context) # endif #else context->libjack = dlopen("libjack.so.0", RTLD_LAZY); - if (!context->libjack) { - context->libjack = dlopen("libjack.so", RTLD_LAZY); - } #endif if (!context->libjack) { return CUBEB_ERROR; @@ -234,10 +230,9 @@ load_jack_lib(cubeb * context) return CUBEB_OK; } -static int +static void cbjack_connect_ports (cubeb_stream * stream) { - int r = CUBEB_ERROR; const char ** phys_in_ports = api_jack_get_ports (stream->context->jack_client, NULL, NULL, JackPortIsInput @@ -247,7 +242,7 @@ cbjack_connect_ports (cubeb_stream * stream) JackPortIsOutput | JackPortIsPhysical); - if (phys_in_ports == NULL || *phys_in_ports == NULL) { + if (*phys_in_ports == NULL) { goto skipplayback; } @@ -257,10 +252,9 @@ cbjack_connect_ports (cubeb_stream * stream) api_jack_connect (stream->context->jack_client, src_port, phys_in_ports[c]); } - r = CUBEB_OK; skipplayback: - if (phys_out_ports == NULL || *phys_out_ports == NULL) { + if (*phys_out_ports == NULL) { goto end; } // Connect inputs to capture @@ -269,15 +263,9 @@ skipplayback: api_jack_connect (stream->context->jack_client, phys_out_ports[c], src_port); } - r = CUBEB_OK; end: - if (phys_out_ports) { - api_jack_free(phys_out_ports); - } - if (phys_in_ports) { - api_jack_free(phys_in_ports); - } - return r; + api_jack_free(phys_out_ports); + api_jack_free(phys_in_ports); } static int @@ -438,6 +426,7 @@ cbjack_process(jack_nframes_t nframes, void * arg) return 0; } + static void cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, float ** bufs_out, jack_nframes_t nframes) { @@ -450,6 +439,7 @@ cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, fl long done_frames = 0; long input_frames_count = (in != NULL) ? nframes : 0; + done_frames = cubeb_resampler_fill(stream->resampler, inptr, &input_frames_count, @@ -746,12 +736,6 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_ if (input_device || output_device) return CUBEB_ERROR_NOT_SUPPORTED; - // Loopback is unsupported - if ((input_stream_params && (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK)) || - (output_stream_params && (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) { - return CUBEB_ERROR_NOT_SUPPORTED; - } - *stream = NULL; // Find a free stream. @@ -883,11 +867,7 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_ } } - if (cbjack_connect_ports(stm) != CUBEB_OK) { - pthread_mutex_unlock(&stm->mutex); - cbjack_stream_destroy(stm); - return CUBEB_ERROR; - } + cbjack_connect_ports(stm); *stream = stm; @@ -960,6 +940,7 @@ cbjack_stream_set_volume(cubeb_stream * stm, float volume) return CUBEB_OK; } + static int cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device) { @@ -997,77 +978,70 @@ cbjack_stream_device_destroy(cubeb_stream * /*stream*/, return CUBEB_OK; } -#define JACK_DEFAULT_IN "JACK capture" -#define JACK_DEFAULT_OUT "JACK playback" - static int cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection * collection) + cubeb_device_collection ** collection) { if (!context) return CUBEB_ERROR; uint32_t rate; + uint8_t i = 0; + uint8_t j; cbjack_get_preferred_sample_rate(context, &rate); - - cubeb_device_info * devices = new cubeb_device_info[2]; - if (!devices) - return CUBEB_ERROR; - PodZero(devices, 2); - collection->count = 0; + const char * j_in = "JACK capture"; + const char * j_out = "JACK playback"; if (type & CUBEB_DEVICE_TYPE_OUTPUT) { - cubeb_device_info * cur = &devices[collection->count]; - cur->device_id = JACK_DEFAULT_OUT; - cur->devid = (cubeb_devid) cur->device_id; - cur->friendly_name = JACK_DEFAULT_OUT; - cur->group_id = JACK_DEFAULT_OUT; - cur->vendor_name = JACK_DEFAULT_OUT; - cur->type = CUBEB_DEVICE_TYPE_OUTPUT; - cur->state = CUBEB_DEVICE_STATE_ENABLED; - cur->preferred = CUBEB_DEVICE_PREF_ALL; - cur->format = CUBEB_DEVICE_FMT_F32NE; - cur->default_format = CUBEB_DEVICE_FMT_F32NE; - cur->max_channels = MAX_CHANNELS; - cur->min_rate = rate; - cur->max_rate = rate; - cur->default_rate = rate; - cur->latency_lo = 0; - cur->latency_hi = 0; - collection->count +=1 ; + context->devinfo[i] = (cubeb_device_info *)malloc(sizeof(cubeb_device_info)); + context->devinfo[i]->device_id = strdup(j_out); + context->devinfo[i]->devid = context->devinfo[i]->device_id; + context->devinfo[i]->friendly_name = strdup(j_out); + context->devinfo[i]->group_id = strdup(j_out); + context->devinfo[i]->vendor_name = strdup(j_out); + context->devinfo[i]->type = CUBEB_DEVICE_TYPE_OUTPUT; + context->devinfo[i]->state = CUBEB_DEVICE_STATE_ENABLED; + context->devinfo[i]->preferred = CUBEB_DEVICE_PREF_ALL; + context->devinfo[i]->format = CUBEB_DEVICE_FMT_F32NE; + context->devinfo[i]->default_format = CUBEB_DEVICE_FMT_F32NE; + context->devinfo[i]->max_channels = MAX_CHANNELS; + context->devinfo[i]->min_rate = rate; + context->devinfo[i]->max_rate = rate; + context->devinfo[i]->default_rate = rate; + context->devinfo[i]->latency_lo = 0; + context->devinfo[i]->latency_hi = 0; + i++; } if (type & CUBEB_DEVICE_TYPE_INPUT) { - cubeb_device_info * cur = &devices[collection->count]; - cur->device_id = JACK_DEFAULT_IN; - cur->devid = (cubeb_devid) cur->device_id; - cur->friendly_name = JACK_DEFAULT_IN; - cur->group_id = JACK_DEFAULT_IN; - cur->vendor_name = JACK_DEFAULT_IN; - cur->type = CUBEB_DEVICE_TYPE_INPUT; - cur->state = CUBEB_DEVICE_STATE_ENABLED; - cur->preferred = CUBEB_DEVICE_PREF_ALL; - cur->format = CUBEB_DEVICE_FMT_F32NE; - cur->default_format = CUBEB_DEVICE_FMT_F32NE; - cur->max_channels = MAX_CHANNELS; - cur->min_rate = rate; - cur->max_rate = rate; - cur->default_rate = rate; - cur->latency_lo = 0; - cur->latency_hi = 0; - collection->count += 1; + context->devinfo[i] = (cubeb_device_info *)malloc(sizeof(cubeb_device_info)); + context->devinfo[i]->device_id = strdup(j_in); + context->devinfo[i]->devid = context->devinfo[i]->device_id; + context->devinfo[i]->friendly_name = strdup(j_in); + context->devinfo[i]->group_id = strdup(j_in); + context->devinfo[i]->vendor_name = strdup(j_in); + context->devinfo[i]->type = CUBEB_DEVICE_TYPE_INPUT; + context->devinfo[i]->state = CUBEB_DEVICE_STATE_ENABLED; + context->devinfo[i]->preferred = CUBEB_DEVICE_PREF_ALL; + context->devinfo[i]->format = CUBEB_DEVICE_FMT_F32NE; + context->devinfo[i]->default_format = CUBEB_DEVICE_FMT_F32NE; + context->devinfo[i]->max_channels = MAX_CHANNELS; + context->devinfo[i]->min_rate = rate; + context->devinfo[i]->max_rate = rate; + context->devinfo[i]->default_rate = rate; + context->devinfo[i]->latency_lo = 0; + context->devinfo[i]->latency_hi = 0; + i++; } - collection->device = devices; + *collection = (cubeb_device_collection *) + malloc(sizeof(cubeb_device_collection) + + i * sizeof(cubeb_device_info *)); - return CUBEB_OK; -} + (*collection)->count = i; -static int -cbjack_device_collection_destroy(cubeb * /*ctx*/, - cubeb_device_collection * collection) -{ - XASSERT(collection); - delete [] collection->device; + for (j = 0; j < i; j++) { + (*collection)->device[j] = context->devinfo[j]; + } return CUBEB_OK; } diff --git a/media/libcubeb/src/cubeb_log.cpp b/media/libcubeb/src/cubeb_log.cpp deleted file mode 100644 index 54c7f4a15..000000000 --- a/media/libcubeb/src/cubeb_log.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright © 2016 Mozilla Foundation - * - * This program is made available under an ISC-style license. See the - * accompanying file LICENSE for details. - */ -#define NOMINMAX - -#include "cubeb_log.h" -#include "cubeb_ringbuffer.h" -#include -#ifdef _WIN32 -#include -#else -#include -#endif - -cubeb_log_level g_cubeb_log_level; -cubeb_log_callback g_cubeb_log_callback; - -/** The maximum size of a log message, after having been formatted. */ -const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256; -/** The maximum number of log messages that can be queued before dropping - * messages. */ -const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40; -/** Number of milliseconds to wait before dequeuing log messages. */ -#define CUBEB_LOG_BATCH_PRINT_INTERVAL_MS 10 - -/** - * This wraps an inline buffer, that represents a log message, that must be - * null-terminated. - * This class should not use system calls or other potentially blocking code. - */ -class cubeb_log_message -{ -public: - cubeb_log_message() - { - *storage = '\0'; - } - cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE]) - { - size_t length = strlen(str); - /* paranoia against malformed message */ - assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE); - if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) { - return; - } - PodCopy(storage, str, length); - storage[length] = '\0'; - } - char const * get() { - return storage; - } -private: - char storage[CUBEB_LOG_MESSAGE_MAX_SIZE]; -}; - -/** Lock-free asynchronous logger, made so that logging from a - * real-time audio callback does not block the audio thread. */ -class cubeb_async_logger -{ -public: - /* This is thread-safe since C++11 */ - static cubeb_async_logger & get() { - static cubeb_async_logger instance; - return instance; - } - void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE]) - { - cubeb_log_message msg(str); - msg_queue.enqueue(msg); - } - void run() - { - std::thread([this]() { - while (true) { - cubeb_log_message msg; - while (msg_queue.dequeue(&msg, 1)) { - LOGV("%s", msg.get()); - } -#ifdef _WIN32 - Sleep(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS); -#else - timespec sleep_duration = sleep_for; - timespec remainder; - do { - if (nanosleep(&sleep_duration, &remainder) == 0 || - errno != EINTR) { - break; - } - sleep_duration = remainder; - } while (remainder.tv_sec || remainder.tv_nsec); -#endif - } - }).detach(); - } - // Tell the underlying queue the producer thread has changed, so it does not - // assert in debug. This should be called with the thread stopped. - void reset_producer_thread() - { - msg_queue.reset_thread_ids(); - } -private: -#ifndef _WIN32 - const struct timespec sleep_for = { - CUBEB_LOG_BATCH_PRINT_INTERVAL_MS/1000, - (CUBEB_LOG_BATCH_PRINT_INTERVAL_MS%1000)*1000*1000 - }; -#endif - cubeb_async_logger() - : msg_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH) - { - run(); - } - /** This is quite a big data structure, but is only instantiated if the - * asynchronous logger is used.*/ - lock_free_queue msg_queue; -}; - - -void cubeb_async_log(char const * fmt, ...) -{ - if (!g_cubeb_log_callback) { - return; - } - // This is going to copy a 256 bytes array around, which is fine. - // We don't want to allocate memory here, because this is made to - // be called from a real-time callback. - va_list args; - va_start(args, fmt); - char msg[CUBEB_LOG_MESSAGE_MAX_SIZE]; - vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args); - cubeb_async_logger::get().push(msg); - va_end(args); -} - -void cubeb_async_log_reset_threads() -{ - if (!g_cubeb_log_callback) { - return; - } - cubeb_async_logger::get().reset_producer_thread(); -} diff --git a/media/libcubeb/src/cubeb_log.h b/media/libcubeb/src/cubeb_log.h index a79976bb3..bca98c96f 100644 --- a/media/libcubeb/src/cubeb_log.h +++ b/media/libcubeb/src/cubeb_log.h @@ -8,8 +8,6 @@ #ifndef CUBEB_LOG #define CUBEB_LOG -#include "cubeb/cubeb.h" - #ifdef __cplusplus extern "C" { #endif @@ -20,10 +18,8 @@ extern "C" { #define PRINTF_FORMAT(fmt, args) #endif -extern cubeb_log_level g_cubeb_log_level; -extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2); -void cubeb_async_log(const char * fmt, ...); -void cubeb_async_log_reset_threads(); +extern cubeb_log_level g_log_level; +extern cubeb_log_callback g_log_callback PRINTF_FORMAT(1, 2); #ifdef __cplusplus } @@ -33,15 +29,9 @@ void cubeb_async_log_reset_threads(); #define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) #define LOG_INTERNAL(level, fmt, ...) do { \ - if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \ - g_cubeb_log_callback("%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ + if (g_log_callback && level <= g_log_level) { \ + g_log_callback("%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ } \ } while(0) -/* Asynchronous verbose logging, to log in real-time callbacks. */ -#define ALOGV(fmt, ...) \ -do { \ - cubeb_async_log(fmt, ##__VA_ARGS__); \ -} while(0) - #endif // CUBEB_LOG diff --git a/media/libcubeb/src/cubeb_mixer.cpp b/media/libcubeb/src/cubeb_mixer.cpp deleted file mode 100644 index 2ab7f673a..000000000 --- a/media/libcubeb/src/cubeb_mixer.cpp +++ /dev/null @@ -1,663 +0,0 @@ -/* - * Copyright © 2016 Mozilla Foundation - * - * This program is made available under an ISC-style license. See the - * accompanying file LICENSE for details. - * - * Adapted from code based on libswresample's rematrix.c - */ - -#define NOMINMAX - -#include -#include -#include -#include -#include -#include -#include -#include "cubeb-internal.h" -#include "cubeb_mixer.h" -#include "cubeb_utils.h" - -#ifndef FF_ARRAY_ELEMS -#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0])) -#endif - -#define CHANNELS_MAX 32 -#define FRONT_LEFT 0 -#define FRONT_RIGHT 1 -#define FRONT_CENTER 2 -#define LOW_FREQUENCY 3 -#define BACK_LEFT 4 -#define BACK_RIGHT 5 -#define FRONT_LEFT_OF_CENTER 6 -#define FRONT_RIGHT_OF_CENTER 7 -#define BACK_CENTER 8 -#define SIDE_LEFT 9 -#define SIDE_RIGHT 10 -#define TOP_CENTER 11 -#define TOP_FRONT_LEFT 12 -#define TOP_FRONT_CENTER 13 -#define TOP_FRONT_RIGHT 14 -#define TOP_BACK_LEFT 15 -#define TOP_BACK_CENTER 16 -#define TOP_BACK_RIGHT 17 -#define NUM_NAMED_CHANNELS 18 - -#ifndef M_SQRT1_2 -#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ -#endif -#ifndef M_SQRT2 -#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ -#endif -#define SQRT3_2 1.22474487139158904909 /* sqrt(3/2) */ - -#define C30DB M_SQRT2 -#define C15DB 1.189207115 -#define C__0DB 1.0 -#define C_15DB 0.840896415 -#define C_30DB M_SQRT1_2 -#define C_45DB 0.594603558 -#define C_60DB 0.5 - -static cubeb_channel_layout -cubeb_channel_layout_check(cubeb_channel_layout l, uint32_t c) -{ - if (l == CUBEB_LAYOUT_UNDEFINED) { - switch (c) { - case 1: return CUBEB_LAYOUT_MONO; - case 2: return CUBEB_LAYOUT_STEREO; - } - } - return l; -} - -unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout x) -{ -#if __GNUC__ || __clang__ - return __builtin_popcount (x); -#else - x -= (x >> 1) & 0x55555555; - x = (x & 0x33333333) + ((x >> 2) & 0x33333333); - x = (x + (x >> 4)) & 0x0F0F0F0F; - x += x >> 8; - return (x + (x >> 16)) & 0x3F; -#endif -} - -struct MixerContext { - MixerContext(cubeb_sample_format f, - uint32_t in_channels, - cubeb_channel_layout in, - uint32_t out_channels, - cubeb_channel_layout out) - : _format(f) - , _in_ch_layout(cubeb_channel_layout_check(in, in_channels)) - , _out_ch_layout(cubeb_channel_layout_check(out, out_channels)) - , _in_ch_count(in_channels) - , _out_ch_count(out_channels) - { - if (in_channels != cubeb_channel_layout_nb_channels(in) || - out_channels != cubeb_channel_layout_nb_channels(out)) { - // Mismatch between channels and layout, aborting. - return; - } - _valid = init() >= 0; - } - - static bool even(cubeb_channel_layout layout) - { - if (!layout) { - return true; - } - if (layout & (layout - 1)) { - return true; - } - return false; - } - - // Ensure that the layout is sane (that is have symmetrical left/right - // channels), if not, layout will be treated as mono. - static cubeb_channel_layout clean_layout(cubeb_channel_layout layout) - { - if (layout && layout != CHANNEL_FRONT_LEFT && !(layout & (layout - 1))) { - LOG("Treating layout as mono"); - return CHANNEL_FRONT_CENTER; - } - - return layout; - } - - static bool sane_layout(cubeb_channel_layout layout) - { - if (!(layout & CUBEB_LAYOUT_3F)) { // at least 1 front speaker - return false; - } - if (!even(layout & (CHANNEL_FRONT_LEFT | - CHANNEL_FRONT_RIGHT))) { // no asymetric front - return false; - } - if (!even(layout & - (CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT))) { // no asymetric side - return false; - } - if (!even(layout & (CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT))) { - return false; - } - if (!even(layout & - (CHANNEL_FRONT_LEFT_OF_CENTER | CHANNEL_FRONT_RIGHT_OF_CENTER))) { - return false; - } - if (cubeb_channel_layout_nb_channels(layout) >= CHANNELS_MAX) { - return false; - } - return true; - } - - int auto_matrix(); - int init(); - - const cubeb_sample_format _format; - const cubeb_channel_layout _in_ch_layout; ///< input channel layout - const cubeb_channel_layout _out_ch_layout; ///< output channel layout - const uint32_t _in_ch_count; ///< input channel count - const uint32_t _out_ch_count; ///< output channel count - const float _surround_mix_level = C_30DB; ///< surround mixing level - const float _center_mix_level = C_30DB; ///< center mixing level - const float _lfe_mix_level = 1; ///< LFE mixing level - double _matrix[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< floating point rematrixing coefficients - float _matrix_flt[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< single precision floating point rematrixing coefficients - int32_t _matrix32[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< 17.15 fixed point rematrixing coefficients - uint8_t _matrix_ch[CHANNELS_MAX][CHANNELS_MAX+1] = {{ 0 }}; ///< Lists of input channels per output channel that have non zero rematrixing coefficients - bool _clipping = false; ///< Set to true if clipping detection is required - bool _valid = false; ///< Set to true if context is valid. -}; - -int MixerContext::auto_matrix() -{ - double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = { { 0 } }; - double maxcoef = 0; - float maxval; - - cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout); - cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout); - - if (!sane_layout(in_ch_layout)) { - // Channel Not Supported - LOG("Input Layout %x is not supported", _in_ch_layout); - return -1; - } - - if (!sane_layout(out_ch_layout)) { - LOG("Output Layout %x is not supported", _out_ch_layout); - return -1; - } - - for (uint32_t i = 0; i < FF_ARRAY_ELEMS(matrix); i++) { - if (in_ch_layout & out_ch_layout & (1U << i)) { - matrix[i][i] = 1.0; - } - } - - cubeb_channel_layout unaccounted = in_ch_layout & ~out_ch_layout; - - // Rematrixing is done via a matrix of coefficient that should be applied to - // all channels. Channels are treated as pair and must be symmetrical (if a - // left channel exists, the corresponding right should exist too) unless the - // output layout has similar layout. Channels are then mixed toward the front - // center or back center if they exist with a slight bias toward the front. - - if (unaccounted & CHANNEL_FRONT_CENTER) { - if ((out_ch_layout & CUBEB_LAYOUT_STEREO) == CUBEB_LAYOUT_STEREO) { - if (in_ch_layout & CUBEB_LAYOUT_STEREO) { - matrix[FRONT_LEFT][FRONT_CENTER] += _center_mix_level; - matrix[FRONT_RIGHT][FRONT_CENTER] += _center_mix_level; - } else { - matrix[FRONT_LEFT][FRONT_CENTER] += M_SQRT1_2; - matrix[FRONT_RIGHT][FRONT_CENTER] += M_SQRT1_2; - } - } - } - if (unaccounted & CUBEB_LAYOUT_STEREO) { - if (out_ch_layout & CHANNEL_FRONT_CENTER) { - matrix[FRONT_CENTER][FRONT_LEFT] += M_SQRT1_2; - matrix[FRONT_CENTER][FRONT_RIGHT] += M_SQRT1_2; - if (in_ch_layout & CHANNEL_FRONT_CENTER) - matrix[FRONT_CENTER][FRONT_CENTER] = _center_mix_level * M_SQRT2; - } - } - - if (unaccounted & CHANNEL_BACK_CENTER) { - if (out_ch_layout & CHANNEL_BACK_LEFT) { - matrix[BACK_LEFT][BACK_CENTER] += M_SQRT1_2; - matrix[BACK_RIGHT][BACK_CENTER] += M_SQRT1_2; - } else if (out_ch_layout & CHANNEL_SIDE_LEFT) { - matrix[SIDE_LEFT][BACK_CENTER] += M_SQRT1_2; - matrix[SIDE_RIGHT][BACK_CENTER] += M_SQRT1_2; - } else if (out_ch_layout & CHANNEL_FRONT_LEFT) { - matrix[FRONT_LEFT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2; - matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2; - } else if (out_ch_layout & CHANNEL_FRONT_CENTER) { - matrix[FRONT_CENTER][BACK_CENTER] += - _surround_mix_level * M_SQRT1_2; - } - } - if (unaccounted & CHANNEL_BACK_LEFT) { - if (out_ch_layout & CHANNEL_BACK_CENTER) { - matrix[BACK_CENTER][BACK_LEFT] += M_SQRT1_2; - matrix[BACK_CENTER][BACK_RIGHT] += M_SQRT1_2; - } else if (out_ch_layout & CHANNEL_SIDE_LEFT) { - if (in_ch_layout & CHANNEL_SIDE_LEFT) { - matrix[SIDE_LEFT][BACK_LEFT] += M_SQRT1_2; - matrix[SIDE_RIGHT][BACK_RIGHT] += M_SQRT1_2; - } else { - matrix[SIDE_LEFT][BACK_LEFT] += 1.0; - matrix[SIDE_RIGHT][BACK_RIGHT] += 1.0; - } - } else if (out_ch_layout & CHANNEL_FRONT_LEFT) { - matrix[FRONT_LEFT][BACK_LEFT] += _surround_mix_level; - matrix[FRONT_RIGHT][BACK_RIGHT] += _surround_mix_level; - } else if (out_ch_layout & CHANNEL_FRONT_CENTER) { - matrix[FRONT_CENTER][BACK_LEFT] += _surround_mix_level * M_SQRT1_2; - matrix[FRONT_CENTER][BACK_RIGHT] += _surround_mix_level * M_SQRT1_2; - } - } - - if (unaccounted & CHANNEL_SIDE_LEFT) { - if (out_ch_layout & CHANNEL_BACK_LEFT) { - /* if back channels do not exist in the input, just copy side - channels to back channels, otherwise mix side into back */ - if (in_ch_layout & CHANNEL_BACK_LEFT) { - matrix[BACK_LEFT][SIDE_LEFT] += M_SQRT1_2; - matrix[BACK_RIGHT][SIDE_RIGHT] += M_SQRT1_2; - } else { - matrix[BACK_LEFT][SIDE_LEFT] += 1.0; - matrix[BACK_RIGHT][SIDE_RIGHT] += 1.0; - } - } else if (out_ch_layout & CHANNEL_BACK_CENTER) { - matrix[BACK_CENTER][SIDE_LEFT] += M_SQRT1_2; - matrix[BACK_CENTER][SIDE_RIGHT] += M_SQRT1_2; - } else if (out_ch_layout & CHANNEL_FRONT_LEFT) { - matrix[FRONT_LEFT][SIDE_LEFT] += _surround_mix_level; - matrix[FRONT_RIGHT][SIDE_RIGHT] += _surround_mix_level; - } else if (out_ch_layout & CHANNEL_FRONT_CENTER) { - matrix[FRONT_CENTER][SIDE_LEFT] += _surround_mix_level * M_SQRT1_2; - matrix[FRONT_CENTER][SIDE_RIGHT] += _surround_mix_level * M_SQRT1_2; - } - } - - if (unaccounted & CHANNEL_FRONT_LEFT_OF_CENTER) { - if (out_ch_layout & CHANNEL_FRONT_LEFT) { - matrix[FRONT_LEFT][FRONT_LEFT_OF_CENTER] += 1.0; - matrix[FRONT_RIGHT][FRONT_RIGHT_OF_CENTER] += 1.0; - } else if (out_ch_layout & CHANNEL_FRONT_CENTER) { - matrix[FRONT_CENTER][FRONT_LEFT_OF_CENTER] += M_SQRT1_2; - matrix[FRONT_CENTER][FRONT_RIGHT_OF_CENTER] += M_SQRT1_2; - } - } - /* mix LFE into front left/right or center */ - if (unaccounted & CHANNEL_LOW_FREQUENCY) { - if (out_ch_layout & CHANNEL_FRONT_CENTER) { - matrix[FRONT_CENTER][LOW_FREQUENCY] += _lfe_mix_level; - } else if (out_ch_layout & CHANNEL_FRONT_LEFT) { - matrix[FRONT_LEFT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2; - matrix[FRONT_RIGHT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2; - } - } - - // Normalize the conversion matrix. - for (uint32_t out_i = 0, i = 0; i < CHANNELS_MAX; i++) { - double sum = 0; - int in_i = 0; - if ((out_ch_layout & (1U << i)) == 0) { - continue; - } - for (uint32_t j = 0; j < CHANNELS_MAX; j++) { - if ((in_ch_layout & (1U << j)) == 0) { - continue; - } - if (i < FF_ARRAY_ELEMS(matrix) && j < FF_ARRAY_ELEMS(matrix[0])) { - _matrix[out_i][in_i] = matrix[i][j]; - } else { - _matrix[out_i][in_i] = - i == j && (in_ch_layout & out_ch_layout & (1U << i)); - } - sum += fabs(_matrix[out_i][in_i]); - in_i++; - } - maxcoef = std::max(maxcoef, sum); - out_i++; - } - - if (_format == CUBEB_SAMPLE_S16NE) { - maxval = 1.0; - } else { - maxval = INT_MAX; - } - - // Normalize matrix if needed. - if (maxcoef > maxval) { - maxcoef /= maxval; - for (uint32_t i = 0; i < CHANNELS_MAX; i++) - for (uint32_t j = 0; j < CHANNELS_MAX; j++) { - _matrix[i][j] /= maxcoef; - } - } - - if (_format == CUBEB_SAMPLE_FLOAT32NE) { - for (uint32_t i = 0; i < FF_ARRAY_ELEMS(_matrix); i++) { - for (uint32_t j = 0; j < FF_ARRAY_ELEMS(_matrix[0]); j++) { - _matrix_flt[i][j] = _matrix[i][j]; - } - } - } - - return 0; -} - -int MixerContext::init() -{ - int r = auto_matrix(); - if (r) { - return r; - } - - // Determine if matrix operation would overflow - if (_format == CUBEB_SAMPLE_S16NE) { - int maxsum = 0; - for (uint32_t i = 0; i < _out_ch_count; i++) { - double rem = 0; - int sum = 0; - - for (uint32_t j = 0; j < _in_ch_count; j++) { - double target = _matrix[i][j] * 32768 + rem; - int value = lrintf(target); - rem += target - value; - sum += std::abs(value); - } - maxsum = std::max(maxsum, sum); - } - if (maxsum > 32768) { - _clipping = true; - } - } - - // FIXME quantize for integers - for (uint32_t i = 0; i < CHANNELS_MAX; i++) { - int ch_in = 0; - for (uint32_t j = 0; j < CHANNELS_MAX; j++) { - _matrix32[i][j] = lrintf(_matrix[i][j] * 32768); - if (_matrix[i][j]) { - _matrix_ch[i][++ch_in] = j; - } - } - _matrix_ch[i][0] = ch_in; - } - - return 0; -} - -template -void -sum2(TYPE_SAMPLE * out, - uint32_t stride_out, - const TYPE_SAMPLE * in1, - const TYPE_SAMPLE * in2, - uint32_t stride_in, - TYPE_COEFF coeff1, - TYPE_COEFF coeff2, - F&& operand, - uint32_t frames) -{ - static_assert( - std::is_same::type>::value, - "function must return the same type as used by matrix_coeff"); - for (uint32_t i = 0; i < frames; i++) { - *out = operand(coeff1 * *in1 + coeff2 * *in2); - out += stride_out; - in1 += stride_in; - in2 += stride_in; - } -} - -template -void -copy(TYPE_SAMPLE * out, - uint32_t stride_out, - const TYPE_SAMPLE * in, - uint32_t stride_in, - TYPE_COEFF coeff, - F&& operand, - uint32_t frames) -{ - static_assert( - std::is_same::type>::value, - "function must return the same type as used by matrix_coeff"); - for (uint32_t i = 0; i < frames; i++) { - *out = operand(coeff * *in); - out += stride_out; - in += stride_in; - } -} - -template -static int rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn, - const TYPE_COEFF (&matrix_coeff)[COLS][COLS], - F&& aF, uint32_t frames) -{ - static_assert( - std::is_same::type>::value, - "function must return the same type as used by matrix_coeff"); - - for (uint32_t out_i = 0; out_i < s->_out_ch_count; out_i++) { - TYPE* out = aOut + out_i; - switch (s->_matrix_ch[out_i][0]) { - case 0: - for (uint32_t i = 0; i < frames; i++) { - out[i * s->_out_ch_count] = 0; - } - break; - case 1: { - int in_i = s->_matrix_ch[out_i][1]; - copy(out, - s->_out_ch_count, - aIn + in_i, - s->_in_ch_count, - matrix_coeff[out_i][in_i], - aF, - frames); - } break; - case 2: - sum2(out, - s->_out_ch_count, - aIn + s->_matrix_ch[out_i][1], - aIn + s->_matrix_ch[out_i][2], - s->_in_ch_count, - matrix_coeff[out_i][s->_matrix_ch[out_i][1]], - matrix_coeff[out_i][s->_matrix_ch[out_i][2]], - aF, - frames); - break; - default: - for (uint32_t i = 0; i < frames; i++) { - TYPE_COEFF v = 0; - for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) { - uint32_t in_i = s->_matrix_ch[out_i][1 + j]; - v += - *(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i]; - } - out[i * s->_out_ch_count] = aF(v); - } - break; - } - } - return 0; -} - -struct cubeb_mixer -{ - cubeb_mixer(cubeb_sample_format format, - uint32_t in_channels, - cubeb_channel_layout in_layout, - uint32_t out_channels, - cubeb_channel_layout out_layout) - : _context(format, in_channels, in_layout, out_channels, out_layout) - { - } - - template - void copy_and_trunc(size_t frames, - const T * input_buffer, - T * output_buffer) const - { - if (_context._in_ch_count <= _context._out_ch_count) { - // Not enough channels to copy, fill the gaps with silence. - if (_context._in_ch_count == 1 && _context._out_ch_count >= 2) { - // Special case for upmixing mono input to stereo and more. We will - // duplicate the mono channel to the first two channels. On most system, - // the first two channels are for left and right. It is commonly - // expected that mono will on both left+right channels - for (uint32_t i = 0; i < frames; i++) { - output_buffer[0] = output_buffer[1] = *input_buffer; - PodZero(output_buffer + 2, _context._out_ch_count - 2); - output_buffer += _context._out_ch_count; - input_buffer++; - } - return; - } - for (uint32_t i = 0; i < frames; i++) { - PodCopy(output_buffer, input_buffer, _context._in_ch_count); - output_buffer += _context._in_ch_count; - input_buffer += _context._in_ch_count; - PodZero(output_buffer, _context._out_ch_count - _context._in_ch_count); - output_buffer += _context._out_ch_count - _context._in_ch_count; - } - } else { - for (uint32_t i = 0; i < frames; i++) { - PodCopy(output_buffer, input_buffer, _context._out_ch_count); - output_buffer += _context._out_ch_count; - input_buffer += _context._in_ch_count; - } - } - } - - int mix(size_t frames, - const void * input_buffer, - size_t input_buffer_size, - void * output_buffer, - size_t output_buffer_size) const - { - if (frames <= 0 || _context._out_ch_count == 0) { - return 0; - } - - // Check if output buffer is of sufficient size. - size_t size_read_needed = - frames * _context._in_ch_count * cubeb_sample_size(_context._format); - if (input_buffer_size < size_read_needed) { - // We don't have enough data to read! - return -1; - } - if (output_buffer_size * _context._in_ch_count < - size_read_needed * _context._out_ch_count) { - return -1; - } - - if (!valid()) { - // The channel layouts were invalid or unsupported, instead we will simply - // either drop the extra channels, or fill with silence the missing ones - if (_context._format == CUBEB_SAMPLE_FLOAT32NE) { - copy_and_trunc(frames, - static_cast(input_buffer), - static_cast(output_buffer)); - } else { - assert(_context._format == CUBEB_SAMPLE_S16NE); - copy_and_trunc(frames, - static_cast(input_buffer), - reinterpret_cast(output_buffer)); - } - return 0; - } - - switch (_context._format) - { - case CUBEB_SAMPLE_FLOAT32NE: { - auto f = [](float x) { return x; }; - return rematrix(&_context, - static_cast(output_buffer), - static_cast(input_buffer), - _context._matrix_flt, - f, - frames); - } - case CUBEB_SAMPLE_S16NE: - if (_context._clipping) { - auto f = [](int x) { - int y = (x + 16384) >> 15; - // clip the signed integer value into the -32768,32767 range. - if ((y + 0x8000U) & ~0xFFFF) { - return (y >> 31) ^ 0x7FFF; - } - return y; - }; - return rematrix(&_context, - static_cast(output_buffer), - static_cast(input_buffer), - _context._matrix32, - f, - frames); - } else { - auto f = [](int x) { return (x + 16384) >> 15; }; - return rematrix(&_context, - static_cast(output_buffer), - static_cast(input_buffer), - _context._matrix32, - f, - frames); - } - break; - default: - assert(false); - break; - } - - return -1; - } - - // Return false if any of the input or ouput layout were invalid. - bool valid() const { return _context._valid; } - - virtual ~cubeb_mixer(){}; - - MixerContext _context; -}; - -cubeb_mixer* cubeb_mixer_create(cubeb_sample_format format, - uint32_t in_channels, - cubeb_channel_layout in_layout, - uint32_t out_channels, - cubeb_channel_layout out_layout) -{ - return new cubeb_mixer( - format, in_channels, in_layout, out_channels, out_layout); -} - -void cubeb_mixer_destroy(cubeb_mixer * mixer) -{ - delete mixer; -} - -int cubeb_mixer_mix(cubeb_mixer * mixer, - size_t frames, - const void * input_buffer, - size_t input_buffer_size, - void * output_buffer, - size_t output_buffer_size) -{ - return mixer->mix( - frames, input_buffer, input_buffer_size, output_buffer, output_buffer_size); -} diff --git a/media/libcubeb/src/cubeb_mixer.h b/media/libcubeb/src/cubeb_mixer.h deleted file mode 100644 index d43a237f9..000000000 --- a/media/libcubeb/src/cubeb_mixer.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright © 2016 Mozilla Foundation - * - * This program is made available under an ISC-style license. See the - * accompanying file LICENSE for details. - */ - -#ifndef CUBEB_MIXER -#define CUBEB_MIXER - -#include "cubeb/cubeb.h" // for cubeb_channel_layout and cubeb_stream_params. - -#if defined(__cplusplus) -extern "C" { -#endif - -typedef struct cubeb_mixer cubeb_mixer; -cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format, - uint32_t in_channels, - cubeb_channel_layout in_layout, - uint32_t out_channels, - cubeb_channel_layout out_layout); -void cubeb_mixer_destroy(cubeb_mixer * mixer); -int cubeb_mixer_mix(cubeb_mixer * mixer, - size_t frames, - const void * input_buffer, - size_t input_buffer_size, - void * output_buffer, - size_t output_buffer_size); - -unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout channel_layout); - -#if defined(__cplusplus) -} -#endif - -#endif // CUBEB_MIXER diff --git a/media/libcubeb/src/cubeb_opensl.c b/media/libcubeb/src/cubeb_opensl.c index 96374ec07..dd5416228 100644 --- a/media/libcubeb/src/cubeb_opensl.c +++ b/media/libcubeb/src/cubeb_opensl.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -20,265 +19,85 @@ #include #include #include -#endif -#include "cubeb/cubeb.h" -#include "cubeb-internal.h" -#include "cubeb_resampler.h" -#include "cubeb-sles.h" -#include "cubeb_array_queue.h" -#include "android/cubeb-output-latency.h" - -#if defined(__ANDROID__) -#ifdef LOG -#undef LOG -#endif -//#define LOGGING_ENABLED -#ifdef LOGGING_ENABLED #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args) -#else -#define LOG(...) -#endif - -//#define TIMESTAMP_ENABLED -#ifdef TIMESTAMP_ENABLED -#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) -#define LOG_TS(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL ES: Timestamp(usec)" , ## args) -#define TIMESTAMP(msg) do { \ - struct timeval timestamp; \ - int ts_ret = gettimeofday(×tamp, NULL); \ - if (ts_ret == 0) { \ - LOG_TS("%lld: %s (%s %s:%d)", timestamp.tv_sec * 1000000LL + timestamp.tv_usec, msg, __FUNCTION__, FILENAME, __LINE__);\ - } else { \ - LOG_TS("Error: %s (%s %s:%d) - %s", msg, __FUNCTION__, FILENAME, __LINE__);\ - } \ -} while(0) -#else -#define TIMESTAMP(...) -#endif - #define ANDROID_VERSION_GINGERBREAD_MR1 10 -#define ANDROID_VERSION_JELLY_BEAN 18 #define ANDROID_VERSION_LOLLIPOP 21 #define ANDROID_VERSION_MARSHMALLOW 23 -#define ANDROID_VERSION_N_MR1 25 #endif - -#define DEFAULT_SAMPLE_RATE 48000 -#define DEFAULT_NUM_OF_FRAMES 480 -// If the latency requested is above this threshold, this stream is considered -// intended for playback (vs. real-time). Tell Android it should favor saving -// power over performance or latency. -// This is around 100ms at 44100 or 48000 -#define POWERSAVE_LATENCY_FRAMES_THRESHOLD 4000 +#include "cubeb/cubeb.h" +#include "cubeb-internal.h" +#include "cubeb_resampler.h" +#include "cubeb-sles.h" static struct cubeb_ops const opensl_ops; struct cubeb { struct cubeb_ops const * ops; void * lib; + void * libmedia; + int32_t (* get_output_latency)(uint32_t * latency, int stream_type); SLInterfaceID SL_IID_BUFFERQUEUE; SLInterfaceID SL_IID_PLAY; #if defined(__ANDROID__) SLInterfaceID SL_IID_ANDROIDCONFIGURATION; - SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE; #endif SLInterfaceID SL_IID_VOLUME; - SLInterfaceID SL_IID_RECORD; SLObjectItf engObj; SLEngineItf eng; SLObjectItf outmixObj; - output_latency_function * p_output_latency_function; }; #define NELEMS(A) (sizeof(A) / sizeof A[0]) -#define NBUFS 2 +#define NBUFS 4 +#define AUDIO_STREAM_TYPE_MUSIC 3 struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; - void * user_ptr; - /**/ pthread_mutex_t mutex; SLObjectItf playerObj; SLPlayItf play; SLBufferQueueItf bufq; SLVolumeItf volume; - void ** queuebuf; - uint32_t queuebuf_capacity; + uint8_t *queuebuf[NBUFS]; int queuebuf_idx; long queuebuf_len; long bytespersec; long framesize; - /* Total number of played frames. - * Synchronized by stream::mutex lock. */ long written; - /* Flag indicating draining. Synchronized - * by stream::mutex lock. */ int draining; - /* Flags to determine in/out.*/ - uint32_t input_enabled; - uint32_t output_enabled; - /* Recorder abstract object. */ - SLObjectItf recorderObj; - /* Recorder Itf for input capture. */ - SLRecordItf recorderItf; - /* Buffer queue for input capture. */ - SLAndroidSimpleBufferQueueItf recorderBufferQueueItf; - /* Store input buffers. */ - void ** input_buffer_array; - /* The capacity of the array. - * On capture only can be small (4). - * On full duplex is calculated to - * store 1 sec of data buffers. */ - uint32_t input_array_capacity; - /* Current filled index of input buffer array. - * It is initiated to -1 indicating buffering - * have not started yet. */ - int input_buffer_index; - /* Length of input buffer.*/ - uint32_t input_buffer_length; - /* Input frame size */ - uint32_t input_frame_size; - /* Device sampling rate. If user rate is not - * accepted an compatible rate is set. If it is - * accepted this is equal to params.rate. */ - uint32_t input_device_rate; - /* Exchange input buffers between input - * and full duplex threads. */ - array_queue * input_queue; - /* Silent input buffer used on full duplex. */ - void * input_silent_buffer; - /* Number of input frames from the start of the stream*/ - uint32_t input_total_frames; - /* Flag to stop the execution of user callback and - * close all working threads. Synchronized by - * stream::mutex lock. */ - uint32_t shutdown; - /* Store user callback. */ + cubeb_stream_type stream_type; + cubeb_data_callback data_callback; - /* Store state callback. */ cubeb_state_callback state_callback; + void * user_ptr; cubeb_resampler * resampler; - unsigned int user_output_rate; - unsigned int output_configured_rate; - unsigned int buffer_size_frames; - // Audio output latency used in cubeb_stream_get_position(). - unsigned int output_latency_ms; + unsigned int inputrate; + unsigned int outputrate; + unsigned int latency; int64_t lastPosition; int64_t lastPositionTimeStamp; int64_t lastCompensativePosition; - int voice; }; -/* Forward declaration. */ -static int opensl_stop_player(cubeb_stream * stm); -static int opensl_stop_recorder(cubeb_stream * stm); - -static int -opensl_get_draining(cubeb_stream * stm) -{ -#ifdef DEBUG - int r = pthread_mutex_trylock(&stm->mutex); - assert((r == EDEADLK || r == EBUSY) && "get_draining: mutex should be locked but it's not."); -#endif - return stm->draining; -} - -static void -opensl_set_draining(cubeb_stream * stm, int value) -{ -#ifdef DEBUG - int r = pthread_mutex_trylock(&stm->mutex); - LOG("set draining try r = %d", r); - assert((r == EDEADLK || r == EBUSY) && "set_draining: mutex should be locked but it's not."); -#endif - assert(value == 0 || value == 1); - stm->draining = value; -} - -static void -opensl_notify_drained(cubeb_stream * stm) -{ - assert(stm); - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - if (draining) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); - if (stm->play) { - LOG("stop player in play_callback"); - r = opensl_stop_player(stm); - assert(r == CUBEB_OK); - } - if (stm->recorderItf) { - r = opensl_stop_recorder(stm); - assert(r == CUBEB_OK); - } - } -} - -static uint32_t -opensl_get_shutdown(cubeb_stream * stm) -{ -#ifdef DEBUG - int r = pthread_mutex_trylock(&stm->mutex); - assert((r == EDEADLK || r == EBUSY) && "get_shutdown: mutex should be locked but it's not."); -#endif - return stm->shutdown; -} - -static void -opensl_set_shutdown(cubeb_stream * stm, uint32_t value) -{ -#ifdef DEBUG - int r = pthread_mutex_trylock(&stm->mutex); - LOG("set shutdown try r = %d", r); - assert((r == EDEADLK || r == EBUSY) && "set_shutdown: mutex should be locked but it's not."); -#endif - assert(value == 0 || value == 1); - stm->shutdown = value; -} - static void play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event) { cubeb_stream * stm = user_ptr; + int draining; assert(stm); switch (event) { - case SL_PLAYEVENT_HEADATMARKER: - opensl_notify_drained(stm); - break; - default: - break; - } -} - -static void -recorder_marker_callback (SLRecordItf caller, void * pContext, SLuint32 event) -{ - cubeb_stream * stm = pContext; - assert(stm); - - if (event == SL_RECORDEVENT_HEADATMARKER) { - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); + case SL_PLAYEVENT_HEADATMARKER: + pthread_mutex_lock(&stm->mutex); + draining = stm->draining; + pthread_mutex_unlock(&stm->mutex); if (draining) { stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); - if (stm->recorderItf) { - r = opensl_stop_recorder(stm); - assert(r == CUBEB_OK); - } - if (stm->play) { - r = opensl_stop_player(stm); - assert(r == CUBEB_OK); - } + (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); } + break; + default: + break; } } @@ -289,333 +108,90 @@ bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) assert(stm); SLBufferQueueState state; SLresult res; - long written = 0; res = (*stm->bufq)->GetState(stm->bufq, &state); assert(res == SL_RESULT_SUCCESS); - if (state.count > 1) { + if (state.count > 1) return; - } - - uint8_t *buf = stm->queuebuf[stm->queuebuf_idx]; - written = 0; - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - uint32_t shutdown = opensl_get_shutdown(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - if (!draining && !shutdown) { - written = cubeb_resampler_fill(stm->resampler, - NULL, NULL, - buf, stm->queuebuf_len / stm->framesize); - LOG("bufferqueue_callback: resampler fill returned %ld frames", written); - if (written < 0 || written * stm->framesize > stm->queuebuf_len) { - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - opensl_stop_player(stm); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return; - } - } - - // Keep sending silent data even in draining mode to prevent the audio - // back-end from being stopped automatically by OpenSL/ES. - assert(stm->queuebuf_len >= written * stm->framesize); - memset(buf + written * stm->framesize, 0, stm->queuebuf_len - written * stm->framesize); - res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity; - if (written > 0) { + SLuint32 i; + for (i = state.count; i < NBUFS; i++) { + uint8_t *buf = stm->queuebuf[stm->queuebuf_idx]; + long written = 0; pthread_mutex_lock(&stm->mutex); - stm->written += written; + int draining = stm->draining; pthread_mutex_unlock(&stm->mutex); - } - if (!draining && written * stm->framesize < stm->queuebuf_len) { - LOG("bufferqueue_callback draining"); - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; - opensl_set_draining(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (written_duration == 0) { - // since we didn't write any sample, it's not possible to reach the marker - // time and trigger the callback. We should initiative notify drained. - opensl_notify_drained(stm); - } else { + if (!draining) { + written = cubeb_resampler_fill(stm->resampler, + NULL, NULL, + buf, stm->queuebuf_len / stm->framesize); + if (written < 0 || written * stm->framesize > stm->queuebuf_len) { + (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); + return; + } + } + + // Keep sending silent data even in draining mode to prevent the audio + // back-end from being stopped automatically by OpenSL/ES. + memset(buf + written * stm->framesize, 0, stm->queuebuf_len - written * stm->framesize); + res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len); + assert(res == SL_RESULT_SUCCESS); + stm->queuebuf_idx = (stm->queuebuf_idx + 1) % NBUFS; + if (written > 0) { + pthread_mutex_lock(&stm->mutex); + stm->written += written; + pthread_mutex_unlock(&stm->mutex); + } + + if (!draining && written * stm->framesize < stm->queuebuf_len) { + pthread_mutex_lock(&stm->mutex); + int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; + stm->draining = 1; + pthread_mutex_unlock(&stm->mutex); // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf // to make sure all the data has been processed. (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); + return; } - return; - } -} - -static int -opensl_enqueue_recorder(cubeb_stream * stm, void ** last_filled_buffer) -{ - assert(stm); - - int current_index = stm->input_buffer_index; - void * last_buffer = NULL; - - if (current_index < 0) { - // This is the first enqueue - current_index = 0; - } else { - // The current index hold the last filled buffer get it before advance index. - last_buffer = stm->input_buffer_array[current_index]; - // Advance to get next available buffer - current_index = (current_index + 1) % stm->input_array_capacity; - } - // enqueue next empty buffer to be filled by the recorder - SLresult res = (*stm->recorderBufferQueueItf)->Enqueue(stm->recorderBufferQueueItf, - stm->input_buffer_array[current_index], - stm->input_buffer_length); - if (res != SL_RESULT_SUCCESS ) { - LOG("Enqueue recorder failed. Error code: %lu", res); - return CUBEB_ERROR; - } - // All good, update buffer and index. - stm->input_buffer_index = current_index; - if (last_filled_buffer) { - *last_filled_buffer = last_buffer; - } - return CUBEB_OK; -} - -// input data callback -void recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context) -{ - assert(context); - cubeb_stream * stm = context; - assert(stm->recorderBufferQueueItf); - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - uint32_t shutdown = opensl_get_shutdown(stm); - int draining = opensl_get_draining(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (shutdown || draining) { - // According to the OpenSL ES 1.1 Specification, 8.14 SLBufferQueueItf - // page 184, on transition to the SL_RECORDSTATE_STOPPED state, - // the application should continue to enqueue buffers onto the queue - // to retrieve the residual recorded data in the system. - r = opensl_enqueue_recorder(stm, NULL); - assert(r == CUBEB_OK); - return; - } - - // Enqueue next available buffer and get the last filled buffer. - void * input_buffer = NULL; - r = opensl_enqueue_recorder(stm, &input_buffer); - assert(r == CUBEB_OK); - assert(input_buffer); - // Fill resampler with last input - long input_frame_count = stm->input_buffer_length / stm->input_frame_size; - long got = cubeb_resampler_fill(stm->resampler, - input_buffer, - &input_frame_count, - NULL, - 0); - // Error case - if (got < 0 || got > input_frame_count) { - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - r = opensl_stop_recorder(stm); - assert(r == CUBEB_OK); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - } - - // Advance total stream frames - stm->input_total_frames += got; - - if (got < input_frame_count) { - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_draining(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - int64_t duration = INT64_C(1000) * stm->input_total_frames / stm->input_device_rate; - (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)duration); - return; - } -} - -void recorder_fullduplex_callback(SLAndroidSimpleBufferQueueItf bq, void * context) -{ - assert(context); - cubeb_stream * stm = context; - assert(stm->recorderBufferQueueItf); - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - uint32_t shutdown = opensl_get_shutdown(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (shutdown || draining) { - /* On draining and shutdown the recorder should have been stoped from - * the one set the flags. Accordint to the doc, on transition to - * the SL_RECORDSTATE_STOPPED state, the application should - * continue to enqueue buffers onto the queue to retrieve the residual - * recorded data in the system. */ - LOG("Input shutdown %d or drain %d", shutdown, draining); - int r = opensl_enqueue_recorder(stm, NULL); - assert(r == CUBEB_OK); - return; - } - - // Enqueue next available buffer and get the last filled buffer. - void * input_buffer = NULL; - r = opensl_enqueue_recorder(stm, &input_buffer); - assert(r == CUBEB_OK); - assert(input_buffer); - - assert(stm->input_queue); - r = array_queue_push(stm->input_queue, input_buffer); - if (r == -1) { - LOG("Input queue is full, drop input ..."); - return; } - - LOG("Input pushed in the queue, input array %zu", - array_queue_get_size(stm->input_queue)); } -static void -player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr) -{ - TIMESTAMP("ENTER"); - cubeb_stream * stm = user_ptr; - assert(stm); - SLresult res; - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - uint32_t shutdown = opensl_get_shutdown(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - // Get output - void * output_buffer = NULL; - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - output_buffer = stm->queuebuf[stm->queuebuf_idx]; - // Advance the output buffer queue index - stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity; - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (shutdown || draining) { - LOG("Shutdown/draining, send silent"); - // Set silent on buffer - memset(output_buffer, 0, stm->queuebuf_len); - - // Enqueue data in player buffer queue - res = (*stm->bufq)->Enqueue(stm->bufq, - output_buffer, - stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - return; - } - - // Get input. - void * input_buffer = array_queue_pop(stm->input_queue); - long input_frame_count = stm->input_buffer_length / stm->input_frame_size; - long frames_needed = stm->queuebuf_len / stm->framesize; - if (!input_buffer) { - LOG("Input hole set silent input buffer"); - input_buffer = stm->input_silent_buffer; - } - - long written = 0; - // Trigger user callback through resampler - written = cubeb_resampler_fill(stm->resampler, - input_buffer, - &input_frame_count, - output_buffer, - frames_needed); - - LOG("Fill: written %ld, frames_needed %ld, input array size %zu", - written, frames_needed, array_queue_get_size(stm->input_queue)); - - if (written < 0 || written > frames_needed) { - // Error case - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - opensl_stop_player(stm); - opensl_stop_recorder(stm); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - memset(output_buffer, 0, stm->queuebuf_len); - - // Enqueue data in player buffer queue - res = (*stm->bufq)->Enqueue(stm->bufq, - output_buffer, - stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - return; - } - - // Advance total out written frames counter - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - stm->written += written; - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if ( written < frames_needed) { - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; - opensl_set_draining(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf - // to make sure all the data has been processed. - (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); +#if defined(__ANDROID__) +static SLuint32 +convert_stream_type_to_sl_stream(cubeb_stream_type stream_type) +{ + switch(stream_type) { + case CUBEB_STREAM_TYPE_SYSTEM: + return SL_ANDROID_STREAM_SYSTEM; + case CUBEB_STREAM_TYPE_MUSIC: + return SL_ANDROID_STREAM_MEDIA; + case CUBEB_STREAM_TYPE_NOTIFICATION: + return SL_ANDROID_STREAM_NOTIFICATION; + case CUBEB_STREAM_TYPE_ALARM: + return SL_ANDROID_STREAM_ALARM; + case CUBEB_STREAM_TYPE_VOICE_CALL: + return SL_ANDROID_STREAM_VOICE; + case CUBEB_STREAM_TYPE_RING: + return SL_ANDROID_STREAM_RING; + case CUBEB_STREAM_TYPE_SYSTEM_ENFORCED: + return SL_ANDROID_STREAM_SYSTEM_ENFORCED; + default: + return 0xFFFFFFFF; } - - // Keep sending silent data even in draining mode to prevent the audio - // back-end from being stopped automatically by OpenSL/ES. - memset((uint8_t *)output_buffer + written * stm->framesize, 0, - stm->queuebuf_len - written * stm->framesize); - - // Enqueue data in player buffer queue - res = (*stm->bufq)->Enqueue(stm->bufq, - output_buffer, - stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - TIMESTAMP("EXIT"); } +#endif static void opensl_destroy(cubeb * ctx); #if defined(__ANDROID__) + #if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) typedef int (system_property_get)(const char*, char*); static int -wrap_system_property_get(const char* name, char* value) +__system_property_get(const char* name, char* value) { void* libc = dlopen("libc.so", RTLD_LAZY); if (!libc) { @@ -640,18 +216,14 @@ get_android_version(void) memset(version_string, 0, PROP_VALUE_MAX); -#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) - int len = wrap_system_property_get("ro.build.version.sdk", version_string); -#else int len = __system_property_get("ro.build.version.sdk", version_string); -#endif if (len <= 0) { LOG("Failed to get Android version!\n"); return len; } int version = (int)strtol(version_string, NULL, 10); - LOG("Android version %d", version); + LOG("%d", version); return version; } #endif @@ -677,11 +249,30 @@ opensl_init(cubeb ** context, char const * context_name) ctx->ops = &opensl_ops; ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY); - if (!ctx->lib) { + ctx->libmedia = dlopen("libmedia.so", RTLD_LAZY); + if (!ctx->lib || !ctx->libmedia) { free(ctx); return CUBEB_ERROR; } + /* Get the latency, in ms, from AudioFlinger */ + /* status_t AudioSystem::getOutputLatency(uint32_t* latency, + * audio_stream_type_t streamType) */ + /* First, try the most recent signature. */ + ctx->get_output_latency = + dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t"); + if (!ctx->get_output_latency) { + /* in case of failure, try the legacy version. */ + /* status_t AudioSystem::getOutputLatency(uint32_t* latency, + * int streamType) */ + ctx->get_output_latency = + dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); + if (!ctx->get_output_latency) { + opensl_destroy(ctx); + return CUBEB_ERROR; + } + } + typedef SLresult (*slCreateEngine_t)(SLObjectItf *, SLuint32, const SLEngineOption *, @@ -696,21 +287,16 @@ opensl_init(cubeb ** context, char const * context_name) ctx->SL_IID_BUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE"); #if defined(__ANDROID__) ctx->SL_IID_ANDROIDCONFIGURATION = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION"); - ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); #endif ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY"); - ctx->SL_IID_RECORD = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_RECORD"); - if (!f_slCreateEngine || !SL_IID_ENGINE || !SL_IID_OUTPUTMIX || !ctx->SL_IID_BUFFERQUEUE || #if defined(__ANDROID__) !ctx->SL_IID_ANDROIDCONFIGURATION || - !ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE || #endif - !ctx->SL_IID_PLAY || - !ctx->SL_IID_RECORD) { + !ctx->SL_IID_PLAY) { opensl_destroy(ctx); return CUBEB_ERROR; } @@ -751,14 +337,8 @@ opensl_init(cubeb ** context, char const * context_name) return CUBEB_ERROR; } - ctx->p_output_latency_function = cubeb_output_latency_load_method(android_version); - if (!ctx->p_output_latency_function) { - LOG("Warning: output latency is not available, cubeb_stream_get_position() is not supported"); - } - *context = ctx; - LOG("Cubeb init (%p) success", ctx); return CUBEB_OK; } @@ -779,258 +359,201 @@ opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) return CUBEB_OK; } -static void -opensl_destroy(cubeb * ctx) -{ - if (ctx->outmixObj) - (*ctx->outmixObj)->Destroy(ctx->outmixObj); - if (ctx->engObj) - cubeb_destroy_sles_engine(&ctx->engObj); - dlclose(ctx->lib); - if (ctx->p_output_latency_function) - cubeb_output_latency_unload_method(ctx->p_output_latency_function); - free(ctx); -} - -static void opensl_stream_destroy(cubeb_stream * stm); - -static int -opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params) -{ - assert(format); - assert(params); - - format->formatType = SL_DATAFORMAT_PCM; - format->numChannels = params->channels; - // samplesPerSec is in milliHertz - format->samplesPerSec = params->rate * 1000; - format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - format->channelMask = params->channels == 1 ? - SL_SPEAKER_FRONT_CENTER : - SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - - switch (params->format) { - case CUBEB_SAMPLE_S16LE: - format->endianness = SL_BYTEORDER_LITTLEENDIAN; - break; - case CUBEB_SAMPLE_S16BE: - format->endianness = SL_BYTEORDER_BIGENDIAN; - break; - default: - return CUBEB_ERROR_INVALID_FORMAT; - } - return CUBEB_OK; -} - static int -opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params) -{ - assert(stm); - assert(params); - - SLDataLocator_AndroidSimpleBufferQueue lDataLocatorOut; - lDataLocatorOut.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - lDataLocatorOut.numBuffers = NBUFS; - - SLDataFormat_PCM lDataFormat; - int r = opensl_set_format(&lDataFormat, params); - if (r != CUBEB_OK) { - return CUBEB_ERROR_INVALID_FORMAT; +opensl_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) +{ + /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html + * We don't want to deal with JNI here (and we don't have Java on b2g anyways), + * so we just dlopen the library and get the two symbols we need. */ + int r; + void * libmedia; + uint32_t (*get_primary_output_samplingrate)(); + uint32_t (*get_output_samplingrate)(int * samplingRate, int streamType); + + libmedia = dlopen("libmedia.so", RTLD_LAZY); + if (!libmedia) { + return CUBEB_ERROR; } - /* For now set device rate to params rate. */ - stm->input_device_rate = params->rate; - - SLDataSink lDataSink; - lDataSink.pLocator = &lDataLocatorOut; - lDataSink.pFormat = &lDataFormat; - - SLDataLocator_IODevice lDataLocatorIn; - lDataLocatorIn.locatorType = SL_DATALOCATOR_IODEVICE; - lDataLocatorIn.deviceType = SL_IODEVICE_AUDIOINPUT; - lDataLocatorIn.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; - lDataLocatorIn.device = NULL; - - SLDataSource lDataSource; - lDataSource.pLocator = &lDataLocatorIn; - lDataSource.pFormat = NULL; - - const SLInterfaceID lSoundRecorderIIDs[] = { stm->context->SL_IID_RECORD, - stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - stm->context->SL_IID_ANDROIDCONFIGURATION }; - - const SLboolean lSoundRecorderReqs[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; - // create the audio recorder abstract object - SLresult res = (*stm->context->eng)->CreateAudioRecorder(stm->context->eng, - &stm->recorderObj, - &lDataSource, - &lDataSink, - NELEMS(lSoundRecorderIIDs), - lSoundRecorderIIDs, - lSoundRecorderReqs); - // Sample rate not supported. Try again with default sample rate! - if (res == SL_RESULT_CONTENT_UNSUPPORTED) { - if (stm->output_enabled && stm->output_configured_rate != 0) { - // Set the same with the player. Since there is no - // api for input device this is a safe choice. - stm->input_device_rate = stm->output_configured_rate; - } else { - // The output preferred rate is used for an input only scenario. - // The default rate expected to be supported from all android devices. - stm->input_device_rate = DEFAULT_SAMPLE_RATE; - } - lDataFormat.samplesPerSec = stm->input_device_rate * 1000; - res = (*stm->context->eng)->CreateAudioRecorder(stm->context->eng, - &stm->recorderObj, - &lDataSource, - &lDataSink, - NELEMS(lSoundRecorderIIDs), - lSoundRecorderIIDs, - lSoundRecorderReqs); - - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to create recorder. Error code: %lu", res); - return CUBEB_ERROR; + /* uint32_t AudioSystem::getPrimaryOutputSamplingRate(void) */ + get_primary_output_samplingrate = + dlsym(libmedia, "_ZN7android11AudioSystem28getPrimaryOutputSamplingRateEv"); + if (!get_primary_output_samplingrate) { + /* fallback to + * status_t AudioSystem::getOutputSamplingRate(int* samplingRate, int streamType) + * if we cannot find getPrimaryOutputSamplingRate. */ + get_output_samplingrate = + dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPj19audio_stream_type_t"); + if (!get_output_samplingrate) { + /* Another signature exists, with a int instead of an audio_stream_type_t */ + get_output_samplingrate = + dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPii"); + if (!get_output_samplingrate) { + dlclose(libmedia); + return CUBEB_ERROR; + } } } - - if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) { - SLAndroidConfigurationItf recorderConfig; - res = (*stm->recorderObj) - ->GetInterface(stm->recorderObj, - stm->context->SL_IID_ANDROIDCONFIGURATION, - &recorderConfig); - - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get the android configuration interface for recorder. Error " - "code: %lu", - res); + if (get_primary_output_samplingrate) { + *rate = get_primary_output_samplingrate(); + } else { + /* We don't really know about the type, here, so we just pass music. */ + r = get_output_samplingrate((int *) rate, AUDIO_STREAM_TYPE_MUSIC); + if (r) { + dlclose(libmedia); return CUBEB_ERROR; } + } - // Voice recognition is the lowest latency, according to the docs. Camcorder - // uses a microphone that is in the same direction as the camera. - SLint32 streamType = stm->voice ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION - : SL_ANDROID_RECORDING_PRESET_CAMCORDER; - - res = (*recorderConfig) - ->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, - &streamType, sizeof(SLint32)); + dlclose(libmedia); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set the android configuration to VOICE for the recorder. " - "Error code: %lu", res); - return CUBEB_ERROR; - } - } - // realize the audio recorder - res = (*stm->recorderObj)->Realize(stm->recorderObj, SL_BOOLEAN_FALSE); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to realize recorder. Error code: %lu", res); - return CUBEB_ERROR; - } - // get the record interface - res = (*stm->recorderObj)->GetInterface(stm->recorderObj, - stm->context->SL_IID_RECORD, - &stm->recorderItf); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get recorder interface. Error code: %lu", res); + /* Depending on which method we called above, we can get a zero back, yet have + * a non-error return value, especially if the audio system is not + * ready/shutting down (i.e. when we can't get our hand on the AudioFlinger + * thread). */ + if (*rate == 0) { return CUBEB_ERROR; } - res = (*stm->recorderItf)->RegisterCallback(stm->recorderItf, recorder_marker_callback, stm); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register recorder marker callback. Error code: %lu", res); - return CUBEB_ERROR; - } + return CUBEB_OK; +} - (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0); +static int +opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) +{ + /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html + * We don't want to deal with JNI here (and we don't have Java on b2g anyways), + * so we just dlopen the library and get the two symbols we need. */ - res = (*stm->recorderItf)->SetCallbackEventsMask(stm->recorderItf, (SLuint32)SL_RECORDEVENT_HEADATMARKER); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set headatmarker event mask. Error code: %lu", res); - return CUBEB_ERROR; - } - // get the simple android buffer queue interface - res = (*stm->recorderObj)->GetInterface(stm->recorderObj, - stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - &stm->recorderBufferQueueItf); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get recorder (android) buffer queue interface. Error code: %lu", res); + int r; + void * libmedia; + size_t (*get_primary_output_frame_count)(void); + int (*get_output_frame_count)(size_t * frameCount, int streamType); + uint32_t primary_sampling_rate; + size_t primary_buffer_size; + + r = opensl_get_preferred_sample_rate(ctx, &primary_sampling_rate); + + if (r) { return CUBEB_ERROR; } - // register callback on record (input) buffer queue - slAndroidSimpleBufferQueueCallback rec_callback = recorder_callback; - if (stm->output_enabled) { - // Register full duplex callback instead. - rec_callback = recorder_fullduplex_callback; - } - res = (*stm->recorderBufferQueueItf)->RegisterCallback(stm->recorderBufferQueueItf, - rec_callback, - stm); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register recorder buffer queue callback. Error code: %lu", res); + libmedia = dlopen("libmedia.so", RTLD_LAZY); + if (!libmedia) { return CUBEB_ERROR; } - // Calculate length of input buffer according to requested latency - stm->input_frame_size = params->channels * sizeof(int16_t); - stm->input_buffer_length = (stm->input_frame_size * stm->buffer_size_frames); - - // Calculate the capacity of input array - stm->input_array_capacity = NBUFS; - if (stm->output_enabled) { - // Full duplex, update capacity to hold 1 sec of data - stm->input_array_capacity = 1 * stm->input_device_rate / stm->input_buffer_length; - } - // Allocate input array - stm->input_buffer_array = (void**)calloc(1, sizeof(void*)*stm->input_array_capacity); - // Buffering has not started yet. - stm->input_buffer_index = -1; - // Prepare input buffers - for(uint32_t i = 0; i < stm->input_array_capacity; ++i) { - stm->input_buffer_array[i] = calloc(1, stm->input_buffer_length); + /* JB variant */ + /* size_t AudioSystem::getPrimaryOutputFrameCount(void) */ + get_primary_output_frame_count = + dlsym(libmedia, "_ZN7android11AudioSystem26getPrimaryOutputFrameCountEv"); + if (!get_primary_output_frame_count) { + /* ICS variant */ + /* status_t AudioSystem::getOutputFrameCount(int* frameCount, int streamType) */ + get_output_frame_count = + dlsym(libmedia, "_ZN7android11AudioSystem19getOutputFrameCountEPii"); + if (!get_output_frame_count) { + dlclose(libmedia); + return CUBEB_ERROR; + } } - // On full duplex allocate input queue and silent buffer - if (stm->output_enabled) { - stm->input_queue = array_queue_create(stm->input_array_capacity); - assert(stm->input_queue); - stm->input_silent_buffer = calloc(1, stm->input_buffer_length); - assert(stm->input_silent_buffer); + if (get_primary_output_frame_count) { + primary_buffer_size = get_primary_output_frame_count(); + } else { + if (get_output_frame_count(&primary_buffer_size, params.stream_type) != 0) { + return CUBEB_ERROR; + } } - // Enqueue buffer to start rolling once recorder started - r = opensl_enqueue_recorder(stm, NULL); - if (r != CUBEB_OK) { - return r; - } + /* To get a fast track in Android's mixer, we need to be at the native + * samplerate, which is device dependant. Some devices might be able to + * resample when playing a fast track, but it's pretty rare. */ + *latency_frames = NBUFS * primary_buffer_size; - LOG("Cubeb stream init recorder success"); + dlclose(libmedia); return CUBEB_OK; } -static int -opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { - assert(stm); - assert(params); +static void +opensl_destroy(cubeb * ctx) +{ + if (ctx->outmixObj) + (*ctx->outmixObj)->Destroy(ctx->outmixObj); + if (ctx->engObj) + cubeb_destroy_sles_engine(&ctx->engObj); + dlclose(ctx->lib); + dlclose(ctx->libmedia); + free(ctx); +} - stm->user_output_rate = params->rate; - stm->framesize = params->channels * sizeof(int16_t); - stm->lastPosition = -1; - stm->lastPositionTimeStamp = 0; - stm->lastCompensativePosition = -1; +static void opensl_stream_destroy(cubeb_stream * stm); + +static int +opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, cubeb_state_callback state_callback, + void * user_ptr) +{ + cubeb_stream * stm; + + assert(ctx); + assert(!input_stream_params && "not supported"); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } + + *stream = NULL; SLDataFormat_PCM format; - int r = opensl_set_format(&format, params); - if (r != CUBEB_OK) { + + format.formatType = SL_DATAFORMAT_PCM; + format.numChannels = output_stream_params->channels; + // samplesPerSec is in milliHertz + format.samplesPerSec = output_stream_params->rate * 1000; + format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + format.channelMask = output_stream_params->channels == 1 ? + SL_SPEAKER_FRONT_CENTER : + SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + + switch (output_stream_params->format) { + case CUBEB_SAMPLE_S16LE: + format.endianness = SL_BYTEORDER_LITTLEENDIAN; + break; + case CUBEB_SAMPLE_S16BE: + format.endianness = SL_BYTEORDER_BIGENDIAN; + break; + default: return CUBEB_ERROR_INVALID_FORMAT; } + stm = calloc(1, sizeof(*stm)); + assert(stm); + + stm->context = ctx; + stm->data_callback = data_callback; + stm->state_callback = state_callback; + stm->user_ptr = user_ptr; + + stm->inputrate = output_stream_params->rate; + stm->latency = latency_frames; + stm->stream_type = output_stream_params->stream_type; + stm->framesize = output_stream_params->channels * sizeof(int16_t); + stm->lastPosition = -1; + stm->lastPositionTimeStamp = 0; + stm->lastCompensativePosition = -1; + + int r = pthread_mutex_init(&stm->mutex, NULL); + assert(r == 0); + SLDataLocator_BufferQueue loc_bufq; loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE; loc_bufq.numBuffers = NBUFS; @@ -1040,15 +563,15 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { SLDataLocator_OutputMix loc_outmix; loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; - loc_outmix.outputMix = stm->context->outmixObj; + loc_outmix.outputMix = ctx->outmixObj; SLDataSink sink; sink.pLocator = &loc_outmix; sink.pFormat = NULL; #if defined(__ANDROID__) - const SLInterfaceID ids[] = {stm->context->SL_IID_BUFFERQUEUE, - stm->context->SL_IID_VOLUME, - stm->context->SL_IID_ANDROIDCONFIGURATION}; + const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, + ctx->SL_IID_VOLUME, + ctx->SL_IID_ANDROIDCONFIGURATION}; const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; #else const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME}; @@ -1056,166 +579,115 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { #endif assert(NELEMS(ids) == NELEMS(req)); - uint32_t preferred_sampling_rate = stm->user_output_rate; + uint32_t preferred_sampling_rate = stm->inputrate; +#if defined(__ANDROID__) + if (get_android_version() >= ANDROID_VERSION_MARSHMALLOW) { + // Reset preferred samping rate to trigger fallback to native sampling rate. + preferred_sampling_rate = 0; + if (opensl_get_min_latency(ctx, *output_stream_params, &latency_frames) != CUBEB_OK) { + // Default to AudioFlinger's advertised fast track latency of 10ms. + latency_frames = 440; + } + stm->latency = latency_frames; + } +#endif + SLresult res = SL_RESULT_CONTENT_UNSUPPORTED; if (preferred_sampling_rate) { - res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng, - &stm->playerObj, - &source, - &sink, - NELEMS(ids), - ids, - req); + res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, &source, + &sink, NELEMS(ids), ids, req); } // Sample rate not supported? Try again with primary sample rate! - if (res == SL_RESULT_CONTENT_UNSUPPORTED && - preferred_sampling_rate != DEFAULT_SAMPLE_RATE) { - preferred_sampling_rate = DEFAULT_SAMPLE_RATE; + if (res == SL_RESULT_CONTENT_UNSUPPORTED) { + if (opensl_get_preferred_sample_rate(ctx, &preferred_sampling_rate)) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + format.samplesPerSec = preferred_sampling_rate * 1000; - res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng, - &stm->playerObj, - &source, - &sink, - NELEMS(ids), - ids, - req); + res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, + &source, &sink, NELEMS(ids), ids, req); } if (res != SL_RESULT_SUCCESS) { - LOG("Failed to create audio player. Error code: %lu", res); + opensl_stream_destroy(stm); return CUBEB_ERROR; } - stm->output_configured_rate = preferred_sampling_rate; - stm->bytespersec = stm->output_configured_rate * stm->framesize; - stm->queuebuf_len = stm->framesize * stm->buffer_size_frames; - - // Calculate the capacity of input array - stm->queuebuf_capacity = NBUFS; - if (stm->output_enabled) { - // Full duplex, update capacity to hold 1 sec of data - stm->queuebuf_capacity = 1 * stm->output_configured_rate / stm->queuebuf_len; - } - // Allocate input array - stm->queuebuf = (void**)calloc(1, sizeof(void*) * stm->queuebuf_capacity); - for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) { - stm->queuebuf[i] = calloc(1, stm->queuebuf_len); - assert(stm->queuebuf[i]); + stm->outputrate = preferred_sampling_rate; + stm->bytespersec = stm->outputrate * stm->framesize; + stm->queuebuf_len = stm->framesize * latency_frames / NBUFS; + // round up to the next multiple of stm->framesize, if needed. + if (stm->queuebuf_len % stm->framesize) { + stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize); } - SLAndroidConfigurationItf playerConfig = NULL; + cubeb_stream_params params = *output_stream_params; + params.rate = preferred_sampling_rate; - if (get_android_version() >= ANDROID_VERSION_N_MR1) { - res = (*stm->playerObj) - ->GetInterface(stm->playerObj, - stm->context->SL_IID_ANDROIDCONFIGURATION, - &playerConfig); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get Android configuration interface. Error code: %lu", res); - return CUBEB_ERROR; - } + stm->resampler = cubeb_resampler_create(stm, NULL, ¶ms, + output_stream_params->rate, + data_callback, + user_ptr, + CUBEB_RESAMPLER_QUALITY_DEFAULT); - SLint32 streamType = SL_ANDROID_STREAM_MEDIA; - if (stm->voice) { - streamType = SL_ANDROID_STREAM_VOICE; - } - res = (*playerConfig)->SetConfiguration(playerConfig, - SL_ANDROID_KEY_STREAM_TYPE, - &streamType, - sizeof(streamType)); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set Android configuration to %d Error code: %lu", - streamType, res); - } + if (!stm->resampler) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } - SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_LATENCY; - if (stm->buffer_size_frames > POWERSAVE_LATENCY_FRAMES_THRESHOLD) { - performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING; - } + int i; + for (i = 0; i < NBUFS; i++) { + stm->queuebuf[i] = malloc(stm->queuebuf_len); + assert(stm->queuebuf[i]); + } +#if defined(__ANDROID__) + SLuint32 stream_type = convert_stream_type_to_sl_stream(output_stream_params->stream_type); + if (stream_type != 0xFFFFFFFF) { + SLAndroidConfigurationItf playerConfig; + res = (*stm->playerObj)->GetInterface(stm->playerObj, + ctx->SL_IID_ANDROIDCONFIGURATION, &playerConfig); res = (*playerConfig)->SetConfiguration(playerConfig, - SL_ANDROID_KEY_PERFORMANCE_MODE, - &performanceMode, - sizeof(performanceMode)); + SL_ANDROID_KEY_STREAM_TYPE, &stream_type, sizeof(SLint32)); if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set Android performance mode to %d Error code: %lu. This is" - " not fatal", performanceMode, res); + opensl_stream_destroy(stm); + return CUBEB_ERROR; } } +#endif res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE); if (res != SL_RESULT_SUCCESS) { - LOG("Failed to realize player object. Error code: %lu", res); + opensl_stream_destroy(stm); return CUBEB_ERROR; } - // There are two ways of getting the audio output latency: - // - a configuration value, only available on some devices (notably devices - // running FireOS) - // - A Java method, that we call using JNI. - // - // The first method is prefered, if available, because it can account for more - // latency causes, and is more precise. - - // Latency has to be queried after the realization of the interface, when - // using SL_IID_ANDROIDCONFIGURATION. - SLuint32 audioLatency = 0; - SLuint32 paramSize = sizeof(SLuint32); - // The reported latency is in milliseconds. - if (playerConfig) { - res = (*playerConfig)->GetConfiguration(playerConfig, - (const SLchar *)"androidGetAudioLatency", - ¶mSize, - &audioLatency); - if (res == SL_RESULT_SUCCESS) { - LOG("Got playback latency using android configuration extension"); - stm->output_latency_ms = audioLatency; - } - } - // `playerConfig` is available, but the above failed, or `playerConfig` is not - // available. In both cases, we need to acquire the output latency by an other - // mean. - if ((playerConfig && res != SL_RESULT_SUCCESS) || - !playerConfig) { - if (cubeb_output_latency_method_is_loaded(stm->context->p_output_latency_function)) { - LOG("Got playback latency using JNI"); - stm->output_latency_ms = cubeb_get_output_latency(stm->context->p_output_latency_function); - } else { - LOG("No alternate latency querying method loaded, A/V sync will be off."); - stm->output_latency_ms = 0; - } - } - - LOG("Audio output latency: %dms", stm->output_latency_ms); - - res = (*stm->playerObj)->GetInterface(stm->playerObj, - stm->context->SL_IID_PLAY, - &stm->play); + res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_PLAY, &stm->play); if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get play interface. Error code: %lu", res); + opensl_stream_destroy(stm); return CUBEB_ERROR; } - res = (*stm->playerObj)->GetInterface(stm->playerObj, - stm->context->SL_IID_BUFFERQUEUE, + res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE, &stm->bufq); if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get bufferqueue interface. Error code: %lu", res); + opensl_stream_destroy(stm); return CUBEB_ERROR; } - res = (*stm->playerObj)->GetInterface(stm->playerObj, - stm->context->SL_IID_VOLUME, + res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_VOLUME, &stm->volume); + if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get volume interface. Error code: %lu", res); + opensl_stream_destroy(stm); return CUBEB_ERROR; } res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm); if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register play callback. Error code: %lu", res); + opensl_stream_destroy(stm); return CUBEB_ERROR; } @@ -1224,17 +696,13 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { res = (*stm->play)->SetCallbackEventsMask(stm->play, (SLuint32)SL_PLAYEVENT_HEADATMARKER); if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set headatmarker event mask. Error code: %lu", res); + opensl_stream_destroy(stm); return CUBEB_ERROR; } - slBufferQueueCallback player_callback = bufferqueue_callback; - if (stm->input_enabled) { - player_callback = player_fullduplex_callback; - } - res = (*stm->bufq)->RegisterCallback(stm->bufq, player_callback, stm); + res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm); if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register bufferqueue callback. Error code: %lu", res); + opensl_stream_destroy(stm); return CUBEB_ERROR; } @@ -1249,340 +717,55 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { assert(res == SL_RESULT_SUCCESS); } - LOG("Cubeb stream init playback success"); - return CUBEB_OK; -} - -static int -opensl_validate_stream_param(cubeb_stream_params * stream_params) -{ - if ((stream_params && - (stream_params->channels < 1 || stream_params->channels > 32))) { - return CUBEB_ERROR_INVALID_FORMAT; - } - if ((stream_params && - (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) { - LOG("Loopback is not supported"); - return CUBEB_ERROR_NOT_SUPPORTED; - } - return CUBEB_OK; -} - -int has_pref_set(cubeb_stream_params* input_params, - cubeb_stream_params* output_params, - cubeb_stream_prefs pref) -{ - return (input_params && input_params->prefs & pref) || - (output_params && output_params->prefs & pref); -} - -static int -opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, - cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency_frames, - cubeb_data_callback data_callback, cubeb_state_callback state_callback, - void * user_ptr) -{ - cubeb_stream * stm; - - assert(ctx); - if (input_device || output_device) { - LOG("Device selection is not supported in Android. The default will be used"); - } - - *stream = NULL; - - int r = opensl_validate_stream_param(output_stream_params); - if(r != CUBEB_OK) { - LOG("Output stream params not valid"); - return r; - } - r = opensl_validate_stream_param(input_stream_params); - if(r != CUBEB_OK) { - LOG("Input stream params not valid"); - return r; - } - - stm = calloc(1, sizeof(*stm)); - assert(stm); - - stm->context = ctx; - stm->data_callback = data_callback; - stm->state_callback = state_callback; - stm->user_ptr = user_ptr; - stm->buffer_size_frames = latency_frames ? latency_frames : DEFAULT_NUM_OF_FRAMES; - stm->input_enabled = (input_stream_params) ? 1 : 0; - stm->output_enabled = (output_stream_params) ? 1 : 0; - stm->shutdown = 1; - stm->voice = has_pref_set(input_stream_params, output_stream_params, CUBEB_STREAM_PREF_VOICE); - - LOG("cubeb stream prefs: voice: %s", stm->voice ? "true" : "false"); - - -#ifdef DEBUG - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); - r = pthread_mutex_init(&stm->mutex, &attr); -#else - r = pthread_mutex_init(&stm->mutex, NULL); -#endif - assert(r == 0); - - if (output_stream_params) { - LOG("Playback params: Rate %d, channels %d, format %d, latency in frames %d.", - output_stream_params->rate, output_stream_params->channels, - output_stream_params->format, stm->buffer_size_frames); - r = opensl_configure_playback(stm, output_stream_params); - if (r != CUBEB_OK) { - opensl_stream_destroy(stm); - return r; - } - } - - if (input_stream_params) { - LOG("Capture params: Rate %d, channels %d, format %d, latency in frames %d.", - input_stream_params->rate, input_stream_params->channels, - input_stream_params->format, stm->buffer_size_frames); - r = opensl_configure_capture(stm, input_stream_params); - if (r != CUBEB_OK) { - opensl_stream_destroy(stm); - return r; - } - } - - /* Configure resampler*/ - uint32_t target_sample_rate; - if (input_stream_params) { - target_sample_rate = input_stream_params->rate; - } else { - assert(output_stream_params); - target_sample_rate = output_stream_params->rate; - } - - // Use the actual configured rates for input - // and output. - cubeb_stream_params input_params; - if (input_stream_params) { - input_params = *input_stream_params; - input_params.rate = stm->input_device_rate; - } - cubeb_stream_params output_params; - if (output_stream_params) { - output_params = *output_stream_params; - output_params.rate = stm->output_configured_rate; - } - - stm->resampler = cubeb_resampler_create(stm, - input_stream_params ? &input_params : NULL, - output_stream_params ? &output_params : NULL, - target_sample_rate, - data_callback, - user_ptr, - CUBEB_RESAMPLER_QUALITY_DEFAULT); - if (!stm->resampler) { - LOG("Failed to create resampler"); - opensl_stream_destroy(stm); - return CUBEB_ERROR; - } - *stream = stm; - LOG("Cubeb stream (%p) init success", stm); return CUBEB_OK; } -static int -opensl_start_player(cubeb_stream * stm) +static void +opensl_stream_destroy(cubeb_stream * stm) { - assert(stm->playerObj); - SLuint32 playerState; - (*stm->playerObj)->GetState(stm->playerObj, &playerState); - if (playerState == SL_OBJECT_STATE_REALIZED) { - SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING); - if(res != SL_RESULT_SUCCESS) { - LOG("Failed to start player. Error code: %lu", res); - return CUBEB_ERROR; - } + if (stm->playerObj) + (*stm->playerObj)->Destroy(stm->playerObj); + int i; + for (i = 0; i < NBUFS; i++) { + free(stm->queuebuf[i]); } - return CUBEB_OK; -} + pthread_mutex_destroy(&stm->mutex); -static int -opensl_start_recorder(cubeb_stream * stm) -{ - assert(stm->recorderObj); - SLuint32 recorderState; - (*stm->recorderObj)->GetState(stm->recorderObj, &recorderState); - if (recorderState == SL_OBJECT_STATE_REALIZED) { - SLresult res = (*stm->recorderItf)->SetRecordState(stm->recorderItf, SL_RECORDSTATE_RECORDING); - if(res != SL_RESULT_SUCCESS) { - LOG("Failed to start recorder. Error code: %lu", res); - return CUBEB_ERROR; - } - } - return CUBEB_OK; + cubeb_resampler_destroy(stm->resampler); + + free(stm); } static int opensl_stream_start(cubeb_stream * stm) { - assert(stm); - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 0); - opensl_set_draining(stm, 0); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (stm->playerObj) { - r = opensl_start_player(stm); - if (r != CUBEB_OK) { - return r; - } - } - - if (stm->recorderObj) { - int r = opensl_start_recorder(stm); - if (r != CUBEB_OK) { - return r; - } - } - + SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING); + if (res != SL_RESULT_SUCCESS) + return CUBEB_ERROR; stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); - LOG("Cubeb stream (%p) started", stm); return CUBEB_OK; } static int -opensl_stop_player(cubeb_stream * stm) +opensl_stream_stop(cubeb_stream * stm) { - assert(stm->playerObj); - assert(stm->shutdown || stm->draining); - SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to stop player. Error code: %lu", res); - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -static int -opensl_stop_recorder(cubeb_stream * stm) -{ - assert(stm->recorderObj); - assert(stm->shutdown || stm->draining); - - SLresult res = (*stm->recorderItf)->SetRecordState(stm->recorderItf, SL_RECORDSTATE_PAUSED); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to stop recorder. Error code: %lu", res); + if (res != SL_RESULT_SUCCESS) return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -static int -opensl_stream_stop(cubeb_stream * stm) -{ - assert(stm); - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (stm->playerObj) { - r = opensl_stop_player(stm); - if (r != CUBEB_OK) { - return r; - } - } - - if (stm->recorderObj) { - int r = opensl_stop_recorder(stm); - if (r != CUBEB_OK) { - return r; - } - } - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - LOG("Cubeb stream (%p) stopped", stm); - return CUBEB_OK; -} - -static int -opensl_destroy_recorder(cubeb_stream * stm) -{ - assert(stm); - assert(stm->recorderObj); - - if (stm->recorderBufferQueueItf) { - SLresult res = (*stm->recorderBufferQueueItf)->Clear(stm->recorderBufferQueueItf); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to clear recorder buffer queue. Error code: %lu", res); - return CUBEB_ERROR; - } - stm->recorderBufferQueueItf = NULL; - for (uint32_t i = 0; i < stm->input_array_capacity; ++i) { - free(stm->input_buffer_array[i]); - } - } - - (*stm->recorderObj)->Destroy(stm->recorderObj); - stm->recorderObj = NULL; - stm->recorderItf = NULL; - - if (stm->input_queue) { - array_queue_destroy(stm->input_queue); - } - free(stm->input_silent_buffer); - return CUBEB_OK; } -static void -opensl_stream_destroy(cubeb_stream * stm) -{ - assert(stm->draining || stm->shutdown); - - if (stm->playerObj) { - (*stm->playerObj)->Destroy(stm->playerObj); - stm->playerObj = NULL; - stm->play = NULL; - stm->bufq = NULL; - for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) { - free(stm->queuebuf[i]); - } - } - - if (stm->recorderObj) { - int r = opensl_destroy_recorder(stm); - assert(r == CUBEB_OK); - } - - if (stm->resampler) { - cubeb_resampler_destroy(stm->resampler); - } - - pthread_mutex_destroy(&stm->mutex); - - LOG("Cubeb stream (%p) destroyed", stm); - free(stm); -} - static int opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) { SLmillisecond msec; - uint32_t compensation_msec = 0; + uint64_t samplerate; SLresult res; + int r; + uint32_t mixer_latency; + uint32_t compensation_msec = 0; res = (*stm->play)->GetPosition(stm->play, &msec); if (res != SL_RESULT_SUCCESS) @@ -1598,23 +781,27 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) stm->lastPosition = msec; } - uint64_t samplerate = stm->user_output_rate; - uint32_t output_latency = stm->output_latency_ms; + samplerate = stm->inputrate; + + r = stm->context->get_output_latency(&mixer_latency, stm->stream_type); + if (r) { + return CUBEB_ERROR; + } pthread_mutex_lock(&stm->mutex); - int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate / stm->output_configured_rate; + int64_t maximum_position = stm->written * (int64_t)stm->inputrate / stm->outputrate; pthread_mutex_unlock(&stm->mutex); assert(maximum_position >= 0); - if (msec > output_latency) { + if (msec > mixer_latency) { int64_t unadjusted_position; if (stm->lastCompensativePosition > msec + compensation_msec) { // Over compensation, use lastCompensativePosition. unadjusted_position = - samplerate * (stm->lastCompensativePosition - output_latency) / 1000; + samplerate * (stm->lastCompensativePosition - mixer_latency) / 1000; } else { unadjusted_position = - samplerate * (msec - output_latency + compensation_msec) / 1000; + samplerate * (msec - mixer_latency + compensation_msec) / 1000; stm->lastCompensativePosition = msec + compensation_msec; } *position = unadjusted_position < maximum_position ? @@ -1625,6 +812,24 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) return CUBEB_OK; } +int +opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + int r; + uint32_t mixer_latency; // The latency returned by AudioFlinger is in ms. + + /* audio_stream_type_t is an int, so this is okay. */ + r = stm->context->get_output_latency(&mixer_latency, stm->stream_type); + if (r) { + return CUBEB_ERROR; + } + + *latency = stm->latency * stm->inputrate / 1000 + // OpenSL latency + mixer_latency * stm->inputrate / 1000; // AudioFlinger latency + + return CUBEB_OK; +} + int opensl_stream_set_volume(cubeb_stream * stm, float volume) { @@ -1660,19 +865,18 @@ static struct cubeb_ops const opensl_ops = { .init = opensl_init, .get_backend_id = opensl_get_backend_id, .get_max_channel_count = opensl_get_max_channel_count, - .get_min_latency = NULL, - .get_preferred_sample_rate = NULL, + .get_min_latency = opensl_get_min_latency, + .get_preferred_sample_rate = opensl_get_preferred_sample_rate, .enumerate_devices = NULL, - .device_collection_destroy = NULL, .destroy = opensl_destroy, .stream_init = opensl_stream_init, .stream_destroy = opensl_stream_destroy, .stream_start = opensl_stream_start, .stream_stop = opensl_stream_stop, - .stream_reset_default_device = NULL, .stream_get_position = opensl_stream_get_position, - .stream_get_latency = NULL, + .stream_get_latency = opensl_stream_get_latency, .stream_set_volume = opensl_stream_set_volume, + .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, diff --git a/media/libcubeb/src/cubeb_pulse.c b/media/libcubeb/src/cubeb_pulse.c index 846ba426a..4f474452d 100644 --- a/media/libcubeb/src/cubeb_pulse.c +++ b/media/libcubeb/src/cubeb_pulse.c @@ -7,14 +7,12 @@ #undef NDEBUG #include #include -#include -#include #include +#include #include -#include "cubeb-internal.h" #include "cubeb/cubeb.h" -#include "cubeb_mixer.h" -#include "cubeb_strings.h" +#include "cubeb-internal.h" +#include #ifdef DISABLE_LIBPULSE_DLOPEN #define WRAP(x) x @@ -22,14 +20,13 @@ #define WRAP(x) cubeb_##x #define LIBPULSE_API_VISIT(X) \ X(pa_channel_map_can_balance) \ - X(pa_channel_map_init) \ + X(pa_channel_map_init_auto) \ X(pa_context_connect) \ X(pa_context_disconnect) \ X(pa_context_drain) \ X(pa_context_get_server_info) \ X(pa_context_get_sink_info_by_name) \ X(pa_context_get_sink_info_list) \ - X(pa_context_get_sink_input_info) \ X(pa_context_get_source_info_list) \ X(pa_context_get_state) \ X(pa_context_new) \ @@ -84,50 +81,33 @@ X(pa_context_set_subscribe_callback) \ X(pa_context_subscribe) \ X(pa_mainloop_api_once) \ - X(pa_get_library_version) \ - X(pa_channel_map_init_auto) \ #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; LIBPULSE_API_VISIT(MAKE_TYPEDEF); #undef MAKE_TYPEDEF #endif -#if PA_CHECK_VERSION(2, 0, 0) -static int has_pulse_v2 = 0; -#endif - static struct cubeb_ops const pulse_ops; -struct cubeb_default_sink_info { - pa_channel_map channel_map; - uint32_t sample_spec_rate; - pa_sink_flags_t flags; -}; - struct cubeb { struct cubeb_ops const * ops; void * libpulse; pa_threaded_mainloop * mainloop; pa_context * context; - struct cubeb_default_sink_info * default_sink_info; + pa_sink_info * default_sink_info; char * context_name; int error; - cubeb_device_collection_changed_callback output_collection_changed_callback; - void * output_collection_changed_user_ptr; - cubeb_device_collection_changed_callback input_collection_changed_callback; - void * input_collection_changed_user_ptr; - cubeb_strings * device_ids; + cubeb_device_collection_changed_callback collection_changed_callback; + void * collection_changed_user_ptr; }; struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; - void * user_ptr; - /**/ pa_stream * output_stream; pa_stream * input_stream; cubeb_data_callback data_callback; cubeb_state_callback state_callback; + void * user_ptr; pa_time_event * drain_timer; pa_sample_spec output_sample_spec; pa_sample_spec input_sample_spec; @@ -144,24 +124,6 @@ enum cork_state { NOTIFY = 1 << 1 }; -static int -intern_device_id(cubeb * ctx, char const ** id) -{ - char const * interned; - - assert(ctx); - assert(id); - - interned = cubeb_strings_intern(ctx->device_ids, *id); - if (!interned) { - return CUBEB_ERROR; - } - - *id = interned; - - return CUBEB_OK; -} - static void sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u) { @@ -169,10 +131,8 @@ sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, voi cubeb * ctx = u; if (!eol) { free(ctx->default_sink_info); - ctx->default_sink_info = malloc(sizeof(struct cubeb_default_sink_info)); - memcpy(&ctx->default_sink_info->channel_map, &info->channel_map, sizeof(pa_channel_map)); - ctx->default_sink_info->sample_spec_rate = info->sample_spec.rate; - ctx->default_sink_info->flags = info->flags; + ctx->default_sink_info = malloc(sizeof(pa_sink_info)); + memcpy(ctx->default_sink_info, info, sizeof(pa_sink_info)); } WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0); } @@ -180,11 +140,7 @@ sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, voi static void server_info_callback(pa_context * context, const pa_server_info * info, void * u) { - pa_operation * o; - o = WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u); - if (o) { - WRAP(pa_operation_unref)(o); - } + WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u); } static void @@ -511,81 +467,12 @@ stream_update_timing_info(cubeb_stream * stm) return r; } -static pa_channel_position_t -cubeb_channel_to_pa_channel(cubeb_channel channel) -{ - switch (channel) { - case CHANNEL_FRONT_LEFT: - return PA_CHANNEL_POSITION_FRONT_LEFT; - case CHANNEL_FRONT_RIGHT: - return PA_CHANNEL_POSITION_FRONT_RIGHT; - case CHANNEL_FRONT_CENTER: - return PA_CHANNEL_POSITION_FRONT_CENTER; - case CHANNEL_LOW_FREQUENCY: - return PA_CHANNEL_POSITION_LFE; - case CHANNEL_BACK_LEFT: - return PA_CHANNEL_POSITION_REAR_LEFT; - case CHANNEL_BACK_RIGHT: - return PA_CHANNEL_POSITION_REAR_RIGHT; - case CHANNEL_FRONT_LEFT_OF_CENTER: - return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; - case CHANNEL_FRONT_RIGHT_OF_CENTER: - return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; - case CHANNEL_BACK_CENTER: - return PA_CHANNEL_POSITION_REAR_CENTER; - case CHANNEL_SIDE_LEFT: - return PA_CHANNEL_POSITION_SIDE_LEFT; - case CHANNEL_SIDE_RIGHT: - return PA_CHANNEL_POSITION_SIDE_RIGHT; - case CHANNEL_TOP_CENTER: - return PA_CHANNEL_POSITION_TOP_CENTER; - case CHANNEL_TOP_FRONT_LEFT: - return PA_CHANNEL_POSITION_TOP_FRONT_LEFT; - case CHANNEL_TOP_FRONT_CENTER: - return PA_CHANNEL_POSITION_TOP_FRONT_CENTER; - case CHANNEL_TOP_FRONT_RIGHT: - return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; - case CHANNEL_TOP_BACK_LEFT: - return PA_CHANNEL_POSITION_TOP_REAR_LEFT; - case CHANNEL_TOP_BACK_CENTER: - return PA_CHANNEL_POSITION_TOP_REAR_CENTER; - case CHANNEL_TOP_BACK_RIGHT: - return PA_CHANNEL_POSITION_TOP_REAR_RIGHT; - default: - return PA_CHANNEL_POSITION_INVALID; - } -} - -static void -layout_to_channel_map(cubeb_channel_layout layout, pa_channel_map * cm) -{ - assert(cm && layout != CUBEB_LAYOUT_UNDEFINED); - - WRAP(pa_channel_map_init)(cm); - - uint32_t channels = 0; - cubeb_channel_layout channelMap = layout; - for (uint32_t i = 0 ; channelMap != 0; ++i) { - uint32_t channel = (channelMap & 1) << i; - if (channel != 0) { - cm->map[channels] = cubeb_channel_to_pa_channel(channel); - channels++; - } - channelMap = channelMap >> 1; - } - unsigned int channels_from_layout = cubeb_channel_layout_nb_channels(layout); - assert(channels_from_layout <= UINT8_MAX); - cm->channels = (uint8_t) channels_from_layout; -} - static void pulse_context_destroy(cubeb * ctx); static void pulse_destroy(cubeb * ctx); static int pulse_context_init(cubeb * ctx) { - int r; - if (ctx->context) { assert(ctx->error == 1); pulse_context_destroy(ctx); @@ -599,9 +486,9 @@ pulse_context_init(cubeb * ctx) WRAP(pa_context_set_state_callback)(ctx->context, context_state_callback, ctx); WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); - r = WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL); + WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL); - if (r < 0 || wait_until_context_ready(ctx) != 0) { + if (wait_until_context_ready(ctx) != 0) { WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); pulse_context_destroy(ctx); ctx->context = NULL; @@ -615,25 +502,18 @@ pulse_context_init(cubeb * ctx) return 0; } -static int pulse_subscribe_notifications(cubeb * context, - pa_subscription_mask_t mask); - /*static*/ int pulse_init(cubeb ** context, char const * context_name) { void * libpulse = NULL; cubeb * ctx; - pa_operation * o; *context = NULL; #ifndef DISABLE_LIBPULSE_DLOPEN libpulse = dlopen("libpulse.so.0", RTLD_LAZY); if (!libpulse) { - libpulse = dlopen("libpulse.so", RTLD_LAZY); - if (!libpulse) { - return CUBEB_ERROR; - } + return CUBEB_ERROR; } #define LOAD(x) { \ @@ -648,20 +528,11 @@ pulse_init(cubeb ** context, char const * context_name) #undef LOAD #endif -#if PA_CHECK_VERSION(2, 0, 0) - const char* version = WRAP(pa_get_library_version)(); - has_pulse_v2 = strtol(version, NULL, 10) >= 2; -#endif - ctx = calloc(1, sizeof(*ctx)); assert(ctx); ctx->ops = &pulse_ops; ctx->libpulse = libpulse; - if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) { - pulse_destroy(ctx); - return CUBEB_ERROR; - } ctx->mainloop = WRAP(pa_threaded_mainloop_new)(); ctx->default_sink_info = NULL; @@ -674,20 +545,10 @@ pulse_init(cubeb ** context, char const * context_name) return CUBEB_ERROR; } - /* server_info_callback performs a second async query, which is - responsible for initializing default_sink_info and signalling the - mainloop to end the wait. */ WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); - o = WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx); - if (o) { - operation_wait(ctx, NULL, o); - WRAP(pa_operation_unref)(o); - } + WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx); WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); - /* Update `default_sink_info` when the default device changes. */ - pulse_subscribe_notifications(ctx, PA_SUBSCRIPTION_MASK_SERVER); - *context = ctx; return CUBEB_OK; @@ -706,8 +567,11 @@ pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) (void)ctx; assert(ctx && max_channels); - if (!ctx->default_sink_info) - return CUBEB_ERROR; + WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); + while (!ctx->default_sink_info) { + WRAP(pa_threaded_mainloop_wait)(ctx->mainloop); + } + WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); *max_channels = ctx->default_sink_info->channel_map.channels; @@ -720,10 +584,13 @@ pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) assert(ctx && rate); (void)ctx; - if (!ctx->default_sink_info) - return CUBEB_ERROR; + WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); + while (!ctx->default_sink_info) { + WRAP(pa_threaded_mainloop_wait)(ctx->mainloop); + } + WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); - *rate = ctx->default_sink_info->sample_spec_rate; + *rate = ctx->default_sink_info->sample_spec.rate; return CUBEB_OK; } @@ -758,7 +625,9 @@ pulse_context_destroy(cubeb * ctx) static void pulse_destroy(cubeb * ctx) { - free(ctx->context_name); + if (ctx->context_name) { + free(ctx->context_name); + } if (ctx->context) { pulse_context_destroy(ctx); } @@ -768,14 +637,12 @@ pulse_destroy(cubeb * ctx) WRAP(pa_threaded_mainloop_free)(ctx->mainloop); } - if (ctx->device_ids) { - cubeb_strings_destroy(ctx->device_ids); - } - if (ctx->libpulse) { dlclose(ctx->libpulse); } - free(ctx->default_sink_info); + if (ctx->default_sink_info) { + free(ctx->default_sink_info); + } free(ctx); } @@ -798,25 +665,6 @@ to_pulse_format(cubeb_sample_format format) } } -static cubeb_channel_layout -pulse_default_layout_for_channels(uint32_t ch) -{ - assert (ch > 0 && ch <= 8); - switch (ch) { - case 1: return CUBEB_LAYOUT_MONO; - case 2: return CUBEB_LAYOUT_STEREO; - case 3: return CUBEB_LAYOUT_3F; - case 4: return CUBEB_LAYOUT_QUAD; - case 5: return CUBEB_LAYOUT_3F2; - case 6: return CUBEB_LAYOUT_3F_LFE | - CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT; - case 7: return CUBEB_LAYOUT_3F3R_LFE; - case 8: return CUBEB_LAYOUT_3F4_LFE; - } - // Never get here! - return CUBEB_LAYOUT_UNDEFINED; -} - static int create_pa_stream(cubeb_stream * stm, pa_stream ** pa_stm, @@ -824,39 +672,15 @@ create_pa_stream(cubeb_stream * stm, char const * stream_name) { assert(stm && stream_params); - assert(&stm->input_stream == pa_stm || (&stm->output_stream == pa_stm && - (stream_params->layout == CUBEB_LAYOUT_UNDEFINED || - (stream_params->layout != CUBEB_LAYOUT_UNDEFINED && - cubeb_channel_layout_nb_channels(stream_params->layout) == stream_params->channels)))); - if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { - return CUBEB_ERROR_NOT_SUPPORTED; - } *pa_stm = NULL; pa_sample_spec ss; ss.format = to_pulse_format(stream_params->format); if (ss.format == PA_SAMPLE_INVALID) return CUBEB_ERROR_INVALID_FORMAT; ss.rate = stream_params->rate; - if (stream_params->channels > UINT8_MAX) - return CUBEB_ERROR_INVALID_FORMAT; - ss.channels = (uint8_t) stream_params->channels; - - if (stream_params->layout == CUBEB_LAYOUT_UNDEFINED) { - pa_channel_map cm; - if (stream_params->channels <= 8 && - !WRAP(pa_channel_map_init_auto)(&cm, stream_params->channels, PA_CHANNEL_MAP_DEFAULT)) { - LOG("Layout undefined and PulseAudio's default layout has not been configured, guess one."); - layout_to_channel_map(pulse_default_layout_for_channels(stream_params->channels), &cm); - *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm); - } else { - LOG("Layout undefined, PulseAudio will use its default."); - *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL); - } - } else { - pa_channel_map cm; - layout_to_channel_map(stream_params->layout, &cm); - *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm); - } + ss.channels = stream_params->channels; + + *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL); return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK; } @@ -929,7 +753,7 @@ pulse_stream_init(cubeb * context, battr = set_buffering_attribute(latency_frames, &stm->output_sample_spec); WRAP(pa_stream_connect_playback)(stm->output_stream, - (char const *) output_device, + output_device, &battr, PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY, @@ -952,7 +776,7 @@ pulse_stream_init(cubeb * context, battr = set_buffering_attribute(latency_frames, &stm->input_sample_spec); WRAP(pa_stream_connect_record)(stm->input_stream, - (char const *) input_device, + input_device, &battr, PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY); @@ -972,7 +796,7 @@ pulse_stream_init(cubeb * context, return CUBEB_ERROR; } - if (g_cubeb_log_level) { + if (g_log_level) { if (output_stream_params){ const pa_buffer_attr * output_att; output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream); @@ -989,7 +813,6 @@ pulse_stream_init(cubeb * context, } *stream = stm; - LOG("Cubeb stream (%p) init successful.", *stream); return CUBEB_OK; } @@ -1021,7 +844,6 @@ pulse_stream_destroy(cubeb_stream * stm) } WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); - LOG("Cubeb stream (%p) destroyed successfully.", stm); free(stm); } @@ -1053,7 +875,6 @@ pulse_stream_start(cubeb_stream * stm) WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); } - LOG("Cubeb stream (%p) started successfully.", stm); return CUBEB_OK; } @@ -1069,7 +890,6 @@ pulse_stream_stop(cubeb_stream * stm) WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); stream_cork(stm, CORK | NOTIFY); - LOG("Cubeb stream (%p) stopped successfully.", stm); return CUBEB_OK; } @@ -1142,7 +962,6 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) pa_volume_t vol; pa_cvolume cvol; const pa_sample_spec * ss; - cubeb * ctx; if (!stm->output_stream) { return CUBEB_ERROR; @@ -1150,11 +969,13 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); + while (!stm->context->default_sink_info) { + WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop); + } + /* if the pulse daemon is configured to use flat volumes, * apply our own gain instead of changing the input volume on the sink. */ - ctx = stm->context; - if (ctx->default_sink_info && - (ctx->default_sink_info->flags & PA_SINK_FLAT_VOLUME)) { + if (stm->context->default_sink_info->flags & PA_SINK_FLAT_VOLUME) { stm->volume = volume; } else { ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream); @@ -1164,40 +985,46 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) index = WRAP(pa_stream_get_index)(stm->output_stream); - op = WRAP(pa_context_set_sink_input_volume)(ctx->context, + op = WRAP(pa_context_set_sink_input_volume)(stm->context->context, index, &cvol, volume_success, stm); if (op) { - operation_wait(ctx, stm->output_stream, op); + operation_wait(stm->context, stm->output_stream, op); WRAP(pa_operation_unref)(op); } } - WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); + WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); return CUBEB_OK; } -struct sink_input_info_result { - pa_cvolume * cvol; - pa_threaded_mainloop * mainloop; -}; - -static void -sink_input_info_cb(pa_context * c, pa_sink_input_info const * i, int eol, void * u) +static int +pulse_stream_set_panning(cubeb_stream * stream, float panning) { - struct sink_input_info_result * r = u; - if (!eol) { - *r->cvol = i->volume; + const pa_channel_map * map; + pa_cvolume vol; + + if (!stream->output_stream) { + return CUBEB_ERROR; } - WRAP(pa_threaded_mainloop_signal)(r->mainloop, 0); + + map = WRAP(pa_stream_get_channel_map)(stream->output_stream); + + if (!WRAP(pa_channel_map_can_balance)(map)) { + return CUBEB_ERROR; + } + + WRAP(pa_cvolume_set_balance)(&vol, map, panning); + + return CUBEB_OK; } typedef struct { char * default_sink_name; char * default_source_name; - cubeb_device_info * devinfo; + cubeb_device_info ** devinfo; uint32_t max; uint32_t count; cubeb * context; @@ -1226,7 +1053,7 @@ pulse_ensure_dev_list_data_list_size (pulse_dev_list_data * list_data) if (list_data->count == list_data->max) { list_data->max += 8; list_data->devinfo = realloc(list_data->devinfo, - sizeof(cubeb_device_info) * list_data->max); + sizeof(cubeb_device_info *) * list_data->max); } } @@ -1235,47 +1062,33 @@ pulse_get_state_from_sink_port(pa_sink_port_info * info) { if (info != NULL) { #if PA_CHECK_VERSION(2, 0, 0) - if (has_pulse_v2 && info->available == PA_PORT_AVAILABLE_NO) + if (info->available == PA_PORT_AVAILABLE_NO) return CUBEB_DEVICE_STATE_UNPLUGGED; else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */ #endif return CUBEB_DEVICE_STATE_ENABLED; } - return CUBEB_DEVICE_STATE_ENABLED; + return CUBEB_DEVICE_STATE_DISABLED; } static void pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, - int eol, void * user_data) + int eol, void * user_data) { pulse_dev_list_data * list_data = user_data; cubeb_device_info * devinfo; - char const * prop = NULL; - char const * device_id = NULL; + const char * prop; (void)context; - if (eol) { - WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); - return; - } - - if (info == NULL) + if (eol || info == NULL) return; - device_id = info->name; - if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) { - assert(NULL); - return; - } - - pulse_ensure_dev_list_data_list_size(list_data); - devinfo = &list_data->devinfo[list_data->count]; - memset(devinfo, 0, sizeof(cubeb_device_info)); + devinfo = calloc(1, sizeof(cubeb_device_info)); - devinfo->device_id = device_id; - devinfo->devid = (cubeb_devid) devinfo->device_id; + devinfo->device_id = strdup(info->name); + devinfo->devid = devinfo->device_id; devinfo->friendly_name = strdup(info->description); prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path"); if (prop) @@ -1286,8 +1099,7 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT; devinfo->state = pulse_get_state_from_sink_port(info->active_port); - devinfo->preferred = (strcmp(info->name, list_data->default_sink_name) == 0) ? - CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; + devinfo->preferred = strcmp(info->name, list_data->default_sink_name) == 0; devinfo->format = CUBEB_DEVICE_FMT_ALL; devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format); @@ -1299,7 +1111,10 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, devinfo->latency_lo = 0; devinfo->latency_hi = 0; - list_data->count += 1; + pulse_ensure_dev_list_data_list_size (list_data); + list_data->devinfo[list_data->count++] = devinfo; + + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); } static cubeb_device_state @@ -1307,14 +1122,14 @@ pulse_get_state_from_source_port(pa_source_port_info * info) { if (info != NULL) { #if PA_CHECK_VERSION(2, 0, 0) - if (has_pulse_v2 && info->available == PA_PORT_AVAILABLE_NO) + if (info->available == PA_PORT_AVAILABLE_NO) return CUBEB_DEVICE_STATE_UNPLUGGED; else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */ #endif return CUBEB_DEVICE_STATE_ENABLED; } - return CUBEB_DEVICE_STATE_ENABLED; + return CUBEB_DEVICE_STATE_DISABLED; } static void @@ -1323,28 +1138,17 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info, { pulse_dev_list_data * list_data = user_data; cubeb_device_info * devinfo; - char const * prop = NULL; - char const * device_id = NULL; + const char * prop; (void)context; - if (eol) { - WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); - return; - } - - device_id = info->name; - if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) { - assert(NULL); + if (eol) return; - } - pulse_ensure_dev_list_data_list_size(list_data); - devinfo = &list_data->devinfo[list_data->count]; - memset(devinfo, 0, sizeof(cubeb_device_info)); + devinfo = calloc(1, sizeof(cubeb_device_info)); - devinfo->device_id = device_id; - devinfo->devid = (cubeb_devid) devinfo->device_id; + devinfo->device_id = strdup(info->name); + devinfo->devid = devinfo->device_id; devinfo->friendly_name = strdup(info->description); prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path"); if (prop) @@ -1355,8 +1159,7 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info, devinfo->type = CUBEB_DEVICE_TYPE_INPUT; devinfo->state = pulse_get_state_from_source_port(info->active_port); - devinfo->preferred = (strcmp(info->name, list_data->default_source_name) == 0) ? - CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; + devinfo->preferred = strcmp(info->name, list_data->default_source_name) == 0; devinfo->format = CUBEB_DEVICE_FMT_ALL; devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format); @@ -1368,7 +1171,10 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info, devinfo->latency_lo = 0; devinfo->latency_hi = 0; - list_data->count += 1; + pulse_ensure_dev_list_data_list_size (list_data); + list_data->devinfo[list_data->count++] = devinfo; + + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); } static void @@ -1380,20 +1186,19 @@ pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata) free(list_data->default_sink_name); free(list_data->default_source_name); - list_data->default_sink_name = - i->default_sink_name ? strdup(i->default_sink_name) : NULL; - list_data->default_source_name = - i->default_source_name ? strdup(i->default_source_name) : NULL; + list_data->default_sink_name = strdup(i->default_sink_name); + list_data->default_source_name = strdup(i->default_source_name); WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); } static int pulse_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection * collection) + cubeb_device_collection ** collection) { pulse_dev_list_data user_data = { NULL, NULL, NULL, 0, 0, context }; pa_operation * o; + uint32_t i; WRAP(pa_threaded_mainloop_lock)(context->mainloop); @@ -1424,26 +1229,15 @@ pulse_enumerate_devices(cubeb * context, cubeb_device_type type, WRAP(pa_threaded_mainloop_unlock)(context->mainloop); - collection->device = user_data.devinfo; - collection->count = user_data.count; + *collection = malloc(sizeof(cubeb_device_collection) + + sizeof(cubeb_device_info *) * (user_data.count > 0 ? user_data.count - 1 : 0)); + (*collection)->count = user_data.count; + for (i = 0; i < user_data.count; i++) + (*collection)->device[i] = user_data.devinfo[i]; free(user_data.default_sink_name); free(user_data.default_source_name); - return CUBEB_OK; -} - -static int -pulse_device_collection_destroy(cubeb * ctx, cubeb_device_collection * collection) -{ - size_t n; - - for (n = 0; n < collection->count; n++) { - free((void *) collection->device[n].friendly_name); - free((void *) collection->device[n].vendor_name); - free((void *) collection->device[n].group_id); - } - - free(collection->device); + free(user_data.devinfo); return CUBEB_OK; } @@ -1491,40 +1285,29 @@ pulse_subscribe_callback(pa_context * ctx, cubeb * context = userdata; switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { - case PA_SUBSCRIPTION_EVENT_SERVER: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) { - LOG("Server changed %d", index); - WRAP(pa_context_get_server_info)(context->context, server_info_callback, context); - } - break; case PA_SUBSCRIPTION_EVENT_SOURCE: case PA_SUBSCRIPTION_EVENT_SINK: - if (g_cubeb_log_level) { + if (g_log_level) { if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - LOG("Removing source index %d", index); + LOG("Removing sink index %d", index); } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - LOG("Adding source index %d", index); + LOG("Adding sink index %d", index); } if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - LOG("Removing sink index %d", index); + LOG("Removing source index %d", index); } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - LOG("Adding sink index %d", index); + LOG("Adding source index %d", index); } } if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE || (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) { - context->input_collection_changed_callback(context, context->input_collection_changed_user_ptr); - } - if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { - context->output_collection_changed_callback(context, context->output_collection_changed_user_ptr); - } + context->collection_changed_callback(context, context->collection_changed_user_ptr); } break; } @@ -1540,15 +1323,34 @@ subscribe_success(pa_context *c, int success, void *userdata) } static int -pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask) { +pulse_register_device_collection_changed(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback collection_changed_callback, + void * user_ptr) +{ + context->collection_changed_callback = collection_changed_callback; + context->collection_changed_user_ptr = user_ptr; + WRAP(pa_threaded_mainloop_lock)(context->mainloop); - WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context); + pa_subscription_mask_t mask; + if (context->collection_changed_callback == NULL) { + // Unregister subscription + WRAP(pa_context_set_subscribe_callback)(context->context, NULL, NULL); + mask = PA_SUBSCRIPTION_MASK_NULL; + } else { + WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context); + if (devtype == CUBEB_DEVICE_TYPE_INPUT) + mask = PA_SUBSCRIPTION_MASK_SOURCE; + else if (devtype == CUBEB_DEVICE_TYPE_OUTPUT) + mask = PA_SUBSCRIPTION_MASK_SINK; + else + mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE; + } pa_operation * o; o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context); if (o == NULL) { - WRAP(pa_threaded_mainloop_unlock)(context->mainloop); LOG("Context subscribe failed"); return CUBEB_ERROR; } @@ -1560,37 +1362,6 @@ pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask) { return CUBEB_OK; } -static int -pulse_register_device_collection_changed(cubeb * context, - cubeb_device_type devtype, - cubeb_device_collection_changed_callback collection_changed_callback, - void * user_ptr) -{ - if (devtype & CUBEB_DEVICE_TYPE_INPUT) { - context->input_collection_changed_callback = collection_changed_callback; - context->input_collection_changed_user_ptr = user_ptr; - } - if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { - context->output_collection_changed_callback = collection_changed_callback; - context->output_collection_changed_user_ptr = user_ptr; - } - - pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_NULL; - if (context->input_collection_changed_callback) { - /* Input added or removed */ - mask |= PA_SUBSCRIPTION_MASK_SOURCE; - } - if (context->output_collection_changed_callback) { - /* Output added or removed */ - mask |= PA_SUBSCRIPTION_MASK_SINK; - } - /* Default device changed, this is always registered in order to update the - * `default_sink_info` when the default device changes. */ - mask |= PA_SUBSCRIPTION_MASK_SERVER; - - return pulse_subscribe_notifications(context, mask); -} - static struct cubeb_ops const pulse_ops = { .init = pulse_init, .get_backend_id = pulse_get_backend_id, @@ -1598,16 +1369,15 @@ static struct cubeb_ops const pulse_ops = { .get_min_latency = pulse_get_min_latency, .get_preferred_sample_rate = pulse_get_preferred_sample_rate, .enumerate_devices = pulse_enumerate_devices, - .device_collection_destroy = pulse_device_collection_destroy, .destroy = pulse_destroy, .stream_init = pulse_stream_init, .stream_destroy = pulse_stream_destroy, .stream_start = pulse_stream_start, .stream_stop = pulse_stream_stop, - .stream_reset_default_device = NULL, .stream_get_position = pulse_stream_get_position, .stream_get_latency = pulse_stream_get_latency, .stream_set_volume = pulse_stream_set_volume, + .stream_set_panning = pulse_stream_set_panning, .stream_get_current_device = pulse_stream_get_current_device, .stream_device_destroy = pulse_stream_device_destroy, .stream_register_device_changed_callback = NULL, diff --git a/media/libcubeb/src/cubeb_resampler.cpp b/media/libcubeb/src/cubeb_resampler.cpp index 2bc889340..f6676946c 100644 --- a/media/libcubeb/src/cubeb_resampler.cpp +++ b/media/libcubeb/src/cubeb_resampler.cpp @@ -35,54 +35,29 @@ to_speex_quality(cubeb_resampler_quality q) } } -uint32_t min_buffered_audio_frame(uint32_t sample_rate) -{ - return sample_rate / 20; -} - -template -passthrough_resampler::passthrough_resampler(cubeb_stream * s, - cubeb_data_callback cb, - void * ptr, - uint32_t input_channels, - uint32_t sample_rate) - : processor(input_channels) - , stream(s) - , data_callback(cb) - , user_ptr(ptr) - , sample_rate(sample_rate) -{ -} - -template -long passthrough_resampler::fill(void * input_buffer, long * input_frames_count, - void * output_buffer, long output_frames) +long noop_resampler::fill(void * input_buffer, long * input_frames_count, + void * output_buffer, long output_frames) { if (input_buffer) { assert(input_frames_count); } - assert((input_buffer && output_buffer) || - (output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) || - (input_buffer && !output_buffer && output_frames == 0)); - - if (input_buffer) { - if (!output_buffer) { - output_frames = *input_frames_count; - } - internal_input_buffer.push(static_cast(input_buffer), - frames_to_samples(*input_frames_count)); + assert((input_buffer && output_buffer && + *input_frames_count >= output_frames) || + (!input_buffer && (!input_frames_count || *input_frames_count == 0)) || + (!output_buffer && output_frames == 0)); + + if (output_buffer == nullptr) { + assert(input_buffer); + output_frames = *input_frames_count; } - long rv = data_callback(stream, user_ptr, internal_input_buffer.data(), - output_buffer, output_frames); - - if (input_buffer) { - internal_input_buffer.pop(nullptr, frames_to_samples(output_frames)); + if (input_buffer && *input_frames_count != output_frames) { + assert(*input_frames_count > output_frames); *input_frames_count = output_frames; - drop_audio_if_needed(); } - return rv; + return data_callback(stream, user_ptr, + input_buffer, output_buffer, output_frames); } template @@ -145,33 +120,28 @@ cubeb_resampler_speex assert(!input_buffer && (!input_frames_count || *input_frames_count == 0) && output_buffer && output_frames_needed); - if (!draining) { - long got = 0; - T * out_unprocessed = nullptr; - long output_frames_before_processing = 0; - - /* fill directly the input buffer of the output processor to save a copy */ - output_frames_before_processing = - output_processor->input_needed_for_output(output_frames_needed); + long got = 0; + T * out_unprocessed = nullptr; + long output_frames_before_processing = 0; - out_unprocessed = - output_processor->input_buffer(output_frames_before_processing); - got = data_callback(stream, user_ptr, - nullptr, out_unprocessed, - output_frames_before_processing); + /* fill directly the input buffer of the output processor to save a copy */ + output_frames_before_processing = + output_processor->input_needed_for_output(output_frames_needed); - if (got < output_frames_before_processing) { - draining = true; + out_unprocessed = + output_processor->input_buffer(output_frames_before_processing); - if (got < 0) { - return got; - } - } + got = data_callback(stream, user_ptr, + nullptr, out_unprocessed, + output_frames_before_processing); - output_processor->written(got); + if (got < 0) { + return got; } + output_processor->written(got); + /* Process the output. If not enough frames have been returned from the * callback, drain the processors. */ return output_processor->output(output_buffer, output_frames_needed); @@ -193,10 +163,7 @@ cubeb_resampler_speex /* process the input, and present exactly `output_frames_needed` in the * callback. */ input_processor->input(input_buffer, *input_frames_count); - - size_t frames_resampled = 0; - resampled_input = input_processor->output(resampled_frame_count, &frames_resampled); - *input_frames_count = frames_resampled; + resampled_input = input_processor->output(resampled_frame_count); long got = data_callback(stream, user_ptr, resampled_input, nullptr, resampled_frame_count); @@ -207,22 +174,18 @@ cubeb_resampler_speex return (*input_frames_count) * (got / resampled_frame_count); } + template long cubeb_resampler_speex ::fill_internal_duplex(T * in_buffer, long * input_frames_count, T * out_buffer, long output_frames_needed) { - if (draining) { - // discard input and drain any signal remaining in the resampler. - return output_processor->output(out_buffer, output_frames_needed); - } - /* The input data, after eventual resampling. This is passed to the callback. */ T * resampled_input = nullptr; /* The output buffer passed down in the callback, that might be resampled. */ T * out_unprocessed = nullptr; - long output_frames_before_processing = 0; + size_t output_frames_before_processing = 0; /* The number of frames returned from the callback. */ long got = 0; @@ -247,11 +210,8 @@ cubeb_resampler_speex /* process the input, and present exactly `output_frames_needed` in the * callback. */ input_processor->input(in_buffer, *input_frames_count); - - size_t frames_resampled = 0; resampled_input = - input_processor->output(output_frames_before_processing, &frames_resampled); - *input_frames_count = frames_resampled; + input_processor->output(output_frames_before_processing); } else { resampled_input = nullptr; } @@ -260,25 +220,15 @@ cubeb_resampler_speex resampled_input, out_unprocessed, output_frames_before_processing); - if (got < output_frames_before_processing) { - draining = true; - - if (got < 0) { - return got; - } + if (got < 0) { + return got; } output_processor->written(got); - input_processor->drop_audio_if_needed(); - /* Process the output. If not enough frames have been returned from the * callback, drain the processors. */ - got = output_processor->output(out_buffer, output_frames_needed); - - output_processor->drop_audio_if_needed(); - - return got; + return output_processor->output(out_buffer, output_frames_needed); } /* Resampler C API */ diff --git a/media/libcubeb/src/cubeb_resampler.h b/media/libcubeb/src/cubeb_resampler.h index f6b551373..020ccc17a 100644 --- a/media/libcubeb/src/cubeb_resampler.h +++ b/media/libcubeb/src/cubeb_resampler.h @@ -25,15 +25,8 @@ typedef enum { * Create a resampler to adapt the requested sample rate into something that * is accepted by the audio backend. * @param stream A cubeb_stream instance supplied to the data callback. - * @param input_params Used to calculate bytes per frame and buffer size for - * resampling of the input side of the stream. NULL if input should not be - * resampled. - * @param output_params Used to calculate bytes per frame and buffer size for - * resampling of the output side of the stream. NULL if output should not be - * resampled. - * @param target_rate The sampling rate after resampling for the input side of - * the stream, and/or the sampling rate prior to resampling of the output side - * of the stream. + * @param params Used to calculate bytes per frame and buffer size for resampling. + * @param target_rate The sampling rate after resampling. * @param callback A callback to request data for resampling. * @param user_ptr User data supplied to the data callback. * @param quality Quality of the resampler. @@ -54,8 +47,8 @@ cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream, * @param input_buffer A buffer of input samples * @param input_frame_count The size of the buffer. Returns the number of frames * consumed. - * @param output_buffer The buffer to be filled. - * @param output_frames_needed Number of frames that should be produced. + * @param buffer The buffer to be filled. + * @param frames_needed Number of frames that should be produced. * @retval Number of frames that are actually produced. * @retval CUBEB_ERROR on error. */ diff --git a/media/libcubeb/src/cubeb_resampler_internal.h b/media/libcubeb/src/cubeb_resampler_internal.h index fb69992ff..3c37a04b9 100644 --- a/media/libcubeb/src/cubeb_resampler_internal.h +++ b/media/libcubeb/src/cubeb_resampler_internal.h @@ -39,13 +39,6 @@ MOZ_END_STD_NAMESPACE /* This header file contains the internal C++ API of the resamplers, for testing. */ -// When dropping audio input frames to prevent building -// an input delay, this function returns the number of frames -// to keep in the buffer. -// @parameter sample_rate The sample rate of the stream. -// @return A number of frames to keep. -uint32_t min_buffered_audio_frame(uint32_t sample_rate); - int to_speex_quality(cubeb_resampler_quality q); struct cubeb_resampler { @@ -55,35 +48,16 @@ struct cubeb_resampler { virtual ~cubeb_resampler() {} }; -/** Base class for processors. This is just used to share methods for now. */ -class processor { +class noop_resampler : public cubeb_resampler { public: - explicit processor(uint32_t channels) - : channels(channels) - {} -protected: - size_t frames_to_samples(size_t frames) const + noop_resampler(cubeb_stream * s, + cubeb_data_callback cb, + void * ptr) + : stream(s) + , data_callback(cb) + , user_ptr(ptr) { - return frames * channels; - } - size_t samples_to_frames(size_t samples) const - { - assert(!(samples % channels)); - return samples / channels; } - /** The number of channel of the audio buffers to be resampled. */ - const uint32_t channels; -}; - -template -class passthrough_resampler : public cubeb_resampler - , public processor { -public: - passthrough_resampler(cubeb_stream * s, - cubeb_data_callback cb, - void * ptr, - uint32_t input_channels, - uint32_t sample_rate); virtual long fill(void * input_buffer, long * input_frames_count, void * output_buffer, long output_frames); @@ -93,23 +67,30 @@ public: return 0; } - void drop_audio_if_needed() - { - uint32_t to_keep = min_buffered_audio_frame(sample_rate); - uint32_t available = samples_to_frames(internal_input_buffer.length()); - if (available > to_keep) { - internal_input_buffer.pop(nullptr, frames_to_samples(available - to_keep)); - } - } - private: cubeb_stream * const stream; const cubeb_data_callback data_callback; void * const user_ptr; - /* This allows to buffer some input to account for the fact that we buffer - * some inputs. */ - auto_array internal_input_buffer; - uint32_t sample_rate; +}; + +/** Base class for processors. This is just used to share methods for now. */ +class processor { +public: + explicit processor(uint32_t channels) + : channels(channels) + {} +protected: + size_t frames_to_samples(size_t frames) + { + return frames * channels; + } + size_t samples_to_frames(size_t samples) + { + assert(!(samples % channels)); + return samples / channels; + } + /** The number of channel of the audio buffers to be resampled. */ + const uint32_t channels; }; /** Bidirectional resampler, can resample an input and an output stream, or just @@ -157,7 +138,6 @@ private: cubeb_stream * const stream; const cubeb_data_callback data_callback; void * const user_ptr; - bool draining = false; }; /** Handles one way of a (possibly) duplex resampler, working on interleaved @@ -183,7 +163,6 @@ public: int quality) : processor(channels) , resampling_ratio(static_cast(source_rate) / target_rate) - , source_rate(source_rate) , additional_latency(0) , leftover_samples(0) { @@ -242,7 +221,7 @@ public: /** Returns a buffer containing exactly `output_frame_count` resampled frames. * The consumer should not hold onto the pointer. */ - T * output(size_t output_frame_count, size_t * input_frames_used) + T * output(size_t output_frame_count) { if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) { resampling_out_buffer.reserve(frames_to_samples(output_frame_count)); @@ -259,7 +238,6 @@ public: /* This shifts back any unresampled samples to the beginning of the input buffer. */ resampling_in_buffer.pop(nullptr, frames_to_samples(in_len)); - *input_frames_used = in_len; return resampling_out_buffer.data(); } @@ -283,7 +261,7 @@ public: * exactly `output_frame_count` resampled frames. This can return a number * slightly bigger than what is strictly necessary, but it guaranteed that the * number of output frames will be exactly equal. */ - uint32_t input_needed_for_output(uint32_t output_frame_count) const + uint32_t input_needed_for_output(uint32_t output_frame_count) { int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length()); int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length()); @@ -316,16 +294,6 @@ public: resampling_in_buffer.set_length(leftover_samples + frames_to_samples(written_frames)); } - - void drop_audio_if_needed() - { - // Keep at most 100ms buffered. - uint32_t available = samples_to_frames(resampling_in_buffer.length()); - uint32_t to_keep = min_buffered_audio_frame(source_rate); - if (available > to_keep) { - resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep)); - } - } private: /** Wrapper for the speex resampling functions to have a typed * interface. */ @@ -362,7 +330,6 @@ private: SpeexResamplerState * speex_resampler; /** Source rate / target rate. */ const float resampling_ratio; - const uint32_t source_rate; /** Storage for the input frames, to be resampled. Also contains * any unresampled frames after resampling. */ auto_array resampling_in_buffer; @@ -381,13 +348,11 @@ class delay_line : public processor { public: /** Constructor * @parameter frames the number of frames of delay. - * @parameter channels the number of channels of this delay line. - * @parameter sample_rate sample-rate of the audio going through this delay line */ - delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate) + * @parameter channels the number of channels of this delay line. */ + delay_line(uint32_t frames, uint32_t channels) : processor(channels) , length(frames) , leftover_samples(0) - , sample_rate(sample_rate) { /* Fill the delay line with some silent frames to add latency. */ delay_input_buffer.push_silence(frames * channels); @@ -410,7 +375,7 @@ public: * @parameter frames_needed the number of frames to be returned. * @return a buffer containing the delayed frames. The consumer should not * hold onto the pointer. */ - T * output(uint32_t frames_needed, size_t * input_frames_used) + T * output(uint32_t frames_needed) { if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) { delay_output_buffer.reserve(frames_to_samples(frames_needed)); @@ -420,7 +385,6 @@ public: delay_output_buffer.push(delay_input_buffer.data(), frames_to_samples(frames_needed)); delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed)); - *input_frames_used = frames_needed; return delay_output_buffer.data(); } @@ -462,7 +426,7 @@ public: * @parameter frames_needed the number of frames one want to write into the * delay_line * @returns the number of frames one will get. */ - size_t input_needed_for_output(uint32_t frames_needed) const + size_t input_needed_for_output(uint32_t frames_needed) { return frames_needed; } @@ -477,15 +441,6 @@ public: { return length; } - - void drop_audio_if_needed() - { - size_t available = samples_to_frames(delay_input_buffer.length()); - uint32_t to_keep = min_buffered_audio_frame(sample_rate); - if (available > to_keep) { - delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep)); - } - } private: /** The length, in frames, of this delay line */ uint32_t length; @@ -497,7 +452,6 @@ private: /** The output buffer. This is only ever used if using the ::output with a * single argument. */ auto_array delay_output_buffer; - uint32_t sample_rate; }; /** This sits behind the C API and is more typed. */ @@ -526,10 +480,7 @@ cubeb_resampler_create_internal(cubeb_stream * stream, (output_params && output_params->rate == target_rate)) || (input_params && !output_params && (input_params->rate == target_rate)) || (output_params && !input_params && (output_params->rate == target_rate))) { - return new passthrough_resampler(stream, callback, - user_ptr, - input_params ? input_params->channels : 0, - target_rate); + return new noop_resampler(stream, callback, user_ptr); } /* Determine if we need to resampler one or both directions, and create the @@ -561,15 +512,13 @@ cubeb_resampler_create_internal(cubeb_stream * stream, * other direction so that the streams are synchronized. */ if (input_resampler && !output_resampler && input_params && output_params) { output_delay.reset(new delay_line(input_resampler->latency(), - output_params->channels, - output_params->rate)); + output_params->channels)); if (!output_delay) { return NULL; } } else if (output_resampler && !input_resampler && input_params && output_params) { input_delay.reset(new delay_line(output_resampler->latency(), - input_params->channels, - output_params->rate)); + input_params->channels)); if (!input_delay) { return NULL; } diff --git a/media/libcubeb/src/cubeb_ringbuffer.h b/media/libcubeb/src/cubeb_ringbuffer.h deleted file mode 100644 index b6696e886..000000000 --- a/media/libcubeb/src/cubeb_ringbuffer.h +++ /dev/null @@ -1,495 +0,0 @@ -/* - * Copyright © 2016 Mozilla Foundation - * - * This program is made available under an ISC-style license. See the - * accompanying file LICENSE for details. - */ - -#ifndef CUBEB_RING_BUFFER_H -#define CUBEB_RING_BUFFER_H - -#include "cubeb_utils.h" -#include -#include -#include -#include -#include - -/** - * Single producer single consumer lock-free and wait-free ring buffer. - * - * This data structure allows producing data from one thread, and consuming it on - * another thread, safely and without explicit synchronization. If used on two - * threads, this data structure uses atomics for thread safety. It is possible - * to disable the use of atomics at compile time and only use this data - * structure on one thread. - * - * The role for the producer and the consumer must be constant, i.e., the - * producer should always be on one thread and the consumer should always be on - * another thread. - * - * Some words about the inner workings of this class: - * - Capacity is fixed. Only one allocation is performed, in the constructor. - * When reading and writing, the return value of the method allows checking if - * the ring buffer is empty or full. - * - We always keep the read index at least one element ahead of the write - * index, so we can distinguish between an empty and a full ring buffer: an - * empty ring buffer is when the write index is at the same position as the - * read index. A full buffer is when the write index is exactly one position - * before the read index. - * - We synchronize updates to the read index after having read the data, and - * the write index after having written the data. This means that the each - * thread can only touch a portion of the buffer that is not touched by the - * other thread. - * - Callers are expected to provide buffers. When writing to the queue, - * elements are copied into the internal storage from the buffer passed in. - * When reading from the queue, the user is expected to provide a buffer. - * Because this is a ring buffer, data might not be contiguous in memory, - * providing an external buffer to copy into is an easy way to have linear - * data for further processing. - */ -template -class ring_buffer_base -{ -public: - /** - * Constructor for a ring buffer. - * - * This performs an allocation, but is the only allocation that will happen - * for the life time of a `ring_buffer_base`. - * - * @param capacity The maximum number of element this ring buffer will hold. - */ - ring_buffer_base(int capacity) - /* One more element to distinguish from empty and full buffer. */ - : capacity_(capacity + 1) - { - assert(storage_capacity() < - std::numeric_limits::max() / 2 && - "buffer too large for the type of index used."); - assert(capacity_ > 0); - - data_.reset(new T[storage_capacity()]); - /* If this queue is using atomics, initializing those members as the last - * action in the constructor acts as a full barrier, and allow capacity() to - * be thread-safe. */ - write_index_ = 0; - read_index_ = 0; - } - /** - * Push `count` zero or default constructed elements in the array. - * - * Only safely called on the producer thread. - * - * @param count The number of elements to enqueue. - * @return The number of element enqueued. - */ - int enqueue_default(int count) - { - return enqueue(nullptr, count); - } - /** - * @brief Put an element in the queue - * - * Only safely called on the producer thread. - * - * @param element The element to put in the queue. - * - * @return 1 if the element was inserted, 0 otherwise. - */ - int enqueue(T& element) - { - return enqueue(&element, 1); - } - /** - * Push `count` elements in the ring buffer. - * - * Only safely called on the producer thread. - * - * @param elements a pointer to a buffer containing at least `count` elements. - * If `elements` is nullptr, zero or default constructed elements are enqueued. - * @param count The number of elements to read from `elements` - * @return The number of elements successfully coped from `elements` and inserted - * into the ring buffer. - */ - int enqueue(T * elements, int count) - { -#ifndef NDEBUG - assert_correct_thread(producer_id); -#endif - - int rd_idx = read_index_.load(std::memory_order::memory_order_relaxed); - int wr_idx = write_index_.load(std::memory_order::memory_order_relaxed); - - if (full_internal(rd_idx, wr_idx)) { - return 0; - } - - int to_write = - std::min(available_write_internal(rd_idx, wr_idx), count); - - /* First part, from the write index to the end of the array. */ - int first_part = std::min(storage_capacity() - wr_idx, - to_write); - /* Second part, from the beginning of the array */ - int second_part = to_write - first_part; - - if (elements) { - Copy(data_.get() + wr_idx, elements, first_part); - Copy(data_.get(), elements + first_part, second_part); - } else { - ConstructDefault(data_.get() + wr_idx, first_part); - ConstructDefault(data_.get(), second_part); - } - - write_index_.store(increment_index(wr_idx, to_write), std::memory_order::memory_order_release); - - return to_write; - } - /** - * Retrieve at most `count` elements from the ring buffer, and copy them to - * `elements`, if non-null. - * - * Only safely called on the consumer side. - * - * @param elements A pointer to a buffer with space for at least `count` - * elements. If `elements` is `nullptr`, `count` element will be discarded. - * @param count The maximum number of elements to dequeue. - * @return The number of elements written to `elements`. - */ - int dequeue(T * elements, int count) - { -#ifndef NDEBUG - assert_correct_thread(consumer_id); -#endif - - int wr_idx = write_index_.load(std::memory_order::memory_order_acquire); - int rd_idx = read_index_.load(std::memory_order::memory_order_relaxed); - - if (empty_internal(rd_idx, wr_idx)) { - return 0; - } - - int to_read = - std::min(available_read_internal(rd_idx, wr_idx), count); - - int first_part = std::min(storage_capacity() - rd_idx, to_read); - int second_part = to_read - first_part; - - if (elements) { - Copy(elements, data_.get() + rd_idx, first_part); - Copy(elements + first_part, data_.get(), second_part); - } - - read_index_.store(increment_index(rd_idx, to_read), std::memory_order::memory_order_relaxed); - - return to_read; - } - /** - * Get the number of available element for consuming. - * - * Only safely called on the consumer thread. - * - * @return The number of available elements for reading. - */ - int available_read() const - { -#ifndef NDEBUG - assert_correct_thread(consumer_id); -#endif - return available_read_internal(read_index_.load(std::memory_order::memory_order_relaxed), - write_index_.load(std::memory_order::memory_order_relaxed)); - } - /** - * Get the number of available elements for consuming. - * - * Only safely called on the producer thread. - * - * @return The number of empty slots in the buffer, available for writing. - */ - int available_write() const - { -#ifndef NDEBUG - assert_correct_thread(producer_id); -#endif - return available_write_internal(read_index_.load(std::memory_order::memory_order_relaxed), - write_index_.load(std::memory_order::memory_order_relaxed)); - } - /** - * Get the total capacity, for this ring buffer. - * - * Can be called safely on any thread. - * - * @return The maximum capacity of this ring buffer. - */ - int capacity() const - { - return storage_capacity() - 1; - } - /** - * Reset the consumer and producer thread identifier, in case the thread are - * being changed. This has to be externally synchronized. This is no-op when - * asserts are disabled. - */ - void reset_thread_ids() - { -#ifndef NDEBUG - consumer_id = producer_id = std::thread::id(); -#endif - } -private: - /** Return true if the ring buffer is empty. - * - * @param read_index the read index to consider - * @param write_index the write index to consider - * @return true if the ring buffer is empty, false otherwise. - **/ - bool empty_internal(int read_index, - int write_index) const - { - return write_index == read_index; - } - /** Return true if the ring buffer is full. - * - * This happens if the write index is exactly one element behind the read - * index. - * - * @param read_index the read index to consider - * @param write_index the write index to consider - * @return true if the ring buffer is full, false otherwise. - **/ - bool full_internal(int read_index, - int write_index) const - { - return (write_index + 1) % storage_capacity() == read_index; - } - /** - * Return the size of the storage. It is one more than the number of elements - * that can be stored in the buffer. - * - * @return the number of elements that can be stored in the buffer. - */ - int storage_capacity() const - { - return capacity_; - } - /** - * Returns the number of elements available for reading. - * - * @return the number of available elements for reading. - */ - int - available_read_internal(int read_index, - int write_index) const - { - if (write_index >= read_index) { - return write_index - read_index; - } else { - return write_index + storage_capacity() - read_index; - } - } - /** - * Returns the number of empty elements, available for writing. - * - * @return the number of elements that can be written into the array. - */ - int - available_write_internal(int read_index, - int write_index) const - { - /* We substract one element here to always keep at least one sample - * free in the buffer, to distinguish between full and empty array. */ - int rv = read_index - write_index - 1; - if (write_index >= read_index) { - rv += storage_capacity(); - } - return rv; - } - /** - * Increments an index, wrapping it around the storage. - * - * @param index a reference to the index to increment. - * @param increment the number by which `index` is incremented. - * @return the new index. - */ - int - increment_index(int index, int increment) const - { - assert(increment >= 0); - return (index + increment) % storage_capacity(); - } - /** - * @brief This allows checking that enqueue (resp. dequeue) are always called - * by the right thread. - * - * @param id the id of the thread that has called the calling method first. - */ -#ifndef NDEBUG - static void assert_correct_thread(std::thread::id& id) - { - if (id == std::thread::id()) { - id = std::this_thread::get_id(); - return; - } - assert(id == std::this_thread::get_id()); - } -#endif - /** Index at which the oldest element is at, in samples. */ - std::atomic read_index_; - /** Index at which to write new elements. `write_index` is always at - * least one element ahead of `read_index_`. */ - std::atomic write_index_; - /** Maximum number of elements that can be stored in the ring buffer. */ - const int capacity_; - /** Data storage */ - std::unique_ptr data_; -#ifndef NDEBUG - /** The id of the only thread that is allowed to read from the queue. */ - mutable std::thread::id consumer_id; - /** The id of the only thread that is allowed to write from the queue. */ - mutable std::thread::id producer_id; -#endif -}; - -/** - * Adapter for `ring_buffer_base` that exposes an interface in frames. - */ -template -class audio_ring_buffer_base -{ -public: - /** - * @brief Constructor. - * - * @param channel_count Number of channels. - * @param capacity_in_frames The capacity in frames. - */ - audio_ring_buffer_base(int channel_count, int capacity_in_frames) - : channel_count(channel_count) - , ring_buffer(frames_to_samples(capacity_in_frames)) - { - assert(channel_count > 0); - } - /** - * @brief Enqueue silence. - * - * Only safely called on the producer thread. - * - * @param frame_count The number of frames of silence to enqueue. - * @return The number of frames of silence actually written to the queue. - */ - int enqueue_default(int frame_count) - { - return samples_to_frames(ring_buffer.enqueue(nullptr, frames_to_samples(frame_count))); - } - /** - * @brief Enqueue `frames_count` frames of audio. - * - * Only safely called from the producer thread. - * - * @param [in] frames If non-null, the frames to enqueue. - * Otherwise, silent frames are enqueued. - * @param frame_count The number of frames to enqueue. - * - * @return The number of frames enqueued - */ - - int enqueue(T * frames, int frame_count) - { - return samples_to_frames(ring_buffer.enqueue(frames, frames_to_samples(frame_count))); - } - - /** - * @brief Removes `frame_count` frames from the buffer, and - * write them to `frames` if it is non-null. - * - * Only safely called on the consumer thread. - * - * @param frames If non-null, the frames are copied to `frames`. - * Otherwise, they are dropped. - * @param frame_count The number of frames to remove. - * - * @return The number of frames actually dequeud. - */ - int dequeue(T * frames, int frame_count) - { - return samples_to_frames(ring_buffer.dequeue(frames, frames_to_samples(frame_count))); - } - /** - * Get the number of available frames of audio for consuming. - * - * Only safely called on the consumer thread. - * - * @return The number of available frames of audio for reading. - */ - int available_read() const - { - return samples_to_frames(ring_buffer.available_read()); - } - /** - * Get the number of available frames of audio for consuming. - * - * Only safely called on the producer thread. - * - * @return The number of empty slots in the buffer, available for writing. - */ - int available_write() const - { - return samples_to_frames(ring_buffer.available_write()); - } - /** - * Get the total capacity, for this ring buffer. - * - * Can be called safely on any thread. - * - * @return The maximum capacity of this ring buffer. - */ - int capacity() const - { - return samples_to_frames(ring_buffer.capacity()); - } -private: - /** - * @brief Frames to samples conversion. - * - * @param frames The number of frames. - * - * @return A number of samples. - */ - int frames_to_samples(int frames) const - { - return frames * channel_count; - } - /** - * @brief Samples to frames conversion. - * - * @param samples The number of samples. - * - * @return A number of frames. - */ - int samples_to_frames(int samples) const - { - return samples / channel_count; - } - /** Number of channels of audio that will stream through this ring buffer. */ - int channel_count; - /** The underlying ring buffer that is used to store the data. */ - ring_buffer_base ring_buffer; -}; - -/** - * Lock-free instantiation of the `ring_buffer_base` type. This is safe to use - * from two threads, one producer, one consumer (that never change role), - * without explicit synchronization. - */ -template -using lock_free_queue = ring_buffer_base; -/** - * Lock-free instantiation of the `audio_ring_buffer` type. This is safe to use - * from two threads, one producer, one consumer (that never change role), - * without explicit synchronization. - */ -template -using lock_free_audio_ring_buffer = audio_ring_buffer_base; - -#endif // CUBEB_RING_BUFFER_H diff --git a/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c index 4a05bd845..793789765 100644 --- a/media/libcubeb/src/cubeb_sndio.c +++ b/media/libcubeb/src/cubeb_sndio.c @@ -4,7 +4,6 @@ * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ -#include #include #include #include @@ -12,7 +11,6 @@ #include #include #include -#include #include #include "cubeb/cubeb.h" #include "cubeb-internal.h" @@ -23,87 +21,39 @@ #define DPR(...) do {} while(0) #endif -#ifdef DISABLE_LIBSNDIO_DLOPEN -#define WRAP(x) x -#else -#define WRAP(x) cubeb_##x -#define LIBSNDIO_API_VISIT(X) \ - X(sio_close) \ - X(sio_eof) \ - X(sio_getpar) \ - X(sio_initpar) \ - X(sio_onmove) \ - X(sio_open) \ - X(sio_pollfd) \ - X(sio_read) \ - X(sio_revents) \ - X(sio_setpar) \ - X(sio_start) \ - X(sio_stop) \ - X(sio_write) \ - -#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; -LIBSNDIO_API_VISIT(MAKE_TYPEDEF); -#undef MAKE_TYPEDEF -#endif - static struct cubeb_ops const sndio_ops; struct cubeb { struct cubeb_ops const * ops; - void * libsndio; }; struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; - void * arg; /* user arg to {data,state}_cb */ - /**/ - pthread_t th; /* to run real-time audio i/o */ - pthread_mutex_t mtx; /* protects hdl and pos */ - struct sio_hdl *hdl; /* link us to sndio */ - int mode; /* bitmap of SIO_{PLAY,REC} */ - int active; /* cubec_start() called */ - int conv; /* need float->s16 conversion */ - unsigned char *rbuf; /* rec data consumed from here */ - unsigned char *pbuf; /* play data is prepared here */ - unsigned int nfr; /* number of frames in ibuf and obuf */ - unsigned int rbpf; /* rec bytes per frame */ - unsigned int pbpf; /* play bytes per frame */ - unsigned int rchan; /* number of rec channels */ - unsigned int pchan; /* number of play channels */ - unsigned int nblks; /* number of blocks in the buffer */ - uint64_t hwpos; /* frame number Joe hears right now */ - uint64_t swpos; /* number of frames produced/consumed */ + pthread_t th; /* to run real-time audio i/o */ + pthread_mutex_t mtx; /* protects hdl and pos */ + struct sio_hdl *hdl; /* link us to sndio */ + int active; /* cubec_start() called */ + int conv; /* need float->s16 conversion */ + unsigned char *buf; /* data is prepared here */ + unsigned int nfr; /* number of frames in buf */ + unsigned int bpf; /* bytes per frame */ + unsigned int pchan; /* number of play channels */ + uint64_t rdpos; /* frame number Joe hears right now */ + uint64_t wrpos; /* number of written frames */ cubeb_data_callback data_cb; /* cb to preapare data */ cubeb_state_callback state_cb; /* cb to notify about state changes */ - float volume; /* current volume */ + void *arg; /* user arg to {data,state}_cb */ }; static void -s16_setvol(void *ptr, long nsamp, float volume) -{ - int16_t *dst = ptr; - int32_t mult = volume * 32768; - int32_t s; - - while (nsamp-- > 0) { - s = *dst; - s = (s * mult) >> 15; - *(dst++) = s; - } -} - -static void -float_to_s16(void *ptr, long nsamp, float volume) +float_to_s16(void *ptr, long nsamp) { int16_t *dst = ptr; float *src = ptr; - float mult = volume * 32768; int s; while (nsamp-- > 0) { - s = lrintf(*(src++) * mult); + s = lrintf(*(src++) * 32768); if (s < -32768) s = -32768; else if (s > 32767) @@ -112,24 +62,12 @@ float_to_s16(void *ptr, long nsamp, float volume) } } -static void -s16_to_float(void *ptr, long nsamp) -{ - int16_t *src = ptr; - float *dst = ptr; - - src += nsamp; - dst += nsamp; - while (nsamp-- > 0) - *(--dst) = (1. / 32768) * *(--src); -} - static void sndio_onmove(void *arg, int delta) { cubeb_stream *s = (cubeb_stream *)arg; - s->hwpos += delta; + s->rdpos += delta * s->bpf; } static void * @@ -138,99 +76,48 @@ sndio_mainloop(void *arg) #define MAXFDS 8 struct pollfd pfds[MAXFDS]; cubeb_stream *s = arg; - int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED; - size_t pstart = 0, pend = 0, rstart = 0, rend = 0; + int n, nfds, revents, state = CUBEB_STATE_STARTED; + size_t start = 0, end = 0; long nfr; DPR("sndio_mainloop()\n"); s->state_cb(s, s->arg, CUBEB_STATE_STARTED); pthread_mutex_lock(&s->mtx); - if (!WRAP(sio_start)(s->hdl)) { + if (!sio_start(s->hdl)) { pthread_mutex_unlock(&s->mtx); return NULL; } DPR("sndio_mainloop(), started\n"); - if (s->mode & SIO_PLAY) { - pstart = pend = s->nfr * s->pbpf; - prime = s->nblks; - if (s->mode & SIO_REC) { - memset(s->rbuf, 0, s->nfr * s->rbpf); - rstart = rend = s->nfr * s->rbpf; - } - } else { - prime = 0; - rstart = 0; - rend = s->nfr * s->rbpf; - } - + start = end = s->nfr; for (;;) { if (!s->active) { DPR("sndio_mainloop() stopped\n"); state = CUBEB_STATE_STOPPED; break; } - - /* do we have a complete block? */ - if ((!(s->mode & SIO_PLAY) || pstart == pend) && - (!(s->mode & SIO_REC) || rstart == rend)) { - - if (eof) { + if (start == end) { + if (end < s->nfr) { DPR("sndio_mainloop() drained\n"); state = CUBEB_STATE_DRAINED; break; } - - if ((s->mode & SIO_REC) && s->conv) - s16_to_float(s->rbuf, s->nfr * s->rchan); - - /* invoke call-back, it returns less that s->nfr if done */ pthread_mutex_unlock(&s->mtx); - nfr = s->data_cb(s, s->arg, s->rbuf, s->pbuf, s->nfr); + nfr = s->data_cb(s, s->arg, NULL, s->buf, s->nfr); pthread_mutex_lock(&s->mtx); if (nfr < 0) { DPR("sndio_mainloop() cb err\n"); state = CUBEB_STATE_ERROR; break; } - s->swpos += nfr; - - /* was this last call-back invocation (aka end-of-stream) ? */ - if (nfr < s->nfr) { - - if (!(s->mode & SIO_PLAY) || nfr == 0) { - state = CUBEB_STATE_DRAINED; - break; - } - - /* need to write (aka drain) the partial play block we got */ - pend = nfr * s->pbpf; - eof = 1; - } - - if (prime > 0) - prime--; - - if (s->mode & SIO_PLAY) { - if (s->conv) - float_to_s16(s->pbuf, nfr * s->pchan, s->volume); - else - s16_setvol(s->pbuf, nfr * s->pchan, s->volume); - } - - if (s->mode & SIO_REC) - rstart = 0; - if (s->mode & SIO_PLAY) - pstart = 0; + if (s->conv) + float_to_s16(s->buf, nfr * s->pchan); + start = 0; + end = nfr * s->bpf; } - - events = 0; - if ((s->mode & SIO_REC) && rstart < rend && prime == 0) - events |= POLLIN; - if ((s->mode & SIO_PLAY) && pstart < pend) - events |= POLLOUT; - nfds = WRAP(sio_pollfd)(s->hdl, pfds, events); - + if (end == 0) + continue; + nfds = sio_pollfd(s->hdl, pfds, POLLOUT); if (nfds > 0) { pthread_mutex_unlock(&s->mtx); n = poll(pfds, nfds, -1); @@ -238,40 +125,22 @@ sndio_mainloop(void *arg) if (n < 0) continue; } - - revents = WRAP(sio_revents)(s->hdl, pfds); - - if (revents & POLLHUP) { - state = CUBEB_STATE_ERROR; + revents = sio_revents(s->hdl, pfds); + if (revents & POLLHUP) break; - } - if (revents & POLLOUT) { - n = WRAP(sio_write)(s->hdl, s->pbuf + pstart, pend - pstart); - if (n == 0 && WRAP(sio_eof)(s->hdl)) { + n = sio_write(s->hdl, s->buf + start, end - start); + if (n == 0) { DPR("sndio_mainloop() werr\n"); state = CUBEB_STATE_ERROR; break; } - pstart += n; + s->wrpos += n; + start += n; } - - if (revents & POLLIN) { - n = WRAP(sio_read)(s->hdl, s->rbuf + rstart, rend - rstart); - if (n == 0 && WRAP(sio_eof)(s->hdl)) { - DPR("sndio_mainloop() rerr\n"); - state = CUBEB_STATE_ERROR; - break; - } - rstart += n; - } - - /* skip rec block, if not recording (yet) */ - if (prime > 0 && (s->mode & SIO_REC)) - rstart = rend; } - WRAP(sio_stop)(s->hdl); - s->hwpos = s->swpos; + sio_stop(s->hdl); + s->rdpos = s->wrpos; pthread_mutex_unlock(&s->mtx); s->state_cb(s, s->arg, state); return NULL; @@ -280,34 +149,8 @@ sndio_mainloop(void *arg) /*static*/ int sndio_init(cubeb **context, char const *context_name) { - void * libsndio = NULL; - -#ifndef DISABLE_LIBSNDIO_DLOPEN - libsndio = dlopen("libsndio.so.7.0", RTLD_LAZY); - if (!libsndio) { - libsndio = dlopen("libsndio.so", RTLD_LAZY); - if (!libsndio) { - DPR("sndio_init(%s) failed dlopen(libsndio.so)\n", context_name); - return CUBEB_ERROR; - } - } - -#define LOAD(x) { \ - cubeb_##x = dlsym(libsndio, #x); \ - if (!cubeb_##x) { \ - DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \ - dlclose(libsndio); \ - return CUBEB_ERROR; \ - } \ - } - - LIBSNDIO_API_VISIT(LOAD); -#undef LOAD -#endif - DPR("sndio_init(%s)\n", context_name); *context = malloc(sizeof(*context)); - (*context)->libsndio = libsndio; (*context)->ops = &sndio_ops; (void)context_name; return CUBEB_OK; @@ -323,8 +166,6 @@ static void sndio_destroy(cubeb *context) { DPR("sndio_destroy()\n"); - if (context->libsndio) - dlclose(context->libsndio); free(context); } @@ -343,49 +184,29 @@ sndio_stream_init(cubeb * context, { cubeb_stream *s; struct sio_par wpar, rpar; - cubeb_sample_format format; - int rate; - size_t bps; - DPR("sndio_stream_init(%s)\n", stream_name); + size_t size; + + assert(!input_stream_params && "not supported."); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } s = malloc(sizeof(cubeb_stream)); if (s == NULL) return CUBEB_ERROR; - memset(s, 0, sizeof(cubeb_stream)); - s->mode = 0; - if (input_stream_params) { - if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { - DPR("sndio_stream_init(), loopback not supported\n"); - goto err; - } - s->mode |= SIO_REC; - format = input_stream_params->format; - rate = input_stream_params->rate; - } - if (output_stream_params) { - if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { - DPR("sndio_stream_init(), loopback not supported\n"); - goto err; - } - s->mode |= SIO_PLAY; - format = output_stream_params->format; - rate = output_stream_params->rate; - } - if (s->mode == 0) { - DPR("sndio_stream_init(), neither playing nor recording\n"); - goto err; - } s->context = context; - s->hdl = WRAP(sio_open)(NULL, s->mode, 1); + s->hdl = sio_open(NULL, SIO_PLAY, 1); if (s->hdl == NULL) { + free(s); DPR("sndio_stream_init(), sio_open() failed\n"); - goto err; + return CUBEB_ERROR; } - WRAP(sio_initpar)(&wpar); + sio_initpar(&wpar); wpar.sig = 1; wpar.bits = 16; - switch (format) { + switch (output_stream_params->format) { case CUBEB_SAMPLE_S16LE: wpar.le = 1; break; @@ -397,70 +218,53 @@ sndio_stream_init(cubeb * context, break; default: DPR("sndio_stream_init() unsupported format\n"); - goto err; + return CUBEB_ERROR_INVALID_FORMAT; } - wpar.rate = rate; - if (s->mode & SIO_REC) - wpar.rchan = input_stream_params->channels; - if (s->mode & SIO_PLAY) - wpar.pchan = output_stream_params->channels; + wpar.rate = output_stream_params->rate; + wpar.pchan = output_stream_params->channels; wpar.appbufsz = latency_frames; - if (!WRAP(sio_setpar)(s->hdl, &wpar) || !WRAP(sio_getpar)(s->hdl, &rpar)) { + if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) { + sio_close(s->hdl); + free(s); DPR("sndio_stream_init(), sio_setpar() failed\n"); - goto err; + return CUBEB_ERROR; } if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig || rpar.rate != wpar.rate || - ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) || - ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) { + rpar.pchan != wpar.pchan) { + sio_close(s->hdl); + free(s); DPR("sndio_stream_init() unsupported params\n"); - goto err; + return CUBEB_ERROR_INVALID_FORMAT; } - WRAP(sio_onmove)(s->hdl, sndio_onmove, s); + sio_onmove(s->hdl, sndio_onmove, s); s->active = 0; s->nfr = rpar.round; - s->rbpf = rpar.bps * rpar.rchan; - s->pbpf = rpar.bps * rpar.pchan; - s->rchan = rpar.rchan; + s->bpf = rpar.bps * rpar.pchan; s->pchan = rpar.pchan; - s->nblks = rpar.bufsz / rpar.round; s->data_cb = data_callback; s->state_cb = state_callback; s->arg = user_ptr; - s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; - s->hwpos = s->swpos = 0; - if (format == CUBEB_SAMPLE_FLOAT32LE) { + s->mtx = PTHREAD_MUTEX_INITIALIZER; + s->rdpos = s->wrpos = 0; + if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) { s->conv = 1; - bps = sizeof(float); + size = rpar.round * rpar.pchan * sizeof(float); } else { s->conv = 0; - bps = rpar.bps; - } - if (s->mode & SIO_PLAY) { - s->pbuf = malloc(bps * rpar.pchan * rpar.round); - if (s->pbuf == NULL) - goto err; + size = rpar.round * rpar.pchan * rpar.bps; } - if (s->mode & SIO_REC) { - s->rbuf = malloc(bps * rpar.rchan * rpar.round); - if (s->rbuf == NULL) - goto err; + s->buf = malloc(size); + if (s->buf == NULL) { + sio_close(s->hdl); + free(s); + return CUBEB_ERROR; } - s->volume = 1.; *stream = s; DPR("sndio_stream_init() end, ok\n"); (void)context; (void)stream_name; return CUBEB_OK; -err: - if (s->hdl) - WRAP(sio_close)(s->hdl); - if (s->pbuf) - free(s->pbuf); - if (s->rbuf) - free(s->pbuf); - free(s); - return CUBEB_ERROR; } static int @@ -476,21 +280,16 @@ sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) static int sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) { - /* - * We've no device-independent prefered rate; any rate will work if - * sndiod is running. If it isn't, 48kHz is what is most likely to - * work as most (but not all) devices support it. - */ - *rate = 48000; + // XXX Not yet implemented. + *rate = 44100; + return CUBEB_OK; } static int sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) { - /* - * We've no device-independent minimum latency. - */ + // XXX Not yet implemented. *latency_frames = 2048; return CUBEB_OK; @@ -500,11 +299,7 @@ static void sndio_stream_destroy(cubeb_stream *s) { DPR("sndio_stream_destroy()\n"); - WRAP(sio_close)(s->hdl); - if (s->mode & SIO_PLAY) - free(s->pbuf); - if (s->mode & SIO_REC) - free(s->rbuf); + sio_close(s->hdl); free(s); } @@ -540,8 +335,8 @@ static int sndio_stream_get_position(cubeb_stream *s, uint64_t *p) { pthread_mutex_lock(&s->mtx); - DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos); - *p = s->hwpos; + DPR("sndio_stream_get_position() %lld\n", s->rdpos); + *p = s->rdpos / s->bpf; pthread_mutex_unlock(&s->mtx); return CUBEB_OK; } @@ -551,11 +346,7 @@ sndio_stream_set_volume(cubeb_stream *s, float volume) { DPR("sndio_stream_set_volume(%f)\n", volume); pthread_mutex_lock(&s->mtx); - if (volume < 0.) - volume = 0.; - else if (volume > 1.0) - volume = 1.; - s->volume = volume; + sio_setvol(s->hdl, SIO_MAXVOL * volume); pthread_mutex_unlock(&s->mtx); return CUBEB_OK; } @@ -565,47 +356,7 @@ sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency) { // http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open // in the "Measuring the latency and buffers usage" paragraph. - *latency = stm->swpos - stm->hwpos; - return CUBEB_OK; -} - -static int -sndio_enumerate_devices(cubeb *context, cubeb_device_type type, - cubeb_device_collection *collection) -{ - static char dev[] = SIO_DEVANY; - cubeb_device_info *device; - - device = malloc(sizeof(cubeb_device_info)); - if (device == NULL) - return CUBEB_ERROR; - - device->devid = dev; /* passed to stream_init() */ - device->device_id = dev; /* printable in UI */ - device->friendly_name = dev; /* same, but friendly */ - device->group_id = dev; /* actual device if full-duplex */ - device->vendor_name = NULL; /* may be NULL */ - device->type = type; /* Input/Output */ - device->state = CUBEB_DEVICE_STATE_ENABLED; - device->preferred = CUBEB_DEVICE_PREF_ALL; - device->format = CUBEB_DEVICE_FMT_S16NE; - device->default_format = CUBEB_DEVICE_FMT_S16NE; - device->max_channels = 16; - device->default_rate = 48000; - device->min_rate = 4000; - device->max_rate = 192000; - device->latency_lo = 480; - device->latency_hi = 9600; - collection->device = device; - collection->count = 1; - return CUBEB_OK; -} - -static int -sndio_device_collection_destroy(cubeb * context, - cubeb_device_collection * collection) -{ - free(collection->device); + *latency = (stm->wrpos - stm->rdpos) / stm->bpf; return CUBEB_OK; } @@ -615,17 +366,16 @@ static struct cubeb_ops const sndio_ops = { .get_max_channel_count = sndio_get_max_channel_count, .get_min_latency = sndio_get_min_latency, .get_preferred_sample_rate = sndio_get_preferred_sample_rate, - .enumerate_devices = sndio_enumerate_devices, - .device_collection_destroy = sndio_device_collection_destroy, + .enumerate_devices = NULL, .destroy = sndio_destroy, .stream_init = sndio_stream_init, .stream_destroy = sndio_stream_destroy, .stream_start = sndio_stream_start, .stream_stop = sndio_stream_stop, - .stream_reset_default_device = NULL, .stream_get_position = sndio_stream_get_position, .stream_get_latency = sndio_stream_get_latency, .stream_set_volume = sndio_stream_set_volume, + .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, diff --git a/media/libcubeb/src/cubeb_strings.c b/media/libcubeb/src/cubeb_strings.c deleted file mode 100644 index 79d7d21b3..000000000 --- a/media/libcubeb/src/cubeb_strings.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright © 2011 Mozilla Foundation - * - * This program is made available under an ISC-style license. See the - * accompanying file LICENSE for details. - */ - -#include "cubeb_strings.h" - -#include -#include -#include - -#define CUBEB_STRINGS_INLINE_COUNT 4 - -struct cubeb_strings { - uint32_t size; - uint32_t count; - char ** data; - char * small_store[CUBEB_STRINGS_INLINE_COUNT]; -}; - -int -cubeb_strings_init(cubeb_strings ** strings) -{ - cubeb_strings* strs = NULL; - - if (!strings) { - return CUBEB_ERROR; - } - - strs = calloc(1, sizeof(cubeb_strings)); - assert(strs); - - if (!strs) { - return CUBEB_ERROR; - } - - strs->size = sizeof(strs->small_store) / sizeof(strs->small_store[0]); - strs->count = 0; - strs->data = strs->small_store; - - *strings = strs; - - return CUBEB_OK; -} - -void -cubeb_strings_destroy(cubeb_strings * strings) -{ - char ** sp = NULL; - char ** se = NULL; - - if (!strings) { - return; - } - - sp = strings->data; - se = sp + strings->count; - - for ( ; sp != se; sp++) { - if (*sp) { - free(*sp); - } - } - - if (strings->data != strings->small_store) { - free(strings->data); - } - - free(strings); -} - -/** Look for string in string storage. - @param strings Opaque pointer to interned string storage. - @param s String to look up. - @retval Read-only string or NULL if not found. */ -static char const * -cubeb_strings_lookup(cubeb_strings * strings, char const * s) -{ - char ** sp = NULL; - char ** se = NULL; - - if (!strings || !s) { - return NULL; - } - - sp = strings->data; - se = sp + strings->count; - - for ( ; sp != se; sp++) { - if (*sp && strcmp(*sp, s) == 0) { - return *sp; - } - } - - return NULL; -} - -static char const * -cubeb_strings_push(cubeb_strings * strings, char const * s) -{ - char * is = NULL; - - if (strings->count == strings->size) { - char ** new_data; - uint32_t value_size = sizeof(char const *); - uint32_t new_size = strings->size * 2; - if (!new_size || value_size > (uint32_t)-1 / new_size) { - // overflow - return NULL; - } - - if (strings->small_store == strings->data) { - // First time heap allocation. - new_data = malloc(new_size * value_size); - if (new_data) { - memcpy(new_data, strings->small_store, sizeof(strings->small_store)); - } - } else { - new_data = realloc(strings->data, new_size * value_size); - } - - if (!new_data) { - // out of memory - return NULL; - } - - strings->size = new_size; - strings->data = new_data; - } - - is = strdup(s); - strings->data[strings->count++] = is; - - return is; -} - -char const * -cubeb_strings_intern(cubeb_strings * strings, char const * s) -{ - char const * is = NULL; - - if (!strings || !s) { - return NULL; - } - - is = cubeb_strings_lookup(strings, s); - if (is) { - return is; - } - - return cubeb_strings_push(strings, s); -} - diff --git a/media/libcubeb/src/cubeb_strings.h b/media/libcubeb/src/cubeb_strings.h deleted file mode 100644 index a918a01c5..000000000 --- a/media/libcubeb/src/cubeb_strings.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright © 2011 Mozilla Foundation - * - * This program is made available under an ISC-style license. See the - * accompanying file LICENSE for details. - */ - -#ifndef CUBEB_STRINGS_H -#define CUBEB_STRINGS_H - -#include "cubeb/cubeb.h" - -#if defined(__cplusplus) -extern "C" { -#endif - -/** Opaque handle referencing interned string storage. */ -typedef struct cubeb_strings cubeb_strings; - -/** Initialize an interned string structure. - @param strings An out param where an opaque pointer to the - interned string storage will be returned. - @retval CUBEB_OK in case of success. - @retval CUBEB_ERROR in case of error. */ -CUBEB_EXPORT int cubeb_strings_init(cubeb_strings ** strings); - -/** Destroy an interned string structure freeing all associated memory. - @param strings An opaque pointer to the interned string storage to - destroy. */ -CUBEB_EXPORT void cubeb_strings_destroy(cubeb_strings * strings); - -/** Add string to internal storage. - @param strings Opaque pointer to interned string storage. - @param s String to add to storage. - @retval CUBEB_OK - @retval CUBEB_ERROR - */ -CUBEB_EXPORT char const * cubeb_strings_intern(cubeb_strings * strings, char const * s); - -#if defined(__cplusplus) -} -#endif - -#endif // !CUBEB_STRINGS_H diff --git a/media/libcubeb/src/cubeb_sun.c b/media/libcubeb/src/cubeb_sun.c index 64ab0b5b1..b768bca56 100644 --- a/media/libcubeb/src/cubeb_sun.c +++ b/media/libcubeb/src/cubeb_sun.c @@ -1,752 +1,504 @@ /* - * Copyright © 2019 Nia Alarie + * Copyright (c) 2013, 2017 Ginn Chen * * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ -#include -#include -#include -#include +#include #include #include #include -#include -#include +#include +#include +#include +#include +#include +#include #include "cubeb/cubeb.h" #include "cubeb-internal.h" -#define BYTES_TO_FRAMES(bytes, channels) \ - (bytes / (channels * sizeof(int16_t))) - -#define FRAMES_TO_BYTES(frames, channels) \ - (frames * (channels * sizeof(int16_t))) - -/* Default to 4 + 1 for the default device. */ -#ifndef SUN_DEVICE_COUNT -#define SUN_DEVICE_COUNT (5) -#endif - -/* Supported well by most hardware. */ -#ifndef SUN_PREFER_RATE -#define SUN_PREFER_RATE (48000) -#endif - -/* Standard acceptable minimum. */ -#ifndef SUN_LATENCY_MS -#define SUN_LATENCY_MS (40) -#endif - -#ifndef SUN_DEFAULT_DEVICE -#define SUN_DEFAULT_DEVICE "/dev/audio" -#endif - -#ifndef SUN_POLL_TIMEOUT -#define SUN_POLL_TIMEOUT (1000) -#endif - -#ifndef SUN_BUFFER_FRAMES -#define SUN_BUFFER_FRAMES (32) -#endif - +/* Macros copied from audio_oss.h */ /* - * Supported on NetBSD regardless of hardware. + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END */ - -#ifndef SUN_MAX_CHANNELS -# ifdef __NetBSD__ -# define SUN_MAX_CHANNELS (12) -# else -# define SUN_MAX_CHANNELS (2) -# endif +/* + * Copyright (C) 4Front Technologies 1996-2008. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#define OSSIOCPARM_MASK 0x1fff /* parameters must be < 8192 bytes */ +#define OSSIOC_VOID 0x00000000 /* no parameters */ +#define OSSIOC_OUT 0x20000000 /* copy out parameters */ +#define OSSIOC_IN 0x40000000 /* copy in parameters */ +#define OSSIOC_INOUT (OSSIOC_IN|OSSIOC_OUT) +#define OSSIOC_SZ(t) ((sizeof (t) & OSSIOCPARM_MASK) << 16) +#define __OSSIO(x, y) ((int)(OSSIOC_VOID|(x<<8)|y)) +#define __OSSIOR(x, y, t) ((int)(OSSIOC_OUT|OSSIOC_SZ(t)|(x<<8)|y)) +#define __OSSIOWR(x, y, t) ((int)(OSSIOC_INOUT|OSSIOC_SZ(t)|(x<<8)|y)) +#define SNDCTL_DSP_SPEED __OSSIOWR('P', 2, int) +#define SNDCTL_DSP_CHANNELS __OSSIOWR('P', 6, int) +#define SNDCTL_DSP_SETFMT __OSSIOWR('P', 5, int) /* Selects ONE fmt */ +#define SNDCTL_DSP_GETODELAY __OSSIOR('P', 23, int) +#define SNDCTL_DSP_HALT_OUTPUT __OSSIO('P', 34) +#define AFMT_S16_LE 0x00000010 +#define AFMT_S16_BE 0x00000020 + +#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) +#define AFMT_S16_NE AFMT_S16_BE +#else +#define AFMT_S16_NE AFMT_S16_LE #endif -#ifndef SUN_MIN_RATE -#define SUN_MIN_RATE (1000) -#endif +#define DEFAULT_AUDIO_DEVICE "/dev/audio" +#define DEFAULT_DSP_DEVICE "/dev/dsp" -#ifndef SUN_MAX_RATE -#define SUN_MAX_RATE (192000) +#define BUF_SIZE_MS 10 + +#if defined(CUBEB_SUNAUDIO_DEBUG) +#define DPR(...) fprintf(stderr, __VA_ARGS__); +#else +#define DPR(...) do {} while(0) #endif -static struct cubeb_ops const sun_ops; +static struct cubeb_ops const sunaudio_ops; struct cubeb { struct cubeb_ops const * ops; }; struct cubeb_stream { - struct cubeb * context; - void * user_ptr; - pthread_t thread; - pthread_mutex_t mutex; /* protects running, volume, frames_written */ - int floating; - int running; - int play_fd; - int record_fd; - float volume; - struct audio_info p_info; /* info for the play fd */ - struct audio_info r_info; /* info for the record fd */ - cubeb_data_callback data_cb; - cubeb_state_callback state_cb; - int16_t * play_buf; - int16_t * record_buf; - float * f_play_buf; - float * f_record_buf; - char input_name[32]; - char output_name[32]; - uint64_t frames_written; - uint64_t blocks_written; + cubeb * context; + pthread_t th; /* to run real-time audio i/o */ + pthread_mutex_t mutex; /* protects fd and frm_played */ + int fd; /* link us to sunaudio */ + int active; /* cubec_start() called */ + int conv; /* need float->s16 conversion */ + int using_oss; + unsigned char *buf; /* data is prepared here */ + unsigned int rate; + unsigned int n_channles; + unsigned int bytes_per_ch; + unsigned int n_frm; + unsigned int buffer_size; + int64_t frm_played; + cubeb_data_callback data_cb; /* cb to preapare data */ + cubeb_state_callback state_cb; /* cb to notify about state changes */ + void *arg; /* user arg to {data,state}_cb */ }; -int -sun_init(cubeb ** context, char const * context_name) -{ - cubeb * c; - - (void)context_name; - if ((c = calloc(1, sizeof(cubeb))) == NULL) { - return CUBEB_ERROR; - } - c->ops = &sun_ops; - *context = c; - return CUBEB_OK; -} - static void -sun_destroy(cubeb * context) +float_to_s16(void *ptr, long nsamp) { - free(context); -} + int16_t *dst = ptr; + float *src = ptr; -static char const * -sun_get_backend_id(cubeb * context) -{ - return "sun"; + while (nsamp-- > 0) + *(dst++) = *(src++) * 32767; } -static int -sun_get_preferred_sample_rate(cubeb * context, uint32_t * rate) +static void * +sunaudio_mainloop(void *arg) { - (void)context; + struct cubeb_stream *s = arg; + int state; - *rate = SUN_PREFER_RATE; - return CUBEB_OK; -} + DPR("sunaudio_mainloop()\n"); -static int -sun_get_max_channel_count(cubeb * context, uint32_t * max_channels) -{ - (void)context; + s->state_cb(s, s->arg, CUBEB_STATE_STARTED); - *max_channels = SUN_MAX_CHANNELS; - return CUBEB_OK; -} + pthread_mutex_lock(&s->mutex); + DPR("sunaudio_mainloop(), started\n"); -static int -sun_get_min_latency(cubeb * context, cubeb_stream_params params, - uint32_t * latency_frames) -{ - (void)context; + for (;;) { + if (!s->active) { + DPR("sunaudio_mainloop() stopped\n"); + state = CUBEB_STATE_STOPPED; + break; + } - *latency_frames = SUN_LATENCY_MS * params.rate / 1000; - return CUBEB_OK; -} + if (!s->using_oss) { + audio_info_t info; + ioctl(s->fd, AUDIO_GETINFO, &info); + if (s->frm_played > info.play.samples + 3 * s->n_frm) { + pthread_mutex_unlock(&s->mutex); + struct timespec ts = {0, 10000}; // 10 ms + nanosleep(&ts, NULL); + pthread_mutex_lock(&s->mutex); + continue; + } + } -static int -sun_get_hwinfo(const char * device, struct audio_info * format, - int * props, struct audio_device * dev) -{ - int fd = -1; + pthread_mutex_unlock(&s->mutex); + unsigned int got = s->data_cb(s, s->arg, NULL, s->buf, s->n_frm); + DPR("sunaudio_mainloop() ask %d got %d\n", s->n_frm, got); + pthread_mutex_lock(&s->mutex); - if ((fd = open(device, O_RDONLY)) == -1) { - goto error; - } -#ifdef AUDIO_GETFORMAT - if (ioctl(fd, AUDIO_GETFORMAT, format) != 0) { - goto error; - } -#endif -#ifdef AUDIO_GETPROPS - if (ioctl(fd, AUDIO_GETPROPS, props) != 0) { - goto error; - } -#endif - if (ioctl(fd, AUDIO_GETDEV, dev) != 0) { - goto error; - } - close(fd); - return CUBEB_OK; -error: - if (fd != -1) { - close(fd); - } - return CUBEB_ERROR; -} + if (got < 0) { + DPR("sunaudio_mainloop() cb err\n"); + state = CUBEB_STATE_ERROR; + break; + } -/* - * XXX: PR kern/54264 - */ -static int -sun_prinfo_verify_sanity(struct audio_prinfo * prinfo) -{ - return prinfo->precision >= 8 && prinfo->precision <= 32 && - prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS && - prinfo->sample_rate < SUN_MAX_RATE && prinfo->sample_rate > SUN_MIN_RATE; -} + if (s->conv) { + float_to_s16(s->buf, got * s->n_channles); + } -static int -sun_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection * collection) -{ - unsigned i; - cubeb_device_info device = {0}; - char dev[16] = SUN_DEFAULT_DEVICE; - char dev_friendly[64]; - struct audio_info hwfmt; - struct audio_device hwname; - struct audio_prinfo *prinfo = NULL; - int hwprops; - - collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info)); - if (collection->device == NULL) { - return CUBEB_ERROR; - } - collection->count = 0; + unsigned int avail = got * 2 * s->n_channles; // coverted to s16 + unsigned int pos = 0; - for (i = 0; i < SUN_DEVICE_COUNT; ++i) { - if (i > 0) { - (void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1); - } - if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) { - continue; - } -#ifdef AUDIO_GETPROPS - device.type = 0; - if ((hwprops & AUDIO_PROP_CAPTURE) != 0 && - sun_prinfo_verify_sanity(&hwfmt.record)) { - /* the device supports recording, probably */ - device.type |= CUBEB_DEVICE_TYPE_INPUT; - } - if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 && - sun_prinfo_verify_sanity(&hwfmt.play)) { - /* the device supports playback, probably */ - device.type |= CUBEB_DEVICE_TYPE_OUTPUT; - } - switch (device.type) { - case 0: - /* device doesn't do input or output, aliens probably involved */ - continue; - case CUBEB_DEVICE_TYPE_INPUT: - if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) { - /* this device is input only, not scanning for those, skip it */ - continue; - } - break; - case CUBEB_DEVICE_TYPE_OUTPUT: - if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) { - /* this device is output only, not scanning for those, skip it */ - continue; + while (avail > 0 && s->active) { + int written = write(s->fd, s->buf + pos, avail); + if (written == -1) { + if (errno != EINTR && errno != EWOULDBLOCK) { + DPR("sunaudio_mainloop() write err\n"); + state = CUBEB_STATE_ERROR; + break; + } + pthread_mutex_unlock(&s->mutex); + struct timespec ts = {0, 10000}; // 10 ms + nanosleep(&ts, NULL); + pthread_mutex_lock(&s->mutex); + } else { + pos += written; + DPR("sunaudio_mainloop() write %d pos %d\n", written, pos); + s->frm_played += written / 2 / s->n_channles; + avail -= written; } - break; - } - if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) { - prinfo = &hwfmt.record; - } - if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) { - prinfo = &hwfmt.play; } -#endif - if (i > 0) { - (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)", - hwname.name, hwname.version, hwname.config, i - 1); - } else { - (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)", - hwname.name, hwname.version, hwname.config); + + if ((got < s->n_frm)) { + DPR("sunaudio_mainloop() drained\n"); + state = CUBEB_STATE_DRAINED; + break; } - device.devid = (void *)(uintptr_t)i; - device.device_id = strdup(dev); - device.friendly_name = strdup(dev_friendly); - device.group_id = strdup(dev); - device.vendor_name = strdup(hwname.name); - device.type = type; - device.state = CUBEB_DEVICE_STATE_ENABLED; - device.preferred = (i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; -#ifdef AUDIO_GETFORMAT - device.max_channels = prinfo->channels; - device.default_rate = prinfo->sample_rate; -#else - device.max_channels = 2; - device.default_rate = SUN_PREFER_RATE; -#endif - device.default_format = CUBEB_DEVICE_FMT_S16NE; - device.format = CUBEB_DEVICE_FMT_S16NE; - device.min_rate = SUN_MIN_RATE; - device.max_rate = SUN_MAX_RATE; - device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000; - device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000; - collection->device[collection->count++] = device; } - return CUBEB_OK; -} -static int -sun_device_collection_destroy(cubeb * context, - cubeb_device_collection * collection) -{ - unsigned i; + pthread_mutex_unlock(&s->mutex); + s->state_cb(s, s->arg, state); - for (i = 0; i < collection->count; ++i) { - free((char *)collection->device[i].device_id); - free((char *)collection->device[i].friendly_name); - free((char *)collection->device[i].group_id); - free((char *)collection->device[i].vendor_name); - } - free(collection->device); - return CUBEB_OK; + return NULL; } -static int -sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params, - struct audio_info * info, struct audio_prinfo * prinfo) +/*static*/ int +sunaudio_init(cubeb **context, char const *context_name) { - prinfo->channels = params->channels; - prinfo->sample_rate = params->rate; - prinfo->precision = 16; -#ifdef AUDIO_ENCODING_SLINEAR_LE - switch (params->format) { - case CUBEB_SAMPLE_S16LE: - prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE; - break; - case CUBEB_SAMPLE_S16BE: - prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE; - break; - case CUBEB_SAMPLE_FLOAT32NE: - stream->floating = 1; - prinfo->encoding = AUDIO_ENCODING_SLINEAR; - break; - default: - LOG("Unsupported format"); - return CUBEB_ERROR_INVALID_FORMAT; - } -#else - switch (params->format) { - case CUBEB_SAMPLE_S16NE: - prinfo->encoding = AUDIO_ENCODING_LINEAR; - break; - case CUBEB_SAMPLE_FLOAT32NE: - stream->floating = 1; - prinfo->encoding = AUDIO_ENCODING_LINEAR; - break; - default: - LOG("Unsupported format"); - return CUBEB_ERROR_INVALID_FORMAT; - } -#endif - if (ioctl(fd, AUDIO_SETINFO, info) == -1) { - return CUBEB_ERROR; - } - if (ioctl(fd, AUDIO_GETINFO, info) == -1) { - return CUBEB_ERROR; - } + DPR("sunaudio_init(%s)\n", context_name); + *context = malloc(sizeof(*context)); + (*context)->ops = &sunaudio_ops; + (void)context_name; return CUBEB_OK; } -static int -sun_stream_stop(cubeb_stream * s) +static char const * +sunaudio_get_backend_id(cubeb *context) { - pthread_mutex_lock(&s->mutex); - if (s->running) { - s->running = 0; - pthread_mutex_unlock(&s->mutex); - pthread_join(s->thread, NULL); - } else { - pthread_mutex_unlock(&s->mutex); - } - return CUBEB_OK; + return "sunaudio"; } static void -sun_stream_destroy(cubeb_stream * s) +sunaudio_destroy(cubeb *context) { - pthread_mutex_destroy(&s->mutex); - sun_stream_stop(s); - if (s->play_fd != -1) { - close(s->play_fd); - } - if (s->record_fd != -1) { - close(s->record_fd); - } - free(s->f_play_buf); - free(s->f_record_buf); - free(s->play_buf); - free(s->record_buf); - free(s); + DPR("sunaudio_destroy()\n"); + free(context); } -static void -sun_float_to_linear(float * in, int16_t * out, - unsigned channels, long frames, float vol) +static int +sunaudio_stream_init(cubeb *context, + cubeb_stream **stream, + char const *stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void *user_ptr) { - unsigned i, sample_count = frames * channels; - float multiplier = vol * 0x8000; - - for (i = 0; i < sample_count; ++i) { - int32_t sample = lrintf(in[i] * multiplier); - if (sample < -0x8000) { - out[i] = -0x8000; - } else if (sample > 0x7fff) { - out[i] = 0x7fff; - } else { - out[i] = sample; + struct cubeb_stream *s; + DPR("sunaudio_stream_init(%s)\n", stream_name); + size_t size; + + s = malloc(sizeof(struct cubeb_stream)); + if (s == NULL) + return CUBEB_ERROR; + s->context = context; + + // If UTAUDIODEV is set, use it with Sun Audio interface + char * sa_device_name = getenv("UTAUDIODEV"); + char * dsp_device_name = NULL; + if (!sa_device_name) { + dsp_device_name = getenv("AUDIODSP"); + if (!dsp_device_name) { + dsp_device_name = DEFAULT_DSP_DEVICE; + } + sa_device_name = getenv("AUDIODEV"); + if (!sa_device_name) { + sa_device_name = DEFAULT_AUDIO_DEVICE; } } -} - -static void -sun_linear_to_float(int16_t * in, float * out, - unsigned channels, long frames) -{ - unsigned i, sample_count = frames * channels; - for (i = 0; i < sample_count; ++i) { - out[i] = (1.0 / 0x8000) * in[i]; + s->using_oss = 0; + // Try to use OSS if available + if (dsp_device_name) { + s->fd = open(dsp_device_name, O_WRONLY | O_NONBLOCK); + if (s->fd >= 0) { + s->using_oss = 1; + } } -} -static void -sun_linear_set_vol(int16_t * buf, unsigned channels, long frames, float vol) -{ - unsigned i, sample_count = frames * channels; - int32_t multiplier = vol * 0x8000; + // Try Sun Audio + if (!s->using_oss) { + s->fd = open(sa_device_name, O_WRONLY | O_NONBLOCK); + } - for (i = 0; i < sample_count; ++i) { - buf[i] = (buf[i] * multiplier) >> 15; + if (s->fd < 0) { + free(s); + DPR("sunaudio_stream_init(), open() failed\n"); + return CUBEB_ERROR; } -} -static void * -sun_io_routine(void * arg) -{ - cubeb_stream *s = arg; - cubeb_state state = CUBEB_STATE_STARTED; - size_t to_read = 0; - long to_write = 0; - size_t write_ofs = 0; - size_t read_ofs = 0; - int drain = 0; - - s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED); - while (state != CUBEB_STATE_ERROR) { - pthread_mutex_lock(&s->mutex); - if (!s->running) { - pthread_mutex_unlock(&s->mutex); - state = CUBEB_STATE_STOPPED; - break; + if (s->using_oss) { + if (ioctl(s->fd, SNDCTL_DSP_SPEED, &output_stream_params->rate) < 0) { + DPR("ioctl SNDCTL_DSP_SPEED failed.\n"); + close(s->fd); + free(s); + return CUBEB_ERROR_INVALID_FORMAT; } - pthread_mutex_unlock(&s->mutex); - if (s->floating) { - if (s->record_fd != -1) { - sun_linear_to_float(s->record_buf, s->f_record_buf, - s->r_info.record.channels, SUN_BUFFER_FRAMES); - } - to_write = s->data_cb(s, s->user_ptr, - s->f_record_buf, s->f_play_buf, SUN_BUFFER_FRAMES); - if (to_write == CUBEB_ERROR) { - state = CUBEB_STATE_ERROR; - break; - } - if (s->play_fd != -1) { - pthread_mutex_lock(&s->mutex); - sun_float_to_linear(s->f_play_buf, s->play_buf, - s->p_info.play.channels, to_write, s->volume); - pthread_mutex_unlock(&s->mutex); - } - } else { - to_write = s->data_cb(s, s->user_ptr, - s->record_buf, s->play_buf, SUN_BUFFER_FRAMES); - if (to_write == CUBEB_ERROR) { - state = CUBEB_STATE_ERROR; - break; - } - if (s->play_fd != -1) { - pthread_mutex_lock(&s->mutex); - sun_linear_set_vol(s->play_buf, s->p_info.play.channels, to_write, s->volume); - pthread_mutex_unlock(&s->mutex); - } - } - if (to_write < SUN_BUFFER_FRAMES) { - drain = 1; - } - to_write = s->play_fd != -1 ? to_write : 0; - to_read = s->record_fd != -1 ? SUN_BUFFER_FRAMES : 0; - write_ofs = 0; - read_ofs = 0; - while (to_write > 0 || to_read > 0) { - size_t bytes; - ssize_t n, frames; - - if (to_write > 0) { - bytes = FRAMES_TO_BYTES(to_write, s->p_info.play.channels); - if ((n = write(s->play_fd, s->play_buf + write_ofs, bytes)) < 0) { - state = CUBEB_STATE_ERROR; - break; - } - frames = BYTES_TO_FRAMES(n, s->p_info.play.channels); - pthread_mutex_lock(&s->mutex); - s->frames_written += frames; - pthread_mutex_unlock(&s->mutex); - to_write -= frames; - write_ofs += frames; - } - if (to_read > 0) { - bytes = FRAMES_TO_BYTES(to_read, s->r_info.record.channels); - if ((n = read(s->record_fd, s->record_buf + read_ofs, bytes)) < 0) { - state = CUBEB_STATE_ERROR; - break; - } - frames = BYTES_TO_FRAMES(n, s->r_info.record.channels); - to_read -= frames; - read_ofs += frames; - } - } - if (drain && state != CUBEB_STATE_ERROR) { - state = CUBEB_STATE_DRAINED; - break; - } - } - s->state_cb(s, s->user_ptr, state); - return NULL; -} -static int -sun_stream_init(cubeb * context, - cubeb_stream ** stream, - char const * stream_name, - cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned latency_frames, - cubeb_data_callback data_callback, - cubeb_state_callback state_callback, - void * user_ptr) -{ - int ret = CUBEB_OK; - cubeb_stream *s = NULL; - - (void)stream_name; - (void)latency_frames; - if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) { - ret = CUBEB_ERROR; - goto error; - } - s->record_fd = -1; - s->play_fd = -1; - if (input_device != 0) { - snprintf(s->input_name, sizeof(s->input_name), - "/dev/audio%zu", (uintptr_t)input_device - 1); - } else { - snprintf(s->input_name, sizeof(s->input_name), "%s", SUN_DEFAULT_DEVICE); - } - if (output_device != 0) { - snprintf(s->output_name, sizeof(s->output_name), - "/dev/audio%zu", (uintptr_t)output_device - 1); - } else { - snprintf(s->output_name, sizeof(s->output_name), "%s", SUN_DEFAULT_DEVICE); - } - if (input_stream_params != NULL) { - if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { - LOG("Loopback not supported"); - ret = CUBEB_ERROR_NOT_SUPPORTED; - goto error; + if (ioctl(s->fd, SNDCTL_DSP_CHANNELS, &output_stream_params->channels) < 0) { + DPR("ioctl SNDCTL_DSP_CHANNELS failed.\n"); + close(s->fd); + free(s); + return CUBEB_ERROR_INVALID_FORMAT; } - if (s->record_fd == -1) { - if ((s->record_fd = open(s->input_name, O_RDONLY)) == -1) { - LOG("Audio device cannot be opened as read-only"); - ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; - goto error; - } + + int format = AFMT_S16_NE; + if (ioctl(s->fd, SNDCTL_DSP_SETFMT, &format) < 0) { + DPR("ioctl SNDCTL_DSP_SETFMT failed.\n"); + close(s->fd); + free(s); + return CUBEB_ERROR_INVALID_FORMAT; } - AUDIO_INITINFO(&s->r_info); -#ifdef AUMODE_RECORD - s->r_info.mode = AUMODE_RECORD; -#endif - if ((ret = sun_copy_params(s->record_fd, s, input_stream_params, - &s->r_info, &s->r_info.record)) != CUBEB_OK) { - LOG("Setting record params failed"); - goto error; + } else { + audio_info_t audio_info; + AUDIO_INITINFO(&audio_info) + audio_info.play.sample_rate = output_stream_params->rate; + audio_info.play.channels = output_stream_params->channels; + audio_info.play.encoding = AUDIO_ENCODING_LINEAR; + audio_info.play.precision = 16; + if (ioctl(s->fd, AUDIO_SETINFO, &audio_info) == -1) { + DPR("ioctl AUDIO_SETINFO failed.\n"); + close(s->fd); + free(s); + return CUBEB_ERROR_INVALID_FORMAT; } } - if (output_stream_params != NULL) { - if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { - LOG("Loopback not supported"); - ret = CUBEB_ERROR_NOT_SUPPORTED; - goto error; - } - if (s->play_fd == -1) { - if ((s->play_fd = open(s->output_name, O_WRONLY)) == -1) { - LOG("Audio device cannot be opened as write-only"); - ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; - goto error; - } - } - AUDIO_INITINFO(&s->p_info); -#ifdef AUMODE_PLAY - s->p_info.mode = AUMODE_PLAY; -#endif - if ((ret = sun_copy_params(s->play_fd, s, output_stream_params, - &s->p_info, &s->p_info.play)) != CUBEB_OK) { - LOG("Setting play params failed"); - goto error; - } + + s->conv = 0; + switch (output_stream_params->format) { + case CUBEB_SAMPLE_S16NE: + s->bytes_per_ch = 2; + break; + case CUBEB_SAMPLE_FLOAT32NE: + s->bytes_per_ch = 4; + s->conv = 1; + break; + default: + DPR("sunaudio_stream_init() unsupported format\n"); + close(s->fd); + free(s); + return CUBEB_ERROR_INVALID_FORMAT; } - s->context = context; - s->volume = 1.0; - s->state_cb = state_callback; + + s->active = 0; + s->rate = output_stream_params->rate; + s->n_channles = output_stream_params->channels; s->data_cb = data_callback; - s->user_ptr = user_ptr; + s->state_cb = state_callback; + s->arg = user_ptr; if (pthread_mutex_init(&s->mutex, NULL) != 0) { - LOG("Failed to create mutex"); - goto error; - } - if (s->play_fd != -1 && (s->play_buf = calloc(SUN_BUFFER_FRAMES, - s->p_info.play.channels * sizeof(int16_t))) == NULL) { - ret = CUBEB_ERROR; - goto error; - } - if (s->record_fd != -1 && (s->record_buf = calloc(SUN_BUFFER_FRAMES, - s->r_info.record.channels * sizeof(int16_t))) == NULL) { - ret = CUBEB_ERROR; - goto error; + free(s); + return CUBEB_ERROR; } - if (s->floating) { - if (s->play_fd != -1 && (s->f_play_buf = calloc(SUN_BUFFER_FRAMES, - s->p_info.play.channels * sizeof(float))) == NULL) { - ret = CUBEB_ERROR; - goto error; - } - if (s->record_fd != -1 && (s->f_record_buf = calloc(SUN_BUFFER_FRAMES, - s->r_info.record.channels * sizeof(float))) == NULL) { - ret = CUBEB_ERROR; - goto error; - } + s->frm_played = 0; + s->n_frm = s->rate * BUF_SIZE_MS / 1000; + s->buffer_size = s->bytes_per_ch * s->n_channles * s->n_frm; + s->buf = malloc(s->buffer_size); + if (s->buf == NULL) { + close(s->fd); + free(s); + return CUBEB_ERROR; } + *stream = s; + DPR("sunaudio_stream_init() end, ok\n"); return CUBEB_OK; -error: - if (s != NULL) { - sun_stream_destroy(s); +} + +static void +sunaudio_stream_destroy(cubeb_stream *s) +{ + DPR("sunaudio_stream_destroy()\n"); + if (s->fd > 0) { + // Flush buffer + if (s->using_oss) { + ioctl(s->fd, SNDCTL_DSP_HALT_OUTPUT); + } else { + ioctl(s->fd, I_FLUSH); + } + close(s->fd); } - return ret; + free(s->buf); + free(s); } static int -sun_stream_start(cubeb_stream * s) +sunaudio_stream_start(cubeb_stream *s) { - s->running = 1; - if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) { - LOG("Couldn't create thread"); + int err; + + DPR("sunaudio_stream_start()\n"); + s->active = 1; + err = pthread_create(&s->th, NULL, sunaudio_mainloop, s); + if (err) { + s->active = 0; return CUBEB_ERROR; } return CUBEB_OK; } static int -sun_stream_get_position(cubeb_stream * s, uint64_t * position) +sunaudio_stream_stop(cubeb_stream *s) { -#ifdef AUDIO_GETOOFFS - struct audio_offset offset; + void *dummy; - if (ioctl(s->play_fd, AUDIO_GETOOFFS, &offset) == -1) { - return CUBEB_ERROR; + DPR("sunaudio_stream_stop()\n"); + if (s->active) { + s->active = 0; + pthread_join(s->th, &dummy); } - s->blocks_written += offset.deltablks; - *position = BYTES_TO_FRAMES(s->blocks_written * s->p_info.blocksize, - s->p_info.play.channels); return CUBEB_OK; -#else +} + +static int +sunaudio_stream_get_position(cubeb_stream *s, uint64_t *p) +{ + int rv = CUBEB_OK; pthread_mutex_lock(&s->mutex); - *position = s->frames_written; + if (s->active && s->fd > 0) { + if (s->using_oss) { + int delay; + ioctl(s->fd, SNDCTL_DSP_GETODELAY, &delay); + int64_t t = s->frm_played - delay / s->n_channles / 2; + if (t < 0) { + *p = 0; + } else { + *p = t; + } + } else { + audio_info_t info; + ioctl(s->fd, AUDIO_GETINFO, &info); + *p = info.play.samples; + } + DPR("sunaudio_stream_get_position() %lld\n", *p); + } else { + rv = CUBEB_ERROR; + } pthread_mutex_unlock(&s->mutex); - return CUBEB_OK; -#endif + return rv; } static int -sun_stream_get_latency(cubeb_stream * stream, uint32_t * latency) +sunaudio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) { -#ifdef AUDIO_GETBUFINFO - struct audio_info info; - - if (ioctl(stream->play_fd, AUDIO_GETBUFINFO, &info) == -1) { + if (!ctx || !max_channels) return CUBEB_ERROR; - } - *latency = BYTES_TO_FRAMES(info.play.seek + info.blocksize, - info.play.channels); - return CUBEB_OK; -#else - cubeb_stream_params params; + *max_channels = 2; - params.rate = stream->p_info.play.sample_rate; - - return sun_get_min_latency(NULL, params, latency); -#endif + return CUBEB_OK; } static int -sun_stream_set_volume(cubeb_stream * stream, float volume) +sunaudio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) { - pthread_mutex_lock(&stream->mutex); - stream->volume = volume; - pthread_mutex_unlock(&stream->mutex); + if (!ctx || !rate) + return CUBEB_ERROR; + + // XXX Not yet implemented. + *rate = 44100; + return CUBEB_OK; } static int -sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device) +sunaudio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) { - *device = calloc(1, sizeof(cubeb_device)); - if (*device == NULL) { + if (!ctx || !latency_ms) return CUBEB_ERROR; - } - (*device)->input_name = stream->record_fd != -1 ? - strdup(stream->input_name) : NULL; - (*device)->output_name = stream->play_fd != -1 ? - strdup(stream->output_name) : NULL; + + // XXX Not yet implemented. + *latency_ms = 20; + return CUBEB_OK; } static int -sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device) +sunaudio_stream_get_latency(cubeb_stream * s, uint32_t * latency) { - (void)stream; - free(device->input_name); - free(device->output_name); - free(device); - return CUBEB_OK; + if (!s || !latency) + return CUBEB_ERROR; + + int rv = CUBEB_OK; + pthread_mutex_lock(&s->mutex); + if (s->active && s->fd > 0) { + if (s->using_oss) { + int delay; + ioctl(s->fd, SNDCTL_DSP_GETODELAY, &delay); + *latency = delay / s->n_channles / 2 / s->rate; + } else { + audio_info_t info; + ioctl(s->fd, AUDIO_GETINFO, &info); + *latency = (s->frm_played - info.play.samples) / s->rate; + } + DPR("sunaudio_stream_get_position() %lld\n", *p); + } else { + rv = CUBEB_ERROR; + } + pthread_mutex_unlock(&s->mutex); + return rv; } -static struct cubeb_ops const sun_ops = { - .init = sun_init, - .get_backend_id = sun_get_backend_id, - .get_max_channel_count = sun_get_max_channel_count, - .get_min_latency = sun_get_min_latency, - .get_preferred_sample_rate = sun_get_preferred_sample_rate, - .enumerate_devices = sun_enumerate_devices, - .device_collection_destroy = sun_device_collection_destroy, - .destroy = sun_destroy, - .stream_init = sun_stream_init, - .stream_destroy = sun_stream_destroy, - .stream_start = sun_stream_start, - .stream_stop = sun_stream_stop, - .stream_reset_default_device = NULL, - .stream_get_position = sun_stream_get_position, - .stream_get_latency = sun_stream_get_latency, - .stream_set_volume = sun_stream_set_volume, - .stream_get_current_device = sun_get_current_device, - .stream_device_destroy = sun_stream_device_destroy, - .stream_register_device_changed_callback = NULL, - .register_device_collection_changed = NULL +static struct cubeb_ops const sunaudio_ops = { + .init = sunaudio_init, + .get_backend_id = sunaudio_get_backend_id, + .destroy = sunaudio_destroy, + .get_preferred_sample_rate = sunaudio_get_preferred_sample_rate, + .stream_init = sunaudio_stream_init, + .stream_destroy = sunaudio_stream_destroy, + .stream_start = sunaudio_stream_start, + .stream_stop = sunaudio_stream_stop, + .stream_get_position = sunaudio_stream_get_position, + .get_max_channel_count = sunaudio_get_max_channel_count, + .get_min_latency = sunaudio_get_min_latency, + .stream_get_latency = sunaudio_stream_get_latency }; diff --git a/media/libcubeb/src/cubeb_utils.cpp b/media/libcubeb/src/cubeb_utils.cpp deleted file mode 100644 index 85572a9fe..000000000 --- a/media/libcubeb/src/cubeb_utils.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright © 2018 Mozilla Foundation - * - * This program is made available under an ISC-style license. See the - * accompanying file LICENSE for details. - */ - -#include "cubeb_utils.h" - -size_t cubeb_sample_size(cubeb_sample_format format) -{ - switch (format) { - case CUBEB_SAMPLE_S16LE: - case CUBEB_SAMPLE_S16BE: - return sizeof(int16_t); - case CUBEB_SAMPLE_FLOAT32LE: - case CUBEB_SAMPLE_FLOAT32BE: - return sizeof(float); - default: - // should never happen as all cases are handled above. - assert(false); - } -} diff --git a/media/libcubeb/src/cubeb_utils.h b/media/libcubeb/src/cubeb_utils.h index df6751155..d8e9928fe 100644 --- a/media/libcubeb/src/cubeb_utils.h +++ b/media/libcubeb/src/cubeb_utils.h @@ -8,16 +8,11 @@ #if !defined(CUBEB_UTILS) #define CUBEB_UTILS -#include "cubeb/cubeb.h" - -#ifdef __cplusplus - #include #include #include -#include #include -#if defined(_WIN32) +#if defined(WIN32) #include "cubeb_utils_win.h" #else #include "cubeb_utils_unix.h" @@ -28,7 +23,6 @@ template void PodCopy(T * destination, const T * source, size_t count) { static_assert(std::is_trivial::value, "Requires trivial type"); - assert(destination && source); memcpy(destination, source, count * sizeof(T)); } @@ -37,7 +31,6 @@ template void PodMove(T * destination, const T * source, size_t count) { static_assert(std::is_trivial::value, "Requires trivial type"); - assert(destination && source); memmove(destination, source, count * sizeof(T)); } @@ -46,67 +39,9 @@ template void PodZero(T * destination, size_t count) { static_assert(std::is_trivial::value, "Requires trivial type"); - assert(destination); memset(destination, 0, count * sizeof(T)); } -namespace { -template -void Copy(T * destination, const T * source, size_t count, Trait) -{ - for (size_t i = 0; i < count; i++) { - destination[i] = source[i]; - } -} - -template -void Copy(T * destination, const T * source, size_t count, std::true_type) -{ - PodCopy(destination, source, count); -} -} - -/** - * This allows copying a number of elements from a `source` pointer to a - * `destination` pointer, using `memcpy` if it is safe to do so, or a loop that - * calls the constructors and destructors otherwise. - */ -template -void Copy(T * destination, const T * source, size_t count) -{ - assert(destination && source); - Copy(destination, source, count, typename std::is_trivial::type()); -} - -namespace { -template -void ConstructDefault(T * destination, size_t count, Trait) -{ - for (size_t i = 0; i < count; i++) { - destination[i] = T(); - } -} - -template -void ConstructDefault(T * destination, - size_t count, std::true_type) -{ - PodZero(destination, count); -} -} - -/** - * This allows zeroing (using memset) or default-constructing a number of - * elements calling the constructors and destructors if necessary. - */ -template -void ConstructDefault(T * destination, size_t count) -{ - assert(destination); - ConstructDefault(destination, count, - typename std::is_arithmetic::type()); -} - template class auto_array { @@ -128,11 +63,6 @@ public: return data_; } - T * end() const - { - return data_ + length_; - } - const T& at(size_t index) const { assert(index < length_ && "out of range"); @@ -268,76 +198,18 @@ private: size_t length_; }; -struct auto_array_wrapper { - virtual void push(void * elements, size_t length) = 0; - virtual size_t length() = 0; - virtual void push_silence(size_t length) = 0; - virtual bool pop(size_t length) = 0; - virtual void * data() = 0; - virtual void * end() = 0; - virtual void clear() = 0; - virtual bool reserve(size_t capacity) = 0; - virtual void set_length(size_t length) = 0; - virtual ~auto_array_wrapper() {} -}; - -template -struct auto_array_wrapper_impl : public auto_array_wrapper { - auto_array_wrapper_impl() {} - - explicit auto_array_wrapper_impl(uint32_t size) - : ar(size) - {} - - void push(void * elements, size_t length) override { - ar.push(static_cast(elements), length); - } - - size_t length() override { - return ar.length(); - } - - void push_silence(size_t length) override { - ar.push_silence(length); - } - - bool pop(size_t length) override { - return ar.pop(nullptr, length); - } - - void * data() override { - return ar.data(); - } - - void * end() override { - return ar.end(); - } - - void clear() override { - ar.clear(); - } - - bool reserve(size_t capacity) override { - return ar.reserve(capacity); - } - - void set_length(size_t length) override { - ar.set_length(length); +struct auto_lock { + explicit auto_lock(owned_critical_section & lock) + : lock(lock) + { + lock.enter(); } - - ~auto_array_wrapper_impl() { - ar.clear(); + ~auto_lock() + { + lock.leave(); } - private: - auto_array ar; + owned_critical_section & lock; }; -extern "C" { - size_t cubeb_sample_size(cubeb_sample_format format); -} - -using auto_lock = std::lock_guard; -#endif // __cplusplus - #endif /* CUBEB_UTILS */ diff --git a/media/libcubeb/src/cubeb_utils_unix.h b/media/libcubeb/src/cubeb_utils_unix.h index 4876d015f..80219d58b 100644 --- a/media/libcubeb/src/cubeb_utils_unix.h +++ b/media/libcubeb/src/cubeb_utils_unix.h @@ -48,7 +48,7 @@ public: #endif } - void lock() + void enter() { #ifndef NDEBUG int r = @@ -59,7 +59,7 @@ public: #endif } - void unlock() + void leave() { #ifndef NDEBUG int r = diff --git a/media/libcubeb/src/cubeb_utils_win.h b/media/libcubeb/src/cubeb_utils_win.h index 0112ad6d3..2b094cd93 100644 --- a/media/libcubeb/src/cubeb_utils_win.h +++ b/media/libcubeb/src/cubeb_utils_win.h @@ -29,7 +29,7 @@ public: DeleteCriticalSection(&critical_section); } - void lock() + void enter() { EnterCriticalSection(&critical_section); #ifndef NDEBUG @@ -38,7 +38,7 @@ public: #endif } - void unlock() + void leave() { #ifndef NDEBUG /* GetCurrentThreadId cannot return 0: it is not a the valid thread id */ diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp index 6acf1c1cc..e88d6becd 100644 --- a/media/libcubeb/src/cubeb_wasapi.cpp +++ b/media/libcubeb/src/cubeb_wasapi.cpp @@ -4,7 +4,6 @@ * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ -#define _WIN32_WINNT 0x0600 #define NOMINMAX #include @@ -24,61 +23,20 @@ #include #include #include -#include #include "cubeb/cubeb.h" #include "cubeb-internal.h" -#include "cubeb_mixer.h" #include "cubeb_resampler.h" -#include "cubeb_strings.h" #include "cubeb_utils.h" -// Windows 10 exposes the IAudioClient3 interface to create low-latency streams. -// Copy the interface definition from audioclient.h here to make the code simpler -// and so that we can still access IAudioClient3 via COM if cubeb was compiled -// against an older SDK. -#ifndef __IAudioClient3_INTERFACE_DEFINED__ -#define __IAudioClient3_INTERFACE_DEFINED__ -MIDL_INTERFACE("7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42") -IAudioClient3 : public IAudioClient -{ -public: - virtual HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod( - /* [annotation][in] */ - _In_ const WAVEFORMATEX *pFormat, - /* [annotation][out] */ - _Out_ UINT32 *pDefaultPeriodInFrames, - /* [annotation][out] */ - _Out_ UINT32 *pFundamentalPeriodInFrames, - /* [annotation][out] */ - _Out_ UINT32 *pMinPeriodInFrames, - /* [annotation][out] */ - _Out_ UINT32 *pMaxPeriodInFrames) = 0; - - virtual HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod( - /* [unique][annotation][out] */ - _Out_ WAVEFORMATEX **ppFormat, - /* [annotation][out] */ - _Out_ UINT32 *pCurrentPeriodInFrames) = 0; - - virtual HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream( - /* [annotation][in] */ - _In_ DWORD StreamFlags, - /* [annotation][in] */ - _In_ UINT32 PeriodInFrames, - /* [annotation][in] */ - _In_ const WAVEFORMATEX *pFormat, - /* [annotation][in] */ - _In_opt_ LPCGUID AudioSessionGuid) = 0; -}; -#ifdef __CRT_UUID_DECL -// Required for MinGW -__CRT_UUID_DECL(IAudioClient3, 0x7ED4EE07, 0x8E67, 0x4CD4, 0x8C, 0x1A, 0x2B, 0x7A, 0x59, 0x87, 0xAD, 0x42) +/* devicetopology.h missing in MinGW. */ +#ifndef __devicetopology_h__ +#include "cubeb_devicetopology.h" #endif -#endif -// Copied from audioclient.h in the Windows 10 SDK -#ifndef AUDCLNT_E_ENGINE_PERIODICITY_LOCKED -#define AUDCLNT_E_ENGINE_PERIODICITY_LOCKED AUDCLNT_ERR(0x028) + +/* Taken from winbase.h, Not in MinGW. */ +#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION +#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only #endif #ifndef PKEY_Device_FriendlyName @@ -89,15 +47,6 @@ DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e #endif namespace { -struct com_heap_ptr_deleter { - void operator()(void * ptr) const noexcept { - CoTaskMemFree(ptr); - } -}; - -template -using com_heap_ptr = std::unique_ptr; - template constexpr size_t ARRAY_LENGTH(T(&)[N]) @@ -105,112 +54,71 @@ ARRAY_LENGTH(T(&)[N]) return N; } -template -class no_addref_release : public T { - ULONG STDMETHODCALLTYPE AddRef() = 0; - ULONG STDMETHODCALLTYPE Release() = 0; -}; - -template -class com_ptr { -public: - com_ptr() noexcept = default; - - com_ptr(com_ptr const & other) noexcept = delete; - com_ptr & operator=(com_ptr const & other) noexcept = delete; - T ** operator&() const noexcept = delete; - - ~com_ptr() noexcept { - release(); +void +SafeRelease(HANDLE handle) +{ + if (handle) { + CloseHandle(handle); } +} - com_ptr(com_ptr && other) noexcept - : ptr(other.ptr) - { - other.ptr = nullptr; +template +void SafeRelease(T * ptr) +{ + if (ptr) { + ptr->Release(); } +} - com_ptr & operator=(com_ptr && other) noexcept { - if (ptr != other.ptr) { - release(); - ptr = other.ptr; - other.ptr = nullptr; +struct auto_com { + auto_com() { + result = CoInitializeEx(NULL, COINIT_MULTITHREADED); + } + ~auto_com() { + if (result == RPC_E_CHANGED_MODE) { + // This is not an error, COM was not initialized by this function, so it is + // not necessary to uninit it. + LOG("COM was already initialized in STA."); + } else if (result == S_FALSE) { + // This is not an error. We are allowed to call CoInitializeEx more than + // once, as long as it is matches by an CoUninitialize call. + // We do that in the dtor which is guaranteed to be called. + LOG("COM was already initialized in MTA"); + } + if (SUCCEEDED(result)) { + CoUninitialize(); } - return *this; - } - - explicit operator bool() const noexcept { - return nullptr != ptr; } - - no_addref_release * operator->() const noexcept { - return static_cast *>(ptr); + bool ok() { + return result == RPC_E_CHANGED_MODE || SUCCEEDED(result); } - - T * get() const noexcept { - return ptr; - } - - T ** receive() noexcept { - XASSERT(ptr == nullptr); - return &ptr; - } - - void ** receive_vpp() noexcept { - return reinterpret_cast(receive()); - } - - com_ptr & operator=(std::nullptr_t) noexcept { - release(); - return *this; - } - - void reset(T * p = nullptr) noexcept { - release(); - ptr = p; - } - private: - void release() noexcept { - T * temp = ptr; - - if (temp) { - ptr = nullptr; - temp->Release(); - } - } - - T * ptr = nullptr; + HRESULT result; }; +typedef HANDLE (WINAPI *set_mm_thread_characteristics_function)( + const char * TaskName, LPDWORD TaskIndex); +typedef BOOL (WINAPI *revert_mm_thread_characteristics_function)(HANDLE handle); + extern cubeb_ops const wasapi_ops; int wasapi_stream_stop(cubeb_stream * stm); int wasapi_stream_start(cubeb_stream * stm); void close_wasapi_stream(cubeb_stream * stm); int setup_wasapi_stream(cubeb_stream * stm); -ERole pref_to_role(cubeb_stream_prefs param); -static char const * wstr_to_utf8(wchar_t const * str); -static std::unique_ptr utf8_to_wstr(char const * str); +static char * wstr_to_utf8(const wchar_t * str); +static std::unique_ptr utf8_to_wstr(char* str); } -class wasapi_collection_notification_client; -class monitor_device_notifications; - -struct cubeb { - cubeb_ops const * ops = &wasapi_ops; - cubeb_strings * device_ids; - /* Device enumerator to get notifications when the - device collection change. */ - com_ptr device_collection_enumerator; - com_ptr collection_notification_client; - /* Collection changed for input (capture) devices. */ - cubeb_device_collection_changed_callback input_collection_changed_callback = nullptr; - void * input_collection_changed_user_ptr = nullptr; - /* Collection changed for output (render) devices. */ - cubeb_device_collection_changed_callback output_collection_changed_callback = nullptr; - void * output_collection_changed_user_ptr = nullptr; +struct cubeb +{ + cubeb_ops const * ops; + /* Library dynamically opened to increase the render thread priority, and + the two function pointers we need. */ + HMODULE mmcss_module; + set_mm_thread_characteristics_function set_mm_thread_characteristics; + revert_mm_thread_characteristics_function revert_mm_thread_characteristics; }; class wasapi_endpoint_notification_client; @@ -224,35 +132,27 @@ class wasapi_endpoint_notification_client; */ typedef bool (*wasapi_refill_callback)(cubeb_stream * stm); -struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ - cubeb * context = nullptr; - void * user_ptr = nullptr; - /**/ - +struct cubeb_stream +{ + cubeb * context; /* Mixer pameters. We need to convert the input stream to this samplerate/channel layout, as WASAPI does not resample nor upmix itself. */ - cubeb_stream_params input_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; - cubeb_stream_params output_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; + cubeb_stream_params input_mix_params; + cubeb_stream_params output_mix_params; /* Stream parameters. This is what the client requested, * and what will be presented in the callback. */ - cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; - cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; - /* A MMDevice role for this stream: either communication or console here. */ - ERole role; + cubeb_stream_params input_stream_params; + cubeb_stream_params output_stream_params; /* The input and output device, or NULL for default. */ - std::unique_ptr input_device; - std::unique_ptr output_device; + cubeb_devid input_device; + cubeb_devid output_device; /* The latency initially requested for this stream, in frames. */ - unsigned latency = 0; - cubeb_state_callback state_callback = nullptr; - cubeb_data_callback data_callback = nullptr; - wasapi_refill_callback refill_callback = nullptr; - /* True when a loopback device is requested with no output device. In this - case a dummy output device is opened to drive the loopback, but should not - be exposed. */ - bool has_dummy_output = false; + unsigned latency; + cubeb_state_callback state_callback; + cubeb_data_callback data_callback; + wasapi_refill_callback refill_callback; + void * user_ptr; /* Lifetime considerations: - client, render_client, audio_clock and audio_stream_volume are interface pointer to the IAudioClient. @@ -260,324 +160,70 @@ struct cubeb_stream { mix_buffer are the same as the cubeb_stream instance. */ /* Main handle on the WASAPI stream. */ - com_ptr output_client; + IAudioClient * output_client; /* Interface pointer to use the event-driven interface. */ - com_ptr render_client; + IAudioRenderClient * render_client; /* Interface pointer to use the volume facilities. */ - com_ptr audio_stream_volume; + IAudioStreamVolume * audio_stream_volume; /* Interface pointer to use the stream audio clock. */ - com_ptr audio_clock; + IAudioClock * audio_clock; /* Frames written to the stream since it was opened. Reset on device change. Uses mix_params.rate. */ - UINT64 frames_written = 0; + UINT64 frames_written; /* Frames written to the (logical) stream since it was first created. Updated on device change. Uses stream_params.rate. */ - UINT64 total_frames_written = 0; + UINT64 total_frames_written; /* Last valid reported stream position. Used to ensure the position reported by stream_get_position increases monotonically. */ - UINT64 prev_position = 0; + UINT64 prev_position; /* Device enumerator to be able to be notified when the default device change. */ - com_ptr device_enumerator; + IMMDeviceEnumerator * device_enumerator; /* Device notification client, to be able to be notified when the default audio device changes and route the audio to the new default audio output device */ - com_ptr notification_client; + wasapi_endpoint_notification_client * notification_client; /* Main andle to the WASAPI capture stream. */ - com_ptr input_client; + IAudioClient * input_client; /* Interface to use the event driven capture interface */ - com_ptr capture_client; + IAudioCaptureClient * capture_client; /* This event is set by the stream_stop and stream_destroy function, so the render loop can exit properly. */ - HANDLE shutdown_event = 0; + HANDLE shutdown_event; /* Set by OnDefaultDeviceChanged when a stream reconfiguration is required. The reconfiguration is handled by the render loop thread. */ - HANDLE reconfigure_event = 0; + HANDLE reconfigure_event; /* This is set by WASAPI when we should refill the stream. */ - HANDLE refill_event = 0; + HANDLE refill_event; /* This is set by WASAPI when we should read from the input stream. In * practice, we read from the input stream in the output callback, so * this is not used, but it is necessary to start getting input data. */ - HANDLE input_available_event = 0; + HANDLE input_available_event; /* Each cubeb_stream has its own thread. */ - HANDLE thread = 0; + HANDLE thread; /* The lock protects all members that are touched by the render thread or change during a device reset, including: audio_clock, audio_stream_volume, client, frames_written, mix_params, total_frames_written, prev_position. */ owned_critical_section stream_reset_lock; /* Maximum number of frames that can be passed down in a callback. */ - uint32_t input_buffer_frame_count = 0; + uint32_t input_buffer_frame_count; /* Maximum number of frames that can be requested in a callback. */ - uint32_t output_buffer_frame_count = 0; + uint32_t output_buffer_frame_count; /* Resampler instance. Resampling will only happen if necessary. */ - std::unique_ptr resampler = { nullptr, cubeb_resampler_destroy }; - /* Mixer interfaces */ - std::unique_ptr output_mixer = { nullptr, cubeb_mixer_destroy }; - std::unique_ptr input_mixer = { nullptr, cubeb_mixer_destroy }; - /* A buffer for up/down mixing multi-channel audio output. */ - std::vector mix_buffer; + cubeb_resampler * resampler; + /* A buffer for up/down mixing multi-channel audio. */ + float * mix_buffer; /* WASAPI input works in "packets". We re-linearize the audio packets * into this buffer before handing it to the resampler. */ - std::unique_ptr linear_input_buffer; - /* Bytes per sample. This multiplied by the number of channels is the number - * of bytes per frame. */ - size_t bytes_per_sample = 0; - /* WAVEFORMATEXTENSIBLE sub-format: either PCM or float. */ - GUID waveformatextensible_sub_format = GUID_NULL; + auto_array linear_input_buffer; /* Stream volume. Set via stream_set_volume and used to reset volume on device changes. */ - float volume = 1.0; + float volume; /* True if the stream is draining. */ - bool draining = false; + bool draining; /* True when we've destroyed the stream. This pointer is leaked on stream * destruction if we could not join the thread. */ - std::atomic*> emergency_bailout { nullptr }; - /* Synchronizes render thread start to ensure safe access to emergency_bailout. */ - HANDLE thread_ready_event = 0; -}; - -class monitor_device_notifications { -public: - monitor_device_notifications(cubeb * context) - : cubeb_context(context) - { - create_thread(); - } - - ~monitor_device_notifications() - { - SetEvent(shutdown); - WaitForSingleObject(thread, INFINITE); - CloseHandle(thread); - - CloseHandle(input_changed); - CloseHandle(output_changed); - CloseHandle(shutdown); - } - - void notify(EDataFlow flow) - { - XASSERT(cubeb_context); - if (flow == eCapture && cubeb_context->input_collection_changed_callback) { - bool res = SetEvent(input_changed); - if (!res) { - LOG("Failed to set input changed event"); - } - return; - } - if (flow == eRender && cubeb_context->output_collection_changed_callback) { - bool res = SetEvent(output_changed); - if (!res) { - LOG("Failed to set output changed event"); - } - } - } -private: - static unsigned int __stdcall - thread_proc(LPVOID args) - { - XASSERT(args); - static_cast(args) - ->notification_thread_loop(); - return 0; - } - - void notification_thread_loop() - { - struct auto_com { - auto_com() { - HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - XASSERT(SUCCEEDED(hr)); - } - ~auto_com() { - CoUninitialize(); - } - } com; - - HANDLE wait_array[3] = { - input_changed, - output_changed, - shutdown, - }; - - while (true) { - Sleep(200); - - DWORD wait_result = WaitForMultipleObjects(ARRAY_LENGTH(wait_array), - wait_array, - FALSE, - INFINITE); - if (wait_result == WAIT_OBJECT_0) { // input changed - cubeb_context->input_collection_changed_callback(cubeb_context, - cubeb_context->input_collection_changed_user_ptr); - } else if (wait_result == WAIT_OBJECT_0 + 1) { // output changed - cubeb_context->output_collection_changed_callback(cubeb_context, - cubeb_context->output_collection_changed_user_ptr); - } else if (wait_result == WAIT_OBJECT_0 + 2) { // shutdown - break; - } else { - LOG("Unexpected result %lu", wait_result); - } - } // loop - } - - void create_thread() - { - output_changed = CreateEvent(nullptr, 0, 0, nullptr); - if (!output_changed) { - LOG("Failed to create output changed event."); - return; - } - - input_changed = CreateEvent(nullptr, 0, 0, nullptr); - if (!input_changed) { - LOG("Failed to create input changed event."); - return; - } - - shutdown = CreateEvent(nullptr, 0, 0, nullptr); - if (!shutdown) { - LOG("Failed to create shutdown event."); - return; - } - - thread = (HANDLE) _beginthreadex(nullptr, - 256 * 1024, - thread_proc, - this, - STACK_SIZE_PARAM_IS_A_RESERVATION, - nullptr); - if (!thread) { - LOG("Failed to create thread."); - return; - } - } - - HANDLE thread = INVALID_HANDLE_VALUE; - HANDLE output_changed = INVALID_HANDLE_VALUE; - HANDLE input_changed = INVALID_HANDLE_VALUE; - HANDLE shutdown = INVALID_HANDLE_VALUE; - - cubeb * cubeb_context = nullptr; -}; - -class wasapi_collection_notification_client : public IMMNotificationClient -{ -public: - /* The implementation of MSCOM was copied from MSDN. */ - ULONG STDMETHODCALLTYPE - AddRef() - { - return InterlockedIncrement(&ref_count); - } - - ULONG STDMETHODCALLTYPE - Release() - { - ULONG ulRef = InterlockedDecrement(&ref_count); - if (0 == ulRef) { - delete this; - } - return ulRef; - } - - HRESULT STDMETHODCALLTYPE - QueryInterface(REFIID riid, VOID **ppvInterface) - { - if (__uuidof(IUnknown) == riid) { - AddRef(); - *ppvInterface = (IUnknown*)this; - } else if (__uuidof(IMMNotificationClient) == riid) { - AddRef(); - *ppvInterface = (IMMNotificationClient*)this; - } else { - *ppvInterface = NULL; - return E_NOINTERFACE; - } - return S_OK; - } - - wasapi_collection_notification_client(cubeb * context) - : ref_count(1) - , cubeb_context(context) - , monitor_notifications(context) - { - XASSERT(cubeb_context); - } - - virtual ~wasapi_collection_notification_client() - { } - - HRESULT STDMETHODCALLTYPE - OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) - { - LOG("collection: Audio device default changed, id = %S.", device_id); - return S_OK; - } - - /* The remaining methods are not implemented, they simply log when called (if - log is enabled), for debugging. */ - HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id) - { - LOG("collection: Audio device added."); - return S_OK; - }; - - HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id) - { - LOG("collection: Audio device removed."); - return S_OK; - } - - HRESULT STDMETHODCALLTYPE - OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) - { - XASSERT(cubeb_context->output_collection_changed_callback || - cubeb_context->input_collection_changed_callback); - LOG("collection: Audio device state changed, id = %S, state = %lu.", device_id, new_state); - EDataFlow flow; - HRESULT hr = GetDataFlow(device_id, &flow); - if (FAILED(hr)) { - return hr; - } - monitor_notifications.notify(flow); - return S_OK; - } - - HRESULT STDMETHODCALLTYPE - OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) - { - //Audio device property value changed. - return S_OK; - } - -private: - HRESULT GetDataFlow(LPCWSTR device_id, EDataFlow * flow) - { - com_ptr device; - com_ptr endpoint; - - HRESULT hr = cubeb_context->device_collection_enumerator - ->GetDevice(device_id, device.receive()); - if (FAILED(hr)) { - LOG("collection: Could not get device: %lx", hr); - return hr; - } - - hr = device->QueryInterface(IID_PPV_ARGS(endpoint.receive())); - if (FAILED(hr)) { - LOG("collection: Could not get endpoint: %lx", hr); - return hr; - } - - return endpoint->GetDataFlow(flow); - } - - /* refcount for this instance, necessary to implement MSCOM semantics. */ - LONG ref_count; - - cubeb * cubeb_context = nullptr; - monitor_device_notifications monitor_notifications; + std::atomic*> emergency_bailout; }; class wasapi_endpoint_notification_client : public IMMNotificationClient @@ -616,10 +262,9 @@ public: return S_OK; } - wasapi_endpoint_notification_client(HANDLE event, ERole role) + wasapi_endpoint_notification_client(HANDLE event) : ref_count(1) , reconfigure_event(event) - , role(role) { } virtual ~wasapi_endpoint_notification_client() @@ -628,16 +273,16 @@ public: HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) { - LOG("endpoint: Audio device default changed."); + LOG("Audio device default changed."); /* we only support a single stream type for now. */ - if (flow != eRender && role != this->role) { + if (flow != eRender && role != eConsole) { return S_OK; } BOOL ok = SetEvent(reconfigure_event); if (!ok) { - LOG("endpoint: SetEvent on reconfigure_event failed: %lx", GetLastError()); + LOG("SetEvent on reconfigure_event failed: %x", GetLastError()); } return S_OK; @@ -647,54 +292,36 @@ public: log is enabled), for debugging. */ HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id) { - LOG("endpoint: Audio device added."); + LOG("Audio device added."); return S_OK; }; HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id) { - LOG("endpoint: Audio device removed."); + LOG("Audio device removed."); return S_OK; } HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) { - LOG("endpoint: Audio device state changed."); + LOG("Audio device state changed."); return S_OK; } HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) { - //Audio device property value changed. + LOG("Audio device property value changed."); return S_OK; } private: /* refcount for this instance, necessary to implement MSCOM semantics. */ LONG ref_count; HANDLE reconfigure_event; - ERole role; }; namespace { - -char const * -intern_device_id(cubeb * ctx, wchar_t const * id) -{ - XASSERT(id); - - char const * tmp = wstr_to_utf8(id); - if (!tmp) - return nullptr; - - char const * interned = cubeb_strings_intern(ctx->device_ids, tmp); - - free((void *) tmp); - - return interned; -} - bool has_input(cubeb_stream * stm) { return stm->input_stream_params.rate != 0; @@ -705,33 +332,22 @@ bool has_output(cubeb_stream * stm) return stm->output_stream_params.rate != 0; } -double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer) +bool should_upmix(cubeb_stream_params & stream, cubeb_stream_params & mixer) { - return double(stream.rate) / mixer.rate; + return mixer.channels > stream.channels; } -/* Convert the channel layout into the corresponding KSAUDIO_CHANNEL_CONFIG. - See more: https://msdn.microsoft.com/en-us/library/windows/hardware/ff537083(v=vs.85).aspx */ +bool should_downmix(cubeb_stream_params & stream, cubeb_stream_params & mixer) +{ + return mixer.channels < stream.channels; +} -cubeb_channel_layout -mask_to_channel_layout(WAVEFORMATEX const * fmt) +double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer) { - cubeb_channel_layout mask = 0; - - if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - WAVEFORMATEXTENSIBLE const * ext = reinterpret_cast(fmt); - mask = ext->dwChannelMask; - } else if (fmt->wFormatTag == WAVE_FORMAT_PCM || - fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { - if (fmt->nChannels == 1) { - mask = CHANNEL_FRONT_CENTER; - } else if (fmt->nChannels == 2) { - mask = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT; - } - } - return mask; + return double(stream.rate) / mixer.rate; } + uint32_t get_rate(cubeb_stream * stm) { @@ -740,21 +356,99 @@ get_rate(cubeb_stream * stm) } uint32_t -hns_to_frames(uint32_t rate, REFERENCE_TIME hns) +ms_to_hns(uint32_t ms) { - return std::ceil((hns - 1) / 10000000.0 * rate); + return ms * 10000; +} + +uint32_t +hns_to_ms(REFERENCE_TIME hns) +{ + return static_cast(hns / 10000); +} + +double +hns_to_s(REFERENCE_TIME hns) +{ + return static_cast(hns) / 10000000; } uint32_t hns_to_frames(cubeb_stream * stm, REFERENCE_TIME hns) { - return hns_to_frames(get_rate(stm), hns); + return hns_to_ms(hns * get_rate(stm)) / 1000; +} + +uint32_t +hns_to_frames(uint32_t rate, REFERENCE_TIME hns) +{ + return hns_to_ms(hns * rate) / 1000; } REFERENCE_TIME frames_to_hns(cubeb_stream * stm, uint32_t frames) { - return std::ceil(frames * 10000000.0 / get_rate(stm)); + return frames * 1000 / get_rate(stm); +} + +/* Upmix function, copies a mono channel into L and R */ +template +void +mono_to_stereo(T * in, long insamples, T * out, int32_t out_channels) +{ + for (int i = 0, j = 0; i < insamples; ++i, j += out_channels) { + out[j] = out[j + 1] = in[i]; + } +} + +template +void +upmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels) +{ + XASSERT(out_channels >= in_channels && in_channels > 0); + + /* Either way, if we have 2 or more channels, the first two are L and R. */ + /* If we are playing a mono stream over stereo speakers, copy the data over. */ + if (in_channels == 1 && out_channels >= 2) { + mono_to_stereo(in, inframes, out, out_channels); + } else { + /* Copy through. */ + for (int i = 0, o = 0; i < inframes * in_channels; + i += in_channels, o += out_channels) { + for (int j = 0; j < in_channels; ++j) { + out[o + j] = in[i + j]; + } + } + } + + /* Check if more channels. */ + if (out_channels <= 2) { + return; + } + + /* Put silence in remaining channels. */ + for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) { + for (int j = 2; j < out_channels; ++j) { + out[o + j] = 0.0; + } + } +} + +template +void +downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels) +{ + XASSERT(in_channels >= out_channels); + /* We could use a downmix matrix here, applying mixing weight based on the + channel, but directsound and winmm simply drop the channels that cannot be + rendered by the hardware, so we do the same for consistency. */ + long out_index = 0; + for (long i = 0; i < inframes * in_channels; i += in_channels) { + for (int j = 0; j < out_channels; ++j) { + out[out_index + j] = in[i + j]; + } + out_index += out_channels; + } } /* This returns the size of a frame in the stream, before the eventual upmix @@ -762,31 +456,30 @@ frames_to_hns(cubeb_stream * stm, uint32_t frames) static size_t frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames) { - // This is called only when we has a output client. - XASSERT(has_output(stm)); - return stm->output_stream_params.channels * stm->bytes_per_sample * frames; + size_t stream_frame_size = stm->output_stream_params.channels * sizeof(float); + return stream_frame_size * frames; } /* This function handles the processing of the input and output audio, * converting it to rate and channel layout specified at initialization. * It then calls the data callback, via the resampler. */ long -refill(cubeb_stream * stm, void * input_buffer, long input_frames_count, - void * output_buffer, long output_frames_needed) +refill(cubeb_stream * stm, float * input_buffer, long input_frames_count, + float * output_buffer, long output_frames_needed) { - XASSERT(!stm->draining); /* If we need to upmix after resampling, resample into the mix buffer to - avoid a copy. Avoid exposing output if it is a dummy stream. */ - void * dest = nullptr; - if (has_output(stm) && !stm->has_dummy_output) { - if (stm->output_mixer) { - dest = stm->mix_buffer.data(); + avoid a copy. */ + float * dest = nullptr; + if (has_output(stm)) { + if (should_upmix(stm->output_stream_params, stm->output_mix_params) || + should_downmix(stm->output_stream_params, stm->output_mix_params)) { + dest = stm->mix_buffer; } else { dest = output_buffer; } } - long out_frames = cubeb_resampler_fill(stm->resampler.get(), + long out_frames = cubeb_resampler_fill(stm->resampler, input_buffer, &input_frames_count, dest, @@ -799,51 +492,47 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count, stm->frames_written += out_frames; } - /* Go in draining mode if we got fewer frames than requested. If the stream - has no output we still expect the callback to return number of frames read - from input, otherwise we stop. */ - if ((out_frames < output_frames_needed) || - (!has_output(stm) && out_frames < input_frames_count)) { + /* Go in draining mode if we got fewer frames than requested. */ + if (out_frames < output_frames_needed) { LOG("start draining."); stm->draining = true; } /* If this is not true, there will be glitches. It is alright to have produced less frames if we are draining, though. */ - XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm) || stm->has_dummy_output); - - // We don't bother mixing dummy output as it will be silenced, otherwise mix output if needed - if (!stm->has_dummy_output && has_output(stm) && stm->output_mixer) { - XASSERT(dest == stm->mix_buffer.data()); - size_t dest_size = - out_frames * stm->output_stream_params.channels * stm->bytes_per_sample; - XASSERT(dest_size <= stm->mix_buffer.size()); - size_t output_buffer_size = - out_frames * stm->output_mix_params.channels * stm->bytes_per_sample; - int ret = cubeb_mixer_mix(stm->output_mixer.get(), - out_frames, - dest, - dest_size, - output_buffer, - output_buffer_size); - if (ret < 0) { - LOG("Error remixing content (%d)", ret); + XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm)); + + if (has_output(stm)) { + if (should_upmix(stm->output_stream_params, stm->output_mix_params)) { + upmix(dest, out_frames, output_buffer, + stm->output_stream_params.channels, stm->output_mix_params.channels); + } else if (should_downmix(stm->output_stream_params, stm->output_mix_params)) { + downmix(dest, out_frames, output_buffer, + stm->output_stream_params.channels, stm->output_mix_params.channels); } } return out_frames; } -int wasapi_stream_reset_default_device(cubeb_stream * stm); - /* This helper grabs all the frames available from a capture client, put them in * linear_input_buffer. linear_input_buffer should be cleared before the - * callback exits. This helper does not work with exclusive mode streams. */ + * callback exits. */ bool get_input_buffer(cubeb_stream * stm) { + HRESULT hr; + UINT32 padding_in; + XASSERT(has_input(stm)); - HRESULT hr; + hr = stm->input_client->GetCurrentPadding(&padding_in); + if (FAILED(hr)) { + LOG("Failed to get padding"); + return false; + } + XASSERT(padding_in <= stm->input_buffer_frame_count); + UINT32 total_available_input = padding_in; + BYTE * input_packet = NULL; DWORD flags; UINT64 dev_pos; @@ -851,97 +540,73 @@ bool get_input_buffer(cubeb_stream * stm) /* Get input packets until we have captured enough frames, and put them in a * contiguous buffer. */ uint32_t offset = 0; - // If the input stream is event driven we should only ever expect to read a - // single packet each time. However, if we're pulling from the stream we may - // need to grab multiple packets worth of frames that have accumulated (so - // need a loop). - for (hr = stm->capture_client->GetNextPacketSize(&next); - next > 0; - hr = stm->capture_client->GetNextPacketSize(&next)) { - if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { - // Application can recover from this error. More info - // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx - LOG("Device invalidated error, reset default device"); - wasapi_stream_reset_default_device(stm); - return true; - } - + while (offset != total_available_input) { + hr = stm->capture_client->GetNextPacketSize(&next); if (FAILED(hr)) { - LOG("cannot get next packet size: %lx", hr); + LOG("cannot get next packet size: %x", hr); return false; } + /* This can happen if the capture stream has stopped. Just return in this + * case. */ + if (!next) { + break; + } - UINT32 frames; + UINT32 packet_size; hr = stm->capture_client->GetBuffer(&input_packet, - &frames, + &packet_size, &flags, &dev_pos, NULL); if (FAILED(hr)) { - LOG("GetBuffer failed for capture: %lx", hr); + LOG("GetBuffer failed for capture: %x", hr); return false; } - XASSERT(frames == next); - - UINT32 input_stream_samples = frames * stm->input_stream_params.channels; - // We do not explicitly handle the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY - // flag. There a two primary (non exhaustive) scenarios we anticipate this - // flag being set in: - // - The first GetBuffer after Start has this flag undefined. In this - // case the flag may be set but is meaningless and can be ignored. - // - If a glitch is introduced into the input. This should not happen - // for event based inputs, and should be mitigated by using a dummy - // stream to drive input in the case of input only loopback. Without - // a dummy output, input only loopback would glitch on silence. However, - // the dummy input should push silence to the loopback and prevent - // discontinuities. See https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/ - // As the first scenario can be ignored, and we anticipate the second - // scenario is mitigated, we ignore the flag. - // For more info: https://msdn.microsoft.com/en-us/library/windows/desktop/dd370859(v=vs.85).aspx, - // https://msdn.microsoft.com/en-us/library/windows/desktop/dd371458(v=vs.85).aspx + XASSERT(packet_size == next); if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { - LOG("insert silence: ps=%u", frames); - stm->linear_input_buffer->push_silence(input_stream_samples); + LOG("insert silence: ps=%u", packet_size); + stm->linear_input_buffer.push_silence(packet_size * stm->input_stream_params.channels); } else { - if (stm->input_mixer) { - bool ok = stm->linear_input_buffer->reserve( - stm->linear_input_buffer->length() + input_stream_samples); - XASSERT(ok); - size_t input_packet_size = - frames * stm->input_mix_params.channels * - cubeb_sample_size(stm->input_mix_params.format); - size_t linear_input_buffer_size = - input_stream_samples * - cubeb_sample_size(stm->input_stream_params.format); - cubeb_mixer_mix(stm->input_mixer.get(), - frames, - input_packet, - input_packet_size, - stm->linear_input_buffer->end(), - linear_input_buffer_size); - stm->linear_input_buffer->set_length( - stm->linear_input_buffer->length() + input_stream_samples); + if (should_upmix(stm->input_mix_params, stm->input_stream_params)) { + bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() + + packet_size * stm->input_stream_params.channels); + assert(ok); + upmix(reinterpret_cast(input_packet), packet_size, + stm->linear_input_buffer.data() + stm->linear_input_buffer.length(), + stm->input_mix_params.channels, + stm->input_stream_params.channels); + stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels); + } else if (should_downmix(stm->input_mix_params, stm->input_stream_params)) { + bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() + + packet_size * stm->input_stream_params.channels); + assert(ok); + downmix(reinterpret_cast(input_packet), packet_size, + stm->linear_input_buffer.data() + stm->linear_input_buffer.length(), + stm->input_mix_params.channels, + stm->input_stream_params.channels); + stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels); } else { - stm->linear_input_buffer->push( - input_packet, input_stream_samples); + stm->linear_input_buffer.push(reinterpret_cast(input_packet), + packet_size * stm->input_stream_params.channels); } } - hr = stm->capture_client->ReleaseBuffer(frames); + hr = stm->capture_client->ReleaseBuffer(packet_size); if (FAILED(hr)) { LOG("FAILED to release intput buffer"); return false; } - offset += input_stream_samples; + offset += packet_size; } - XASSERT(stm->linear_input_buffer->length() >= offset); + assert(stm->linear_input_buffer.length() >= total_available_input && + offset == total_available_input); return true; } /* Get an output buffer from the render_client. It has to be released before * exiting the callback. */ -bool get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count) +bool get_output_buffer(cubeb_stream * stm, float *& buffer, size_t & frame_count) { UINT32 padding_out; HRESULT hr; @@ -949,19 +614,10 @@ bool get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count) XASSERT(has_output(stm)); hr = stm->output_client->GetCurrentPadding(&padding_out); - if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { - // Application can recover from this error. More info - // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx - LOG("Device invalidated error, reset default device"); - wasapi_stream_reset_default_device(stm); - return true; - } - if (FAILED(hr)) { - LOG("Failed to get padding: %lx", hr); + LOG("Failed to get padding: %x", hr); return false; } - XASSERT(padding_out <= stm->output_buffer_frame_count); if (stm->draining) { @@ -983,7 +639,7 @@ bool get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count) return false; } - buffer = output_buffer; + buffer = reinterpret_cast(output_buffer); return true; } @@ -995,7 +651,7 @@ bool refill_callback_duplex(cubeb_stream * stm) { HRESULT hr; - void * output_buffer = nullptr; + float * output_buffer = nullptr; size_t output_frames = 0; size_t input_frames; bool rv; @@ -1007,7 +663,7 @@ refill_callback_duplex(cubeb_stream * stm) return rv; } - input_frames = stm->linear_input_buffer->length() / stm->input_stream_params.channels; + input_frames = stm->linear_input_buffer.length() / stm->input_stream_params.channels; if (!input_frames) { return true; } @@ -1024,43 +680,29 @@ refill_callback_duplex(cubeb_stream * stm) return true; } - /* Wait for draining is not important on duplex. */ - if (stm->draining) { - return false; + // When WASAPI has not filled the input buffer yet, send silence. + double output_duration = double(output_frames) / stm->output_mix_params.rate; + double input_duration = double(stm->linear_input_buffer.length() / stm->input_stream_params.channels) / stm->input_mix_params.rate; + if (input_duration < output_duration) { + size_t padding = size_t(round((output_duration - input_duration) * stm->input_mix_params.rate)); + LOG("padding silence: out=%f in=%f pad=%u", output_duration, input_duration, padding); + stm->linear_input_buffer.push_front_silence(padding * stm->input_stream_params.channels); } - if (stm->has_dummy_output) { - ALOGV("Duplex callback (dummy output): input frames: %Iu, output frames: %Iu", - input_frames, output_frames); + LOGV("Duplex callback: input frames: %zu, output frames: %zu", + stm->linear_input_buffer.length(), output_frames); - // We don't want to expose the dummy output to the callback so don't pass - // the output buffer (it will be released later with silence in it) - refill(stm, - stm->linear_input_buffer->data(), - input_frames, - nullptr, - 0); - } else { - ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu", - input_frames, output_frames); - - refill(stm, - stm->linear_input_buffer->data(), - input_frames, - output_buffer, - output_frames); - } + refill(stm, + stm->linear_input_buffer.data(), + stm->linear_input_buffer.length(), + output_buffer, + output_frames); - stm->linear_input_buffer->clear(); + stm->linear_input_buffer.clear(); - if (stm->has_dummy_output) { - // If output is a dummy output, make sure it's silent - hr = stm->render_client->ReleaseBuffer(output_frames, AUDCLNT_BUFFERFLAGS_SILENT); - } else { - hr = stm->render_client->ReleaseBuffer(output_frames, 0); - } + hr = stm->render_client->ReleaseBuffer(output_frames, 0); if (FAILED(hr)) { - LOG("failed to release buffer: %lx", hr); + LOG("failed to release buffer: %x", hr); return false; } return true; @@ -1069,8 +711,7 @@ refill_callback_duplex(cubeb_stream * stm) bool refill_callback_input(cubeb_stream * stm) { - bool rv; - size_t input_frames; + bool rv, consumed_all_buffer; XASSERT(has_input(stm) && !has_output(stm)); @@ -1079,24 +720,24 @@ refill_callback_input(cubeb_stream * stm) return rv; } - input_frames = stm->linear_input_buffer->length() / stm->input_stream_params.channels; - if (!input_frames) { + // This can happen at the very beginning of the stream. + if (!stm->linear_input_buffer.length()) { return true; } - ALOGV("Input callback: input frames: %Iu", input_frames); + LOGV("Input callback: input frames: %zu", stm->linear_input_buffer.length()); long read = refill(stm, - stm->linear_input_buffer->data(), - input_frames, + stm->linear_input_buffer.data(), + stm->linear_input_buffer.length(), nullptr, 0); - XASSERT(read >= 0); + consumed_all_buffer = read == stm->linear_input_buffer.length(); - stm->linear_input_buffer->clear(); + stm->linear_input_buffer.clear(); - return !stm->draining; + return consumed_all_buffer; } bool @@ -1104,7 +745,7 @@ refill_callback_output(cubeb_stream * stm) { bool rv; HRESULT hr; - void * output_buffer = nullptr; + float * output_buffer = nullptr; size_t output_frames = 0; XASSERT(!has_input(stm) && has_output(stm)); @@ -1124,19 +765,19 @@ refill_callback_output(cubeb_stream * stm) output_buffer, output_frames); - ALOGV("Output callback: output frames requested: %Iu, got %ld", - output_frames, got); + LOGV("Output callback: output frames requested: %zu, got %ld", + output_frames, got); XASSERT(got >= 0); - XASSERT(size_t(got) == output_frames || stm->draining); + XASSERT(got == output_frames || stm->draining); hr = stm->render_client->ReleaseBuffer(got, 0); if (FAILED(hr)) { - LOG("failed to release buffer: %lx", hr); + LOG("failed to release buffer: %x", hr); return false; } - return size_t(got) == output_frames || stm->draining; + return got == output_frames || stm->draining; } static unsigned int __stdcall @@ -1145,13 +786,6 @@ wasapi_stream_render_loop(LPVOID stream) cubeb_stream * stm = static_cast(stream); std::atomic * emergency_bailout = stm->emergency_bailout; - // Signal wasapi_stream_start that we've copied emergency_bailout. - BOOL ok = SetEvent(stm->thread_ready_event); - if (!ok) { - LOG("thread_ready SetEvent failed: %lx", GetLastError()); - return 0; - } - bool is_playing = true; HANDLE wait_array[4] = { stm->shutdown_event, @@ -1162,22 +796,25 @@ wasapi_stream_render_loop(LPVOID stream) HANDLE mmcss_handle = NULL; HRESULT hr = 0; DWORD mmcss_task_index = 0; - struct auto_com { - auto_com() { - HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - XASSERT(SUCCEEDED(hr)); - } - ~auto_com() { - CoUninitialize(); - } - } com; + auto_com com; + if (!com.ok()) { + LOG("COM initialization failed on render_loop thread."); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + return 0; + } /* We could consider using "Pro Audio" here for WebAudio and maybe WebRTC. */ - mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index); + mmcss_handle = + stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index); if (!mmcss_handle) { /* This is not fatal, but we might glitch under heavy load. */ - LOG("Unable to use mmcss to bump the render thread priority: %lx", GetLastError()); + LOG("Unable to use mmcss to bump the render thread priority: %x", GetLastError()); + } + + // This has already been nulled out, simply exit. + if (!emergency_bailout) { + is_playing = false; } /* WaitForMultipleObjects timeout can trigger in cases where we don't want to @@ -1185,7 +822,7 @@ wasapi_stream_render_loop(LPVOID stream) the timeout error handling only when the timeout_limit is reached, which is reset on each successful loop. */ unsigned timeout_count = 0; - const unsigned timeout_limit = 3; + const unsigned timeout_limit = 5; while (is_playing) { // We want to check the emergency bailout variable before a // and after the WaitForMultipleObject, because the handles WaitForMultipleObjects @@ -1246,28 +883,18 @@ wasapi_stream_render_loop(LPVOID stream) } XASSERT(stm->output_client || stm->input_client); if (stm->output_client) { - hr = stm->output_client->Start(); - if (FAILED(hr)) { - LOG("Error starting output after reconfigure, error: %lx", hr); - is_playing = false; - continue; - } + stm->output_client->Start(); LOG("Output started after reconfigure."); } if (stm->input_client) { - hr = stm->input_client->Start(); - if (FAILED(hr)) { - LOG("Error starting input after reconfiguring, error: %lx", hr); - is_playing = false; - continue; - } + stm->input_client->Start(); LOG("Input started after reconfigure."); } break; } case WAIT_OBJECT_0 + 2: /* refill */ - XASSERT((has_input(stm) && has_output(stm)) || - (!has_input(stm) && has_output(stm))); + XASSERT(has_input(stm) && has_output(stm) || + !has_input(stm) && has_output(stm)); is_playing = stm->refill_callback(stm); break; case WAIT_OBJECT_0 + 3: /* input available */ @@ -1283,7 +910,7 @@ wasapi_stream_render_loop(LPVOID stream) } break; default: - LOG("case %lu not handled in render loop.", waitResult); + LOG("case %d not handled in render loop.", waitResult); abort(); } } @@ -1292,31 +919,41 @@ wasapi_stream_render_loop(LPVOID stream) stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); } - if (mmcss_handle) { - AvRevertMmThreadCharacteristics(mmcss_handle); - } + stm->context->revert_mm_thread_characteristics(mmcss_handle); return 0; } void wasapi_destroy(cubeb * context); +HANDLE WINAPI set_mm_thread_characteristics_noop(const char *, LPDWORD mmcss_task_index) +{ + return (HANDLE)1; +} + +BOOL WINAPI revert_mm_thread_characteristics_noop(HANDLE mmcss_handle) +{ + return true; +} + HRESULT register_notification_client(cubeb_stream * stm) { HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(stm->device_enumerator.receive())); + IID_PPV_ARGS(&stm->device_enumerator)); if (FAILED(hr)) { - LOG("Could not get device enumerator: %lx", hr); + LOG("Could not get device enumerator: %x", hr); return hr; } - stm->notification_client.reset(new wasapi_endpoint_notification_client(stm->reconfigure_event, stm->role)); + stm->notification_client = new wasapi_endpoint_notification_client(stm->reconfigure_event); - hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client.get()); + hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client); if (FAILED(hr)) { - LOG("Could not register endpoint notification callback: %lx", hr); + LOG("Could not register endpoint notification callback: %x", hr); + SafeRelease(stm->notification_client); stm->notification_client = nullptr; + SafeRelease(stm->device_enumerator); stm->device_enumerator = nullptr; } @@ -1328,97 +965,66 @@ HRESULT unregister_notification_client(cubeb_stream * stm) XASSERT(stm); HRESULT hr; - if (!stm->device_enumerator) { - return S_OK; - } - - hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(stm->notification_client.get()); - if (FAILED(hr)) { - // We can't really do anything here, we'll probably leak the - // notification client, but we can at least release the enumerator. - stm->device_enumerator = nullptr; - return S_OK; - } - - stm->notification_client = nullptr; - stm->device_enumerator = nullptr; - - return S_OK; -} - -HRESULT get_endpoint(com_ptr & device, LPCWSTR devid) -{ - com_ptr enumerator; - HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), - NULL, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(enumerator.receive())); - if (FAILED(hr)) { - LOG("Could not get device enumerator: %lx", hr); - return hr; + if (!stm->device_enumerator) { + return S_OK; } - hr = enumerator->GetDevice(devid, device.receive()); + hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(stm->notification_client); if (FAILED(hr)) { - LOG("Could not get device: %lx", hr); - return hr; + // We can't really do anything here, we'll probably leak the + // notification client, but we can at least release the enumerator. + SafeRelease(stm->device_enumerator); + return S_OK; } + SafeRelease(stm->notification_client); + SafeRelease(stm->device_enumerator); + return S_OK; } -HRESULT register_collection_notification_client(cubeb * context) +HRESULT get_endpoint(IMMDevice ** device, LPCWSTR devid) { + IMMDeviceEnumerator * enumerator; HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(context->device_collection_enumerator.receive())); + IID_PPV_ARGS(&enumerator)); if (FAILED(hr)) { - LOG("Could not get device enumerator: %lx", hr); + LOG("Could not get device enumerator: %x", hr); return hr; } - context->collection_notification_client.reset(new wasapi_collection_notification_client(context)); - - hr = context->device_collection_enumerator->RegisterEndpointNotificationCallback( - context->collection_notification_client.get()); - if (FAILED(hr)) { - LOG("Could not register endpoint notification callback: %lx", hr); - context->collection_notification_client.reset(); - context->device_collection_enumerator.reset(); - } - - return hr; -} - -HRESULT unregister_collection_notification_client(cubeb * context) -{ - HRESULT hr = context->device_collection_enumerator-> - UnregisterEndpointNotificationCallback(context->collection_notification_client.get()); + hr = enumerator->GetDevice(devid, device); if (FAILED(hr)) { + LOG("Could not get device: %x", hr); + SafeRelease(enumerator); return hr; } - context->collection_notification_client = nullptr; - context->device_collection_enumerator = nullptr; + SafeRelease(enumerator); - return hr; + return S_OK; } -HRESULT get_default_endpoint(com_ptr & device, EDataFlow direction, ERole role) +HRESULT get_default_endpoint(IMMDevice ** device, EDataFlow direction) { - com_ptr enumerator; + IMMDeviceEnumerator * enumerator; HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(enumerator.receive())); + IID_PPV_ARGS(&enumerator)); if (FAILED(hr)) { - LOG("Could not get device enumerator: %lx", hr); + LOG("Could not get device enumerator: %x", hr); return hr; } - hr = enumerator->GetDefaultAudioEndpoint(direction, role, device.receive()); + hr = enumerator->GetDefaultAudioEndpoint(direction, eConsole, device); if (FAILED(hr)) { - LOG("Could not get default audio endpoint: %lx", hr); + LOG("Could not get default audio endpoint: %x", hr); + SafeRelease(enumerator); return hr; } + SafeRelease(enumerator); + return ERROR_SUCCESS; } @@ -1437,14 +1043,14 @@ current_stream_delay(cubeb_stream * stm) UINT64 freq; HRESULT hr = stm->audio_clock->GetFrequency(&freq); if (FAILED(hr)) { - LOG("GetFrequency failed: %lx", hr); + LOG("GetFrequency failed: %x", hr); return 0; } UINT64 pos; hr = stm->audio_clock->GetPosition(&pos, NULL); if (FAILED(hr)) { - LOG("GetPosition failed: %lx", hr); + LOG("GetPosition failed: %x", hr); return 0; } @@ -1467,8 +1073,8 @@ stream_set_volume(cubeb_stream * stm, float volume) uint32_t channels; HRESULT hr = stm->audio_stream_volume->GetChannelCount(&channels); - if (FAILED(hr)) { - LOG("could not get the channel count: %lx", hr); + if (hr != S_OK) { + LOG("could not get the channel count: %x", hr); return CUBEB_ERROR; } @@ -1483,8 +1089,8 @@ stream_set_volume(cubeb_stream * stm, float volume) } hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes); - if (FAILED(hr)) { - LOG("could not set the channels volume: %lx", hr); + if (hr != S_OK) { + LOG("could not set the channels volume: %x", hr); return CUBEB_ERROR; } @@ -1495,27 +1101,49 @@ stream_set_volume(cubeb_stream * stm, float volume) extern "C" { int wasapi_init(cubeb ** context, char const * context_name) { + HRESULT hr; + auto_com com; + if (!com.ok()) { + return CUBEB_ERROR; + } + /* We don't use the device yet, but need to make sure we can initialize one so that this backend is not incorrectly enabled on platforms that don't support WASAPI. */ - com_ptr device; - HRESULT hr = get_default_endpoint(device, eRender, eConsole); + IMMDevice * device; + hr = get_default_endpoint(&device, eRender); if (FAILED(hr)) { - XASSERT(hr != CO_E_NOTINITIALIZED); - LOG("It wasn't able to find a default rendering device: %lx", hr); - hr = get_default_endpoint(device, eCapture, eConsole); - if (FAILED(hr)) { - LOG("It wasn't able to find a default capture device: %lx", hr); - return CUBEB_ERROR; - } + LOG("Could not get device: %x", hr); + return CUBEB_ERROR; } + SafeRelease(device); - cubeb * ctx = new cubeb(); + cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb)); + if (!ctx) { + return CUBEB_ERROR; + } ctx->ops = &wasapi_ops; - if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) { - delete ctx; - return CUBEB_ERROR; + + ctx->mmcss_module = LoadLibraryA("Avrt.dll"); + + if (ctx->mmcss_module) { + ctx->set_mm_thread_characteristics = + (set_mm_thread_characteristics_function) GetProcAddress( + ctx->mmcss_module, "AvSetMmThreadCharacteristicsA"); + ctx->revert_mm_thread_characteristics = + (revert_mm_thread_characteristics_function) GetProcAddress( + ctx->mmcss_module, "AvRevertMmThreadCharacteristics"); + if (!(ctx->set_mm_thread_characteristics && ctx->revert_mm_thread_characteristics)) { + LOG("Could not load AvSetMmThreadCharacteristics or AvRevertMmThreadCharacteristics: %x", GetLastError()); + FreeLibrary(ctx->mmcss_module); + } + } else { + // This is not a fatal error, but we might end up glitching when + // the system is under high load. + LOG("Could not load Avrt.dll"); + ctx->set_mm_thread_characteristics = &set_mm_thread_characteristics_noop; + ctx->revert_mm_thread_characteristics = &revert_mm_thread_characteristics_noop; } *context = ctx; @@ -1542,22 +1170,31 @@ bool stop_and_join_render_thread(cubeb_stream * stm) BOOL ok = SetEvent(stm->shutdown_event); if (!ok) { - LOG("Destroy SetEvent failed: %lx", GetLastError()); + LOG("Destroy SetEvent failed: %d", GetLastError()); } /* Wait five seconds for the rendering thread to return. It's supposed to * check its event loop very often, five seconds is rather conservative. */ DWORD r = WaitForSingleObject(stm->thread, 5000); - if (r != WAIT_OBJECT_0) { + if (r == WAIT_TIMEOUT) { /* Something weird happened, leak the thread and continue the shutdown * process. */ *(stm->emergency_bailout) = true; // We give the ownership to the rendering thread. stm->emergency_bailout = nullptr; - LOG("Destroy WaitForSingleObject on thread failed: %lx, %lx", r, GetLastError()); + LOG("Destroy WaitForSingleObject on thread timed out," + " leaking the thread: %d", GetLastError()); + rv = false; + } + if (r == WAIT_FAILED) { + *(stm->emergency_bailout) = true; + // We give the ownership to the rendering thread. + stm->emergency_bailout = nullptr; + LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError()); rv = false; } + // Only attempts to close and null out the thread and event if the // WaitForSingleObject above succeeded, so that calling this function again // attemps to clean up the thread and event each time. @@ -1575,11 +1212,10 @@ bool stop_and_join_render_thread(cubeb_stream * stm) void wasapi_destroy(cubeb * context) { - if (context->device_ids) { - cubeb_strings_destroy(context->device_ids); + if (context->mmcss_module) { + FreeLibrary(context->mmcss_module); } - - delete context; + free(context); } char const * wasapi_get_backend_id(cubeb * context) @@ -1590,285 +1226,209 @@ char const * wasapi_get_backend_id(cubeb * context) int wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) { + HRESULT hr; + IAudioClient * client; + WAVEFORMATEX * mix_format; + auto_com com; + if (!com.ok()) { + return CUBEB_ERROR; + } + XASSERT(ctx && max_channels); - com_ptr device; - HRESULT hr = get_default_endpoint(device, eRender, eConsole); + IMMDevice * device; + hr = get_default_endpoint(&device, eRender); if (FAILED(hr)) { return CUBEB_ERROR; } - com_ptr client; hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, - NULL, client.receive_vpp()); + NULL, (void **)&client); + SafeRelease(device); if (FAILED(hr)) { return CUBEB_ERROR; } - WAVEFORMATEX * tmp = nullptr; - hr = client->GetMixFormat(&tmp); + hr = client->GetMixFormat(&mix_format); if (FAILED(hr)) { + SafeRelease(client); return CUBEB_ERROR; } - com_heap_ptr mix_format(tmp); *max_channels = mix_format->nChannels; + CoTaskMemFree(mix_format); + SafeRelease(client); + return CUBEB_OK; } int wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) { - if (params.format != CUBEB_SAMPLE_FLOAT32NE && params.format != CUBEB_SAMPLE_S16NE) { - return CUBEB_ERROR_INVALID_FORMAT; + HRESULT hr; + IAudioClient * client; + REFERENCE_TIME default_period; + auto_com com; + if (!com.ok()) { + return CUBEB_ERROR; } - ERole role = pref_to_role(params.prefs); + if (params.format != CUBEB_SAMPLE_FLOAT32NE) { + return CUBEB_ERROR_INVALID_FORMAT; + } - com_ptr device; - HRESULT hr = get_default_endpoint(device, eRender, role); + IMMDevice * device; + hr = get_default_endpoint(&device, eRender); if (FAILED(hr)) { - LOG("Could not get default endpoint: %lx", hr); + LOG("Could not get default endpoint: %x", hr); return CUBEB_ERROR; } - com_ptr client; hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, - NULL, client.receive_vpp()); + NULL, (void **)&client); + SafeRelease(device); if (FAILED(hr)) { - LOG("Could not activate device for latency: %lx", hr); + LOG("Could not activate device for latency: %x", hr); return CUBEB_ERROR; } - REFERENCE_TIME minimum_period; - REFERENCE_TIME default_period; - hr = client->GetDevicePeriod(&default_period, &minimum_period); + /* The second parameter is for exclusive mode, that we don't use. */ + hr = client->GetDevicePeriod(&default_period, NULL); if (FAILED(hr)) { - LOG("Could not get device period: %lx", hr); + SafeRelease(client); + LOG("Could not get device period: %x", hr); return CUBEB_ERROR; } - LOG("default device period: %I64d, minimum device period: %I64d", default_period, minimum_period); + LOG("default device period: %lld", default_period); - /* If we're on Windows 10, we can use IAudioClient3 to get minimal latency. - Otherwise, according to the docs, the best latency we can achieve is by - synchronizing the stream and the engine. + /* According to the docs, the best latency we can achieve is by synchronizing + the stream and the engine. http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */ - #ifdef _WIN32_WINNT_WIN10 - *latency_frames = hns_to_frames(params.rate, minimum_period); - #else - *latency_frames = hns_to_frames(params.rate, default_period); - #endif + *latency_frames = hns_to_frames(params.rate, default_period); LOG("Minimum latency in frames: %u", *latency_frames); + SafeRelease(client); + return CUBEB_OK; } int wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) { - com_ptr device; - HRESULT hr = get_default_endpoint(device, eRender, eConsole); + HRESULT hr; + IAudioClient * client; + WAVEFORMATEX * mix_format; + auto_com com; + if (!com.ok()) { + return CUBEB_ERROR; + } + + IMMDevice * device; + hr = get_default_endpoint(&device, eRender); if (FAILED(hr)) { return CUBEB_ERROR; } - com_ptr client; hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, - NULL, client.receive_vpp()); + NULL, (void **)&client); + SafeRelease(device); if (FAILED(hr)) { return CUBEB_ERROR; } - WAVEFORMATEX * tmp = nullptr; - hr = client->GetMixFormat(&tmp); + hr = client->GetMixFormat(&mix_format); if (FAILED(hr)) { + SafeRelease(client); return CUBEB_ERROR; } - com_heap_ptr mix_format(tmp); *rate = mix_format->nSamplesPerSec; LOG("Preferred sample rate for output: %u", *rate); + CoTaskMemFree(mix_format); + SafeRelease(client); + return CUBEB_OK; } void wasapi_stream_destroy(cubeb_stream * stm); -static void -waveformatex_update_derived_properties(WAVEFORMATEX * format) -{ - format->nBlockAlign = format->wBitsPerSample * format->nChannels / 8; - format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign; - if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(format); - format_pcm->Samples.wValidBitsPerSample = format->wBitsPerSample; - } -} - /* Based on the mix format and the stream format, try to find a way to play what the user requested. */ static void -handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptr & mix_format, const cubeb_stream_params * stream_params) +handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cubeb_stream_params * stream_params) { - com_ptr & audio_client = (direction == eRender) ? stm->output_client : stm->input_client; - XASSERT(audio_client); + /* Common case: the hardware is stereo. Up-mixing and down-mixing will be + handled in the callback. */ + if ((*mix_format)->nChannels <= 2) { + return; + } + /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1], so the reinterpret_cast below should be safe. In practice, this is not true, and we just want to bail out and let the rest of the code find a good conversion path instead of trying to make WASAPI do it by itself. [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/ - if (mix_format->wFormatTag != WAVE_FORMAT_EXTENSIBLE) { + if ((*mix_format)->wFormatTag != WAVE_FORMAT_EXTENSIBLE) { return; } - WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(mix_format.get()); + WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(*mix_format); /* Stash a copy of the original mix format in case we need to restore it later. */ WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm; - /* Get the channel mask by the channel layout. - If the layout is not supported, we will get a closest settings below. */ - format_pcm->dwChannelMask = stream_params->layout; - mix_format->nChannels = stream_params->channels; - waveformatex_update_derived_properties(mix_format.get()); + /* The hardware is in surround mode, we want to only use front left and front + right. Try that, and check if it works. */ + switch (stream_params->channels) { + case 1: /* Mono */ + format_pcm->dwChannelMask = KSAUDIO_SPEAKER_MONO; + break; + case 2: /* Stereo */ + format_pcm->dwChannelMask = KSAUDIO_SPEAKER_STEREO; + break; + default: + XASSERT(false && "Channel layout not supported."); + break; + } + (*mix_format)->nChannels = stream_params->channels; + (*mix_format)->nBlockAlign = ((*mix_format)->wBitsPerSample * (*mix_format)->nChannels) / 8; + (*mix_format)->nAvgBytesPerSec = (*mix_format)->nSamplesPerSec * (*mix_format)->nBlockAlign; + format_pcm->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + (*mix_format)->wBitsPerSample = 32; + format_pcm->Samples.wValidBitsPerSample = (*mix_format)->wBitsPerSample; /* Check if wasapi will accept our channel layout request. */ WAVEFORMATEX * closest; - HRESULT hr = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, - mix_format.get(), - &closest); + HRESULT hr = stm->output_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, + *mix_format, + &closest); if (hr == S_FALSE) { - /* Channel layout not supported, but WASAPI gives us a suggestion. Use it, - and handle the eventual upmix/downmix ourselves. Ignore the subformat of - the suggestion, since it seems to always be IEEE_FLOAT. */ + /* Not supported, but WASAPI gives us a suggestion. Use it, and handle the + eventual upmix/downmix ourselves */ LOG("Using WASAPI suggested format: channels: %d", closest->nChannels); - XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE); WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast(closest); - format_pcm->dwChannelMask = closest_pcm->dwChannelMask; - mix_format->nChannels = closest->nChannels; - waveformatex_update_derived_properties(mix_format.get()); + XASSERT(closest_pcm->SubFormat == format_pcm->SubFormat); + CoTaskMemFree(*mix_format); + *mix_format = closest; } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { /* Not supported, no suggestion. This should not happen, but it does in the field with some sound cards. We restore the mix format, and let the rest of the code figure out the right conversion path. */ - XASSERT(mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE); - *reinterpret_cast(mix_format.get()) = hw_mix_format; + *reinterpret_cast(*mix_format) = hw_mix_format; } else if (hr == S_OK) { LOG("Requested format accepted by WASAPI."); } else { - LOG("IsFormatSupported unhandled error: %lx", hr); - } -} - -static bool -initialize_iaudioclient3(com_ptr & audio_client, - cubeb_stream * stm, - const com_heap_ptr & mix_format, - DWORD flags, - EDataFlow direction) -{ - com_ptr audio_client3; - audio_client->QueryInterface(audio_client3.receive()); - if (!audio_client3) { - LOG("Could not get IAudioClient3 interface"); - return false; - } - - if (flags & AUDCLNT_STREAMFLAGS_LOOPBACK) { - // IAudioClient3 doesn't work with loopback streams, and will return error - // 88890021: AUDCLNT_E_INVALID_STREAM_FLAG - LOG("Audio stream is loopback, not using IAudioClient3"); - return false; - } - - // IAudioClient3 doesn't support AUDCLNT_STREAMFLAGS_NOPERSIST, and will return - // AUDCLNT_E_INVALID_STREAM_FLAG. This is undocumented. - flags = flags ^ AUDCLNT_STREAMFLAGS_NOPERSIST; - - // Some people have reported glitches with capture streams: - // http://blog.nirbheek.in/2018/03/low-latency-audio-on-windows-with.html - if (direction == eCapture) { - LOG("Audio stream is capture, not using IAudioClient3"); - return false; - } - - // Possibly initialize a shared-mode stream using IAudioClient3. Initializing - // a stream this way lets you request lower latencies, but also locks the global - // WASAPI engine at that latency. - // - If we request a shared-mode stream, streams created with IAudioClient will - // have their latency adjusted to match. When the shared-mode stream is - // closed, they'll go back to normal. - // - If there's already a shared-mode stream running, then we cannot request - // the engine change to a different latency - we have to match it. - // - It's antisocial to lock the WASAPI engine at its default latency. If we - // would do this, then stop and use IAudioClient instead. - - HRESULT hr; - uint32_t default_period = 0, fundamental_period = 0, min_period = 0, max_period = 0; - hr = audio_client3->GetSharedModeEnginePeriod(mix_format.get(), &default_period, &fundamental_period, &min_period, &max_period); - if (FAILED(hr)) { - LOG("Could not get shared mode engine period: error: %lx", hr); - return false; - } - uint32_t requested_latency = stm->latency; - if (requested_latency >= default_period) { - LOG("Requested latency %i greater than default latency %i, not using IAudioClient3", requested_latency, default_period); - return false; - } - LOG("Got shared mode engine period: default=%i fundamental=%i min=%i max=%i", default_period, fundamental_period, min_period, max_period); - // Snap requested latency to a valid value - uint32_t old_requested_latency = requested_latency; - if (requested_latency < min_period) { - requested_latency = min_period; - } - requested_latency -= (requested_latency - min_period) % fundamental_period; - if (requested_latency != old_requested_latency) { - LOG("Requested latency %i was adjusted to %i", old_requested_latency, requested_latency); - } - - hr = audio_client3->InitializeSharedAudioStream(flags, requested_latency, mix_format.get(), NULL); - if (SUCCEEDED(hr)) { - return true; - } - else if (hr == AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) { - LOG("Got AUDCLNT_E_ENGINE_PERIODICITY_LOCKED, adjusting latency request"); - } else { - LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr); - return false; - } - - uint32_t current_period = 0; - WAVEFORMATEX* current_format = nullptr; - // We have to pass a valid WAVEFORMATEX** and not nullptr, otherwise - // GetCurrentSharedModeEnginePeriod will return E_POINTER - hr = audio_client3->GetCurrentSharedModeEnginePeriod(¤t_format, ¤t_period); - CoTaskMemFree(current_format); - if (FAILED(hr)) { - LOG("Could not get current shared mode engine period: error: %lx", hr); - return false; - } - - if (current_period >= default_period) { - LOG("Current shared mode engine period %i too high, not using IAudioClient", current_period); - return false; - } - - hr = audio_client3->InitializeSharedAudioStream(flags, current_period, mix_format.get(), NULL); - if (SUCCEEDED(hr)) { - LOG("Current shared mode engine period is %i instead of requested %i", current_period, requested_latency); - return true; + LOG("IsFormatSupported unhandled error: %x", hr); } - - LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr); - return false; } #define DIRECTION_NAME (direction == eCapture ? "capture" : "render") @@ -1876,22 +1436,18 @@ initialize_iaudioclient3(com_ptr & audio_client, template int setup_wasapi_stream_one_side(cubeb_stream * stm, cubeb_stream_params * stream_params, - wchar_t const * devid, + cubeb_devid devid, EDataFlow direction, REFIID riid, - com_ptr & audio_client, + IAudioClient ** audio_client, uint32_t * buffer_frame_count, HANDLE & event, - T & render_or_capture_client, + T ** render_or_capture_client, cubeb_stream_params * mix_params) { - com_ptr device; + IMMDevice * device; + WAVEFORMATEX * mix_format; HRESULT hr; - bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK; - if (is_loopback && direction != eCapture) { - LOG("Loopback pref can only be used with capture streams!\n"); - return CUBEB_ERROR; - } stm->stream_reset_lock.assert_current_thread_owns(); bool try_again = false; @@ -1899,46 +1455,35 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, // possibilities. do { if (devid) { - hr = get_endpoint(device, devid); + std::unique_ptr id(utf8_to_wstr(reinterpret_cast(devid))); + hr = get_endpoint(&device, id.get()); if (FAILED(hr)) { - LOG("Could not get %s endpoint, error: %lx\n", DIRECTION_NAME, hr); + LOG("Could not get %s endpoint, error: %x\n", DIRECTION_NAME, hr); return CUBEB_ERROR; } - } else { - // If caller has requested loopback but not specified a device, look for - // the default render device. Otherwise look for the default device - // appropriate to the direction. - hr = get_default_endpoint(device, is_loopback ? eRender : direction, pref_to_role(stream_params->prefs)); + } + else { + hr = get_default_endpoint(&device, direction); if (FAILED(hr)) { - if (is_loopback) { - LOG("Could not get default render endpoint for loopback, error: %lx\n", hr); - } else { - LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME, hr); - } + LOG("Could not get default %s endpoint, error: %x\n", DIRECTION_NAME, hr); return CUBEB_ERROR; } } /* Get a client. We will get all other interfaces we need from * this pointer. */ - // hr = device->Activate(__uuidof(IAudioClient3), - // CLSCTX_INPROC_SERVER, - // NULL, audio_client.receive_vpp()); - // if (hr == E_NOINTERFACE) { - hr = device->Activate(__uuidof(IAudioClient), - CLSCTX_INPROC_SERVER, - NULL, audio_client.receive_vpp()); - //} - + hr = device->Activate(__uuidof(IAudioClient), + CLSCTX_INPROC_SERVER, + NULL, (void **)audio_client); + SafeRelease(device); if (FAILED(hr)) { LOG("Could not activate the device to get an audio" - " client for %s: error: %lx\n", DIRECTION_NAME, hr); + " client for %s: error: %x\n", DIRECTION_NAME, hr); // A particular device can't be activated because it has been // unplugged, try fall back to the default audio device. if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED) { LOG("Trying again with the default %s audio device.", DIRECTION_NAME); devid = nullptr; - device = nullptr; try_again = true; } else { return CUBEB_ERROR; @@ -1950,85 +1495,62 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, /* We have to distinguish between the format the mixer uses, * and the format the stream we want to play uses. */ - WAVEFORMATEX * tmp = nullptr; - hr = audio_client->GetMixFormat(&tmp); + hr = (*audio_client)->GetMixFormat(&mix_format); if (FAILED(hr)) { LOG("Could not fetch current mix format from the audio" - " client for %s: error: %lx", DIRECTION_NAME, hr); + " client for %s: error: %x", DIRECTION_NAME, hr); return CUBEB_ERROR; } - com_heap_ptr mix_format(tmp); - mix_format->wBitsPerSample = stm->bytes_per_sample * 8; - if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(mix_format.get()); - format_pcm->SubFormat = stm->waveformatextensible_sub_format; - } - waveformatex_update_derived_properties(mix_format.get()); - - /* Set channel layout only when there're more than two channels. Otherwise, - * use the default setting retrieved from the stream format of the audio - * engine's internal processing by GetMixFormat. */ - if (mix_format->nChannels > 2) { - handle_channel_layout(stm, direction, mix_format, stream_params); - } + handle_channel_layout(stm, &mix_format, stream_params); - mix_params->format = stream_params->format; + /* Shared mode WASAPI always supports float32 sample format, so this + * is safe. */ + mix_params->format = CUBEB_SAMPLE_FLOAT32NE; mix_params->rate = mix_format->nSamplesPerSec; mix_params->channels = mix_format->nChannels; - mix_params->layout = mask_to_channel_layout(mix_format.get()); - - LOG("Setup requested=[f=%d r=%u c=%u l=%u] mix=[f=%d r=%u c=%u l=%u]", + LOG("Setup requested=[f=%d r=%u c=%u] mix=[f=%d r=%u c=%u]", stream_params->format, stream_params->rate, stream_params->channels, - stream_params->layout, - mix_params->format, mix_params->rate, mix_params->channels, - mix_params->layout); - - DWORD flags = AUDCLNT_STREAMFLAGS_NOPERSIST; - - // Check if a loopback device should be requested. Note that event callbacks - // do not work with loopback devices, so only request these if not looping. - if (is_loopback) { - flags |= AUDCLNT_STREAMFLAGS_LOOPBACK; - } else { - flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; - } - - // if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) { - // LOG("Initialized with IAudioClient3"); - // } else { - hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, - flags, - frames_to_hns(stm, stm->latency), - 0, - mix_format.get(), - NULL); - // } + mix_params->format, mix_params->rate, mix_params->channels); + + hr = (*audio_client)->Initialize(AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | + AUDCLNT_STREAMFLAGS_NOPERSIST, + frames_to_hns(stm, stm->latency), + 0, + mix_format, + NULL); if (FAILED(hr)) { - LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr); + LOG("Unable to initialize audio client for %s: %x.", DIRECTION_NAME, hr); return CUBEB_ERROR; } - hr = audio_client->GetBufferSize(buffer_frame_count); + CoTaskMemFree(mix_format); + + hr = (*audio_client)->GetBufferSize(buffer_frame_count); if (FAILED(hr)) { LOG("Could not get the buffer size from the client" - " for %s %lx.", DIRECTION_NAME, hr); + " for %s %x.", DIRECTION_NAME, hr); return CUBEB_ERROR; } - // Events are used if not looping back - if (!is_loopback) { - hr = audio_client->SetEventHandle(event); - if (FAILED(hr)) { - LOG("Could set the event handle for the %s client %lx.", - DIRECTION_NAME, hr); - return CUBEB_ERROR; - } + // Input is up/down mixed when depacketized in get_input_buffer. + if (has_output(stm) && + (should_upmix(*stream_params, *mix_params) || + should_downmix(*stream_params, *mix_params))) { + stm->mix_buffer = (float *)malloc(frames_to_bytes_before_mix(stm, *buffer_frame_count)); + } + + hr = (*audio_client)->SetEventHandle(event); + if (FAILED(hr)) { + LOG("Could set the event handle for the %s client %x.", + DIRECTION_NAME, hr); + return CUBEB_ERROR; } - hr = audio_client->GetService(riid, render_or_capture_client.receive_vpp()); + hr = (*audio_client)->GetService(riid, (void **)render_or_capture_client); if (FAILED(hr)) { - LOG("Could not get the %s client %lx.", DIRECTION_NAME, hr); + LOG("Could not get the %s client %x.", DIRECTION_NAME, hr); return CUBEB_ERROR; } @@ -2039,94 +1561,66 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, int setup_wasapi_stream(cubeb_stream * stm) { + HRESULT hr; int rv; stm->stream_reset_lock.assert_current_thread_owns(); + auto_com com; + if (!com.ok()) { + LOG("Failure to initialize COM."); + return CUBEB_ERROR; + } + XASSERT((!stm->output_client || !stm->input_client) && "WASAPI stream already setup, close it first."); if (has_input(stm)) { - LOG("(%p) Setup capture: device=%p", stm, stm->input_device.get()); + LOG("Setup capture: device=%x", (int)stm->input_device); rv = setup_wasapi_stream_one_side(stm, &stm->input_stream_params, - stm->input_device.get(), + stm->input_device, eCapture, __uuidof(IAudioCaptureClient), - stm->input_client, + &stm->input_client, &stm->input_buffer_frame_count, stm->input_available_event, - stm->capture_client, + &stm->capture_client, &stm->input_mix_params); if (rv != CUBEB_OK) { LOG("Failure to open the input side."); return rv; } - - // We initializing an input stream, buffer ahead two buffers worth of silence. - // This delays the input side slightly, but allow to not glitch when no input - // is available when calling into the resampler to call the callback: the input - // refill event will be set shortly after to compensate for this lack of data. - // In debug, four buffers are used, to avoid tripping up assertions down the line. -#if !defined(DEBUG) - const int silent_buffer_count = 2; -#else - const int silent_buffer_count = 6; -#endif - stm->linear_input_buffer->push_silence(stm->input_buffer_frame_count * - stm->input_stream_params.channels * - silent_buffer_count); - } - - // If we don't have an output device but are requesting a loopback device, - // we attempt to open that same device in output mode in order to drive the - // loopback via the output events. - stm->has_dummy_output = false; - if (!has_output(stm) && stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) { - stm->output_stream_params.rate = stm->input_stream_params.rate; - stm->output_stream_params.channels = stm->input_stream_params.channels; - stm->output_stream_params.layout = stm->input_stream_params.layout; - if (stm->input_device) { - size_t len = wcslen(stm->input_device.get()); - std::unique_ptr tmp(new wchar_t[len + 1]); - if (wcsncpy_s(tmp.get(), len + 1, stm->input_device.get(), len) != 0) { - LOG("Failed to copy device identifier while copying input stream" - " configuration to output stream configuration to drive loopback."); - return CUBEB_ERROR; - } - stm->output_device = move(tmp); - } - stm->has_dummy_output = true; } if (has_output(stm)) { - LOG("(%p) Setup render: device=%p", stm, stm->output_device.get()); + LOG("Setup render: device=%x", (int)stm->output_device); rv = setup_wasapi_stream_one_side(stm, &stm->output_stream_params, - stm->output_device.get(), + stm->output_device, eRender, __uuidof(IAudioRenderClient), - stm->output_client, + &stm->output_client, &stm->output_buffer_frame_count, stm->refill_event, - stm->render_client, + &stm->render_client, &stm->output_mix_params); if (rv != CUBEB_OK) { LOG("Failure to open the output side."); return rv; } - HRESULT hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume), - stm->audio_stream_volume.receive_vpp()); + hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume), + (void **)&stm->audio_stream_volume); if (FAILED(hr)) { - LOG("Could not get the IAudioStreamVolume: %lx", hr); + LOG("Could not get the IAudioStreamVolume: %x", hr); return CUBEB_ERROR; } XASSERT(stm->frames_written == 0); hr = stm->output_client->GetService(__uuidof(IAudioClock), - stm->audio_clock.receive_vpp()); + (void **)&stm->audio_clock); if (FAILED(hr)) { - LOG("Could not get the IAudioClock: %lx", hr); + LOG("Could not get the IAudioClock: %x", hr); return CUBEB_ERROR; } @@ -2141,7 +1635,7 @@ int setup_wasapi_stream(cubeb_stream * stm) * the highest sample rate available. */ int32_t target_sample_rate; if (has_input(stm) && has_output(stm)) { - XASSERT(stm->input_stream_params.rate == stm->output_stream_params.rate); + assert(stm->input_stream_params.rate == stm->output_stream_params.rate); target_sample_rate = stm->input_stream_params.rate; } else if (has_input(stm)) { target_sample_rate = stm->input_stream_params.rate; @@ -2161,14 +1655,14 @@ int setup_wasapi_stream(cubeb_stream * stm) cubeb_stream_params output_params = stm->output_mix_params; output_params.channels = stm->output_stream_params.channels; - stm->resampler.reset( + stm->resampler = cubeb_resampler_create(stm, has_input(stm) ? &input_params : nullptr, has_output(stm) ? &output_params : nullptr, target_sample_rate, stm->data_callback, stm->user_ptr, - CUBEB_RESAMPLER_QUALITY_DESKTOP)); + CUBEB_RESAMPLER_QUALITY_DESKTOP); if (!stm->resampler) { LOG("Could not get a resampler"); return CUBEB_ERROR; @@ -2184,52 +1678,9 @@ int setup_wasapi_stream(cubeb_stream * stm) stm->refill_callback = refill_callback_output; } - // Create input mixer. - if (has_input(stm) && - ((stm->input_mix_params.layout != CUBEB_LAYOUT_UNDEFINED && - stm->input_mix_params.layout != stm->input_stream_params.layout) || - (stm->input_mix_params.channels != stm->input_stream_params.channels))) { - if (stm->input_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) { - LOG("Input stream using undefined layout! Any mixing may be " - "unpredictable!\n"); - } - stm->input_mixer.reset(cubeb_mixer_create(stm->input_stream_params.format, - stm->input_mix_params.channels, - stm->input_mix_params.layout, - stm->input_stream_params.channels, - stm->input_stream_params.layout)); - assert(stm->input_mixer); - } - - // Create output mixer. - if (has_output(stm) && stm->output_mix_params.layout != stm->output_stream_params.layout) { - if (stm->output_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) { - LOG("Output stream using undefined layout! Any mixing may be unpredictable!\n"); - } - stm->output_mixer.reset(cubeb_mixer_create(stm->output_stream_params.format, - stm->output_stream_params.channels, - stm->output_stream_params.layout, - stm->output_mix_params.channels, - stm->output_mix_params.layout)); - assert(stm->output_mixer); - // Input is up/down mixed when depacketized in get_input_buffer. - stm->mix_buffer.resize( - frames_to_bytes_before_mix(stm, stm->output_buffer_frame_count)); - } - return CUBEB_OK; } -ERole -pref_to_role(cubeb_stream_prefs prefs) -{ - if (prefs & CUBEB_STREAM_PREF_VOICE) { - return eCommunications; - } - - return eConsole; -} - int wasapi_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, @@ -2240,100 +1691,92 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, unsigned int latency_frames, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) { + HRESULT hr; int rv; + auto_com com; + if (!com.ok()) { + return CUBEB_ERROR; + } XASSERT(context && stream && (input_stream_params || output_stream_params)); - if (output_stream_params && input_stream_params && - output_stream_params->format != input_stream_params->format) { + if (output_stream_params && output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE || + input_stream_params && input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE) { + LOG("Invalid format, %p %p %d %d", + output_stream_params, input_stream_params, + output_stream_params && output_stream_params->format, + input_stream_params && input_stream_params->format); return CUBEB_ERROR_INVALID_FORMAT; } - std::unique_ptr stm(new cubeb_stream(), wasapi_stream_destroy); + cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream)); + + XASSERT(stm); stm->context = context; stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; - - if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_VOICE || - stm->input_stream_params.prefs & CUBEB_STREAM_PREF_VOICE) { - stm->role = eCommunications; - } else { - stm->role = eConsole; - } - + stm->draining = false; if (input_stream_params) { stm->input_stream_params = *input_stream_params; - stm->input_device = utf8_to_wstr(reinterpret_cast(input_device)); + stm->input_device = input_device; } if (output_stream_params) { stm->output_stream_params = *output_stream_params; - stm->output_device = utf8_to_wstr(reinterpret_cast(output_device)); - } - - switch (output_stream_params ? output_stream_params->format : input_stream_params->format) { - case CUBEB_SAMPLE_S16NE: - stm->bytes_per_sample = sizeof(short); - stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_PCM; - stm->linear_input_buffer.reset(new auto_array_wrapper_impl); - break; - case CUBEB_SAMPLE_FLOAT32NE: - stm->bytes_per_sample = sizeof(float); - stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - stm->linear_input_buffer.reset(new auto_array_wrapper_impl); - break; - default: - return CUBEB_ERROR_INVALID_FORMAT; + stm->output_device = output_device; } stm->latency = latency_frames; + stm->volume = 1.0; + + // Placement new to call ctor. + new (&stm->stream_reset_lock) owned_critical_section(); stm->reconfigure_event = CreateEvent(NULL, 0, 0, NULL); if (!stm->reconfigure_event) { - LOG("Can't create the reconfigure event, error: %lx", GetLastError()); + LOG("Can't create the reconfigure event, error: %x", GetLastError()); + wasapi_stream_destroy(stm); return CUBEB_ERROR; } /* Unconditionally create the two events so that the wait logic is simpler. */ stm->refill_event = CreateEvent(NULL, 0, 0, NULL); if (!stm->refill_event) { - LOG("Can't create the refill event, error: %lx", GetLastError()); + LOG("Can't create the refill event, error: %x", GetLastError()); + wasapi_stream_destroy(stm); return CUBEB_ERROR; } stm->input_available_event = CreateEvent(NULL, 0, 0, NULL); if (!stm->input_available_event) { - LOG("Can't create the input available event , error: %lx", GetLastError()); + LOG("Can't create the input available event , error: %x", GetLastError()); + wasapi_stream_destroy(stm); return CUBEB_ERROR; } + { /* Locking here is not strictly necessary, because we don't have a notification client that can reset the stream yet, but it lets us assert that the lock is held in the function. */ auto_lock lock(stm->stream_reset_lock); - rv = setup_wasapi_stream(stm.get()); + rv = setup_wasapi_stream(stm); } if (rv != CUBEB_OK) { + wasapi_stream_destroy(stm); return rv; } - if (!((input_stream_params ? - (input_stream_params->prefs & CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) : 0) || - (output_stream_params ? - (output_stream_params->prefs & CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) : 0))) { - HRESULT hr = register_notification_client(stm.get()); - if (FAILED(hr)) { - /* this is not fatal, we can still play audio, but we won't be able - to keep using the default audio endpoint if it changes. */ - LOG("failed to register notification client, %lx", hr); - } + hr = register_notification_client(stm); + if (FAILED(hr)) { + /* this is not fatal, we can still play audio, but we won't be able + to keep using the default audio endpoint if it changes. */ + LOG("failed to register notification client, %x", hr); } - *stream = stm.release(); + *stream = stm; - LOG("Stream init succesfull (%p)", *stream); return CUBEB_OK; } @@ -2343,55 +1786,61 @@ void close_wasapi_stream(cubeb_stream * stm) stm->stream_reset_lock.assert_current_thread_owns(); - stm->output_client = nullptr; - stm->render_client = nullptr; + SafeRelease(stm->output_client); + stm->output_client = NULL; + SafeRelease(stm->input_client); + stm->input_client = NULL; - stm->input_client = nullptr; - stm->capture_client = nullptr; + SafeRelease(stm->render_client); + stm->render_client = NULL; - stm->audio_stream_volume = nullptr; + SafeRelease(stm->capture_client); + stm->capture_client = NULL; - stm->audio_clock = nullptr; + SafeRelease(stm->audio_stream_volume); + stm->audio_stream_volume = NULL; + + SafeRelease(stm->audio_clock); + stm->audio_clock = NULL; stm->total_frames_written += static_cast(round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, stm->output_mix_params))); stm->frames_written = 0; - stm->resampler.reset(); - stm->output_mixer.reset(); - stm->input_mixer.reset(); - stm->mix_buffer.clear(); + if (stm->resampler) { + cubeb_resampler_destroy(stm->resampler); + stm->resampler = NULL; + } + + free(stm->mix_buffer); + stm->mix_buffer = NULL; } void wasapi_stream_destroy(cubeb_stream * stm) { XASSERT(stm); - LOG("Stream destroy (%p)", stm); - // Only free stm->emergency_bailout if we could join the thread. - // If we could not join the thread, stm->emergency_bailout is true + // Only free stm->emergency_bailout if we could not join the thread. + // If we could not join the thread, stm->emergency_bailout is true // and is still alive until the thread wakes up and exits cleanly. if (stop_and_join_render_thread(stm)) { delete stm->emergency_bailout.load(); stm->emergency_bailout = nullptr; } - if (stm->notification_client) { - unregister_notification_client(stm); - } - - CloseHandle(stm->reconfigure_event); - CloseHandle(stm->refill_event); - CloseHandle(stm->input_available_event); + unregister_notification_client(stm); - // The variables intialized in wasapi_stream_init, - // must be destroyed in wasapi_stream_destroy. - stm->linear_input_buffer.reset(); + SafeRelease(stm->reconfigure_event); + SafeRelease(stm->refill_event); + SafeRelease(stm->input_available_event); { auto_lock lock(stm->stream_reset_lock); close_wasapi_stream(stm); } - delete stm; + // Need to call dtor to free the resource in owned_critical_section. + stm->stream_reset_lock.~owned_critical_section(); + + free(stm); } enum StreamDirection { @@ -2411,7 +1860,7 @@ int stream_start_one_side(cubeb_stream * stm, StreamDirection dir) BOOL ok = ResetEvent(stm->reconfigure_event); if (!ok) { - LOG("resetting reconfig event failed for %s stream: %lx", + LOG("resetting reconfig event failed for %s stream: %x", dir == OUTPUT ? "output" : "input", GetLastError()); } @@ -2424,12 +1873,12 @@ int stream_start_one_side(cubeb_stream * stm, StreamDirection dir) HRESULT hr2 = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start(); if (FAILED(hr2)) { - LOG("could not start the %s stream after reconfig: %lx", + LOG("could not start the %s stream after reconfig: %x", dir == OUTPUT ? "output" : "input", hr); return CUBEB_ERROR; } } else if (FAILED(hr)) { - LOG("could not start the %s stream: %lx.", + LOG("could not start the %s stream: %x.", dir == OUTPUT ? "output" : "input", hr); return CUBEB_ERROR; } @@ -2462,31 +1911,16 @@ int wasapi_stream_start(cubeb_stream * stm) stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL); if (!stm->shutdown_event) { - LOG("Can't create the shutdown event, error: %lx", GetLastError()); - return CUBEB_ERROR; - } - - stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL); - if (!stm->thread_ready_event) { - LOG("Can't create the thread_ready event, error: %lx", GetLastError()); + LOG("Can't create the shutdown event, error: %x", GetLastError()); return CUBEB_ERROR; } - cubeb_async_log_reset_threads(); stm->thread = (HANDLE) _beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); if (stm->thread == NULL) { LOG("could not create WASAPI render thread."); return CUBEB_ERROR; } - // Wait for wasapi_stream_render_loop to signal that emergency_bailout has - // been read, avoiding a bailout situation where we could free `stm` - // before wasapi_stream_render_loop had a chance to run. - HRESULT hr = WaitForSingleObject(stm->thread_ready_event, INFINITE); - XASSERT(hr == WAIT_OBJECT_0); - CloseHandle(stm->thread_ready_event); - stm->thread_ready_event = 0; - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); return CUBEB_OK; @@ -2516,32 +1950,21 @@ int wasapi_stream_stop(cubeb_stream * stm) } } + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); } if (stop_and_join_render_thread(stm)) { - delete stm->emergency_bailout.load(); - stm->emergency_bailout = nullptr; - } else { - // If we could not join the thread, put the stream in error. - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return CUBEB_ERROR; + // This is null if we've given the pointer to the other thread + if (stm->emergency_bailout.load()) { + delete stm->emergency_bailout.load(); + stm->emergency_bailout = nullptr; + } } return CUBEB_OK; } -int wasapi_stream_reset_default_device(cubeb_stream * stm) -{ - XASSERT(stm && stm->reconfigure_event); - BOOL ok = SetEvent(stm->reconfigure_event); - if (!ok) { - LOG("SetEvent on reconfigure_event failed: %lx", GetLastError()); - return CUBEB_ERROR; - } - return CUBEB_OK; -} - int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position) { XASSERT(stm && position); @@ -2614,329 +2037,253 @@ int wasapi_stream_set_volume(cubeb_stream * stm, float volume) return CUBEB_OK; } -static char const * +static char * wstr_to_utf8(LPCWSTR str) { - int size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, NULL, NULL); - if (size <= 0) { - return nullptr; + char * ret = NULL; + int size; + + size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, 0, NULL, NULL); + if (size > 0) { + ret = static_cast(malloc(size)); + ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL); } - char * ret = static_cast(malloc(size)); - ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL); return ret; } -static std::unique_ptr -utf8_to_wstr(char const * str) +static std::unique_ptr +utf8_to_wstr(char* str) { - int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); - if (size <= 0) { - return nullptr; + std::unique_ptr ret; + int size; + + size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); + if (size > 0) { + ret.reset(new wchar_t[size]); + ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size); } - std::unique_ptr ret(new wchar_t[size]); - ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size); - return ret; + return std::move(ret); } -static com_ptr +static IMMDevice * wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev) { - com_ptr ret; - com_ptr devtopo; - com_ptr connector; - - if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, devtopo.receive_vpp())) && - SUCCEEDED(devtopo->GetConnector(0, connector.receive()))) { - wchar_t * tmp = nullptr; - if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&tmp))) { - com_heap_ptr filterid(tmp); - if (FAILED(enumerator->GetDevice(filterid.get(), ret.receive()))) + IMMDevice * ret = NULL; + IDeviceTopology * devtopo = NULL; + IConnector * connector = NULL; + + if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&devtopo)) && + SUCCEEDED(devtopo->GetConnector(0, &connector))) { + LPWSTR filterid; + if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&filterid))) { + if (FAILED(enumerator->GetDevice(filterid, &ret))) ret = NULL; + CoTaskMemFree(filterid); } } + SafeRelease(connector); + SafeRelease(devtopo); return ret; } static BOOL wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id, - IMMDeviceEnumerator * enumerator) + IMMDeviceEnumerator * enumerator) { BOOL ret = FALSE; - com_ptr dev; + IMMDevice * dev; HRESULT hr; - hr = enumerator->GetDefaultAudioEndpoint(flow, role, dev.receive()); + hr = enumerator->GetDefaultAudioEndpoint(flow, role, &dev); if (SUCCEEDED(hr)) { - wchar_t * tmp = nullptr; - if (SUCCEEDED(dev->GetId(&tmp))) { - com_heap_ptr defdevid(tmp); - ret = (wcscmp(defdevid.get(), device_id) == 0); - } + LPWSTR defdevid = NULL; + if (SUCCEEDED(dev->GetId(&defdevid))) + ret = (wcscmp(defdevid, device_id) == 0); + if (defdevid != NULL) + CoTaskMemFree(defdevid); + SafeRelease(dev); } return ret; } -int -wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator * enumerator, IMMDevice * dev) +static cubeb_device_info * +wasapi_create_device(IMMDeviceEnumerator * enumerator, IMMDevice * dev) { - com_ptr endpoint; - com_ptr devnode; - com_ptr client; + IMMEndpoint * endpoint = NULL; + IMMDevice * devnode = NULL; + IAudioClient * client = NULL; + cubeb_device_info * ret = NULL; EDataFlow flow; + LPWSTR device_id = NULL; DWORD state = DEVICE_STATE_NOTPRESENT; - com_ptr propstore; + IPropertyStore * propstore = NULL; + PROPVARIANT propvar; REFERENCE_TIME def_period, min_period; HRESULT hr; - struct prop_variant : public PROPVARIANT { - prop_variant() { PropVariantInit(this); } - ~prop_variant() { PropVariantClear(this); } - prop_variant(prop_variant const &) = delete; - prop_variant & operator=(prop_variant const &) = delete; - }; + PropVariantInit(&propvar); - hr = dev->QueryInterface(IID_PPV_ARGS(endpoint.receive())); - if (FAILED(hr)) return CUBEB_ERROR; + hr = dev->QueryInterface(IID_PPV_ARGS(&endpoint)); + if (FAILED(hr)) goto done; hr = endpoint->GetDataFlow(&flow); - if (FAILED(hr)) return CUBEB_ERROR; + if (FAILED(hr)) goto done; - wchar_t * tmp = nullptr; - hr = dev->GetId(&tmp); - if (FAILED(hr)) return CUBEB_ERROR; - com_heap_ptr device_id(tmp); - - char const * device_id_intern = intern_device_id(ctx, device_id.get()); - if (!device_id_intern) { - return CUBEB_ERROR; - } + hr = dev->GetId(&device_id); + if (FAILED(hr)) goto done; - hr = dev->OpenPropertyStore(STGM_READ, propstore.receive()); - if (FAILED(hr)) return CUBEB_ERROR; + hr = dev->OpenPropertyStore(STGM_READ, &propstore); + if (FAILED(hr)) goto done; hr = dev->GetState(&state); - if (FAILED(hr)) return CUBEB_ERROR; + if (FAILED(hr)) goto done; + + ret = (cubeb_device_info *)calloc(1, sizeof(cubeb_device_info)); - ret.device_id = device_id_intern; - ret.devid = reinterpret_cast(ret.device_id); - prop_variant namevar; - hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar); + ret->devid = ret->device_id = wstr_to_utf8(device_id); + hr = propstore->GetValue(PKEY_Device_FriendlyName, &propvar); if (SUCCEEDED(hr)) - ret.friendly_name = wstr_to_utf8(namevar.pwszVal); + ret->friendly_name = wstr_to_utf8(propvar.pwszVal); devnode = wasapi_get_device_node(enumerator, dev); - if (devnode) { - com_ptr ps; - hr = devnode->OpenPropertyStore(STGM_READ, ps.receive()); - if (FAILED(hr)) return CUBEB_ERROR; + if (devnode != NULL) { + IPropertyStore * ps = NULL; + hr = devnode->OpenPropertyStore(STGM_READ, &ps); + if (FAILED(hr)) goto done; - prop_variant instancevar; - hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar); + PropVariantClear(&propvar); + hr = ps->GetValue(PKEY_Device_InstanceId, &propvar); if (SUCCEEDED(hr)) { - ret.group_id = wstr_to_utf8(instancevar.pwszVal); + ret->group_id = wstr_to_utf8(propvar.pwszVal); } + SafeRelease(ps); } - ret.preferred = CUBEB_DEVICE_PREF_NONE; - if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) - ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA); - if (wasapi_is_default_device(flow, eCommunications, device_id.get(), enumerator)) - ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE); - if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) - ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_NOTIFICATION); + ret->preferred = CUBEB_DEVICE_PREF_NONE; + if (wasapi_is_default_device(flow, eConsole, device_id, enumerator)) + ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_MULTIMEDIA); + if (wasapi_is_default_device(flow, eCommunications, device_id, enumerator)) + ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_VOICE); + if (wasapi_is_default_device(flow, eConsole, device_id, enumerator)) + ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_NOTIFICATION); - if (flow == eRender) ret.type = CUBEB_DEVICE_TYPE_OUTPUT; - else if (flow == eCapture) ret.type = CUBEB_DEVICE_TYPE_INPUT; + if (flow == eRender) ret->type = CUBEB_DEVICE_TYPE_OUTPUT; + else if (flow == eCapture) ret->type = CUBEB_DEVICE_TYPE_INPUT; switch (state) { case DEVICE_STATE_ACTIVE: - ret.state = CUBEB_DEVICE_STATE_ENABLED; + ret->state = CUBEB_DEVICE_STATE_ENABLED; break; case DEVICE_STATE_UNPLUGGED: - ret.state = CUBEB_DEVICE_STATE_UNPLUGGED; + ret->state = CUBEB_DEVICE_STATE_UNPLUGGED; break; default: - ret.state = CUBEB_DEVICE_STATE_DISABLED; + ret->state = CUBEB_DEVICE_STATE_DISABLED; break; }; - ret.format = static_cast(CUBEB_DEVICE_FMT_F32NE | CUBEB_DEVICE_FMT_S16NE); - ret.default_format = CUBEB_DEVICE_FMT_F32NE; - prop_variant fmtvar; - hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &fmtvar); - if (SUCCEEDED(hr) && fmtvar.vt == VT_BLOB) { - if (fmtvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) { - const PCMWAVEFORMAT * pcm = reinterpret_cast(fmtvar.blob.pBlobData); + ret->format = CUBEB_DEVICE_FMT_F32NE; /* cubeb only supports 32bit float at the moment */ + ret->default_format = CUBEB_DEVICE_FMT_F32NE; + PropVariantClear(&propvar); + hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &propvar); + if (SUCCEEDED(hr) && propvar.vt == VT_BLOB) { + if (propvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) { + const PCMWAVEFORMAT * pcm = reinterpret_cast(propvar.blob.pBlobData); - ret.max_rate = ret.min_rate = ret.default_rate = pcm->wf.nSamplesPerSec; - ret.max_channels = pcm->wf.nChannels; - } else if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX)) { - WAVEFORMATEX* wfx = reinterpret_cast(fmtvar.blob.pBlobData); + ret->max_rate = ret->min_rate = ret->default_rate = pcm->wf.nSamplesPerSec; + ret->max_channels = pcm->wf.nChannels; + } else if (propvar.blob.cbSize >= sizeof(WAVEFORMATEX)) { + WAVEFORMATEX* wfx = reinterpret_cast(propvar.blob.pBlobData); - if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize || + if (propvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize || wfx->wFormatTag == WAVE_FORMAT_PCM) { - ret.max_rate = ret.min_rate = ret.default_rate = wfx->nSamplesPerSec; - ret.max_channels = wfx->nChannels; + ret->max_rate = ret->min_rate = ret->default_rate = wfx->nSamplesPerSec; + ret->max_channels = wfx->nChannels; } } } - if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, client.receive_vpp())) && + if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&client)) && SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) { - ret.latency_lo = hns_to_frames(ret.default_rate, min_period); - ret.latency_hi = hns_to_frames(ret.default_rate, def_period); + ret->latency_lo = hns_to_frames(ret->default_rate, min_period); + ret->latency_hi = hns_to_frames(ret->default_rate, def_period); } else { - ret.latency_lo = 0; - ret.latency_hi = 0; - } - - return CUBEB_OK; + ret->latency_lo = 0; + ret->latency_hi = 0; + } + SafeRelease(client); + +done: + SafeRelease(devnode); + SafeRelease(endpoint); + SafeRelease(propstore); + if (device_id != NULL) + CoTaskMemFree(device_id); + PropVariantClear(&propvar); + return ret; } static int wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection * out) + cubeb_device_collection ** out) { - com_ptr enumerator; - com_ptr collection; + auto_com com; + IMMDeviceEnumerator * enumerator; + IMMDeviceCollection * collection; + IMMDevice * dev; + cubeb_device_info * cur; HRESULT hr; UINT cc, i; EDataFlow flow; + *out = NULL; + + if (!com.ok()) + return CUBEB_ERROR; + hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, - CLSCTX_INPROC_SERVER, IID_PPV_ARGS(enumerator.receive())); + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator)); if (FAILED(hr)) { - LOG("Could not get device enumerator: %lx", hr); + LOG("Could not get device enumerator: %x", hr); return CUBEB_ERROR; } if (type == CUBEB_DEVICE_TYPE_OUTPUT) flow = eRender; else if (type == CUBEB_DEVICE_TYPE_INPUT) flow = eCapture; - else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) flow = eAll; + else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_INPUT)) flow = eAll; else return CUBEB_ERROR; - hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL, collection.receive()); + hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL, &collection); if (FAILED(hr)) { - LOG("Could not enumerate audio endpoints: %lx", hr); + LOG("Could not enumerate audio endpoints: %x", hr); return CUBEB_ERROR; } hr = collection->GetCount(&cc); if (FAILED(hr)) { - LOG("IMMDeviceCollection::GetCount() failed: %lx", hr); + LOG("IMMDeviceCollection::GetCount() failed: %x", hr); return CUBEB_ERROR; } - cubeb_device_info * devices = new cubeb_device_info[cc]; - if (!devices) + *out = (cubeb_device_collection *) malloc(sizeof(cubeb_device_collection) + + sizeof(cubeb_device_info*) * (cc > 0 ? cc - 1 : 0)); + if (!*out) { return CUBEB_ERROR; - - PodZero(devices, cc); - out->count = 0; - for (i = 0; i < cc; i++) { - com_ptr dev; - hr = collection->Item(i, dev.receive()); - if (FAILED(hr)) { - LOG("IMMDeviceCollection::Item(%u) failed: %lx", i-1, hr); - continue; - } - if (wasapi_create_device(context, devices[out->count], - enumerator.get(), dev.get()) == CUBEB_OK) { - out->count += 1; - } - } - - out->device = devices; - return CUBEB_OK; -} - -static int -wasapi_device_collection_destroy(cubeb * /*ctx*/, cubeb_device_collection * collection) -{ - XASSERT(collection); - - for (size_t n = 0; n < collection->count; n++) { - cubeb_device_info& dev = collection->device[n]; - delete [] dev.friendly_name; - delete [] dev.group_id; } - - delete [] collection->device; - return CUBEB_OK; -} - -static int -wasapi_register_device_collection_changed(cubeb * context, - cubeb_device_type devtype, - cubeb_device_collection_changed_callback collection_changed_callback, - void * user_ptr) -{ - if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) { - return CUBEB_ERROR_INVALID_PARAMETER; - } - - if (collection_changed_callback) { - // Make sure it has been unregistered first. - XASSERT(((devtype & CUBEB_DEVICE_TYPE_INPUT) && - !context->input_collection_changed_callback) || - ((devtype & CUBEB_DEVICE_TYPE_OUTPUT) && - !context->output_collection_changed_callback)); - - // Stop the notification client. Notifications arrive on - // a separate thread. We stop them here to avoid - // synchronization issues during the update. - if (context->device_collection_enumerator.get()) { - HRESULT hr = unregister_collection_notification_client(context); - if (FAILED(hr)) { - return CUBEB_ERROR; - } - } - - if (devtype & CUBEB_DEVICE_TYPE_INPUT) { - context->input_collection_changed_callback = collection_changed_callback; - context->input_collection_changed_user_ptr = user_ptr; - } - if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { - context->output_collection_changed_callback = collection_changed_callback; - context->output_collection_changed_user_ptr = user_ptr; - } - - HRESULT hr = register_collection_notification_client(context); - if (FAILED(hr)) { - return CUBEB_ERROR; - } - } else { - if (!context->device_collection_enumerator.get()) { - // Already unregistered, ignore it. - return CUBEB_OK; - } - - HRESULT hr = unregister_collection_notification_client(context); + (*out)->count = 0; + for (i = 0; i < cc; i++) { + hr = collection->Item(i, &dev); if (FAILED(hr)) { - return CUBEB_ERROR; - } - if (devtype & CUBEB_DEVICE_TYPE_INPUT) { - context->input_collection_changed_callback = nullptr; - context->input_collection_changed_user_ptr = nullptr; - } - if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { - context->output_collection_changed_callback = nullptr; - context->output_collection_changed_user_ptr = nullptr; - } - - // If after the updates we still have registered - // callbacks restart the notification client. - if (context->input_collection_changed_callback || - context->output_collection_changed_callback) { - hr = register_collection_notification_client(context); - if (FAILED(hr)) { - return CUBEB_ERROR; - } + LOG("IMMDeviceCollection::Item(%u) failed: %x", i-1, hr); + } else if ((cur = wasapi_create_device(enumerator, dev)) != NULL) { + (*out)->device[(*out)->count++] = cur; } } + SafeRelease(collection); + SafeRelease(enumerator); return CUBEB_OK; } @@ -2947,19 +2294,18 @@ cubeb_ops const wasapi_ops = { /*.get_min_latency =*/ wasapi_get_min_latency, /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate, /*.enumerate_devices =*/ wasapi_enumerate_devices, - /*.device_collection_destroy =*/ wasapi_device_collection_destroy, /*.destroy =*/ wasapi_destroy, /*.stream_init =*/ wasapi_stream_init, /*.stream_destroy =*/ wasapi_stream_destroy, /*.stream_start =*/ wasapi_stream_start, /*.stream_stop =*/ wasapi_stream_stop, - /*.stream_reset_default_device =*/ wasapi_stream_reset_default_device, /*.stream_get_position =*/ wasapi_stream_get_position, /*.stream_get_latency =*/ wasapi_stream_get_latency, /*.stream_set_volume =*/ wasapi_stream_set_volume, + /*.stream_set_panning =*/ NULL, /*.stream_get_current_device =*/ NULL, /*.stream_device_destroy =*/ NULL, /*.stream_register_device_changed_callback =*/ NULL, - /*.register_device_collection_changed =*/ wasapi_register_device_collection_changed, + /*.register_device_collection_changed =*/ NULL }; } // namespace anonymous diff --git a/media/libcubeb/src/cubeb_winmm.c b/media/libcubeb/src/cubeb_winmm.c index e064ca079..585d11e89 100644 --- a/media/libcubeb/src/cubeb_winmm.c +++ b/media/libcubeb/src/cubeb_winmm.c @@ -4,6 +4,7 @@ * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ +#define __MSVCRT_VERSION__ 0x0700 #undef WINVER #define WINVER 0x0501 #undef WIN32_LEAN_AND_MEAN @@ -92,13 +93,11 @@ struct cubeb { }; struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; - void * user_ptr; - /**/ cubeb_stream_params params; cubeb_data_callback data_callback; cubeb_state_callback state_callback; + void * user_ptr; WAVEHDR buffers[NBUFS]; size_t buffer_size; int next_buffer; @@ -403,7 +402,6 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n XASSERT(context); XASSERT(stream); - XASSERT(output_stream_params); if (input_stream_params) { /* Capture support not yet implemented. */ @@ -415,11 +413,6 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n return CUBEB_ERROR_DEVICE_UNAVAILABLE; } - if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { - /* Loopback is not supported */ - return CUBEB_ERROR_NOT_SUPPORTED; - } - *stream = NULL; memset(&wfx, 0, sizeof(wfx)); @@ -519,6 +512,7 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n return CUBEB_ERROR; } + for (i = 0; i < NBUFS; ++i) { WAVEHDR * hdr = &stm->buffers[i]; @@ -775,6 +769,7 @@ winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats) } } + #define MM_S16_MASK (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4M16 | \ WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16) static int @@ -808,11 +803,11 @@ winmm_query_supported_formats(UINT devid, DWORD formats, static char * guid_to_cstr(LPGUID guid) { - char * ret = malloc(40); + char * ret = malloc(sizeof(char) * 40); if (!ret) { return NULL; } - _snprintf_s(ret, 40, _TRUNCATE, + _snprintf_s(ret, sizeof(char) * 40, _TRUNCATE, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid->Data1, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], @@ -826,12 +821,12 @@ winmm_query_preferred_out_device(UINT devid) DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status; cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE; - if (waveOutMessage((HWAVEOUT) WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, + if (waveOutMessage((HWAVEOUT)(size_t)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && devid == mmpref) ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION; - if (waveOutMessage((HWAVEOUT) WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, + if (waveOutMessage((HWAVEOUT)(size_t)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && devid == compref) ret |= CUBEB_DEVICE_PREF_VOICE; @@ -842,7 +837,7 @@ winmm_query_preferred_out_device(UINT devid) static char * device_id_idx(UINT devid) { - char * ret = malloc(16); + char * ret = (char *)malloc(sizeof(char)*16); if (!ret) { return NULL; } @@ -850,11 +845,16 @@ device_id_idx(UINT devid) return ret; } -static void -winmm_create_device_from_outcaps2(cubeb_device_info * ret, LPWAVEOUTCAPS2A caps, UINT devid) +static cubeb_device_info * +winmm_create_device_from_outcaps2(LPWAVEOUTCAPS2A caps, UINT devid) { - XASSERT(ret); - ret->devid = (cubeb_devid) devid; + cubeb_device_info * ret; + + ret = calloc(1, sizeof(cubeb_device_info)); + if (!ret) { + return NULL; + } + ret->devid = (cubeb_devid)(size_t)devid; ret->device_id = device_id_idx(devid); ret->friendly_name = _strdup(caps->szPname); ret->group_id = guid_to_cstr(&caps->ProductGuid); @@ -869,16 +869,23 @@ winmm_create_device_from_outcaps2(cubeb_device_info * ret, LPWAVEOUTCAPS2A caps, winmm_query_supported_formats(devid, caps->dwFormats, &ret->format, &ret->default_format); - /* Hardcoded latency estimates... */ + /* Hardcoed latency estimates... */ ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_hi = 200 * ret->default_rate / 1000; + + return ret; } -static void -winmm_create_device_from_outcaps(cubeb_device_info * ret, LPWAVEOUTCAPSA caps, UINT devid) +static cubeb_device_info * +winmm_create_device_from_outcaps(LPWAVEOUTCAPSA caps, UINT devid) { - XASSERT(ret); - ret->devid = (cubeb_devid) devid; + cubeb_device_info * ret; + + ret = calloc(1, sizeof(cubeb_device_info)); + if (!ret) { + return NULL; + } + ret->devid = (cubeb_devid)(size_t)devid; ret->device_id = device_id_idx(devid); ret->friendly_name = _strdup(caps->szPname); ret->group_id = NULL; @@ -893,9 +900,11 @@ winmm_create_device_from_outcaps(cubeb_device_info * ret, LPWAVEOUTCAPSA caps, U winmm_query_supported_formats(devid, caps->dwFormats, &ret->format, &ret->default_format); - /* Hardcoded latency estimates... */ + /* Hardcoed latency estimates... */ ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_hi = 200 * ret->default_rate / 1000; + + return ret; } static cubeb_device_pref @@ -904,12 +913,12 @@ winmm_query_preferred_in_device(UINT devid) DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status; cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE; - if (waveInMessage((HWAVEIN) WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, + if (waveInMessage((HWAVEIN)(size_t)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && devid == mmpref) ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION; - if (waveInMessage((HWAVEIN) WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, + if (waveInMessage((HWAVEIN)(size_t)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && devid == compref) ret |= CUBEB_DEVICE_PREF_VOICE; @@ -917,11 +926,16 @@ winmm_query_preferred_in_device(UINT devid) return ret; } -static void -winmm_create_device_from_incaps2(cubeb_device_info * ret, LPWAVEINCAPS2A caps, UINT devid) +static cubeb_device_info * +winmm_create_device_from_incaps2(LPWAVEINCAPS2A caps, UINT devid) { - XASSERT(ret); - ret->devid = (cubeb_devid) devid; + cubeb_device_info * ret; + + ret = calloc(1, sizeof(cubeb_device_info)); + if (!ret) { + return NULL; + } + ret->devid = (cubeb_devid)(size_t)devid; ret->device_id = device_id_idx(devid); ret->friendly_name = _strdup(caps->szPname); ret->group_id = guid_to_cstr(&caps->ProductGuid); @@ -936,16 +950,23 @@ winmm_create_device_from_incaps2(cubeb_device_info * ret, LPWAVEINCAPS2A caps, U winmm_query_supported_formats(devid, caps->dwFormats, &ret->format, &ret->default_format); - /* Hardcoded latency estimates... */ + /* Hardcoed latency estimates... */ ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_hi = 200 * ret->default_rate / 1000; + + return ret; } -static void -winmm_create_device_from_incaps(cubeb_device_info * ret, LPWAVEINCAPSA caps, UINT devid) +static cubeb_device_info * +winmm_create_device_from_incaps(LPWAVEINCAPSA caps, UINT devid) { - XASSERT(ret); - ret->devid = (cubeb_devid) devid; + cubeb_device_info * ret; + + ret = calloc(1, sizeof(cubeb_device_info)); + if (!ret) { + return NULL; + } + ret->devid = (cubeb_devid)(size_t)devid; ret->device_id = device_id_idx(devid); ret->friendly_name = _strdup(caps->szPname); ret->group_id = NULL; @@ -960,25 +981,29 @@ winmm_create_device_from_incaps(cubeb_device_info * ret, LPWAVEINCAPSA caps, UIN winmm_query_supported_formats(devid, caps->dwFormats, &ret->format, &ret->default_format); - /* Hardcoded latency estimates... */ + /* Hardcoed latency estimates... */ ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_hi = 200 * ret->default_rate / 1000; + + return ret; } static int winmm_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection * collection) + cubeb_device_collection ** collection) { UINT i, incount, outcount, total; - cubeb_device_info * devices; - cubeb_device_info * dev; + cubeb_device_info * cur; outcount = waveOutGetNumDevs(); incount = waveInGetNumDevs(); total = outcount + incount; - - devices = calloc(total, sizeof(cubeb_device_info)); - collection->count = 0; + if (total > 0) { + total -= 1; + } + *collection = malloc(sizeof(cubeb_device_collection) + + sizeof(cubeb_device_info*) * total); + (*collection)->count = 0; if (type & CUBEB_DEVICE_TYPE_OUTPUT) { WAVEOUTCAPSA woc; @@ -988,13 +1013,12 @@ winmm_enumerate_devices(cubeb * context, cubeb_device_type type, ZeroMemory(&woc2, sizeof(woc2)); for (i = 0; i < outcount; i++) { - dev = &devices[collection->count]; - if (waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) == MMSYSERR_NOERROR) { - winmm_create_device_from_outcaps2(dev, &woc2, i); - collection->count += 1; - } else if (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR) { - winmm_create_device_from_outcaps(dev, &woc, i); - collection->count += 1; + if ((waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) == MMSYSERR_NOERROR && + (cur = winmm_create_device_from_outcaps2(&woc2, i)) != NULL) || + (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR && + (cur = winmm_create_device_from_outcaps(&woc, i)) != NULL) + ) { + (*collection)->device[(*collection)->count++] = cur; } } } @@ -1007,39 +1031,16 @@ winmm_enumerate_devices(cubeb * context, cubeb_device_type type, ZeroMemory(&wic2, sizeof(wic2)); for (i = 0; i < incount; i++) { - dev = &devices[collection->count]; - if (waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) == MMSYSERR_NOERROR) { - winmm_create_device_from_incaps2(dev, &wic2, i); - collection->count += 1; - } else if (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR) { - winmm_create_device_from_incaps(dev, &wic, i); - collection->count += 1; + if ((waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) == MMSYSERR_NOERROR && + (cur = winmm_create_device_from_incaps2(&wic2, i)) != NULL) || + (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR && + (cur = winmm_create_device_from_incaps(&wic, i)) != NULL) + ) { + (*collection)->device[(*collection)->count++] = cur; } } } - collection->device = devices; - - return CUBEB_OK; -} - -static int -winmm_device_collection_destroy(cubeb * ctx, - cubeb_device_collection * collection) -{ - uint32_t i; - XASSERT(collection); - - (void) ctx; - - for (i = 0; i < collection->count; i++) { - free((void *) collection->device[i].device_id); - free((void *) collection->device[i].friendly_name); - free((void *) collection->device[i].group_id); - free((void *) collection->device[i].vendor_name); - } - - free(collection->device); return CUBEB_OK; } @@ -1050,16 +1051,15 @@ static struct cubeb_ops const winmm_ops = { /*.get_min_latency=*/ winmm_get_min_latency, /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate, /*.enumerate_devices =*/ winmm_enumerate_devices, - /*.device_collection_destroy =*/ winmm_device_collection_destroy, /*.destroy =*/ winmm_destroy, /*.stream_init =*/ winmm_stream_init, /*.stream_destroy =*/ winmm_stream_destroy, /*.stream_start =*/ winmm_stream_start, /*.stream_stop =*/ winmm_stream_stop, - /*.stream_reset_default_device =*/ NULL, /*.stream_get_position =*/ winmm_stream_get_position, /*.stream_get_latency = */ winmm_stream_get_latency, /*.stream_set_volume =*/ winmm_stream_set_volume, + /*.stream_set_panning =*/ NULL, /*.stream_get_current_device =*/ NULL, /*.stream_device_destroy =*/ NULL, /*.stream_register_device_changed_callback=*/ NULL, diff --git a/media/libcubeb/src/moz.build b/media/libcubeb/src/moz.build index 4b36b80fb..772aa6d39 100644 --- a/media/libcubeb/src/moz.build +++ b/media/libcubeb/src/moz.build @@ -10,11 +10,7 @@ Library('cubeb') SOURCES += [ 'cubeb.c', - 'cubeb_log.cpp', - 'cubeb_mixer.cpp', - 'cubeb_panner.cpp', - 'cubeb_strings.c', - 'cubeb_utils.cpp' + 'cubeb_panner.cpp' ] if CONFIG['MOZ_ALSA']: @@ -80,7 +76,6 @@ if CONFIG['OS_TARGET'] == 'WINNT': if CONFIG['OS_TARGET'] == 'Android': SOURCES += ['cubeb_opensl.c'] SOURCES += ['cubeb_resampler.cpp'] - SOURCES += ['cubeb-jni.cpp'] DEFINES['USE_OPENSL'] = True SOURCES += [ 'cubeb_audiotrack.c', @@ -90,7 +85,6 @@ if CONFIG['OS_TARGET'] == 'Android': FINAL_LIBRARY = 'gkmedias' CFLAGS += CONFIG['MOZ_ALSA_CFLAGS'] -CFLAGS += CONFIG['MOZ_JACK_CFLAGS'] CFLAGS += CONFIG['MOZ_PULSEAUDIO_CFLAGS'] # We allow warnings for third-party code that can be updated from upstream. diff --git a/media/libcubeb/unresampled-frames.patch b/media/libcubeb/unresampled-frames.patch new file mode 100644 index 000000000..714f3d4ba --- /dev/null +++ b/media/libcubeb/unresampled-frames.patch @@ -0,0 +1,36 @@ +From 46d12e9ae6fa9c233bc32812b13185ee7df8d3fd Mon Sep 17 00:00:00 2001 +From: Paul Adenot +Date: Thu, 10 Nov 2016 06:20:16 +0100 +Subject: [PATCH] Prevent underflowing the number of input frames needed in + input when resampling. (#188) + +--- + src/cubeb_resampler_internal.h | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/src/cubeb_resampler_internal.h b/src/cubeb_resampler_internal.h +index e165cc2..3c37a04 100644 +--- a/src/cubeb_resampler_internal.h ++++ b/src/cubeb_resampler_internal.h +@@ -263,9 +263,15 @@ public: + * number of output frames will be exactly equal. */ + uint32_t input_needed_for_output(uint32_t output_frame_count) + { +- return (uint32_t)ceilf((output_frame_count - samples_to_frames(resampling_out_buffer.length())) +- * resampling_ratio); +- ++ int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length()); ++ int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length()); ++ float input_frames_needed = ++ (output_frame_count - unresampled_frames_left) * resampling_ratio ++ - resampled_frames_left; ++ if (input_frames_needed < 0) { ++ return 0; ++ } ++ return (uint32_t)ceilf(input_frames_needed); + } + + /** Returns a pointer to the input buffer, that contains empty space for at +-- +2.7.4 + diff --git a/media/libcubeb/update.sh b/media/libcubeb/update.sh index 0e8d21456..235b963e2 100755 --- a/media/libcubeb/update.sh +++ b/media/libcubeb/update.sh @@ -1,11 +1,6 @@ -#!/bin/bash # Usage: sh update.sh - set -e -[[ -n "$1" ]] || ( echo "syntax: $0 update_src_directory"; exit 1 ) -[[ -e "$1/src/cubeb.c" ]] || ( echo "$1: cubeb not found"; exit 1 ) - cp $1/AUTHORS . cp $1/LICENSE . cp $1/README.md . @@ -16,32 +11,21 @@ cp $1/src/cubeb-internal.h src cp $1/src/cubeb-speex-resampler.h src cp $1/src/cubeb.c src cp $1/src/cubeb_alsa.c src -cp $1/src/cubeb_array_queue.h src +cp $1/src/cubeb_log.h src cp $1/src/cubeb_audiotrack.c src cp $1/src/cubeb_audiounit.cpp src +cp $1/src/cubeb_osx_run_loop.h src cp $1/src/cubeb_jack.cpp src -cp $1/src/cubeb_log.cpp src -cp $1/src/cubeb_log.h src -cp $1/src/cubeb_mixer.cpp src -cp $1/src/cubeb_mixer.h src cp $1/src/cubeb_opensl.c src -cp $1/src/cubeb-jni.cpp src -cp $1/src/cubeb-jni.h src -cp $1/src/android/cubeb-output-latency.h src/android -cp $1/src/android/cubeb_media_library.h src/android -cp $1/src/cubeb_osx_run_loop.h src +cp $1/src/cubeb_panner.cpp src +cp $1/src/cubeb_panner.h src cp $1/src/cubeb_pulse.c src cp $1/src/cubeb_resampler.cpp src cp $1/src/cubeb_resampler.h src cp $1/src/cubeb_resampler_internal.h src cp $1/src/cubeb_ring_array.h src -cp $1/src/cubeb_ringbuffer.h src cp $1/src/cubeb_sndio.c src -cp $1/src/cubeb_strings.c src -cp $1/src/cubeb_strings.h src -cp $1/src/cubeb_sun.c src cp $1/src/cubeb_utils.h src -cp $1/src/cubeb_utils.cpp src cp $1/src/cubeb_utils_unix.h src cp $1/src/cubeb_utils_win.h src cp $1/src/cubeb_wasapi.cpp src @@ -59,12 +43,7 @@ cp $1/test/test_utils.cpp tests/test_utils.cpp if [ -d $1/.git ]; then rev=$(cd $1 && git rev-parse --verify HEAD) - date=$(cd $1 && git show -s --format=%ci HEAD) dirty=$(cd $1 && git diff-index --name-only HEAD) - set +e - pre_rev=$(grep -o '[[:xdigit:]]\{40\}' moz.yaml) - commits=$(cd $1 && git log --pretty=format:'%h - %s' $pre_rev..$rev) - set -e fi if [ -n "$rev" ]; then @@ -73,18 +52,38 @@ if [ -n "$rev" ]; then version=$version-dirty echo "WARNING: updating from a dirty git repository." fi - sed -i.bak -e "s/^ *release:.*/ release: \"$version ($date)\"/" moz.yaml - if [[ ! "$( grep "$version" moz.yaml )" ]]; then - echo "Updating moz.yaml failed." - exit 1 - fi - rm moz.yaml.bak - [[ -n "$commits" ]] && echo -e "Pick commits:\n$commits" + sed -i.bak -e "/The git commit ID used was/ s/[0-9a-f]\{40\}\(-dirty\)\{0,1\}\./$version./" README_MOZILLA + rm README_MOZILLA.bak else - echo "Remember to update moz.yaml with the version details." + echo "Remember to update README_MOZILLA with the version details." fi -echo "Applying disable-assert.patch on top of $rev" -patch -p3 < disable-assert.patch -echo "Applying disable-iaudioclient3.patch on top of $rev" -patch -p3 < disable-iaudioclient3.patch +echo "Applying a patch on top of $version" +patch -p1 < ./unresampled-frames.patch + +echo "Applying a patch on top of $version" +patch -p1 < ./bug1302231_emergency_bailout.patch + +echo "Applying a patch on top of $version" +patch -p1 < ./osx-linearize-operations.patch + +echo "Applying a patch on top of $version" +patch -p1 < ./prevent-double-free.patch + +echo "Applying a patch on top of $version" +patch -p1 < ./bug1292803_pulse_assert.patch + +echo "Applying a patch on top of $version" +patch -p1 < ./uplift-wasapi-part-to-beta.patch + +echo "Applying a patch on top of $version" +patch -p3 < ./fix-crashes.patch + +echo "Applying a patch on top of $version" +patch -p3 < ./uplift-part-of-f07ee6d-esr52.patch + +echo "Applying a patch on top of $version" +patch -p3 < ./uplift-system-listener-patch.patch + +echo "Applying a patch on top of $version" +patch -p1 < ./uplift-patch-7a4c711.patch diff --git a/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch b/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch new file mode 100644 index 000000000..0eb1aca82 --- /dev/null +++ b/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch @@ -0,0 +1,167 @@ +# HG changeset patch +# User Alex Chronopoulos +# Parent 00c051cd38c7a6cb3178fd0890d52056f83abfdc +Bug 1345049 - Uplift part of cubeb upstream f07ee6d to esr52. r=padenot. a=xxxxxx + +diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp +--- a/media/libcubeb/src/cubeb_audiounit.cpp ++++ b/media/libcubeb/src/cubeb_audiounit.cpp +@@ -590,33 +590,43 @@ audiounit_get_input_device_id(AudioDevic + device_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + return CUBEB_OK; + } + ++static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); ++static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); ++ + static int + audiounit_reinit_stream(cubeb_stream * stm, bool is_started) + { ++ auto_lock context_lock(stm->context->mutex); + if (is_started) { + audiounit_stream_stop_internal(stm); + } + + { + auto_lock lock(stm->mutex); ++ float volume = 0.0; ++ int vol_rv = audiounit_stream_get_volume(stm, &volume); + + audiounit_close_stream(stm); + + if (audiounit_setup_stream(stm) != CUBEB_OK) { + LOG("(%p) Stream reinit failed.", stm); + return CUBEB_ERROR; + } + ++ if (vol_rv == CUBEB_OK) { ++ audiounit_stream_set_volume(stm, volume); ++ } ++ + // Reset input frames to force new stream pre-buffer + // silence if needed, check `is_extra_input_needed()` + stm->frames_read = 0; + + // If the stream was running, start it again. + if (is_started) { + audiounit_stream_start_internal(stm); + } +@@ -1007,20 +1017,22 @@ audiounit_get_preferred_sample_rate(cube + static OSStatus audiounit_remove_device_listener(cubeb * context); + + static void + audiounit_destroy(cubeb * ctx) + { + // Disabling this assert for bug 1083664 -- we seem to leak a stream + // assert(ctx->active_streams == 0); + +- /* Unregister the callback if necessary. */ +- if(ctx->collection_changed_callback) { ++ { + auto_lock lock(ctx->mutex); +- audiounit_remove_device_listener(ctx); ++ /* Unregister the callback if necessary. */ ++ if(ctx->collection_changed_callback) { ++ audiounit_remove_device_listener(ctx); ++ } + } + + ctx->~cubeb(); + free(ctx); + } + + static void audiounit_stream_destroy(cubeb_stream * stm); + +@@ -1861,17 +1873,17 @@ audiounit_close_stream(cubeb_stream *stm + cubeb_resampler_destroy(stm->resampler); + } + + static void + audiounit_stream_destroy(cubeb_stream * stm) + { + stm->shutdown = true; + +- auto_lock context_locl(stm->context->mutex); ++ auto_lock context_lock(stm->context->mutex); + audiounit_stream_stop_internal(stm); + + { + auto_lock lock(stm->mutex); + audiounit_close_stream(stm); + } + + #if !TARGET_OS_IPHONE +@@ -1905,17 +1917,17 @@ audiounit_stream_start_internal(cubeb_st + } + + static int + audiounit_stream_start(cubeb_stream * stm) + { + stm->shutdown = false; + stm->draining = false; + +- auto_lock context_locl(stm->context->mutex); ++ auto_lock context_lock(stm->context->mutex); + audiounit_stream_start_internal(stm); + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); + + LOG("Cubeb stream (%p) started successfully.", stm); + return CUBEB_OK; + } + +@@ -1933,17 +1945,17 @@ audiounit_stream_stop_internal(cubeb_str + } + } + + static int + audiounit_stream_stop(cubeb_stream * stm) + { + stm->shutdown = true; + +- auto_lock context_locl(stm->context->mutex); ++ auto_lock context_lock(stm->context->mutex); + audiounit_stream_stop_internal(stm); + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + + LOG("Cubeb stream (%p) stopped successfully.", stm); + return CUBEB_OK; + } + +@@ -2030,16 +2042,31 @@ audiounit_stream_get_latency(cubeb_strea + } + + *latency = stm->hw_latency_frames + stm->current_latency_frames; + + return CUBEB_OK; + #endif + } + ++static int ++audiounit_stream_get_volume(cubeb_stream * stm, float * volume) ++{ ++ assert(stm->output_unit); ++ OSStatus r = AudioUnitGetParameter(stm->output_unit, ++ kHALOutputParam_Volume, ++ kAudioUnitScope_Global, ++ 0, volume); ++ if (r != noErr) { ++ LOG("AudioUnitGetParameter/kHALOutputParam_Volume rv=%d", r); ++ return CUBEB_ERROR; ++ } ++ return CUBEB_OK; ++} ++ + int audiounit_stream_set_volume(cubeb_stream * stm, float volume) + { + OSStatus r; + + r = AudioUnitSetParameter(stm->output_unit, + kHALOutputParam_Volume, + kAudioUnitScope_Global, + 0, volume, 0); diff --git a/media/libcubeb/uplift-patch-7a4c711.patch b/media/libcubeb/uplift-patch-7a4c711.patch new file mode 100644 index 000000000..188bdf8b2 --- /dev/null +++ b/media/libcubeb/uplift-patch-7a4c711.patch @@ -0,0 +1,69 @@ +From 7a4c711d6e998b451326a0a87dd2e9dab5a257ef Mon Sep 17 00:00:00 2001 +From: Alex Chronopoulos +Date: Mon, 15 May 2017 16:47:26 +0300 +Subject: [PATCH] audiounit: synchronize destroy stream and reinit (Bug + 1361657) + +--- + src/cubeb_audiounit.cpp | 22 +++++++++++++++------- + 1 file changed, 15 insertions(+), 7 deletions(-) + +diff --git a/src/cubeb_audiounit.cpp b/src/cubeb_audiounit.cpp +index 8aa40d54..331bc735 100644 +--- a/src/cubeb_audiounit.cpp ++++ b/src/cubeb_audiounit.cpp +@@ -603,6 +603,7 @@ audiounit_get_input_device_id(AudioDeviceID * device_id) + + static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); + static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); ++static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm); + + static int + audiounit_reinit_stream(cubeb_stream * stm) +@@ -612,6 +613,11 @@ audiounit_reinit_stream(cubeb_stream * stm) + audiounit_stream_stop_internal(stm); + } + ++ int r = audiounit_uninstall_device_changed_callback(stm); ++ if (r != CUBEB_OK) { ++ LOG("(%p) Could not uninstall the device changed callback", stm); ++ } ++ + { + auto_lock lock(stm->mutex); + float volume = 0.0; +@@ -2516,11 +2522,6 @@ audiounit_close_stream(cubeb_stream *stm) + { + stm->mutex.assert_current_thread_owns(); + +- int r = audiounit_uninstall_device_changed_callback(stm); +- if (r != CUBEB_OK) { +- LOG("(%p) Could not uninstall the device changed callback", stm); +- } +- + if (stm->input_unit) { + AudioUnitUninitialize(stm->input_unit); + AudioComponentInstanceDispose(stm->input_unit); +@@ -2554,13 +2555,20 @@ audiounit_stream_destroy(cubeb_stream * stm) + LOG("(%p) Could not uninstall the device changed callback", stm); + } + ++ r = audiounit_uninstall_device_changed_callback(stm); ++ if (r != CUBEB_OK) { ++ LOG("(%p) Could not uninstall the device changed callback", stm); ++ } ++ + auto_lock context_lock(stm->context->mutex); + audiounit_stream_stop_internal(stm); + +- { ++ // Execute close in serial queue to avoid collision ++ // with reinit when un/plug devices ++ dispatch_sync(stm->context->serial_queue, ^() { + auto_lock lock(stm->mutex); + audiounit_close_stream(stm); +- } ++ }); + + assert(stm->context->active_streams >= 1); + stm->context->active_streams -= 1; diff --git a/media/libcubeb/uplift-system-listener-patch.patch b/media/libcubeb/uplift-system-listener-patch.patch new file mode 100644 index 000000000..5064d7fb3 --- /dev/null +++ b/media/libcubeb/uplift-system-listener-patch.patch @@ -0,0 +1,402 @@ +diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp +--- a/media/libcubeb/src/cubeb_audiounit.cpp ++++ b/media/libcubeb/src/cubeb_audiounit.cpp +@@ -594,20 +594,20 @@ audiounit_get_input_device_id(AudioDevic + + return CUBEB_OK; + } + + static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); + static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); + + static int +-audiounit_reinit_stream(cubeb_stream * stm, bool is_started) ++audiounit_reinit_stream(cubeb_stream * stm) + { + auto_lock context_lock(stm->context->mutex); +- if (is_started) { ++ if (!stm->shutdown) { + audiounit_stream_stop_internal(stm); + } + + { + auto_lock lock(stm->mutex); + float volume = 0.0; + int vol_rv = audiounit_stream_get_volume(stm, &volume); + +@@ -622,32 +622,30 @@ audiounit_reinit_stream(cubeb_stream * s + audiounit_stream_set_volume(stm, volume); + } + + // Reset input frames to force new stream pre-buffer + // silence if needed, check `is_extra_input_needed()` + stm->frames_read = 0; + + // If the stream was running, start it again. +- if (is_started) { ++ if (!stm->shutdown) { + audiounit_stream_start_internal(stm); + } + } + return CUBEB_OK; + } + + static OSStatus + audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count, + const AudioObjectPropertyAddress * addresses, + void * user) + { + cubeb_stream * stm = (cubeb_stream*) user; + stm->switching_device = true; +- // Note if the stream was running or not +- bool was_running = !stm->shutdown; + + LOG("(%p) Audio device changed, %d events.", stm, address_count); + for (UInt32 i = 0; i < address_count; i++) { + switch(addresses[i].mSelector) { + case kAudioHardwarePropertyDefaultOutputDevice: { + LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i); + // Allow restart to choose the new default + stm->output_device = nullptr; +@@ -666,19 +664,20 @@ audiounit_property_listener_callback(Aud + if (stm->is_default_input) { + LOG("It's the default input device, ignore the event"); + return noErr; + } + // Allow restart to choose the new default. Event register only for input. + stm->input_device = nullptr; + } + break; +- case kAudioDevicePropertyDataSource: +- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i); +- break; ++ case kAudioDevicePropertyDataSource: { ++ LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i); ++ return noErr; ++ } + } + } + + for (UInt32 i = 0; i < address_count; i++) { + switch(addresses[i].mSelector) { + case kAudioHardwarePropertyDefaultOutputDevice: + case kAudioHardwarePropertyDefaultInputDevice: + case kAudioDevicePropertyDeviceIsAlive: +@@ -691,17 +690,17 @@ audiounit_property_listener_callback(Aud + break; + } + } + } + + // Use a new thread, through the queue, to avoid deadlock when calling + // Get/SetProperties method from inside notify callback + dispatch_async(stm->context->serial_queue, ^() { +- if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) { ++ if (audiounit_reinit_stream(stm) != CUBEB_OK) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + LOG("(%p) Could not reopen the stream after switching.", stm); + } + stm->switching_device = false; + }); + + return noErr; + } +@@ -752,27 +751,16 @@ audiounit_install_device_changed_callbac + } + + r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); + if (r != noErr) { + PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource", r); + return CUBEB_ERROR; + } +- +- /* This event will notify us when the default audio device changes, +- * for example when the user plugs in a USB headset and the system chooses it +- * automatically as the default, or when another device is chosen in the +- * dropdown list. */ +- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, +- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); +- if (r != noErr) { +- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice", r); +- return CUBEB_ERROR; +- } + } + + if (stm->input_unit) { + /* This event will notify us when the data source on the input device changes. */ + AudioDeviceID input_dev_id; + r = audiounit_get_input_device_id(&input_dev_id); + if (r != noErr) { + return CUBEB_ERROR; +@@ -780,78 +768,112 @@ audiounit_install_device_changed_callbac + + r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); + if (r != noErr) { + PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource", r); + return CUBEB_ERROR; + } + +- /* This event will notify us when the default input device changes. */ +- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, +- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); +- if (r != noErr) { +- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice", r); +- return CUBEB_ERROR; +- } +- + /* Event to notify when the input is going away. */ + AudioDeviceID dev = stm->input_device ? reinterpret_cast(stm->input_device) : + audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); + r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + if (r != noErr) { + PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive", r); + return CUBEB_ERROR; + } + } + + return CUBEB_OK; + } + + static int ++audiounit_install_system_changed_callback(cubeb_stream * stm) ++{ ++ OSStatus r; ++ ++ if (stm->output_unit) { ++ /* This event will notify us when the default audio device changes, ++ * for example when the user plugs in a USB headset and the system chooses it ++ * automatically as the default, or when another device is chosen in the ++ * dropdown list. */ ++ r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, ++ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); ++ if (r != noErr) { ++ LOG("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv=%d", r); ++ return CUBEB_ERROR; ++ } ++ } ++ ++ if (stm->input_unit) { ++ /* This event will notify us when the default input device changes. */ ++ r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, ++ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); ++ if (r != noErr) { ++ LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r); ++ return CUBEB_ERROR; ++ } ++ } ++ ++ return CUBEB_OK; ++} ++ ++static int + audiounit_uninstall_device_changed_callback(cubeb_stream * stm) + { + OSStatus r; + + if (stm->output_unit) { + AudioDeviceID output_dev_id; + r = audiounit_get_output_device_id(&output_dev_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); + if (r != noErr) { + return CUBEB_ERROR; + } +- +- r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, +- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); +- if (r != noErr) { +- return CUBEB_ERROR; +- } + } + + if (stm->input_unit) { + AudioDeviceID input_dev_id; + r = audiounit_get_input_device_id(&input_dev_id); + if (r != noErr) { + return CUBEB_ERROR; + } + + r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); + if (r != noErr) { + return CUBEB_ERROR; + } +- ++ } ++ return CUBEB_OK; ++} ++ ++static int ++audiounit_uninstall_system_changed_callback(cubeb_stream * stm) ++{ ++ OSStatus r; ++ ++ if (stm->output_unit) { ++ r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, ++ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); ++ if (r != noErr) { ++ return CUBEB_ERROR; ++ } ++ } ++ ++ if (stm->input_unit) { + r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, +- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); ++ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); + if (r != noErr) { + return CUBEB_ERROR; + } + } + return CUBEB_OK; + } + + /* Get the acceptable buffer size (in frames) that this device can work with. */ +@@ -1764,16 +1786,22 @@ audiounit_setup_stream(cubeb_stream * st + + if (stm->input_unit && stm->output_unit) { + // According to the I/O hardware rate it is expected a specific pattern of callbacks + // for example is input is 44100 and output is 48000 we expected no more than 2 + // out callback in a row. + stm->expected_output_callbacks_in_a_row = ceilf(stm->output_hw_rate / stm->input_hw_rate); + } + ++ r = audiounit_install_device_changed_callback(stm); ++ if (r != CUBEB_OK) { ++ LOG("(%p) Could not install the device change callback.", stm); ++ return r; ++ } ++ + return CUBEB_OK; + } + + static int + audiounit_stream_init(cubeb * context, + cubeb_stream ** stream, + char const * /* stream_name */, + cubeb_devid input_device, +@@ -1838,31 +1866,37 @@ audiounit_stream_init(cubeb * context, + } + + if (r != CUBEB_OK) { + LOG("(%p) Could not setup the audiounit stream.", stm); + audiounit_stream_destroy(stm); + return r; + } + +- r = audiounit_install_device_changed_callback(stm); ++ r = audiounit_install_system_changed_callback(stm); + if (r != CUBEB_OK) { + LOG("(%p) Could not install the device change callback.", stm); + return r; + } + + *stream = stm; + LOG("Cubeb stream (%p) init successful.", stm); + return CUBEB_OK; + } + + static void + audiounit_close_stream(cubeb_stream *stm) + { + stm->mutex.assert_current_thread_owns(); ++ ++ int r = audiounit_uninstall_device_changed_callback(stm); ++ if (r != CUBEB_OK) { ++ LOG("(%p) Could not uninstall the device changed callback", stm); ++ } ++ + if (stm->input_unit) { + AudioUnitUninitialize(stm->input_unit); + AudioComponentInstanceDispose(stm->input_unit); + } + + audiounit_destroy_input_linear_buffer(stm); + + if (stm->output_unit) { +@@ -1873,31 +1907,29 @@ audiounit_close_stream(cubeb_stream *stm + cubeb_resampler_destroy(stm->resampler); + } + + static void + audiounit_stream_destroy(cubeb_stream * stm) + { + stm->shutdown = true; + ++ int r = audiounit_uninstall_system_changed_callback(stm); ++ if (r != CUBEB_OK) { ++ LOG("(%p) Could not uninstall the device changed callback", stm); ++ } ++ + auto_lock context_lock(stm->context->mutex); + audiounit_stream_stop_internal(stm); + + { + auto_lock lock(stm->mutex); + audiounit_close_stream(stm); + } + +-#if !TARGET_OS_IPHONE +- int r = audiounit_uninstall_device_changed_callback(stm); +- if (r != CUBEB_OK) { +- LOG("(%p) Could not uninstall the device changed callback", stm); +- } +-#endif +- + assert(stm->context->active_streams >= 1); + stm->context->active_streams -= 1; + + LOG("Cubeb stream (%p) destroyed successful.", stm); + + stm->~cubeb_stream(); + free(stm); + } +@@ -1914,20 +1946,20 @@ audiounit_stream_start_internal(cubeb_st + r = AudioOutputUnitStart(stm->output_unit); + assert(r == 0); + } + } + + static int + audiounit_stream_start(cubeb_stream * stm) + { ++ auto_lock context_lock(stm->context->mutex); + stm->shutdown = false; + stm->draining = false; + +- auto_lock context_lock(stm->context->mutex); + audiounit_stream_start_internal(stm); + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); + + LOG("Cubeb stream (%p) started successfully.", stm); + return CUBEB_OK; + } + +@@ -1943,19 +1975,19 @@ audiounit_stream_stop_internal(cubeb_str + r = AudioOutputUnitStop(stm->output_unit); + assert(r == 0); + } + } + + static int + audiounit_stream_stop(cubeb_stream * stm) + { ++ auto_lock context_lock(stm->context->mutex); + stm->shutdown = true; + +- auto_lock context_lock(stm->context->mutex); + audiounit_stream_stop_internal(stm); + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + + LOG("Cubeb stream (%p) stopped successfully.", stm); + return CUBEB_OK; + } + diff --git a/media/libcubeb/uplift-wasapi-part-to-beta.patch b/media/libcubeb/uplift-wasapi-part-to-beta.patch new file mode 100644 index 000000000..90e827830 --- /dev/null +++ b/media/libcubeb/uplift-wasapi-part-to-beta.patch @@ -0,0 +1,118 @@ +# HG changeset patch +# User Alex Chronopoulos +# Parent b7bb31e5a851d6f8e142c39dc077e3774719eced +Bug 1342363 - Uplift wasapi fixes in Beta. r?kinetik + +diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp +--- a/media/libcubeb/src/cubeb_wasapi.cpp ++++ b/media/libcubeb/src/cubeb_wasapi.cpp +@@ -807,16 +807,20 @@ wasapi_stream_render_loop(LPVOID stream) + maybe WebRTC. */ + mmcss_handle = + stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index); + if (!mmcss_handle) { + /* This is not fatal, but we might glitch under heavy load. */ + LOG("Unable to use mmcss to bump the render thread priority: %x", GetLastError()); + } + ++ // This has already been nulled out, simply exit. ++ if (!emergency_bailout) { ++ is_playing = false; ++ } + + /* WaitForMultipleObjects timeout can trigger in cases where we don't want to + treat it as a timeout, such as across a system sleep/wake cycle. Trigger + the timeout error handling only when the timeout_limit is reached, which is + reset on each successful loop. */ + unsigned timeout_count = 0; + const unsigned timeout_limit = 5; + while (is_playing) { +@@ -1158,22 +1162,26 @@ bool stop_and_join_render_thread(cubeb_s + + /* Wait five seconds for the rendering thread to return. It's supposed to + * check its event loop very often, five seconds is rather conservative. */ + DWORD r = WaitForSingleObject(stm->thread, 5000); + if (r == WAIT_TIMEOUT) { + /* Something weird happened, leak the thread and continue the shutdown + * process. */ + *(stm->emergency_bailout) = true; ++ // We give the ownership to the rendering thread. ++ stm->emergency_bailout = nullptr; + LOG("Destroy WaitForSingleObject on thread timed out," + " leaking the thread: %d", GetLastError()); + rv = false; + } + if (r == WAIT_FAILED) { + *(stm->emergency_bailout) = true; ++ // We give the ownership to the rendering thread. ++ stm->emergency_bailout = nullptr; + LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError()); + rv = false; + } + + + // Only attempts to close and null out the thread and event if the + // WaitForSingleObject above succeeded, so that calling this function again + // attemps to clean up the thread and event each time. +@@ -1798,19 +1806,16 @@ void wasapi_stream_destroy(cubeb_stream + XASSERT(stm); + + // Only free stm->emergency_bailout if we could not join the thread. + // If we could not join the thread, stm->emergency_bailout is true + // and is still alive until the thread wakes up and exits cleanly. + if (stop_and_join_render_thread(stm)) { + delete stm->emergency_bailout.load(); + stm->emergency_bailout = nullptr; +- } else { +- // If we're leaking, it must be that this is true. +- assert(*(stm->emergency_bailout)); + } + + unregister_notification_client(stm); + + SafeRelease(stm->reconfigure_event); + SafeRelease(stm->refill_event); + SafeRelease(stm->input_available_event); + +@@ -1865,21 +1870,21 @@ int stream_start_one_side(cubeb_stream * + return CUBEB_ERROR; + } + + return CUBEB_OK; + } + + int wasapi_stream_start(cubeb_stream * stm) + { ++ auto_lock lock(stm->stream_reset_lock); ++ + XASSERT(stm && !stm->thread && !stm->shutdown_event); + XASSERT(stm->output_client || stm->input_client); + +- auto_lock lock(stm->stream_reset_lock); +- + stm->emergency_bailout = new std::atomic(false); + + if (stm->output_client) { + int rv = stream_start_one_side(stm, OUTPUT); + if (rv != CUBEB_OK) { + return rv; + } + } +@@ -1932,16 +1937,17 @@ int wasapi_stream_stop(cubeb_stream * st + } + } + + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + } + + if (stop_and_join_render_thread(stm)) { ++ // This is null if we've given the pointer to the other thread + if (stm->emergency_bailout.load()) { + delete stm->emergency_bailout.load(); + stm->emergency_bailout = nullptr; + } + } + + return CUBEB_OK; + } -- cgit v1.2.3 From ee8c5d3878456b4e194dc3724616d194907ed466 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 3 Nov 2019 22:36:56 +0100 Subject: Bump Goanna version. --- config/milestone.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/milestone.txt b/config/milestone.txt index 67b4fdd4e..210727cac 100644 --- a/config/milestone.txt +++ b/config/milestone.txt @@ -10,4 +10,4 @@ # hardcoded milestones in the tree from these two files. #-------------------------------------------------------- -4.4.1 \ No newline at end of file +4.4.2 \ No newline at end of file -- cgit v1.2.3 From ff0f4b11ab54ba9820290c34f4486b625d841120 Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Sun, 3 Nov 2019 19:19:07 -0500 Subject: Issue #1271 - Fix build failure in current in-tree libcubeb sndio module Fixes build error due to errant typecast in PTHREAD_MUTEX_INITIALIZER. --- media/libcubeb/src/cubeb_sndio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c index 793789765..c7ac18446 100644 --- a/media/libcubeb/src/cubeb_sndio.c +++ b/media/libcubeb/src/cubeb_sndio.c @@ -245,7 +245,7 @@ sndio_stream_init(cubeb * context, s->data_cb = data_callback; s->state_cb = state_callback; s->arg = user_ptr; - s->mtx = PTHREAD_MUTEX_INITIALIZER; + s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; s->rdpos = s->wrpos = 0; if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) { s->conv = 1; -- cgit v1.2.3