forked from mirrors/gecko-dev
Merge autoland to mozilla-central. a=merge
This commit is contained in:
commit
010ccb86d4
184 changed files with 8567 additions and 6367 deletions
|
|
@ -65,19 +65,19 @@ git = "https://github.com/mozilla/application-services"
|
|||
rev = "e0563d725f852f617878ecc13a03cdf50c85cd5a"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."git+https://github.com/mozilla/audioipc?rev=409e11f8de6288e9ddfe269654523735302e59e6"]
|
||||
[source."git+https://github.com/mozilla/audioipc?rev=3495905752a4263827f5d43737f9ca3ed0243ce0"]
|
||||
git = "https://github.com/mozilla/audioipc"
|
||||
rev = "409e11f8de6288e9ddfe269654523735302e59e6"
|
||||
rev = "3495905752a4263827f5d43737f9ca3ed0243ce0"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=4ca174cf83ebe32b3198478c2211d69678845bc7"]
|
||||
[source."git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=0989726a1b9b640a30dfdf3ea005a12c73ab8155"]
|
||||
git = "https://github.com/mozilla/cubeb-coreaudio-rs"
|
||||
rev = "4ca174cf83ebe32b3198478c2211d69678845bc7"
|
||||
rev = "0989726a1b9b640a30dfdf3ea005a12c73ab8155"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."git+https://github.com/mozilla/cubeb-pulse-rs?rev=8ff972c8e2ec1782ff262ac4071c0415e69b1367"]
|
||||
[source."git+https://github.com/mozilla/cubeb-pulse-rs?rev=8678dcab1c287de79c4c184ccc2e065bc62b70e2"]
|
||||
git = "https://github.com/mozilla/cubeb-pulse-rs"
|
||||
rev = "8ff972c8e2ec1782ff262ac4071c0415e69b1367"
|
||||
rev = "8678dcab1c287de79c4c184ccc2e065bc62b70e2"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."git+https://github.com/mozilla/midir.git?rev=85156e360a37d851734118104619f86bd18e94c6"]
|
||||
|
|
|
|||
55
Cargo.lock
generated
55
Cargo.lock
generated
|
|
@ -160,7 +160,7 @@ version = "0.38.0+1.3.281"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
|
||||
dependencies = [
|
||||
"libloading 0.8.3",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -260,7 +260,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "audioipc2"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/mozilla/audioipc?rev=409e11f8de6288e9ddfe269654523735302e59e6#409e11f8de6288e9ddfe269654523735302e59e6"
|
||||
source = "git+https://github.com/mozilla/audioipc?rev=3495905752a4263827f5d43737f9ca3ed0243ce0#3495905752a4263827f5d43737f9ca3ed0243ce0"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"ashmem",
|
||||
|
|
@ -288,7 +288,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "audioipc2-client"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/mozilla/audioipc?rev=409e11f8de6288e9ddfe269654523735302e59e6#409e11f8de6288e9ddfe269654523735302e59e6"
|
||||
source = "git+https://github.com/mozilla/audioipc?rev=3495905752a4263827f5d43737f9ca3ed0243ce0#3495905752a4263827f5d43737f9ca3ed0243ce0"
|
||||
dependencies = [
|
||||
"audio_thread_priority",
|
||||
"audioipc2",
|
||||
|
|
@ -299,7 +299,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "audioipc2-server"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/mozilla/audioipc?rev=409e11f8de6288e9ddfe269654523735302e59e6#409e11f8de6288e9ddfe269654523735302e59e6"
|
||||
source = "git+https://github.com/mozilla/audioipc?rev=3495905752a4263827f5d43737f9ca3ed0243ce0#3495905752a4263827f5d43737f9ca3ed0243ce0"
|
||||
dependencies = [
|
||||
"audio_thread_priority",
|
||||
"audioipc2",
|
||||
|
|
@ -746,13 +746,13 @@ checksum = "bb7bdea464ae038f09197b82430b921c53619fc8d2bcaf7b151013b3ca008017"
|
|||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ed9a53e5d4d9c573ae844bfac6872b159cb1d1585a83b29e7a64b7eef7332a"
|
||||
checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading 0.7.999",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -921,7 +921,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "coreaudio-sys-utils"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=4ca174cf83ebe32b3198478c2211d69678845bc7#4ca174cf83ebe32b3198478c2211d69678845bc7"
|
||||
source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=0989726a1b9b640a30dfdf3ea005a12c73ab8155#0989726a1b9b640a30dfdf3ea005a12c73ab8155"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"coreaudio-sys",
|
||||
|
|
@ -997,7 +997,7 @@ dependencies = [
|
|||
"fluent",
|
||||
"gtkbind",
|
||||
"intl-memoizer",
|
||||
"libloading 0.8.3",
|
||||
"libloading",
|
||||
"log",
|
||||
"mozbuild",
|
||||
"mozilla-central-workspace-hack",
|
||||
|
|
@ -1140,27 +1140,27 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cubeb"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db57570f2617f0214c11721e8d2325816d9dc936c2c472661ac5d90a30fba98"
|
||||
checksum = "3d105547cf8036cdb30e796ce0d06832af4766106a44574402fa2fd3c861a042"
|
||||
dependencies = [
|
||||
"cubeb-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cubeb-backend"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b00b0f3b84e315571bd8c4e18794180633066267a413f2f05bca65001adc8410"
|
||||
checksum = "67361fe9b49b4599e2a230ce322529b6ddd91df14897c872dcede716f8fbca81"
|
||||
dependencies = [
|
||||
"cubeb-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cubeb-core"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2380c03a7df0ea3744f6a210d6340f423935e53cbf2fd68ada84b5e808e46ac7"
|
||||
checksum = "ac08d314dd1ec6d41d9ccdeec70899c98ed3b89845367000dd6096099481bc73"
|
||||
dependencies = [
|
||||
"bitflags 1.999.999",
|
||||
"cubeb-sys",
|
||||
|
|
@ -1169,7 +1169,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cubeb-coreaudio"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=4ca174cf83ebe32b3198478c2211d69678845bc7#4ca174cf83ebe32b3198478c2211d69678845bc7"
|
||||
source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=0989726a1b9b640a30dfdf3ea005a12c73ab8155#0989726a1b9b640a30dfdf3ea005a12c73ab8155"
|
||||
dependencies = [
|
||||
"atomic",
|
||||
"audio-mixer",
|
||||
|
|
@ -1188,7 +1188,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cubeb-pulse"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/mozilla/cubeb-pulse-rs?rev=8ff972c8e2ec1782ff262ac4071c0415e69b1367#8ff972c8e2ec1782ff262ac4071c0415e69b1367"
|
||||
source = "git+https://github.com/mozilla/cubeb-pulse-rs?rev=8678dcab1c287de79c4c184ccc2e065bc62b70e2#8678dcab1c287de79c4c184ccc2e065bc62b70e2"
|
||||
dependencies = [
|
||||
"cubeb-backend",
|
||||
"pulse",
|
||||
|
|
@ -1199,9 +1199,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cubeb-sys"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c20c457d7b34dad6e0c1a9c759c96b4420b9e9917a572998b81835799a07e1d"
|
||||
checksum = "26073cd50c7b6ba4272204839f56921557609a0d67e092882cbb903df94cab39"
|
||||
dependencies = [
|
||||
"cmake",
|
||||
"pkg-config",
|
||||
|
|
@ -1213,7 +1213,7 @@ version = "0.20.0"
|
|||
source = "git+https://github.com/gfx-rs/wgpu?rev=18b758e3889bdd6ffa769085de15e2b96a0c1eb5#18b758e3889bdd6ffa769085de15e2b96a0c1eb5"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"libloading 0.8.3",
|
||||
"libloading",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
|
|
@ -3265,13 +3265,6 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.999"
|
||||
dependencies = [
|
||||
"libloading 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.3"
|
||||
|
|
@ -4292,7 +4285,7 @@ dependencies = [
|
|||
"core-foundation",
|
||||
"env_logger",
|
||||
"lazy_static",
|
||||
"libloading 0.8.3",
|
||||
"libloading",
|
||||
"log",
|
||||
"mozilla-central-workspace-hack",
|
||||
"pkcs11-bindings",
|
||||
|
|
@ -4635,7 +4628,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "pulse"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/mozilla/cubeb-pulse-rs?rev=8ff972c8e2ec1782ff262ac4071c0415e69b1367#8ff972c8e2ec1782ff262ac4071c0415e69b1367"
|
||||
source = "git+https://github.com/mozilla/cubeb-pulse-rs?rev=8678dcab1c287de79c4c184ccc2e065bc62b70e2#8678dcab1c287de79c4c184ccc2e065bc62b70e2"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"pulse-ffi",
|
||||
|
|
@ -4644,7 +4637,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "pulse-ffi"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/cubeb-pulse-rs?rev=8ff972c8e2ec1782ff262ac4071c0415e69b1367#8ff972c8e2ec1782ff262ac4071c0415e69b1367"
|
||||
source = "git+https://github.com/mozilla/cubeb-pulse-rs?rev=8678dcab1c287de79c4c184ccc2e065bc62b70e2#8678dcab1c287de79c4c184ccc2e065bc62b70e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -6674,7 +6667,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"khronos-egl",
|
||||
"libc",
|
||||
"libloading 0.8.3",
|
||||
"libloading",
|
||||
"log",
|
||||
"metal",
|
||||
"naga",
|
||||
|
|
|
|||
|
|
@ -173,9 +173,6 @@ memoffset = { path = "build/rust/memoffset" }
|
|||
# Patch `hashbrown` 0.12.* to depend on 0.14.*
|
||||
hashbrown = { path = "build/rust/hashbrown" }
|
||||
|
||||
# Patch `libloading` 0.7 to depend on 0.8, which moves from `winapi` to `windows-sys`.
|
||||
libloading = { path = "build/rust/libloading" }
|
||||
|
||||
# Patch `socket2` 0.4 to 0.5
|
||||
socket2 = { path = "build/rust/socket2" }
|
||||
|
||||
|
|
|
|||
|
|
@ -403,12 +403,20 @@ int32_t AccessibleWrap::GetInputType(const nsString& aInputTypeAttr) {
|
|||
}
|
||||
|
||||
void AccessibleWrap::GetTextEquiv(nsString& aText) {
|
||||
if (nsTextEquivUtils::HasNameRule(this, eNameFromSubtreeIfReqRule)) {
|
||||
// This is an accessible that normally doesn't get its name from its
|
||||
// subtree, so we collect the text equivalent explicitly.
|
||||
nsTextEquivUtils::GetTextEquivFromSubtree(this, aText);
|
||||
} else {
|
||||
Name(aText);
|
||||
// 1. Start with the name, since it might have been explicitly specified.
|
||||
if (Name(aText) != eNameFromSubtree) {
|
||||
// 2. If the name didn't come from the subtree, add the text from the
|
||||
// subtree.
|
||||
if (aText.IsEmpty()) {
|
||||
nsTextEquivUtils::GetTextEquivFromSubtree(this, aText);
|
||||
} else {
|
||||
nsAutoString subtree;
|
||||
nsTextEquivUtils::GetTextEquivFromSubtree(this, subtree);
|
||||
if (!subtree.IsEmpty()) {
|
||||
aText.Append(' ');
|
||||
aText.Append(subtree);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "NotificationController.h"
|
||||
|
||||
#include "CssAltContent.h"
|
||||
#include "DocAccessible-inl.h"
|
||||
#include "DocAccessibleChild.h"
|
||||
#include "LocalAccessible-inl.h"
|
||||
|
|
@ -823,6 +824,14 @@ void NotificationController::WillRefresh(mozilla::TimeStamp aTime) {
|
|||
}
|
||||
#endif
|
||||
|
||||
if (CssAltContent(textNode)) {
|
||||
// A11y doesn't care about the text rendered by layout if there is CSS
|
||||
// content alt text. We skip this here rather than when the update is
|
||||
// queued because the TextLeafAccessible might not exist yet and we
|
||||
// might need to create it below.
|
||||
continue;
|
||||
}
|
||||
|
||||
TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString);
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ using namespace mozilla::a11y;
|
|||
/**
|
||||
* The accessible for which we are computing a text equivalent. It is useful
|
||||
* for bailing out during recursive text computation, or for special cases
|
||||
* like step f. of the ARIA implementation guide.
|
||||
* like the "Embedded Control" section of the AccName spec.
|
||||
*/
|
||||
static const Accessible* sInitiatorAcc = nullptr;
|
||||
|
||||
|
|
@ -252,7 +252,7 @@ nsresult nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
|
|||
bool isEmptyTextEquiv = true;
|
||||
|
||||
// Attempt to find the value. If it's non-empty, append and return it. See the
|
||||
// "embedded control" section of the name spec.
|
||||
// "Embedded Control" section of the name spec.
|
||||
nsAutoString val;
|
||||
nsresult rv = AppendFromValue(aAccessible, &val);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
|
@ -261,16 +261,15 @@ nsresult nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
// If the name is from tooltip then append it to result string in the end
|
||||
// (see h. step of name computation guide).
|
||||
// If the name is from tooltip, we retrieve it now but only append it to the
|
||||
// result string later as a last resort. Otherwise, we append the name now.
|
||||
nsAutoString text;
|
||||
if (aAccessible->Name(text) != eNameFromTooltip) {
|
||||
isEmptyTextEquiv = !AppendString(aString, text);
|
||||
}
|
||||
|
||||
// Implementation of g) step of text equivalent computation guide. Go down
|
||||
// into subtree if accessible allows "text equivalent from subtree rule" or
|
||||
// it's not root and not control.
|
||||
// Implementation of the "Name From Content" step of the text alternative
|
||||
// computation guide. Traverse the accessible's subtree if allowed.
|
||||
if (isEmptyTextEquiv) {
|
||||
if (ShouldIncludeInSubtreeCalculation(aAccessible)) {
|
||||
rv = AppendFromAccessibleChildren(aAccessible, aString);
|
||||
|
|
@ -280,7 +279,7 @@ nsresult nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
|
|||
}
|
||||
}
|
||||
|
||||
// Implementation of h. step
|
||||
// Implementation of the "Tooltip" step
|
||||
if (isEmptyTextEquiv && !text.IsEmpty()) {
|
||||
AppendString(aString, text);
|
||||
if (isHTMLBlock) {
|
||||
|
|
@ -305,7 +304,6 @@ nsresult nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
|
|||
// computation. If the given accessible is not the root accessible (the
|
||||
// accessible the text alternative is computed for in the end) then append the
|
||||
// accessible value.
|
||||
|
||||
if (aAccessible == sInitiatorAcc) {
|
||||
return NS_OK_NO_NAME_CLAUSE_HANDLED;
|
||||
}
|
||||
|
|
@ -323,6 +321,7 @@ nsresult nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
|
|||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// For other accessibles, get the value directly.
|
||||
aAccessible->Value(text);
|
||||
|
||||
return AppendString(aString, text) ? NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
|
||||
|
|
|
|||
|
|
@ -20,17 +20,19 @@ class LocalAccessible;
|
|||
} // namespace mozilla
|
||||
|
||||
/**
|
||||
* Text equivalent computation rules (see nsTextEquivUtils::gRoleToNameRulesMap)
|
||||
* Text equivalent computation rules. These rules are mapped to accessible roles
|
||||
* in RoleMap.h.
|
||||
*/
|
||||
enum ETextEquivRule {
|
||||
// No rule.
|
||||
// No rule. Equivalent to "name from author."
|
||||
eNoNameRule = 0x00,
|
||||
|
||||
// Walk into subtree only if the currently navigated accessible is not root
|
||||
// accessible (i.e. if the accessible is part of text equivalent computation).
|
||||
eNameFromSubtreeIfReqRule = 0x01,
|
||||
|
||||
// Text equivalent computation from subtree is allowed.
|
||||
// Text equivalent computation from subtree is allowed. Equivalent to "name
|
||||
// from content."
|
||||
eNameFromSubtreeRule = 0x03,
|
||||
|
||||
// The accessible allows to append its value to text equivalent.
|
||||
|
|
@ -41,7 +43,9 @@ enum ETextEquivRule {
|
|||
|
||||
/**
|
||||
* The class provides utils methods to compute the accessible name and
|
||||
* description.
|
||||
* description. Note that, as of the Accessible Name and Description Computation
|
||||
* 1.2 specification, the phrases "text equivalent" and "text alternative" are
|
||||
* used interchangably.
|
||||
*/
|
||||
class nsTextEquivUtils {
|
||||
public:
|
||||
|
|
@ -61,7 +65,7 @@ class nsTextEquivUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Calculates the name from accessible subtree if allowed.
|
||||
* Calculates the name from the given accessible's subtree, if allowed.
|
||||
*
|
||||
* @param aAccessible [in] the given accessible
|
||||
* @param aName [out] accessible name
|
||||
|
|
@ -70,8 +74,10 @@ class nsTextEquivUtils {
|
|||
nsAString& aName);
|
||||
|
||||
/**
|
||||
* Calculates text equivalent from the subtree. Similar to GetNameFromSubtree.
|
||||
* However it returns not empty result for things like HTML p.
|
||||
* Calculates text equivalent from the subtree. This function is similar to
|
||||
* GetNameFromSubtree, but it returns a non-empty result for things like
|
||||
* HTML:p, since it does not verify that the given accessible allows name
|
||||
* from content.
|
||||
*/
|
||||
static void GetTextEquivFromSubtree(const Accessible* aAccessible,
|
||||
nsString& aTextEquiv) {
|
||||
|
|
@ -82,8 +88,8 @@ class nsTextEquivUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Calculates text equivalent for the given accessible from its IDRefs
|
||||
* attribute (like aria-labelledby or aria-describedby).
|
||||
* Calculates the text equivalent for the given accessible from one of its
|
||||
* IDRefs attributes (like aria-labelledby or aria-describedby).
|
||||
*
|
||||
* @param aAccessible [in] the accessible text equivalent is computed for
|
||||
* @param aIDRefsAttr [in] IDRefs attribute on DOM node of the accessible
|
||||
|
|
@ -94,8 +100,8 @@ class nsTextEquivUtils {
|
|||
nsAString& aTextEquiv);
|
||||
|
||||
/**
|
||||
* Calculates the text equivalent from the given content and its subtree if
|
||||
* allowed and appends it to the given string.
|
||||
* Calculates the text equivalent from the given content - and its subtree, if
|
||||
* allowed - and appends it to the given string.
|
||||
*
|
||||
* @param aInitiatorAcc [in] the accessible text equivalent is computed for
|
||||
* in the end (root accessible of text equivalent
|
||||
|
|
@ -119,8 +125,8 @@ class nsTextEquivUtils {
|
|||
nsAString* aString);
|
||||
|
||||
/**
|
||||
* Iterates DOM children and calculates text equivalent from each child node.
|
||||
* Then, appends found text to the given string.
|
||||
* Iterates DOM children and calculates the text equivalent from each child
|
||||
* node. Then, appends the calculated text to the given string.
|
||||
*
|
||||
* @param aContent [in] the node to fetch DOM children from
|
||||
* @param aString [in, out] the string
|
||||
|
|
@ -130,33 +136,35 @@ class nsTextEquivUtils {
|
|||
|
||||
private:
|
||||
/**
|
||||
* Iterates accessible children and calculates text equivalent from each
|
||||
* child.
|
||||
* Iterates the given accessible's children and calculates the text equivalent
|
||||
* from each child. Then, appends the calculated text to the given string.
|
||||
*/
|
||||
static nsresult AppendFromAccessibleChildren(const Accessible* aAccessible,
|
||||
nsAString* aString);
|
||||
|
||||
/**
|
||||
* Calculates text equivalent from the given accessible and its subtree if
|
||||
* allowed.
|
||||
* Calculates the text equivalent from the given accessible - and its subtree,
|
||||
* if allowed. Then, appends the calculated text to the given string.
|
||||
*/
|
||||
static nsresult AppendFromAccessible(Accessible* aAccessible,
|
||||
nsAString* aString);
|
||||
|
||||
/**
|
||||
* Calculates text equivalent from the value of given accessible.
|
||||
* Calculates the text equivalent from the value of the given accessible.
|
||||
* Then, appends the calculated text to the given string. This function
|
||||
* implements the "Embedded Control" section of the AccName spec.
|
||||
*/
|
||||
static nsresult AppendFromValue(Accessible* aAccessible, nsAString* aString);
|
||||
|
||||
/**
|
||||
* Calculates text equivalent from the given DOM node and its subtree if
|
||||
* allowed.
|
||||
* Calculates the text equivalent from the given DOM node - and its subtree,
|
||||
* if allowed. Then, appends the calculated text to the given string.
|
||||
*/
|
||||
static nsresult AppendFromDOMNode(nsIContent* aContent, nsAString* aString);
|
||||
|
||||
/**
|
||||
* Concatenates strings and appends space between them. Returns true if
|
||||
* text equivalent string was appended.
|
||||
* Concatenates the given strings and appends space between them. Returns true
|
||||
* if the text equivalent string was appended.
|
||||
*/
|
||||
static bool AppendString(nsAString* aString,
|
||||
const nsAString& aTextEquivalent);
|
||||
|
|
@ -173,7 +181,7 @@ class nsTextEquivUtils {
|
|||
static bool ShouldIncludeInSubtreeCalculation(Accessible* aAccessible);
|
||||
|
||||
/**
|
||||
* Returns true if a given accessible is a text leaf containing only
|
||||
* Returns true if the given accessible is a text leaf containing only
|
||||
* whitespace.
|
||||
*/
|
||||
static bool IsWhitespaceLeaf(Accessible* aAccessible);
|
||||
|
|
|
|||
|
|
@ -802,6 +802,9 @@ pref("browser.shopping.experience2023.sidebarClosedCount", 0);
|
|||
// When conditions are met, shows a prompt on the shopping sidebar asking users if they want to disable auto-open behavior
|
||||
pref("browser.shopping.experience2023.showKeepSidebarClosedMessage", true);
|
||||
|
||||
// Spin the cursor while the page is loading
|
||||
pref("browser.spin_cursor_while_busy", false);
|
||||
|
||||
// Enable display of megalist option in browser sidebar
|
||||
// Keep it hidden from about:config for now.
|
||||
// pref("browser.megalist.enabled", false);
|
||||
|
|
|
|||
|
|
@ -3435,6 +3435,10 @@ var XULBrowserWindow = {
|
|||
) {
|
||||
this.busyUI = true;
|
||||
|
||||
if (this.spinCursorWhileBusy) {
|
||||
window.setCursor("progress");
|
||||
}
|
||||
|
||||
// XXX: This needs to be based on window activity...
|
||||
this.stopCommand.removeAttribute("disabled");
|
||||
CombinedStopReload.switchToStop(aRequest, aWebProgress);
|
||||
|
|
@ -3502,6 +3506,8 @@ var XULBrowserWindow = {
|
|||
if (this.busyUI && aWebProgress.isTopLevel) {
|
||||
this.busyUI = false;
|
||||
|
||||
window.setCursor("auto");
|
||||
|
||||
this.stopCommand.setAttribute("disabled", "true");
|
||||
CombinedStopReload.switchToReload(aRequest, aWebProgress);
|
||||
}
|
||||
|
|
@ -3967,6 +3973,12 @@ var XULBrowserWindow = {
|
|||
},
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
XULBrowserWindow,
|
||||
"spinCursorWhileBusy",
|
||||
"browser.spin_cursor_while_busy"
|
||||
);
|
||||
|
||||
var LinkTargetDisplay = {
|
||||
get DELAY_SHOW() {
|
||||
delete this.DELAY_SHOW;
|
||||
|
|
|
|||
|
|
@ -233,6 +233,34 @@ newtab:
|
|||
send_in_pings:
|
||||
- newtab
|
||||
|
||||
wallpaper_click:
|
||||
type: event
|
||||
description: >
|
||||
Recorded when a user clicks on a wallpaper option
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1896004
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1896004
|
||||
data_sensitivity:
|
||||
- interaction
|
||||
notification_emails:
|
||||
- nbarrett@mozilla.com
|
||||
expires: never
|
||||
extra_keys:
|
||||
selected_wallpaper:
|
||||
description: >
|
||||
Which wallpaper has been selected by the user
|
||||
Will be the title of a Wallpaper or 'none' for users
|
||||
that reset the background to default
|
||||
type: string
|
||||
had_previous_wallpaper:
|
||||
description: >
|
||||
Wheather or not user had a previously set wallpaper
|
||||
type: boolean
|
||||
newtab_visit_id: *newtab_visit_id
|
||||
send_in_pings:
|
||||
- newtab
|
||||
|
||||
weather_change_display:
|
||||
type: event
|
||||
description: >
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "99c7f10cf8f804a1db6f875e9d0f668951fe1230"
|
||||
"revision": "6a0ec19cbba9c1626509305a0af58c7925f4bbdd"
|
||||
},
|
||||
"bg": {
|
||||
"pin": false,
|
||||
|
|
@ -358,7 +358,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "941aa270f1bc978b45cd96192dd67beb2b53656b"
|
||||
"revision": "39847764e40b764c0a3cfc5fe9e79a212b318f22"
|
||||
},
|
||||
"da": {
|
||||
"pin": false,
|
||||
|
|
@ -396,7 +396,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "df2005e53260bbf47b32c2f092c9b1d72707d011"
|
||||
"revision": "5261e1350e923eaaf014c329a93e04fe292fec57"
|
||||
},
|
||||
"dsb": {
|
||||
"pin": false,
|
||||
|
|
@ -415,7 +415,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "757c97c67dbb5795e042146d99e36619ee81b897"
|
||||
"revision": "4d0723a62e375d3c62909892735dce95f5b719a9"
|
||||
},
|
||||
"el": {
|
||||
"pin": false,
|
||||
|
|
@ -529,7 +529,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b3e50d2e42a769cffb3ae30c953d09f9a435a6af"
|
||||
"revision": "8dce7cc5172e4fec2ffa59a1743bbd5d903bb21f"
|
||||
},
|
||||
"es-ES": {
|
||||
"pin": false,
|
||||
|
|
@ -719,7 +719,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "f10afc727e91f871d898d31b5a9b0c6d56b092c0"
|
||||
"revision": "39c000ffab7363ba9cc8c7e15c6ad14c581336f9"
|
||||
},
|
||||
"ga-IE": {
|
||||
"pin": false,
|
||||
|
|
@ -890,7 +890,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "3db9ee337797d21a90f164ed037bfae7d04e7609"
|
||||
"revision": "f2bf14543268340a4a926e2312bebbf8685df5d3"
|
||||
},
|
||||
"hu": {
|
||||
"pin": false,
|
||||
|
|
@ -909,7 +909,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "43352212205f762577018f1b5fc8c796d77f82c7"
|
||||
"revision": "8bbfe04b73c31a07a8e773db7c66eee0ca28ecc3"
|
||||
},
|
||||
"hy-AM": {
|
||||
"pin": false,
|
||||
|
|
@ -1257,7 +1257,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "a424b2c5b2a4aa0576a4ffb7a381091c1b212473"
|
||||
"revision": "9477991e2e620b21d5bc07249c1221b76a5fc8f5"
|
||||
},
|
||||
"meh": {
|
||||
"pin": false,
|
||||
|
|
@ -1409,7 +1409,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "a3af3d2d9c45eeb055b21f769abf8dcc496898cf"
|
||||
"revision": "deccbe803c4925d91fd4ca0ace0d3858af619c3d"
|
||||
},
|
||||
"nn-NO": {
|
||||
"pin": false,
|
||||
|
|
@ -1504,7 +1504,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "3e6a5b601d803f0782b9a39301d26cd2d02e2cda"
|
||||
"revision": "53d7e4683243d8dd28b9f71a6b0c376de7402bc0"
|
||||
},
|
||||
"pt-PT": {
|
||||
"pin": false,
|
||||
|
|
@ -1580,7 +1580,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "565aa25b27feb6e54292a13f6ec95d6db0a899ca"
|
||||
"revision": "cbfbe72a7049d2934b239628f15aed063d998880"
|
||||
},
|
||||
"sat": {
|
||||
"pin": false,
|
||||
|
|
@ -1694,7 +1694,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "c8cc5fa50635433e6b9945b2eaba48544dad80d3"
|
||||
"revision": "67e2b9de3a6db4a9f354110ff7403df9e47195a7"
|
||||
},
|
||||
"skr": {
|
||||
"pin": false,
|
||||
|
|
@ -1884,7 +1884,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "23144faf9ad3ec97302d41ecfb0a3c307ce40987"
|
||||
"revision": "e3c7eaf5ca18eaa4b2a841306bd71ff60ac45049"
|
||||
},
|
||||
"th": {
|
||||
"pin": false,
|
||||
|
|
@ -1941,7 +1941,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "81c113dcfdc12cc9d156e478978ef5abc7a4e34e"
|
||||
"revision": "e76162369cf8cffca8ce5c307e6f5f0979a1e505"
|
||||
},
|
||||
"trs": {
|
||||
"pin": false,
|
||||
|
|
@ -1979,7 +1979,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "c71cbc1877a43b1c8013d29d512602efb9f72f3a"
|
||||
"revision": "22962edbb5280103883a3a6b239e9cac575d65ce"
|
||||
},
|
||||
"ur": {
|
||||
"pin": false,
|
||||
|
|
@ -2112,6 +2112,6 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "2e36f22fa527800bfc302c000fd0255f43fd9293"
|
||||
"revision": "2a77385c1f92f1974f7847d883a78431130d5066"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
[package]
|
||||
name = "libloading"
|
||||
version = "0.7.999"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies.libloading]
|
||||
version = "0.8.3"
|
||||
|
|
@ -1,4 +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/. */
|
||||
pub use libloading::*;
|
||||
|
|
@ -210,6 +210,15 @@ static void _relrhack_init(void) {
|
|||
}
|
||||
}
|
||||
|
||||
# ifdef ANDROID
|
||||
// This creates a value that uses a relative relocation.
|
||||
// In the case we've not set DT_RELRHACK_BIT on the RELR tags in the
|
||||
// dynamic section, if the dynamic loader applied the relocation,
|
||||
// the value will correctly point to the function address in memory.
|
||||
__attribute__((visibility("hidden"))) void (*__relrhack_init)(void) =
|
||||
_relrhack_init;
|
||||
# endif
|
||||
|
||||
// The Android CRT doesn't contain an init function.
|
||||
# ifndef ANDROID
|
||||
extern __attribute__((visibility("hidden"))) void _init(int argc, char** argv,
|
||||
|
|
@ -217,6 +226,13 @@ extern __attribute__((visibility("hidden"))) void _init(int argc, char** argv,
|
|||
# endif
|
||||
|
||||
void _relrhack_wrap_init(int argc, char** argv, char** env) {
|
||||
# ifdef ANDROID
|
||||
// When the dynamic loader has applied the relocations itself, do nothing.
|
||||
// (see comment above __relrhack_init)
|
||||
if (__relrhack_init == _relrhack_init) {
|
||||
return;
|
||||
}
|
||||
# endif
|
||||
_relrhack_init();
|
||||
# ifndef ANDROID
|
||||
_init(argc, argv, env);
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ struct RelR : public Elf<bits> {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool hack(std::fstream& f);
|
||||
static bool hack(std::fstream& f, bool set_relrhack_bit);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
|
@ -150,7 +150,7 @@ void write_vector_at(std::ostream& out, off_t pos, const std::vector<T>& vec) {
|
|||
}
|
||||
|
||||
template <int bits>
|
||||
bool RelR<bits>::hack(std::fstream& f) {
|
||||
bool RelR<bits>::hack(std::fstream& f, bool set_relrhack_bit) {
|
||||
auto ehdr = read_one_at<Elf_Ehdr>(f, 0);
|
||||
if (ehdr.e_phentsize != sizeof(Elf_Phdr)) {
|
||||
throw std::runtime_error("Invalid ELF?");
|
||||
|
|
@ -225,9 +225,11 @@ bool RelR<bits>::hack(std::fstream& f) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Change DT_RELR* tags to add DT_RELRHACK_BIT.
|
||||
for (const auto tag : {DT_RELR, DT_RELRSZ, DT_RELRENT}) {
|
||||
write_one_at(f, dyn_info.offset(tag), tag | DT_RELRHACK_BIT);
|
||||
if (set_relrhack_bit) {
|
||||
// Change DT_RELR* tags to add DT_RELRHACK_BIT.
|
||||
for (const auto tag : {DT_RELR, DT_RELRSZ, DT_RELRENT}) {
|
||||
write_one_at(f, dyn_info.offset(tag), tag | DT_RELRHACK_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_glibc = false;
|
||||
|
|
@ -611,10 +613,14 @@ int main(int argc, char* argv[]) {
|
|||
f.exceptions(f.failbit);
|
||||
auto elf_class = get_elf_class(f);
|
||||
f.seekg(0, std::ios::beg);
|
||||
// On Android, we don't set the relrhack bit so that the system linker
|
||||
// can handle the RELR relocations when supported. On desktop, the
|
||||
// glibc unfortunately rejects the binaries without the bit set.
|
||||
// (see comment about GLIBC_ABI_DT_RELR)
|
||||
if (elf_class == ELFCLASS32) {
|
||||
hacked = RelR<32>::hack(f);
|
||||
hacked = RelR<32>::hack(f, /* set_relrhack_bit = */ !is_android);
|
||||
} else if (elf_class == ELFCLASS64) {
|
||||
hacked = RelR<64>::hack(f);
|
||||
hacked = RelR<64>::hack(f, /* set_relrhack_bit = */ !is_android);
|
||||
}
|
||||
} catch (const std::runtime_error& err) {
|
||||
std::cerr << "Failed to hack " << output->string() << ": " << err.what()
|
||||
|
|
|
|||
13
dom/base/crashtests/1896225.html
Normal file
13
dom/base/crashtests/1896225.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.find("A")
|
||||
let s = window.getSelection()
|
||||
s.extend(a.attachShadow({mode: "open"}), 0)
|
||||
s.extend(b)
|
||||
document.execCommand("insertUnorderedList", false)
|
||||
})
|
||||
</script>
|
||||
A
|
||||
<select autofocus=""></select>
|
||||
<h2 id="a">
|
||||
<del id="b" contenteditable="true">
|
||||
|
|
@ -277,4 +277,5 @@ load 1887963_1.html
|
|||
load 1887963_2.html
|
||||
asserts(0-1) load 1887974.html
|
||||
load 1890888.html
|
||||
load 1896225.html
|
||||
load 1897248.html
|
||||
|
|
|
|||
|
|
@ -474,15 +474,21 @@ bool nsFrameMessageManager::GetParamsForMessage(JSContext* aCx,
|
|||
// properly cases when interface is implemented in JS and used
|
||||
// as a dictionary.
|
||||
nsAutoString json;
|
||||
NS_ENSURE_TRUE(
|
||||
nsContentUtils::StringifyJSON(aCx, v, json, UndefinedIsNullStringLiteral),
|
||||
false);
|
||||
if (!nsContentUtils::StringifyJSON(aCx, v, json,
|
||||
UndefinedIsNullStringLiteral)) {
|
||||
NS_WARNING("nsContentUtils::StringifyJSON() failed");
|
||||
JS_ClearPendingException(aCx);
|
||||
return false;
|
||||
}
|
||||
NS_ENSURE_TRUE(!json.IsEmpty(), false);
|
||||
|
||||
JS::Rooted<JS::Value> val(aCx, JS::NullValue());
|
||||
NS_ENSURE_TRUE(JS_ParseJSON(aCx, static_cast<const char16_t*>(json.get()),
|
||||
json.Length(), &val),
|
||||
false);
|
||||
if (!JS_ParseJSON(aCx, static_cast<const char16_t*>(json.get()),
|
||||
json.Length(), &val)) {
|
||||
NS_WARNING("JS_ParseJSON");
|
||||
JS_ClearPendingException(aCx);
|
||||
return false;
|
||||
}
|
||||
|
||||
aData.Write(aCx, val, rv);
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
|
|
|
|||
|
|
@ -3580,7 +3580,7 @@ void EventStateManager::DecideGestureEvent(WidgetGestureNotifyEvent* aEvent,
|
|||
displayPanFeedback = true;
|
||||
}
|
||||
} // scrollableFrame
|
||||
} // ancestor chain
|
||||
} // ancestor chain
|
||||
aEvent->mDisplayPanFeedback = displayPanFeedback;
|
||||
aEvent->mPanDirection = panDirection;
|
||||
}
|
||||
|
|
@ -4821,6 +4821,11 @@ nsresult EventStateManager::SetCursor(StyleCursorKind aCursor,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
bool EventStateManager::CursorSettingManagerHasLockedCursor() {
|
||||
return sCursorSettingManager &&
|
||||
sCursorSettingManager->mLockCursor != kInvalidCursorKind;
|
||||
}
|
||||
|
||||
class MOZ_STACK_CLASS ESMEventCB : public EventDispatchingCallback {
|
||||
public:
|
||||
explicit ESMEventCB(nsIContent* aTarget) : mTarget(aTarget) {}
|
||||
|
|
|
|||
|
|
@ -425,6 +425,9 @@ class EventStateManager : public nsSupportsWeakReference, public nsIObserver {
|
|||
static EventStateManager* sCursorSettingManager;
|
||||
static void ClearCursorSettingManager() { sCursorSettingManager = nullptr; }
|
||||
|
||||
// Checks if the manager in this process has a locked cursor
|
||||
static bool CursorSettingManagerHasLockedCursor();
|
||||
|
||||
static EventStateManager* GetActiveEventStateManager() { return sActiveESM; }
|
||||
|
||||
// Sets aNewESM to be the active event state manager, and
|
||||
|
|
|
|||
|
|
@ -1652,8 +1652,7 @@ FetchDriver::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
|
|||
// we need to strip Authentication headers for cross-origin requests
|
||||
// Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
|
||||
bool skipAuthHeader =
|
||||
(StaticPrefs::network_fetch_redirect_stripAuthHeader() &&
|
||||
NS_ShouldRemoveAuthHeaderOnRedirect(aOldChannel, aNewChannel, aFlags));
|
||||
NS_ShouldRemoveAuthHeaderOnRedirect(aOldChannel, aNewChannel, aFlags);
|
||||
|
||||
SetRequestHeaders(newHttpChannel, rewriteToGET, skipAuthHeader);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1456,8 +1456,10 @@ void BrowserParent::MouseEnterIntoWidget() {
|
|||
// When we mouseenter the remote target, the remote target's cursor should
|
||||
// become the current cursor. When we mouseexit, we stop.
|
||||
mRemoteTargetSetsCursor = true;
|
||||
widget->SetCursor(mCursor);
|
||||
EventStateManager::ClearCursorSettingManager();
|
||||
if (!EventStateManager::CursorSettingManagerHasLockedCursor()) {
|
||||
widget->SetCursor(mCursor);
|
||||
EventStateManager::ClearCursorSettingManager();
|
||||
}
|
||||
}
|
||||
|
||||
// Mark that we have missed a mouse enter event, so that
|
||||
|
|
@ -1495,8 +1497,10 @@ void BrowserParent::SendRealMouseEvent(WidgetMouseEvent& aEvent) {
|
|||
// become the current cursor. When we mouseexit, we stop.
|
||||
if (eMouseEnterIntoWidget == aEvent.mMessage) {
|
||||
mRemoteTargetSetsCursor = true;
|
||||
widget->SetCursor(mCursor);
|
||||
EventStateManager::ClearCursorSettingManager();
|
||||
if (!EventStateManager::CursorSettingManagerHasLockedCursor()) {
|
||||
widget->SetCursor(mCursor);
|
||||
EventStateManager::ClearCursorSettingManager();
|
||||
}
|
||||
} else if (eMouseExitFromWidget == aEvent.mMessage) {
|
||||
mRemoteTargetSetsCursor = false;
|
||||
}
|
||||
|
|
@ -2354,6 +2358,10 @@ mozilla::ipc::IPCResult BrowserParent::RecvSetCursor(
|
|||
return IPC_OK();
|
||||
}
|
||||
|
||||
if (EventStateManager::CursorSettingManagerHasLockedCursor()) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
widget->SetCursor(mCursor);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
|
|
|||
36
dom/media/gtest/TestMediaInfo.cpp
Normal file
36
dom/media/gtest/TestMediaInfo.cpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* -*- 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 "MediaInfo.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
TEST(TestMediaInfo, CloneVideoInfo)
|
||||
{
|
||||
VideoInfo info;
|
||||
info.mDisplay = info.mImage = gfx::IntSize{640, 360};
|
||||
info.mStereoMode = StereoMode::BOTTOM_TOP;
|
||||
info.mRotation = VideoRotation::kDegree_270;
|
||||
info.mColorDepth = gfx::ColorDepth::COLOR_16;
|
||||
info.mColorRange = gfx::ColorRange::FULL;
|
||||
|
||||
UniquePtr<TrackInfo> clone1 = info.Clone();
|
||||
UniquePtr<TrackInfo> clone2 = info.Clone();
|
||||
|
||||
auto* info1 = clone1->GetAsVideoInfo();
|
||||
auto* info2 = clone2->GetAsVideoInfo();
|
||||
|
||||
EXPECT_EQ(info1->mDisplay, info2->mDisplay);
|
||||
EXPECT_EQ(info1->mImage, info2->mImage);
|
||||
EXPECT_EQ(info1->mStereoMode, info2->mStereoMode);
|
||||
EXPECT_EQ(info1->mRotation, info2->mRotation);
|
||||
EXPECT_EQ(info1->mColorDepth, info2->mColorDepth);
|
||||
EXPECT_EQ(info1->mColorRange, info2->mColorRange);
|
||||
// They should have their own media byte buffers which have different address
|
||||
EXPECT_NE(info1->mExtraData.get(), info2->mExtraData.get());
|
||||
EXPECT_NE(info1->mCodecSpecificConfig.get(),
|
||||
info2->mCodecSpecificConfig.get());
|
||||
}
|
||||
|
|
@ -53,6 +53,7 @@ UNIFIED_SOURCES += [
|
|||
"TestMediaDataDecoder.cpp",
|
||||
"TestMediaDataEncoder.cpp",
|
||||
"TestMediaEventSource.cpp",
|
||||
"TestMediaInfo.cpp",
|
||||
"TestMediaMIMETypes.cpp",
|
||||
"TestMediaQueue.cpp",
|
||||
"TestMediaSpan.cpp",
|
||||
|
|
|
|||
|
|
@ -3542,7 +3542,6 @@ XMLHttpRequestMainThread::AsyncOnChannelRedirect(
|
|||
// we need to strip Authentication headers for cross-origin requests
|
||||
// Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
|
||||
bool stripAuth =
|
||||
StaticPrefs::network_fetch_redirect_stripAuthHeader() &&
|
||||
NS_ShouldRemoveAuthHeaderOnRedirect(aOldChannel, aNewChannel, aFlags);
|
||||
|
||||
OnRedirectVerifyCallback(NS_OK, stripAuth);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
--blinterp-eager
|
||||
--cache-ir-stubs=off
|
||||
--cache-ir-stubs=on
|
||||
--enable-json-parse-with-source
|
||||
--ion-check-range-analysis
|
||||
--ion-eager
|
||||
--ion-edgecase-analysis=off
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ diff --git a/test/test_duplex.cpp b/test/test_duplex.cpp
|
|||
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
|
||||
#define INPUT_CHANNELS 1
|
||||
#define INPUT_LAYOUT CUBEB_LAYOUT_MONO
|
||||
@@ -201,16 +203,18 @@ TEST(cubeb, duplex_collection_change)
|
||||
ASSERT_EQ(r, CUBEB_OK);
|
||||
@@ -202,16 +204,18 @@ TEST(cubeb, duplex_collection_change)
|
||||
}
|
||||
|
||||
#ifdef GTEST_HAS_DEATH_TEST
|
||||
TEST(cubeb, duplex_collection_change_no_unregister)
|
||||
{
|
||||
cubeb * ctx;
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
From 68ef0eb5691aa7b9d634b4d1af85f9d66fdfc06e Mon Sep 17 00:00:00 2001
|
||||
From: Mike Hommey <mh@glandium.org>
|
||||
Date: Thu, 7 Mar 2024 08:01:32 +0900
|
||||
Subject: [PATCH] Only build duplex_collection_change_no_unregister when death
|
||||
tests are supported
|
||||
|
||||
---
|
||||
test/test_duplex.cpp | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/test/test_duplex.cpp b/test/test_duplex.cpp
|
||||
index 518f44f..98a6701 100644
|
||||
--- a/test/test_duplex.cpp
|
||||
+++ b/test/test_duplex.cpp
|
||||
@@ -201,6 +201,7 @@ TEST(cubeb, duplex_collection_change)
|
||||
ASSERT_EQ(r, CUBEB_OK);
|
||||
}
|
||||
|
||||
+#ifdef GTEST_HAS_DEATH_TEST
|
||||
TEST(cubeb, duplex_collection_change_no_unregister)
|
||||
{
|
||||
cubeb * ctx;
|
||||
@@ -221,6 +222,7 @@ TEST(cubeb, duplex_collection_change_no_unregister)
|
||||
|
||||
duplex_collection_change_impl(ctx);
|
||||
}
|
||||
+#endif
|
||||
|
||||
long
|
||||
data_cb_input(cubeb_stream * stream, void * user, const void * inputbuffer,
|
||||
--
|
||||
2.44.0.1.g9765aa7075
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ diff --git a/test/test_duplex.cpp b/test/test_duplex.cpp
|
|||
#include "common.h"
|
||||
|
||||
#define SAMPLE_FREQUENCY 48000
|
||||
@@ -294,16 +296,28 @@ TEST(cubeb, one_duplex_one_input)
|
||||
@@ -294,16 +296,27 @@ TEST(cubeb, one_duplex_one_input)
|
||||
cubeb * ctx;
|
||||
cubeb_stream * duplex_stream;
|
||||
cubeb_stream_params input_params;
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
From 19fcbefe1a9c5e22f8111af251df27b41658bc77 Mon Sep 17 00:00:00 2001
|
||||
From: John Lin <jolin@mozilla.com>
|
||||
Date: Mon, 29 Apr 2024 13:46:57 -0700
|
||||
Subject: [PATCH] Invalidate timing info buffers when destorying AAudio stream.
|
||||
|
||||
aaudio_stream_get_position() returns incorrect result because
|
||||
aaudio_stream_init() recycled destroyed stream where the
|
||||
timing_info buffers contain stale data.
|
||||
---
|
||||
src/cubeb_aaudio.cpp | 2 ++
|
||||
src/cubeb_triple_buffer.h | 7 +++++++
|
||||
test/test_triple_buffer.cpp | 3 +++
|
||||
3 files changed, 12 insertions(+)
|
||||
|
||||
diff --git a/src/cubeb_aaudio.cpp b/src/cubeb_aaudio.cpp
|
||||
index cfae2d6f..8b5eb231 100644
|
||||
--- a/src/cubeb_aaudio.cpp
|
||||
+++ b/src/cubeb_aaudio.cpp
|
||||
@@ -1049,6 +1049,8 @@ aaudio_stream_destroy_locked(cubeb_stream * stm, lock_guard<mutex> & lock)
|
||||
stm->istream = nullptr;
|
||||
}
|
||||
|
||||
+ stm->timing_info.invalidate();
|
||||
+
|
||||
if (stm->resampler) {
|
||||
cubeb_resampler_destroy(stm->resampler);
|
||||
stm->resampler = nullptr;
|
||||
diff --git a/src/cubeb_triple_buffer.h b/src/cubeb_triple_buffer.h
|
||||
index a5a5978f..759b92e6 100644
|
||||
--- a/src/cubeb_triple_buffer.h
|
||||
+++ b/src/cubeb_triple_buffer.h
|
||||
@@ -42,6 +42,13 @@ template <typename T> class triple_buffer {
|
||||
{
|
||||
return (shared_state.load(std::memory_order_relaxed) & BACK_DIRTY_BIT) != 0;
|
||||
}
|
||||
+ // Reset state and indices to initial values.
|
||||
+ void invalidate()
|
||||
+ {
|
||||
+ shared_state.store(0, std::memory_order_release);
|
||||
+ input_idx = 1;
|
||||
+ output_idx = 2;
|
||||
+ }
|
||||
|
||||
private:
|
||||
// Publish a value to the consumer. Returns true if the data was overwritten
|
||||
diff --git a/test/test_triple_buffer.cpp b/test/test_triple_buffer.cpp
|
||||
index a6e0049b..d463c07e 100644
|
||||
--- a/test/test_triple_buffer.cpp
|
||||
+++ b/test/test_triple_buffer.cpp
|
||||
@@ -64,4 +64,7 @@ TEST(cubeb, triple_buffer)
|
||||
}
|
||||
|
||||
t.join();
|
||||
+
|
||||
+ buffer.invalidate();
|
||||
+ ASSERT_FALSE(buffer.updated());
|
||||
}
|
||||
|
|
@ -9,8 +9,8 @@ origin:
|
|||
description: "Cross platform audio library"
|
||||
url: https://github.com/mozilla/cubeb
|
||||
license: ISC
|
||||
release: 74d6b0546576d9ee13a078ed51f87a6eede62014 (2024-02-15T12:42:53Z).
|
||||
revision: 74d6b0546576d9ee13a078ed51f87a6eede62014
|
||||
release: 6c1a6e151c1f981a2800d40af7c041cfcccc710e (2024-05-21T16:11:12Z).
|
||||
revision: 6c1a6e151c1f981a2800d40af7c041cfcccc710e
|
||||
|
||||
vendoring:
|
||||
url: https://github.com/mozilla/cubeb
|
||||
|
|
@ -19,10 +19,8 @@ vendoring:
|
|||
patches:
|
||||
- 0001-disable-aaudio-before-android-31.patch
|
||||
- 0002-disable-crash-reporter-death-test.patch
|
||||
- 0003-Only-build-duplex_collection_change_no_unregister-wh.patch
|
||||
- 0004-audiounit-ios-compile-fixes.patch
|
||||
- 0005-aaudio-timing-fix.patch
|
||||
- 0006-disable-cubeb_one_duplex_one_input-macos10.15.patch
|
||||
- 0003-audiounit-ios-compile-fixes.patch
|
||||
- 0004-disable-cubeb_one_duplex_one_input-macos10.15.patch
|
||||
skip-vendoring-steps:
|
||||
- update-moz-build
|
||||
exclude:
|
||||
|
|
|
|||
|
|
@ -95,7 +95,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 > 384000 ||
|
||||
output_stream_params->rate > 768000 ||
|
||||
output_stream_params->channels < 1 ||
|
||||
output_stream_params->channels > UINT8_MAX) {
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
|
|
@ -103,7 +103,7 @@ validate_stream_params(cubeb_stream_params * input_stream_params,
|
|||
}
|
||||
if (input_stream_params) {
|
||||
if (input_stream_params->rate < 1000 ||
|
||||
input_stream_params->rate > 384000 ||
|
||||
input_stream_params->rate > 768000 ||
|
||||
input_stream_params->channels < 1 ||
|
||||
input_stream_params->channels > UINT8_MAX) {
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
|
|
|
|||
|
|
@ -245,13 +245,24 @@ shutdown_with_error(cubeb_stream * stm)
|
|||
}
|
||||
|
||||
int64_t poll_frequency_ns = NS_PER_S * stm->out_frame_size / stm->sample_rate;
|
||||
int rv;
|
||||
if (stm->istream) {
|
||||
wait_for_state_change(stm->istream, AAUDIO_STREAM_STATE_STOPPED,
|
||||
poll_frequency_ns);
|
||||
rv = wait_for_state_change(stm->istream, AAUDIO_STREAM_STATE_STOPPED,
|
||||
poll_frequency_ns);
|
||||
if (rv != CUBEB_OK) {
|
||||
LOG("Failure when waiting for stream change on the input side when "
|
||||
"shutting down in error");
|
||||
// Not much we can do, carry on
|
||||
}
|
||||
}
|
||||
if (stm->ostream) {
|
||||
wait_for_state_change(stm->ostream, AAUDIO_STREAM_STATE_STOPPED,
|
||||
poll_frequency_ns);
|
||||
rv = wait_for_state_change(stm->ostream, AAUDIO_STREAM_STATE_STOPPED,
|
||||
poll_frequency_ns);
|
||||
if (rv != CUBEB_OK) {
|
||||
LOG("Failure when waiting for stream change on the output side when "
|
||||
"shutting down in error");
|
||||
// Not much we can do, carry on
|
||||
}
|
||||
}
|
||||
|
||||
assert(!stm->in_data_callback.load());
|
||||
|
|
@ -921,7 +932,7 @@ aaudio_error_cb(AAudioStream * astream, void * user_data, aaudio_result_t error)
|
|||
assert(stm->ostream == astream || stm->istream == astream);
|
||||
|
||||
// Device change -- reinitialize on the new default device.
|
||||
if (error == AAUDIO_ERROR_DISCONNECTED) {
|
||||
if (error == AAUDIO_ERROR_DISCONNECTED || error == AAUDIO_ERROR_TIMEOUT) {
|
||||
LOG("Audio device change, reinitializing stream");
|
||||
reinitialize_stream(stm);
|
||||
return;
|
||||
|
|
|
|||
231
media/libcubeb/src/cubeb_audio_dump.cpp
Normal file
231
media/libcubeb/src/cubeb_audio_dump.cpp
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Copyright © 2023 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#define NOMINMAX
|
||||
|
||||
#include "cubeb_audio_dump.h"
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb_ringbuffer.h"
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using std::thread;
|
||||
using std::vector;
|
||||
|
||||
uint32_t
|
||||
bytes_per_sample(cubeb_stream_params params)
|
||||
{
|
||||
switch (params.format) {
|
||||
case CUBEB_SAMPLE_S16LE:
|
||||
case CUBEB_SAMPLE_S16BE:
|
||||
return sizeof(int16_t);
|
||||
case CUBEB_SAMPLE_FLOAT32LE:
|
||||
case CUBEB_SAMPLE_FLOAT32BE:
|
||||
return sizeof(float);
|
||||
};
|
||||
}
|
||||
|
||||
struct cubeb_audio_dump_stream {
|
||||
public:
|
||||
explicit cubeb_audio_dump_stream(cubeb_stream_params params)
|
||||
: sample_size(bytes_per_sample(params)),
|
||||
ringbuffer(
|
||||
static_cast<int>(params.rate * params.channels * sample_size))
|
||||
{
|
||||
}
|
||||
|
||||
int open(const char * name)
|
||||
{
|
||||
file = fopen(name, "wb");
|
||||
if (!file) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
int close()
|
||||
{
|
||||
if (fclose(file)) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
// Directly write to the file. Useful to write the header.
|
||||
size_t write(uint8_t * data, uint32_t count)
|
||||
{
|
||||
return fwrite(data, count, 1, file);
|
||||
}
|
||||
|
||||
size_t write_all()
|
||||
{
|
||||
size_t written = 0;
|
||||
const int buf_sz = 16 * 1024;
|
||||
uint8_t buf[buf_sz];
|
||||
while (int rv = ringbuffer.dequeue(buf, buf_sz)) {
|
||||
written += fwrite(buf, rv, 1, file);
|
||||
}
|
||||
return written;
|
||||
}
|
||||
int dump(void * samples, uint32_t count)
|
||||
{
|
||||
int bytes = static_cast<int>(count * sample_size);
|
||||
int rv = ringbuffer.enqueue(static_cast<uint8_t *>(samples), bytes);
|
||||
return rv == bytes;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t sample_size;
|
||||
FILE * file{};
|
||||
lock_free_queue<uint8_t> ringbuffer;
|
||||
};
|
||||
|
||||
struct cubeb_audio_dump_session {
|
||||
public:
|
||||
cubeb_audio_dump_session() = default;
|
||||
~cubeb_audio_dump_session()
|
||||
{
|
||||
assert(streams.empty());
|
||||
session_thread.join();
|
||||
}
|
||||
cubeb_audio_dump_session(const cubeb_audio_dump_session &) = delete;
|
||||
cubeb_audio_dump_session &
|
||||
operator=(const cubeb_audio_dump_session &) = delete;
|
||||
cubeb_audio_dump_session & operator=(cubeb_audio_dump_session &&) = delete;
|
||||
|
||||
cubeb_audio_dump_stream_t create_stream(cubeb_stream_params params,
|
||||
const char * name)
|
||||
{
|
||||
if (running) {
|
||||
return nullptr;
|
||||
}
|
||||
auto * stream = new cubeb_audio_dump_stream(params);
|
||||
streams.push_back(stream);
|
||||
int rv = stream->open(name);
|
||||
if (rv != CUBEB_OK) {
|
||||
delete stream;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct riff_header {
|
||||
char chunk_id[4] = {'R', 'I', 'F', 'F'};
|
||||
int32_t chunk_size = 0;
|
||||
char format[4] = {'W', 'A', 'V', 'E'};
|
||||
|
||||
char subchunk_id_1[4] = {'f', 'm', 't', 0x20};
|
||||
int32_t subchunk_1_size = 16;
|
||||
int16_t audio_format{};
|
||||
int16_t num_channels{};
|
||||
int32_t sample_rate{};
|
||||
int32_t byte_rate{};
|
||||
int16_t block_align{};
|
||||
int16_t bits_per_sample{};
|
||||
|
||||
char subchunk_id_2[4] = {'d', 'a', 't', 'a'};
|
||||
int32_t subchunkd_2_size = std::numeric_limits<int32_t>::max();
|
||||
};
|
||||
|
||||
riff_header header;
|
||||
// 1 is integer PCM, 3 is float PCM
|
||||
header.audio_format = bytes_per_sample(params) == 2 ? 1 : 3;
|
||||
header.num_channels = params.channels;
|
||||
header.sample_rate = params.rate;
|
||||
header.byte_rate = bytes_per_sample(params) * params.rate * params.channels;
|
||||
header.block_align = params.channels * bytes_per_sample(params);
|
||||
header.bits_per_sample = bytes_per_sample(params) * 8;
|
||||
|
||||
stream->write(reinterpret_cast<uint8_t *>(&header), sizeof(riff_header));
|
||||
|
||||
return stream;
|
||||
}
|
||||
int delete_stream(cubeb_audio_dump_stream * stream)
|
||||
{
|
||||
assert(!running);
|
||||
stream->close();
|
||||
streams.erase(std::remove(streams.begin(), streams.end(), stream),
|
||||
streams.end());
|
||||
return CUBEB_OK;
|
||||
}
|
||||
int start()
|
||||
{
|
||||
assert(!running);
|
||||
running = true;
|
||||
session_thread = std::thread([this] {
|
||||
while (running) {
|
||||
for (auto * stream : streams) {
|
||||
stream->write_all();
|
||||
}
|
||||
const int DUMP_INTERVAL = 10;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(DUMP_INTERVAL));
|
||||
}
|
||||
});
|
||||
return CUBEB_OK;
|
||||
}
|
||||
int stop()
|
||||
{
|
||||
assert(running);
|
||||
running = false;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
thread session_thread;
|
||||
vector<cubeb_audio_dump_stream_t> streams{};
|
||||
std::atomic<bool> running = false;
|
||||
};
|
||||
|
||||
int
|
||||
cubeb_audio_dump_init(cubeb_audio_dump_session_t * session)
|
||||
{
|
||||
*session = new cubeb_audio_dump_session;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_audio_dump_shutdown(cubeb_audio_dump_session_t session)
|
||||
{
|
||||
delete session;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_audio_dump_stream_init(cubeb_audio_dump_session_t session,
|
||||
cubeb_audio_dump_stream_t * stream,
|
||||
cubeb_stream_params stream_params,
|
||||
const char * name)
|
||||
{
|
||||
*stream = session->create_stream(stream_params, name);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_audio_dump_stream_shutdown(cubeb_audio_dump_session_t session,
|
||||
cubeb_audio_dump_stream_t stream)
|
||||
{
|
||||
return session->delete_stream(stream);
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_audio_dump_start(cubeb_audio_dump_session_t session)
|
||||
{
|
||||
return session->start();
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_audio_dump_stop(cubeb_audio_dump_session_t session)
|
||||
{
|
||||
return session->stop();
|
||||
}
|
||||
|
||||
int
|
||||
cubeb_audio_dump_write(cubeb_audio_dump_stream_t stream, void * audio_samples,
|
||||
uint32_t count)
|
||||
{
|
||||
stream->dump(audio_samples, count);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
108
media/libcubeb/src/cubeb_audio_dump.h
Normal file
108
media/libcubeb/src/cubeb_audio_dump.h
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright © 2023 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#ifndef CUBEB_AUDIO_DUMP
|
||||
#define CUBEB_AUDIO_DUMP
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct cubeb_audio_dump_stream * cubeb_audio_dump_stream_t;
|
||||
typedef struct cubeb_audio_dump_session * cubeb_audio_dump_session_t;
|
||||
|
||||
// Start audio dumping session
|
||||
// This can only be called if the other API functions
|
||||
// aren't currently being called: synchronized externally.
|
||||
// This is not real-time safe.
|
||||
//
|
||||
// This is generally called when deciding to start logging some audio.
|
||||
//
|
||||
// Returns 0 in case of success.
|
||||
int
|
||||
cubeb_audio_dump_init(cubeb_audio_dump_session_t * session);
|
||||
|
||||
// End audio dumping session
|
||||
// This can only be called if the other API functions
|
||||
// aren't currently being called: synchronized externally.
|
||||
//
|
||||
// This is generally called when deciding to stop logging some audio.
|
||||
//
|
||||
// This is not real-time safe.
|
||||
// Returns 0 in case of success.
|
||||
int
|
||||
cubeb_audio_dump_shutdown(cubeb_audio_dump_session_t session);
|
||||
|
||||
// Register a stream for dumping to a file
|
||||
// This can only be called if cubeb_audio_dump_write
|
||||
// isn't currently being called: synchronized externally.
|
||||
//
|
||||
// This is generally called when setting up a system-level stream side (either
|
||||
// input or output).
|
||||
//
|
||||
// This is not real-time safe.
|
||||
// Returns 0 in case of success.
|
||||
int
|
||||
cubeb_audio_dump_stream_init(cubeb_audio_dump_session_t session,
|
||||
cubeb_audio_dump_stream_t * stream,
|
||||
cubeb_stream_params stream_params,
|
||||
const char * name);
|
||||
|
||||
// Unregister a stream for dumping to a file
|
||||
// This can only be called if cubeb_audio_dump_write
|
||||
// isn't currently being called: synchronized externally.
|
||||
//
|
||||
// This is generally called when a system-level audio stream side
|
||||
// (input/output) has been stopped and drained, and the audio callback isn't
|
||||
// going to be called.
|
||||
//
|
||||
// This is not real-time safe.
|
||||
// Returns 0 in case of success.
|
||||
int
|
||||
cubeb_audio_dump_stream_shutdown(cubeb_audio_dump_session_t session,
|
||||
cubeb_audio_dump_stream_t stream);
|
||||
|
||||
// Start dumping.
|
||||
// cubeb_audio_dump_write can now be called.
|
||||
//
|
||||
// This starts dumping the audio to disk. Generally this is called when
|
||||
// cubeb_stream_start is caled is called, but can be called at the beginning of
|
||||
// the application.
|
||||
//
|
||||
// This is not real-time safe.
|
||||
// Returns 0 in case of success.
|
||||
int
|
||||
cubeb_audio_dump_start(cubeb_audio_dump_session_t session);
|
||||
|
||||
// Stop dumping.
|
||||
// cubeb_audio_dump_write can't be called at this point.
|
||||
//
|
||||
// This stops dumping the audio to disk cubeb_stream_stop is caled is called,
|
||||
// but can be called before exiting the application.
|
||||
//
|
||||
// This is not real-time safe.
|
||||
// Returns 0 in case of success.
|
||||
int
|
||||
cubeb_audio_dump_stop(cubeb_audio_dump_session_t session);
|
||||
|
||||
// Dump some audio samples for audio stream id.
|
||||
//
|
||||
// This is generally called from the real-time audio callback.
|
||||
//
|
||||
// This is real-time safe.
|
||||
// Returns 0 in case of success.
|
||||
int
|
||||
cubeb_audio_dump_write(cubeb_audio_dump_stream_t stream, void * audio_samples,
|
||||
uint32_t count);
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
74
media/libcubeb/test/test_audio_dump.cpp
Normal file
74
media/libcubeb/test/test_audio_dump.cpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright © 2023 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#define NOMINMAX
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
#include <ratio>
|
||||
|
||||
#include "cubeb_audio_dump.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
TEST(cubeb, audio_dump)
|
||||
{
|
||||
cubeb_audio_dump_session_t session;
|
||||
int rv = cubeb_audio_dump_init(&session);
|
||||
ASSERT_EQ(rv, 0);
|
||||
|
||||
cubeb_stream_params params;
|
||||
params.rate = 44100;
|
||||
params.channels = 2;
|
||||
params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||
|
||||
cubeb_audio_dump_stream_t dump_stream;
|
||||
rv = cubeb_audio_dump_stream_init(session, &dump_stream, params, "test.wav");
|
||||
ASSERT_EQ(rv, 0);
|
||||
|
||||
rv = cubeb_audio_dump_start(session);
|
||||
ASSERT_EQ(rv, 0);
|
||||
|
||||
float phase = 0;
|
||||
const size_t buf_sz = 2 * 44100 / 50;
|
||||
float buf[buf_sz];
|
||||
for (uint32_t iteration = 0; iteration < 50; iteration++) {
|
||||
uint32_t write_idx = 0;
|
||||
for (uint32_t i = 0; i < buf_sz / params.channels; i++) {
|
||||
for (uint32_t j = 0; j < params.channels; j++) {
|
||||
buf[write_idx++] = sin(phase);
|
||||
}
|
||||
phase += 440 * M_PI * 2 / 44100;
|
||||
if (phase > 2 * M_PI) {
|
||||
phase -= 2 * M_PI;
|
||||
}
|
||||
}
|
||||
rv = cubeb_audio_dump_write(dump_stream, buf, 2 * 44100 / 50);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
ASSERT_EQ(rv, 0);
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
rv = cubeb_audio_dump_stop(session);
|
||||
ASSERT_EQ(rv, 0);
|
||||
|
||||
rv = cubeb_audio_dump_stream_shutdown(session, dump_stream);
|
||||
ASSERT_EQ(rv, 0);
|
||||
|
||||
rv = cubeb_audio_dump_shutdown(session);
|
||||
ASSERT_EQ(rv, 0);
|
||||
|
||||
std::ifstream file("test.wav");
|
||||
ASSERT_TRUE(file.good());
|
||||
}
|
||||
|
||||
#undef NOMINMAX
|
||||
|
|
@ -153,9 +153,10 @@ class PermissionsDialogFragment : AddonDialogFragment() {
|
|||
},
|
||||
addon.translateName(requireContext()),
|
||||
)
|
||||
rootView.findViewById<TextView>(R.id.optional_or_required_text).text = buildOptionalOrRequiredText()
|
||||
|
||||
val listPermissions = buildPermissionsList()
|
||||
rootView.findViewById<TextView>(R.id.optional_or_required_text).text =
|
||||
buildOptionalOrRequiredText(listPermissions.isNotEmpty())
|
||||
|
||||
val permissionsRecyclerView = rootView.findViewById<RecyclerView>(R.id.permissions)
|
||||
val positiveButton = rootView.findViewById<Button>(R.id.allow_button)
|
||||
val negativeButton = rootView.findViewById<Button>(R.id.deny_button)
|
||||
|
|
@ -211,7 +212,10 @@ class PermissionsDialogFragment : AddonDialogFragment() {
|
|||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun buildOptionalOrRequiredText(): String {
|
||||
internal fun buildOptionalOrRequiredText(hasPermissions: Boolean): String {
|
||||
if (!hasPermissions) {
|
||||
return ""
|
||||
}
|
||||
val optionalOrRequiredText = if (forOptionalPermissions) {
|
||||
getString(R.string.mozac_feature_addons_optional_permissions_dialog_subtitle)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ class PermissionsDialogFragmentTest {
|
|||
val optionalOrRequiredTextView = dialog.findViewById<TextView>(R.id.optional_or_required_text)
|
||||
val permissionsRecyclerView = dialog.findViewById<RecyclerView>(R.id.permissions)
|
||||
val recyclerAdapter = permissionsRecyclerView.adapter!! as RequiredPermissionsAdapter
|
||||
val optionalOrRequiredText = fragment.buildOptionalOrRequiredText()
|
||||
val permissionList = fragment.buildPermissionsList()
|
||||
val optionalOrRequiredText = fragment.buildOptionalOrRequiredText(hasPermissions = permissionList.isNotEmpty())
|
||||
|
||||
assertTrue(titleTextView.text.contains(name))
|
||||
assertTrue(optionalOrRequiredText.contains(testContext.getString(R.string.mozac_feature_addons_permissions_dialog_subtitle)))
|
||||
|
|
@ -152,12 +152,12 @@ class PermissionsDialogFragmentTest {
|
|||
val optionalOrRequiredTextView = dialog.findViewById<TextView>(R.id.optional_or_required_text)
|
||||
val permissionsRecyclerView = dialog.findViewById<RecyclerView>(R.id.permissions)
|
||||
val recyclerAdapter = permissionsRecyclerView.adapter!! as RequiredPermissionsAdapter
|
||||
val optionalOrRequiredText = fragment.buildOptionalOrRequiredText()
|
||||
val permissionList = fragment.buildPermissionsList()
|
||||
val optionalOrRequiredText = fragment.buildOptionalOrRequiredText(hasPermissions = permissionList.isNotEmpty())
|
||||
|
||||
assertTrue(titleTextView.text.contains(name))
|
||||
assertTrue(optionalOrRequiredText.contains(testContext.getString(R.string.mozac_feature_addons_permissions_dialog_subtitle)))
|
||||
assertTrue(optionalOrRequiredTextView.text.contains(testContext.getString(R.string.mozac_feature_addons_permissions_dialog_subtitle)))
|
||||
assertFalse(optionalOrRequiredText.contains(testContext.getString(R.string.mozac_feature_addons_permissions_dialog_subtitle)))
|
||||
assertFalse(optionalOrRequiredTextView.text.contains(testContext.getString(R.string.mozac_feature_addons_permissions_dialog_subtitle)))
|
||||
assertEquals(0, recyclerAdapter.itemCount)
|
||||
assertFalse(permissionList.contains(testContext.getString(R.string.mozac_feature_addons_permissions_privacy_description)))
|
||||
assertFalse(permissionList.contains(testContext.getString(R.string.mozac_feature_addons_permissions_all_urls_description)))
|
||||
|
|
@ -184,8 +184,8 @@ class PermissionsDialogFragmentTest {
|
|||
val recyclerAdapter = permissionsRecyclerView.adapter!! as RequiredPermissionsAdapter
|
||||
val allowButton = dialog.findViewById<Button>(R.id.allow_button)
|
||||
val denyButton = dialog.findViewById<Button>(R.id.deny_button)
|
||||
val optionalOrRequiredText = fragment.buildOptionalOrRequiredText()
|
||||
val permissionsList = fragment.buildPermissionsList()
|
||||
val optionalOrRequiredText = fragment.buildOptionalOrRequiredText(hasPermissions = permissionsList.isNotEmpty())
|
||||
|
||||
assertEquals(
|
||||
titleTextView.text,
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ class AppLinksFeature(
|
|||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun isSameCallerAndApp(tab: SessionState, appIntent: Intent): Boolean {
|
||||
return (tab.source as? SessionState.Source.External)?.let { externalSource ->
|
||||
return (tab.source as? SessionState.Source.External.CustomTab)?.let { externalSource ->
|
||||
when (externalSource.caller?.packageId) {
|
||||
null -> false
|
||||
appIntent.component?.packageName -> true
|
||||
|
|
|
|||
|
|
@ -222,6 +222,42 @@ class AppLinksFeatureTest {
|
|||
verify(mockDialog, never()).showNow(eq(mockFragmentManager), anyString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN non-custom tab and caller is the same as external app THEN an external app dialog is shown`() {
|
||||
feature = spy(
|
||||
AppLinksFeature(
|
||||
context = mockContext,
|
||||
store = store,
|
||||
fragmentManager = mockFragmentManager,
|
||||
useCases = mockUseCases,
|
||||
dialog = mockDialog,
|
||||
loadUrlUseCase = mockLoadUrlUseCase,
|
||||
shouldPrompt = { true },
|
||||
),
|
||||
).also {
|
||||
it.start()
|
||||
}
|
||||
|
||||
val tab =
|
||||
createCustomTab(
|
||||
id = "d",
|
||||
url = webUrl,
|
||||
source = SessionState.Source.External.ActionView(
|
||||
ExternalPackage("com.zxing.app", PackageCategory.PRODUCTIVITY),
|
||||
),
|
||||
)
|
||||
|
||||
val appIntent: Intent = mock()
|
||||
val componentName: ComponentName = mock()
|
||||
doReturn(componentName).`when`(appIntent).component
|
||||
doReturn("com.zxing.app").`when`(componentName).packageName
|
||||
|
||||
feature.handleAppIntent(tab, intentUrl, appIntent)
|
||||
|
||||
verify(mockDialog).showNow(eq(mockFragmentManager), anyString())
|
||||
verify(mockOpenRedirect, never()).invoke(any(), anyBoolean(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN should prompt and in private mode THEN an external app dialog is shown`() {
|
||||
feature = spy(
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import org.mozilla.fenix.components.menu.compose.TOOLS_MENU_ROUTE
|
|||
import org.mozilla.fenix.components.menu.compose.ToolsSubmenu
|
||||
import org.mozilla.fenix.components.menu.middleware.MenuDialogMiddleware
|
||||
import org.mozilla.fenix.components.menu.middleware.MenuNavigationMiddleware
|
||||
import org.mozilla.fenix.components.menu.middleware.MenuTelemetryMiddleware
|
||||
import org.mozilla.fenix.components.menu.store.BrowserMenuState
|
||||
import org.mozilla.fenix.components.menu.store.MenuAction
|
||||
import org.mozilla.fenix.components.menu.store.MenuState
|
||||
|
|
@ -109,6 +110,9 @@ class MenuDialogFragment : BottomSheetDialogFragment() {
|
|||
openToBrowser = ::openToBrowser,
|
||||
scope = coroutineScope,
|
||||
),
|
||||
MenuTelemetryMiddleware(
|
||||
accessPoint = args.accesspoint,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
/* 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/. */
|
||||
|
||||
package org.mozilla.fenix.components.menu.middleware
|
||||
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.MiddlewareContext
|
||||
import mozilla.telemetry.glean.private.NoExtras
|
||||
import org.mozilla.fenix.GleanMetrics.AppMenu
|
||||
import org.mozilla.fenix.GleanMetrics.Events
|
||||
import org.mozilla.fenix.GleanMetrics.HomeMenu
|
||||
import org.mozilla.fenix.GleanMetrics.HomeScreen
|
||||
import org.mozilla.fenix.GleanMetrics.Translations
|
||||
import org.mozilla.fenix.components.menu.MenuAccessPoint
|
||||
import org.mozilla.fenix.components.menu.store.MenuAction
|
||||
import org.mozilla.fenix.components.menu.store.MenuState
|
||||
import org.mozilla.fenix.components.menu.store.MenuStore
|
||||
|
||||
/**
|
||||
* A [Middleware] for recording telemetry based on [MenuAction]s that are dispatch to the
|
||||
* [MenuStore].
|
||||
*
|
||||
* @param accessPoint The [MenuAccessPoint] that was used to navigate to the menu dialog.
|
||||
*/
|
||||
class MenuTelemetryMiddleware(
|
||||
private val accessPoint: MenuAccessPoint,
|
||||
) : Middleware<MenuState, MenuAction> {
|
||||
|
||||
@Suppress("CyclomaticComplexMethod", "LongMethod")
|
||||
override fun invoke(
|
||||
context: MiddlewareContext<MenuState, MenuAction>,
|
||||
next: (MenuAction) -> Unit,
|
||||
action: MenuAction,
|
||||
) {
|
||||
next(action)
|
||||
|
||||
when (action) {
|
||||
MenuAction.AddBookmark,
|
||||
MenuAction.Navigate.EditBookmark,
|
||||
-> Events.browserMenuAction.record(
|
||||
Events.BrowserMenuActionExtra(
|
||||
item = "bookmark",
|
||||
),
|
||||
)
|
||||
|
||||
MenuAction.Navigate.Bookmarks -> Events.browserMenuAction.record(
|
||||
Events.BrowserMenuActionExtra(
|
||||
item = "bookmarks",
|
||||
),
|
||||
)
|
||||
|
||||
MenuAction.Navigate.CustomizeHomepage -> {
|
||||
AppMenu.customizeHomepage.record(NoExtras())
|
||||
HomeScreen.customizeHomeClicked.record(NoExtras())
|
||||
}
|
||||
|
||||
MenuAction.Navigate.Downloads -> Events.browserMenuAction.record(
|
||||
Events.BrowserMenuActionExtra(
|
||||
item = "downloads",
|
||||
),
|
||||
)
|
||||
|
||||
MenuAction.Navigate.Help -> HomeMenu.helpTapped.record(NoExtras())
|
||||
|
||||
MenuAction.Navigate.History -> Events.browserMenuAction.record(
|
||||
Events.BrowserMenuActionExtra(
|
||||
item = "history",
|
||||
),
|
||||
)
|
||||
|
||||
MenuAction.Navigate.ManageExtensions -> Events.browserMenuAction.record(
|
||||
Events.BrowserMenuActionExtra(
|
||||
item = "addons_manager",
|
||||
),
|
||||
)
|
||||
|
||||
is MenuAction.Navigate.MozillaAccount -> {
|
||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra(item = "sync_account"))
|
||||
AppMenu.signIntoSync.add()
|
||||
}
|
||||
|
||||
MenuAction.Navigate.NewTab -> Events.browserMenuAction.record(
|
||||
Events.BrowserMenuActionExtra(
|
||||
item = "new_tab",
|
||||
),
|
||||
)
|
||||
|
||||
MenuAction.Navigate.Passwords -> Events.browserMenuAction.record(
|
||||
Events.BrowserMenuActionExtra(
|
||||
item = "passwords",
|
||||
),
|
||||
)
|
||||
|
||||
MenuAction.Navigate.ReleaseNotes -> Events.whatsNewTapped.record(NoExtras())
|
||||
|
||||
MenuAction.Navigate.Settings -> {
|
||||
when (accessPoint) {
|
||||
MenuAccessPoint.Browser -> Events.browserMenuAction.record(
|
||||
Events.BrowserMenuActionExtra(
|
||||
item = "settings",
|
||||
),
|
||||
)
|
||||
|
||||
MenuAccessPoint.Home -> HomeMenu.settingsItemClicked.record(NoExtras())
|
||||
|
||||
MenuAccessPoint.External -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
MenuAction.Navigate.Share -> Events.browserMenuAction.record(
|
||||
Events.BrowserMenuActionExtra(
|
||||
item = "share",
|
||||
),
|
||||
)
|
||||
|
||||
MenuAction.Navigate.Translate -> {
|
||||
Translations.action.record(Translations.ActionExtra(item = "main_flow_browser"))
|
||||
|
||||
Events.browserMenuAction.record(
|
||||
Events.BrowserMenuActionExtra(
|
||||
item = "translate",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
MenuAction.InitAction,
|
||||
MenuAction.Navigate.Back,
|
||||
MenuAction.Navigate.DiscoverMoreExtensions,
|
||||
MenuAction.Navigate.Extensions,
|
||||
MenuAction.Navigate.NewPrivateTab,
|
||||
MenuAction.Navigate.Save,
|
||||
MenuAction.Navigate.Tools,
|
||||
is MenuAction.UpdateBookmarkState,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -115,6 +115,7 @@ fun TextListItem(
|
|||
* an optional [IconButton] at the end.
|
||||
*
|
||||
* @param label The label in the list item.
|
||||
* @param modifier [Modifier] to be applied to the layout.
|
||||
* @param description An optional description text below the label.
|
||||
* @param faviconPainter Optional painter to use when fetching a new favicon is unnecessary.
|
||||
* @param onClick Called when the user clicks on the item.
|
||||
|
|
@ -126,6 +127,7 @@ fun TextListItem(
|
|||
@Composable
|
||||
fun FaviconListItem(
|
||||
label: String,
|
||||
modifier: Modifier = Modifier,
|
||||
description: String? = null,
|
||||
faviconPainter: Painter? = null,
|
||||
onClick: (() -> Unit)? = null,
|
||||
|
|
@ -136,6 +138,7 @@ fun FaviconListItem(
|
|||
) {
|
||||
ListItem(
|
||||
label = label,
|
||||
modifier = modifier,
|
||||
description = description,
|
||||
onClick = onClick,
|
||||
beforeListAction = {
|
||||
|
|
@ -179,6 +182,7 @@ fun FaviconListItem(
|
|||
* text and an optional [IconButton] or [Icon] at the end.
|
||||
*
|
||||
* @param label The label in the list item.
|
||||
* @param modifier [Modifier] to be applied to the layout.
|
||||
* @param labelTextColor [Color] to be applied to the label.
|
||||
* @param description An optional description text below the label.
|
||||
* @param enabled Controls the enabled state of the list item. When `false`, the list item will not
|
||||
|
|
@ -196,6 +200,7 @@ fun FaviconListItem(
|
|||
@Composable
|
||||
fun IconListItem(
|
||||
label: String,
|
||||
modifier: Modifier = Modifier,
|
||||
labelTextColor: Color = FirefoxTheme.colors.textPrimary,
|
||||
description: String? = null,
|
||||
enabled: Boolean = true,
|
||||
|
|
@ -210,6 +215,7 @@ fun IconListItem(
|
|||
) {
|
||||
ListItem(
|
||||
label = label,
|
||||
modifier = modifier,
|
||||
labelTextColor = labelTextColor,
|
||||
description = description,
|
||||
enabled = enabled,
|
||||
|
|
@ -256,6 +262,7 @@ fun IconListItem(
|
|||
* text and an optional [TextButton] at the end.
|
||||
*
|
||||
* @param label The label in the list item.
|
||||
* @param modifier [Modifier] to be applied to the layout.
|
||||
* @param labelTextColor [Color] to be applied to the label.
|
||||
* @param description An optional description text below the label.
|
||||
* @param enabled Controls the enabled state of the list item. When `false`, the list item will not
|
||||
|
|
@ -271,6 +278,7 @@ fun IconListItem(
|
|||
@Composable
|
||||
fun IconListItem(
|
||||
label: String,
|
||||
modifier: Modifier = Modifier,
|
||||
labelTextColor: Color = FirefoxTheme.colors.textPrimary,
|
||||
description: String? = null,
|
||||
enabled: Boolean = true,
|
||||
|
|
@ -284,6 +292,7 @@ fun IconListItem(
|
|||
) {
|
||||
ListItem(
|
||||
label = label,
|
||||
modifier = modifier,
|
||||
labelTextColor = labelTextColor,
|
||||
description = description,
|
||||
enabled = enabled,
|
||||
|
|
|
|||
|
|
@ -5,27 +5,22 @@
|
|||
package org.mozilla.fenix.home.recentvisits.view
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowColumn
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -34,22 +29,22 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTag
|
||||
import androidx.compose.ui.semantics.testTagsAsResourceId
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import mozilla.components.support.ktx.kotlin.trimmed
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.compose.ContextualMenu
|
||||
import org.mozilla.fenix.compose.Divider
|
||||
import org.mozilla.fenix.compose.Favicon
|
||||
import org.mozilla.fenix.compose.MenuItem
|
||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||
import org.mozilla.fenix.compose.ext.thenConditional
|
||||
import org.mozilla.fenix.compose.list.FaviconListItem
|
||||
import org.mozilla.fenix.compose.list.IconListItem
|
||||
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem
|
||||
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem.RecentHistoryGroup
|
||||
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem.RecentHistoryHighlight
|
||||
|
|
@ -58,28 +53,10 @@ import org.mozilla.fenix.theme.FirefoxTheme
|
|||
// Number of recently visited items per column.
|
||||
private const val VISITS_PER_COLUMN = 3
|
||||
|
||||
private val itemRowHeight = 56.dp
|
||||
private val recentlyVisitedItemMaxWidth = 320.dp
|
||||
|
||||
private val horizontalArrangementSpacing = 32.dp
|
||||
private val contentPadding = 16.dp
|
||||
private val imageSize = 24.dp
|
||||
private val imageSpacer = 16.dp
|
||||
private val textSpacer = 2.dp
|
||||
|
||||
/**
|
||||
* The [Dp] width of UI elements to deduct from the screen width for a single column.
|
||||
*
|
||||
* Box start padding, Row start padding, Box end padding, Row end padding.
|
||||
*/
|
||||
private val singleColumnWidth: Dp = contentPadding * 4
|
||||
|
||||
/**
|
||||
* The [Dp] width of UI elements to deduct from the screen width for multiple columns to show (peek) the
|
||||
* second column icons.
|
||||
*
|
||||
* Box start padding, Row start padding, Spacer, Image size, Image spacer.
|
||||
*/
|
||||
private val multipleColumnsWidth: Dp =
|
||||
contentPadding + contentPadding + horizontalArrangementSpacing + imageSize + imageSpacer
|
||||
|
||||
/**
|
||||
* A list of recently visited items.
|
||||
|
|
@ -87,56 +64,82 @@ private val multipleColumnsWidth: Dp =
|
|||
* @param recentVisits List of [RecentlyVisitedItem] to display.
|
||||
* @param menuItems List of [RecentVisitMenuItem] shown long clicking a [RecentlyVisitedItem].
|
||||
* @param backgroundColor The background [Color] of each item.
|
||||
* @param onRecentVisitClick Invoked when the user clicks on a recent visit.
|
||||
* @param onRecentVisitClick Invoked when the user clicks on a recent visit. The first parameter is
|
||||
* the [RecentlyVisitedItem] that was clicked and the second parameter is the "page" or column number
|
||||
* the item resides in.
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun RecentlyVisited(
|
||||
recentVisits: List<RecentlyVisitedItem>,
|
||||
menuItems: List<RecentVisitMenuItem>,
|
||||
backgroundColor: Color = FirefoxTheme.colors.layer2,
|
||||
onRecentVisitClick: (RecentlyVisitedItem, Int) -> Unit = { _, _ -> },
|
||||
onRecentVisitClick: (RecentlyVisitedItem, pageNumber: Int) -> Unit = { _, _ -> },
|
||||
) {
|
||||
val itemsMatrix: List<List<RecentlyVisitedItem>> = recentVisits.chunked(VISITS_PER_COLUMN)
|
||||
val isSingleColumn by remember(recentVisits) { derivedStateOf { recentVisits.size <= VISITS_PER_COLUMN } }
|
||||
|
||||
BoxWithConstraints(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.semantics {
|
||||
testTagsAsResourceId = true
|
||||
testTag = "recent.visits"
|
||||
},
|
||||
.thenConditional(
|
||||
modifier = Modifier.horizontalScroll(state = rememberScrollState()),
|
||||
predicate = { !isSingleColumn },
|
||||
)
|
||||
.padding(
|
||||
horizontal = contentPadding,
|
||||
vertical = 8.dp,
|
||||
),
|
||||
) {
|
||||
val boxWithConstraintsScope = this
|
||||
|
||||
val isSingleColumn = itemsMatrix.size == 1
|
||||
val widthToDeduct = if (isSingleColumn) {
|
||||
singleColumnWidth
|
||||
} else {
|
||||
multipleColumnsWidth
|
||||
}
|
||||
val rowWidth = boxWithConstraintsScope.maxWidth - widthToDeduct
|
||||
|
||||
LazyRow(
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentPadding = PaddingValues(horizontal = contentPadding),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
backgroundColor = backgroundColor,
|
||||
elevation = 6.dp,
|
||||
) {
|
||||
item {
|
||||
RecentlyVisitedCard(backgroundColor) {
|
||||
Row(modifier = Modifier.padding(contentPadding)) {
|
||||
itemsMatrix.mapIndexed { pageIndex, items ->
|
||||
RecentlyVisitedColumn(
|
||||
modifier = Modifier.width(rowWidth),
|
||||
FlowColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
maxItemsInEachColumn = VISITS_PER_COLUMN,
|
||||
horizontalArrangement = Arrangement.spacedBy(horizontalArrangementSpacing),
|
||||
) {
|
||||
recentVisits.forEachIndexed { index, recentVisit ->
|
||||
// Don't display the divider when its the last item in a column or the last item
|
||||
// in the table.
|
||||
val showDivider = (index + 1) % VISITS_PER_COLUMN != 0 &&
|
||||
index != recentVisits.lastIndex
|
||||
val pageIndex = index / VISITS_PER_COLUMN
|
||||
val pageNumber = pageIndex + 1
|
||||
|
||||
Box(
|
||||
modifier = if (isSingleColumn) {
|
||||
Modifier.fillMaxWidth()
|
||||
} else {
|
||||
Modifier.widthIn(max = recentlyVisitedItemMaxWidth)
|
||||
},
|
||||
) {
|
||||
when (recentVisit) {
|
||||
is RecentHistoryHighlight -> RecentlyVisitedHistoryHighlight(
|
||||
recentVisit = recentVisit,
|
||||
menuItems = menuItems,
|
||||
items = items,
|
||||
pageIndex = pageIndex,
|
||||
onRecentVisitClick = onRecentVisitClick,
|
||||
onRecentVisitClick = {
|
||||
onRecentVisitClick(it, pageNumber)
|
||||
},
|
||||
)
|
||||
|
||||
val isLastColumn = pageIndex == itemsMatrix.lastIndex
|
||||
if (!isLastColumn) {
|
||||
Spacer(modifier = Modifier.width(horizontalArrangementSpacing))
|
||||
}
|
||||
is RecentHistoryGroup -> RecentlyVisitedHistoryGroup(
|
||||
recentVisit = recentVisit,
|
||||
menuItems = menuItems,
|
||||
onRecentVisitClick = {
|
||||
onRecentVisitClick(it, pageNumber)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (showDivider) {
|
||||
Divider(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(horizontal = contentPadding),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,57 +148,11 @@ fun RecentlyVisited(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RecentlyVisitedCard(backgroundColor: Color, content: @Composable () -> Unit) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
backgroundColor = backgroundColor,
|
||||
elevation = 6.dp,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RecentlyVisitedColumn(
|
||||
modifier: Modifier,
|
||||
menuItems: List<RecentVisitMenuItem>,
|
||||
items: List<RecentlyVisitedItem>,
|
||||
pageIndex: Int,
|
||||
onRecentVisitClick: (RecentlyVisitedItem, Int) -> Unit = { _, _ -> },
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
items.forEachIndexed { index, recentVisit ->
|
||||
when (recentVisit) {
|
||||
is RecentHistoryHighlight -> RecentlyVisitedHistoryHighlight(
|
||||
recentVisit = recentVisit,
|
||||
menuItems = menuItems,
|
||||
showDividerLine = index < items.size - 1,
|
||||
onRecentVisitClick = {
|
||||
onRecentVisitClick(it, pageIndex + 1)
|
||||
},
|
||||
)
|
||||
|
||||
is RecentHistoryGroup -> RecentlyVisitedHistoryGroup(
|
||||
recentVisit = recentVisit,
|
||||
menuItems = menuItems,
|
||||
showDividerLine = index < items.size - 1,
|
||||
onRecentVisitClick = {
|
||||
onRecentVisitClick(it, pageIndex + 1)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A recently visited history group.
|
||||
*
|
||||
* @param recentVisit The [RecentHistoryGroup] to display.
|
||||
* @param menuItems List of [RecentVisitMenuItem] to display in a recent visit dropdown menu.
|
||||
* @param showDividerLine Whether to show a divider line at the bottom.
|
||||
* @param onRecentVisitClick Invoked when the user clicks on a recent visit.
|
||||
*/
|
||||
@OptIn(
|
||||
|
|
@ -206,130 +163,26 @@ private fun RecentlyVisitedColumn(
|
|||
private fun RecentlyVisitedHistoryGroup(
|
||||
recentVisit: RecentHistoryGroup,
|
||||
menuItems: List<RecentVisitMenuItem>,
|
||||
showDividerLine: Boolean,
|
||||
onRecentVisitClick: (RecentHistoryGroup) -> Unit = { _ -> },
|
||||
) {
|
||||
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onRecentVisitClick(recentVisit) },
|
||||
onLongClick = { isMenuExpanded = true },
|
||||
)
|
||||
.height(itemRowHeight)
|
||||
.fillMaxWidth()
|
||||
.semantics {
|
||||
testTagsAsResourceId = true
|
||||
testTag = "recent.visits.group"
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_multiple_tabs),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(imageSize),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(imageSpacer))
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
RecentlyVisitedTitle(
|
||||
text = recentVisit.title,
|
||||
modifier = Modifier
|
||||
.semantics {
|
||||
testTagsAsResourceId = true
|
||||
testTag = "recent.visits.group.title"
|
||||
},
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(textSpacer))
|
||||
|
||||
RecentlyVisitedCaption(
|
||||
count = recentVisit.historyMetadata.size,
|
||||
modifier = Modifier
|
||||
.semantics {
|
||||
testTagsAsResourceId = true
|
||||
testTag = "recent.visits.group.caption"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (showDividerLine) {
|
||||
Divider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
}
|
||||
|
||||
ContextualMenu(
|
||||
showMenu = isMenuExpanded,
|
||||
onDismissRequest = { isMenuExpanded = false },
|
||||
menuItems = menuItems.map { MenuItem(it.title) { it.onClick(recentVisit) } },
|
||||
modifier = Modifier.semantics {
|
||||
testTagsAsResourceId = true
|
||||
testTag = "recent.visit.menu"
|
||||
},
|
||||
)
|
||||
val captionId = if (recentVisit.historyMetadata.size == 1) {
|
||||
R.string.history_search_group_site_1
|
||||
} else {
|
||||
R.string.history_search_group_sites_1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A recently visited history item.
|
||||
*
|
||||
* @param recentVisit The [RecentHistoryHighlight] to display.
|
||||
* @param menuItems List of [RecentVisitMenuItem] to display in a recent visit dropdown menu.
|
||||
* @param showDividerLine Whether to show a divider line at the bottom.
|
||||
* @param onRecentVisitClick Invoked when the user clicks on a recent visit.
|
||||
*/
|
||||
@OptIn(
|
||||
ExperimentalFoundationApi::class,
|
||||
ExperimentalComposeUiApi::class,
|
||||
)
|
||||
@Composable
|
||||
private fun RecentlyVisitedHistoryHighlight(
|
||||
recentVisit: RecentHistoryHighlight,
|
||||
menuItems: List<RecentVisitMenuItem>,
|
||||
showDividerLine: Boolean,
|
||||
onRecentVisitClick: (RecentHistoryHighlight) -> Unit = { _ -> },
|
||||
) {
|
||||
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onRecentVisitClick(recentVisit) },
|
||||
onLongClick = { isMenuExpanded = true },
|
||||
)
|
||||
.height(itemRowHeight)
|
||||
.fillMaxWidth()
|
||||
.semantics {
|
||||
testTagsAsResourceId = true
|
||||
testTag = "recent.visits.highlight"
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Favicon(url = recentVisit.url, size = imageSize)
|
||||
|
||||
Spacer(modifier = Modifier.width(imageSpacer))
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
RecentlyVisitedTitle(
|
||||
text = recentVisit.title.trimmed(),
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterStart)
|
||||
.semantics {
|
||||
testTagsAsResourceId = true
|
||||
testTag = "recent.visits.highlight.title"
|
||||
},
|
||||
)
|
||||
|
||||
if (showDividerLine) {
|
||||
Divider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
}
|
||||
Box {
|
||||
IconListItem(
|
||||
label = recentVisit.title.trimmed(),
|
||||
modifier = Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onRecentVisitClick(recentVisit) },
|
||||
onLongClick = { isMenuExpanded = true },
|
||||
),
|
||||
beforeIconPainter = painterResource(R.drawable.ic_multiple_tabs),
|
||||
description = stringResource(id = captionId, recentVisit.historyMetadata.size),
|
||||
)
|
||||
|
||||
ContextualMenu(
|
||||
showMenu = isMenuExpanded,
|
||||
|
|
@ -344,69 +197,67 @@ private fun RecentlyVisitedHistoryHighlight(
|
|||
}
|
||||
|
||||
/**
|
||||
* The title of a recent visit.
|
||||
* A recently visited history item.
|
||||
*
|
||||
* @param text [String] that will be display. Will be ellipsized if cannot fit on one line.
|
||||
* @param modifier [Modifier] allowing to perfectly place this.
|
||||
* @param recentVisit The [RecentHistoryHighlight] to display.
|
||||
* @param menuItems List of [RecentVisitMenuItem] to display in a recent visit dropdown menu.
|
||||
* @param onRecentVisitClick Invoked when the user clicks on a recent visit.
|
||||
*/
|
||||
@OptIn(
|
||||
ExperimentalFoundationApi::class,
|
||||
ExperimentalComposeUiApi::class,
|
||||
)
|
||||
@Composable
|
||||
private fun RecentlyVisitedTitle(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
private fun RecentlyVisitedHistoryHighlight(
|
||||
recentVisit: RecentHistoryHighlight,
|
||||
menuItems: List<RecentVisitMenuItem>,
|
||||
onRecentVisitClick: (RecentHistoryHighlight) -> Unit = { _ -> },
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier,
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
fontSize = 16.sp,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
/**
|
||||
* The caption text for a recent visit.
|
||||
*
|
||||
* @param count Number of recently visited items to display in the caption.
|
||||
* @param modifier [Modifier] allowing to perfectly place this.
|
||||
*/
|
||||
@Composable
|
||||
private fun RecentlyVisitedCaption(
|
||||
count: Int,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val stringId = if (count == 1) {
|
||||
R.string.history_search_group_site_1
|
||||
} else {
|
||||
R.string.history_search_group_sites_1
|
||||
Box {
|
||||
FaviconListItem(
|
||||
label = recentVisit.title.trimmed(),
|
||||
url = recentVisit.url,
|
||||
modifier = Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onRecentVisitClick(recentVisit) },
|
||||
onLongClick = { isMenuExpanded = true },
|
||||
),
|
||||
)
|
||||
|
||||
ContextualMenu(
|
||||
showMenu = isMenuExpanded,
|
||||
onDismissRequest = { isMenuExpanded = false },
|
||||
menuItems = menuItems.map { item -> MenuItem(item.title) { item.onClick(recentVisit) } },
|
||||
modifier = Modifier.semantics {
|
||||
testTagsAsResourceId = true
|
||||
testTag = "recent.visit.menu"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = String.format(LocalContext.current.getString(stringId), count),
|
||||
modifier = modifier,
|
||||
color = when (isSystemInDarkTheme()) {
|
||||
true -> FirefoxTheme.colors.textPrimary
|
||||
false -> FirefoxTheme.colors.textSecondary
|
||||
},
|
||||
fontSize = 12.sp,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@LightDarkPreview
|
||||
private fun RecentlyVisitedMultipleColumnsPreview() {
|
||||
FirefoxTheme {
|
||||
RecentlyVisited(
|
||||
recentVisits = listOf(
|
||||
RecentHistoryGroup(title = "running shoes"),
|
||||
RecentHistoryGroup(title = "mozilla"),
|
||||
RecentHistoryGroup(title = "firefox"),
|
||||
RecentHistoryGroup(title = "pocket"),
|
||||
),
|
||||
menuItems = emptyList(),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(color = FirefoxTheme.colors.layer1)
|
||||
.padding(vertical = contentPadding),
|
||||
) {
|
||||
RecentlyVisited(
|
||||
recentVisits = listOf(
|
||||
RecentHistoryGroup(title = "running shoes"),
|
||||
RecentHistoryGroup(title = "mozilla"),
|
||||
RecentHistoryGroup(title = "firefox"),
|
||||
RecentHistoryGroup(title = "pocket"),
|
||||
RecentHistoryHighlight(title = "Mozilla", url = "www.mozilla.com"),
|
||||
),
|
||||
menuItems = emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -414,13 +265,24 @@ private fun RecentlyVisitedMultipleColumnsPreview() {
|
|||
@LightDarkPreview
|
||||
private fun RecentlyVisitedSingleColumnPreview() {
|
||||
FirefoxTheme {
|
||||
RecentlyVisited(
|
||||
recentVisits = listOf(
|
||||
RecentHistoryGroup(title = "running shoes"),
|
||||
RecentHistoryGroup(title = "mozilla"),
|
||||
RecentHistoryGroup(title = "firefox"),
|
||||
),
|
||||
menuItems = emptyList(),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(color = FirefoxTheme.colors.layer1)
|
||||
.padding(vertical = contentPadding),
|
||||
) {
|
||||
RecentlyVisited(
|
||||
recentVisits = listOf(
|
||||
RecentHistoryGroup(title = "running shoes"),
|
||||
RecentHistoryHighlight(title = "Mozilla", url = "www.mozilla.com"),
|
||||
),
|
||||
menuItems = emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(widthDp = 250)
|
||||
private fun RecentlyVisitedSingleColumnSmallPreview() {
|
||||
RecentlyVisitedSingleColumnPreview()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import org.mozilla.fenix.components.menu.compose.EXTENSIONS_MENU_ROUTE
|
|||
import org.mozilla.fenix.components.menu.compose.SAVE_MENU_ROUTE
|
||||
import org.mozilla.fenix.components.menu.compose.TOOLS_MENU_ROUTE
|
||||
import org.mozilla.fenix.components.menu.middleware.MenuNavigationMiddleware
|
||||
import org.mozilla.fenix.components.menu.store.BookmarkState
|
||||
import org.mozilla.fenix.components.menu.store.BrowserMenuState
|
||||
import org.mozilla.fenix.components.menu.store.MenuAction
|
||||
import org.mozilla.fenix.components.menu.store.MenuState
|
||||
|
|
@ -258,6 +259,34 @@ class MenuNavigationMiddlewareTest {
|
|||
verify { navHostController.popBackStack() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigate to edit bookmark action is dispatched THEN navigate to bookmark edit fragment`() = runTest {
|
||||
val tab = createTab(url = "https://www.mozilla.org")
|
||||
val store = createStore(
|
||||
menuState = MenuState(
|
||||
browserMenuState = BrowserMenuState(
|
||||
selectedTab = tab,
|
||||
bookmarkState = BookmarkState(
|
||||
guid = BookmarkRoot.Mobile.id,
|
||||
isBookmarked = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatch(MenuAction.Navigate.EditBookmark).join()
|
||||
|
||||
verify {
|
||||
navController.nav(
|
||||
R.id.menuDialogFragment,
|
||||
MenuDialogFragmentDirections.actionGlobalBookmarkEditFragment(
|
||||
guidToEdit = BookmarkRoot.Mobile.id,
|
||||
requiresSnackbarPaddingForToolbar = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigate to translate action is dispatched THEN navigate to translation dialog`() = runTest {
|
||||
val tab = createTab(url = "https://www.mozilla.org")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,245 @@
|
|||
/* 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/. */
|
||||
|
||||
package org.mozilla.fenix.components.menu
|
||||
|
||||
import mozilla.components.service.fxa.manager.AccountState
|
||||
import mozilla.components.service.glean.private.EventMetricType
|
||||
import mozilla.components.service.glean.private.NoExtras
|
||||
import mozilla.components.service.glean.testing.GleanTestRule
|
||||
import mozilla.components.support.test.ext.joinBlocking
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import mozilla.telemetry.glean.internal.CounterMetric
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.GleanMetrics.AppMenu
|
||||
import org.mozilla.fenix.GleanMetrics.Events
|
||||
import org.mozilla.fenix.GleanMetrics.HomeMenu
|
||||
import org.mozilla.fenix.GleanMetrics.HomeScreen
|
||||
import org.mozilla.fenix.GleanMetrics.Translations
|
||||
import org.mozilla.fenix.components.menu.middleware.MenuTelemetryMiddleware
|
||||
import org.mozilla.fenix.components.menu.store.MenuAction
|
||||
import org.mozilla.fenix.components.menu.store.MenuState
|
||||
import org.mozilla.fenix.components.menu.store.MenuStore
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class MenuTelemetryMiddlewareTest {
|
||||
@get:Rule
|
||||
val gleanTestRule = GleanTestRule(testContext)
|
||||
|
||||
@Test
|
||||
fun `WHEN adding a bookmark THEN record the bookmark browser menu telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.AddBookmark).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.browserMenuAction, item = "bookmark")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigating to edit a bookmark THEN record the bookmark browser menu telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.EditBookmark).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.browserMenuAction, item = "bookmark")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigating to bookmarks THEN record the bookmarks browser menu telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.Bookmarks).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.browserMenuAction, item = "bookmarks")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigating to customize homepage THEN record the customize homepage telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(AppMenu.customizeHomepage.testGetValue())
|
||||
assertNull(HomeScreen.customizeHomeClicked.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.CustomizeHomepage).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(AppMenu.customizeHomepage)
|
||||
assertTelemetryRecorded(HomeScreen.customizeHomeClicked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigating to downloads THEN record the downloads browser menu telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.Downloads).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.browserMenuAction, item = "downloads")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigating to the help SUMO page THEN record the help interaction telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(HomeMenu.helpTapped.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.Help).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(HomeMenu.helpTapped)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigating to history THEN record the history browser menu telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.History).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.browserMenuAction, item = "history")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigating to manage extensions THEN record the manage extensions browser menu telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.ManageExtensions).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.browserMenuAction, item = "addons_manager")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigating to sync account THEN record the sync account browser menu telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
|
||||
store.dispatch(
|
||||
MenuAction.Navigate.MozillaAccount(
|
||||
accountState = AccountState.NotAuthenticated,
|
||||
accesspoint = MenuAccessPoint.Browser,
|
||||
),
|
||||
).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.browserMenuAction, item = "sync_account")
|
||||
assertTelemetryRecorded(AppMenu.signIntoSync)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN opening a new tab THEN record the new tab browser menu telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.NewTab).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.browserMenuAction, item = "new_tab")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigating to passwords THEN record the passwords browser menu telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.Passwords).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.browserMenuAction, item = "passwords")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigating to the release notes page THEN record the whats new interaction telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(HomeMenu.helpTapped.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.ReleaseNotes).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.whatsNewTapped)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigating to the share sheet THEN record the share browser menu telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.Share).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.browserMenuAction, item = "share")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN the menu accesspoint is from the browser WHEN navigating to the settings THEN record the settings browser menu telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
assertNull(HomeMenu.settingsItemClicked.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.Settings).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.browserMenuAction, item = "settings")
|
||||
assertNull(HomeMenu.settingsItemClicked.testGetValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN the menu accesspoint is from the home screen WHEN navigating to the settings THEN record the home menu interaction telemetry`() {
|
||||
val store = createStore(accessPoint = MenuAccessPoint.Home)
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
assertNull(HomeMenu.settingsItemClicked.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.Settings).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(HomeMenu.settingsItemClicked)
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN navigating to translations dialog THEN record the translate browser menu telemetry`() {
|
||||
val store = createStore()
|
||||
assertNull(Events.browserMenuAction.testGetValue())
|
||||
assertNull(Translations.action.testGetValue())
|
||||
|
||||
store.dispatch(MenuAction.Navigate.Translate).joinBlocking()
|
||||
|
||||
assertTelemetryRecorded(Events.browserMenuAction, item = "translate")
|
||||
|
||||
val snapshot = Translations.action.testGetValue()!!
|
||||
assertEquals(1, snapshot.size)
|
||||
assertEquals("main_flow_browser", snapshot.single().extra?.getValue("item"))
|
||||
}
|
||||
|
||||
private fun assertTelemetryRecorded(
|
||||
event: EventMetricType<Events.BrowserMenuActionExtra>,
|
||||
item: String,
|
||||
) {
|
||||
assertNotNull(event.testGetValue())
|
||||
|
||||
val snapshot = event.testGetValue()!!
|
||||
assertEquals(1, snapshot.size)
|
||||
assertEquals(item, snapshot.single().extra?.getValue("item"))
|
||||
}
|
||||
|
||||
private fun assertTelemetryRecorded(event: EventMetricType<NoExtras>) {
|
||||
assertNotNull(event.testGetValue())
|
||||
assertEquals(1, event.testGetValue()!!.size)
|
||||
}
|
||||
|
||||
private fun assertTelemetryRecorded(event: CounterMetric) {
|
||||
assertNotNull(event.testGetValue())
|
||||
assertEquals(1, event.testGetValue()!!)
|
||||
}
|
||||
|
||||
private fun createStore(
|
||||
menuState: MenuState = MenuState(),
|
||||
accessPoint: MenuAccessPoint = MenuAccessPoint.Browser,
|
||||
) = MenuStore(
|
||||
initialState = menuState,
|
||||
middleware = listOf(
|
||||
MenuTelemetryMiddleware(
|
||||
accessPoint = accessPoint,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="status" role="status"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1202,6 +1202,19 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
})
|
||||
}
|
||||
|
||||
@Test fun testLiveRegionStatus() {
|
||||
loadTestPage("test-live-region-status")
|
||||
waitForInitialFocus()
|
||||
|
||||
mainSession.evaluateJS("document.querySelector('#status').textContent = 'hello';")
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAnnouncement(event: AccessibilityEvent) {
|
||||
assertThat("Announcement is correct", event.text[0].toString(), equalTo("hello"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun screenContainsNode(nodeId: Int): Boolean {
|
||||
var node = createNodeInfo(nodeId)
|
||||
var nodeBounds = Rect()
|
||||
|
|
|
|||
|
|
@ -11801,13 +11801,6 @@
|
|||
value: true
|
||||
mirror: always
|
||||
|
||||
# Whether to strip auth headers for redirected fetch/xhr channels
|
||||
# https://fetch.spec.whatwg.org/#http-redirect-fetch
|
||||
- name: network.fetch.redirect.stripAuthHeader
|
||||
type: RelaxedAtomicBool
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# If true, cross origin fetch (or XHR) requests would be keyed
|
||||
# with a different cache key.
|
||||
- name: network.fetch.cache_partition_cross_origin
|
||||
|
|
@ -11822,13 +11815,6 @@
|
|||
value: true
|
||||
mirror: always
|
||||
|
||||
# Whether to strip auth headers for redirected http channels
|
||||
# https://fetch.spec.whatwg.org/#http-redirect-fetch
|
||||
- name: network.http.redirect.stripAuthHeader
|
||||
type: RelaxedAtomicBool
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# Prefs allowing granular control of referers.
|
||||
# 0=don't send any, 1=send only on clicks, 2=send on image requests as well
|
||||
- name: network.http.sendRefererHeader
|
||||
|
|
|
|||
|
|
@ -173,11 +173,10 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext,
|
|||
|
||||
// Construct a LoadInfo object to use when creating the internal channel for an
|
||||
// Object/Embed load.
|
||||
static auto CreateObjectLoadInfo(nsDocShellLoadState* aLoadState,
|
||||
uint64_t aInnerWindowId,
|
||||
nsContentPolicyType aContentPolicyType,
|
||||
uint32_t aSandboxFlags)
|
||||
-> already_AddRefed<LoadInfo> {
|
||||
static auto CreateObjectLoadInfo(
|
||||
nsDocShellLoadState* aLoadState, uint64_t aInnerWindowId,
|
||||
nsContentPolicyType aContentPolicyType,
|
||||
uint32_t aSandboxFlags) -> already_AddRefed<LoadInfo> {
|
||||
RefPtr<WindowGlobalParent> wgp =
|
||||
WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
|
||||
MOZ_RELEASE_ASSERT(wgp);
|
||||
|
|
@ -810,6 +809,24 @@ auto DocumentLoadListener::Open(nsDocShellLoadState* aLoadState,
|
|||
if (cos && aUrgentStart) {
|
||||
cos->AddClassFlags(nsIClassOfService::UrgentStart);
|
||||
}
|
||||
|
||||
// ClientChannelHelper below needs us to have finalized the principal for
|
||||
// the channel because it will request that StoragePrincipalHelper mint us a
|
||||
// principal that needs to match the same principal that a later call to
|
||||
// StoragePrincipalHelper will mint when determining the right origin to
|
||||
// look up the ServiceWorker.
|
||||
//
|
||||
// Because nsHttpChannel::AsyncOpen calls UpdateAntiTrackingInfoForChannel
|
||||
// which potentially flips the third party bit/flag on the partition key on
|
||||
// the cookie jar which impacts the principal that will be minted, it is
|
||||
// essential that UpdateAntiTrackingInfoForChannel is called before
|
||||
// AddClientChannelHelperInParent below.
|
||||
//
|
||||
// Because the call to UpdateAntiTrackingInfoForChannel is largely
|
||||
// idempotent, we currently just make the call ourselves right now. The one
|
||||
// caveat is that the RFPRandomKey may be spuriously regenerated for
|
||||
// top-level documents.
|
||||
AntiTrackingUtils::UpdateAntiTrackingInfoForChannel(httpChannel);
|
||||
}
|
||||
|
||||
// Setup a ClientChannelHelper to watch for redirects, and copy
|
||||
|
|
@ -995,8 +1012,8 @@ auto DocumentLoadListener::OpenObject(
|
|||
uint64_t aInnerWindowId, nsLoadFlags aLoadFlags,
|
||||
nsContentPolicyType aContentPolicyType, bool aUrgentStart,
|
||||
dom::ContentParent* aContentParent,
|
||||
ObjectUpgradeHandler* aObjectUpgradeHandler, nsresult* aRv)
|
||||
-> RefPtr<OpenPromise> {
|
||||
ObjectUpgradeHandler* aObjectUpgradeHandler,
|
||||
nsresult* aRv) -> RefPtr<OpenPromise> {
|
||||
LOG(("DocumentLoadListener [%p] OpenObject [uri=%s]", this,
|
||||
aLoadState->URI()->GetSpecOrDefault().get()));
|
||||
|
||||
|
|
@ -1203,10 +1220,9 @@ void DocumentLoadListener::CleanupParentLoadAttempt(uint64_t aLoadIdent) {
|
|||
registrar->DeregisterChannels(aLoadIdent);
|
||||
}
|
||||
|
||||
auto DocumentLoadListener::ClaimParentLoad(DocumentLoadListener** aListener,
|
||||
uint64_t aLoadIdent,
|
||||
Maybe<uint64_t> aChannelId)
|
||||
-> RefPtr<OpenPromise> {
|
||||
auto DocumentLoadListener::ClaimParentLoad(
|
||||
DocumentLoadListener** aListener, uint64_t aLoadIdent,
|
||||
Maybe<uint64_t> aChannelId) -> RefPtr<OpenPromise> {
|
||||
nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
|
||||
RedirectChannelRegistrar::GetOrCreate();
|
||||
|
||||
|
|
|
|||
|
|
@ -513,6 +513,21 @@ networking:
|
|||
- necko@mozilla.com
|
||||
expires: 130
|
||||
|
||||
http_onstart_suspend_total_time:
|
||||
type: timing_distribution
|
||||
time_unit: millisecond
|
||||
telemetry_mirror: HTTP_ONSTART_SUSPEND_TOTAL_TIME
|
||||
description: >
|
||||
Time in milliseconds that http channel spent suspended between AsyncOpen and OnStartRequest.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1897209
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1347948
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1897209
|
||||
notification_emails:
|
||||
- necko@mozilla.com
|
||||
expires: never
|
||||
|
||||
http_1_download_throughput:
|
||||
type: custom_distribution
|
||||
unit: mbps
|
||||
|
|
|
|||
|
|
@ -96,7 +96,11 @@ nsresult nsAboutCache::Channel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo) {
|
|||
|
||||
if (!mOverview) {
|
||||
mBuffer.AppendLiteral(
|
||||
"<a href=\"about:cache?storage=\">Back to overview</a>");
|
||||
"<a href=\"about:cache?storage=\">Back to overview</a>\n");
|
||||
mBuffer.AppendLiteral(
|
||||
"<p id=\"explanation-dataSize\">Data sizes refer to the size of the "
|
||||
"response body and do not reflect the amount of disk space that the "
|
||||
"file occupies.</p>\n");
|
||||
}
|
||||
|
||||
rv = FlushBuffer();
|
||||
|
|
|
|||
|
|
@ -5191,14 +5191,12 @@ nsresult HttpBaseChannel::SetupReplacementChannel(nsIURI* newURI,
|
|||
// we need to strip Authentication headers for cross-origin requests
|
||||
// Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
|
||||
nsAutoCString authHeader;
|
||||
if (StaticPrefs::network_http_redirect_stripAuthHeader() &&
|
||||
NS_SUCCEEDED(
|
||||
httpChannel->GetRequestHeader("Authorization"_ns, authHeader))) {
|
||||
if (NS_ShouldRemoveAuthHeaderOnRedirect(static_cast<nsIChannel*>(this),
|
||||
newChannel, redirectFlags)) {
|
||||
rv = httpChannel->SetRequestHeader("Authorization"_ns, ""_ns, false);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
if (NS_SUCCEEDED(
|
||||
httpChannel->GetRequestHeader("Authorization"_ns, authHeader)) &&
|
||||
NS_ShouldRemoveAuthHeaderOnRedirect(static_cast<nsIChannel*>(this),
|
||||
newChannel, redirectFlags)) {
|
||||
rv = httpChannel->SetRequestHeader("Authorization"_ns, ""_ns, false);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
|
|
|||
|
|
@ -840,7 +840,6 @@ nsCORSListenerProxy::AsyncOnChannelRedirect(
|
|||
// cross-origin redirects.
|
||||
// See Bug 1874132
|
||||
bool stripAuthHeader =
|
||||
StaticPrefs::network_fetch_redirect_stripAuthHeader() &&
|
||||
NS_ShouldRemoveAuthHeaderOnRedirect(aOldChannel, aNewChannel, aFlags);
|
||||
|
||||
nsCOMPtr<nsIHttpChannel> oldHttpChannel = do_QueryInterface(aOldChannel);
|
||||
|
|
|
|||
|
|
@ -6086,8 +6086,7 @@ nsHttpChannel::Resume() {
|
|||
LogCallingScriptLocation(this);
|
||||
|
||||
if (--mSuspendCount == 0) {
|
||||
mSuspendTotalTime +=
|
||||
(TimeStamp::NowLoRes() - mSuspendTimestamp).ToMilliseconds();
|
||||
mSuspendTotalTime += TimeStamp::NowLoRes() - mSuspendTimestamp;
|
||||
|
||||
if (mCallOnResume) {
|
||||
// Resume the interrupted procedure first, then resume
|
||||
|
|
@ -7497,8 +7496,8 @@ nsHttpChannel::OnStartRequest(nsIRequest* request) {
|
|||
mOnStartRequestTimestamp = TimeStamp::Now();
|
||||
}
|
||||
|
||||
Telemetry::Accumulate(Telemetry::HTTP_ONSTART_SUSPEND_TOTAL_TIME,
|
||||
mSuspendTotalTime);
|
||||
mozilla::glean::networking::http_onstart_suspend_total_time
|
||||
.AccumulateRawDuration(mSuspendTotalTime);
|
||||
|
||||
if (mTransaction) {
|
||||
mProxyConnectResponseCode = mTransaction->GetProxyConnectResponseCode();
|
||||
|
|
|
|||
|
|
@ -617,7 +617,7 @@ class nsHttpChannel final : public HttpBaseChannel,
|
|||
|
||||
// Total time the channel spent suspended. This value is reported to
|
||||
// telemetry in nsHttpChannel::OnStartRequest().
|
||||
uint32_t mSuspendTotalTime{0};
|
||||
TimeDuration mSuspendTotalTime{0};
|
||||
|
||||
friend class AutoRedirectVetoNotifier;
|
||||
friend class HttpAsyncAborter<nsHttpChannel>;
|
||||
|
|
|
|||
|
|
@ -3360,15 +3360,6 @@ GeckoDriver.prototype.setPermission = async function (cmd) {
|
|||
lazy.permissions.validateDescriptor(descriptor);
|
||||
lazy.permissions.validateState(state);
|
||||
|
||||
// Bug 1878741: Allowing this permission causes timing related Android crash.
|
||||
if (
|
||||
descriptor.name === "notifications" &&
|
||||
lazy.permissions.isNotificationPreferenceSet()
|
||||
) {
|
||||
// Okay, do nothing. The notifications module will work without permission.
|
||||
return;
|
||||
}
|
||||
|
||||
let params;
|
||||
try {
|
||||
params =
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ ChromeUtils.defineLazyGetter(lazy, "UNLOAD_TIMEOUT_MULTIPLIER", () => {
|
|||
|
||||
export const DEFAULT_UNLOAD_TIMEOUT = 200;
|
||||
|
||||
// Load flag for an error page from the DocShell (0x0001U << 16)
|
||||
const LOAD_FLAG_ERROR_PAGE = 0x10000;
|
||||
|
||||
const STATE_START = Ci.nsIWebProgressListener.STATE_START;
|
||||
const STATE_STOP = Ci.nsIWebProgressListener.STATE_STOP;
|
||||
|
||||
/**
|
||||
* Returns the multiplier used for the unload timer. Useful for tests which
|
||||
* assert the behavior of this timeout.
|
||||
|
|
@ -107,7 +113,14 @@ export async function waitForInitialNavigationCompleted(
|
|||
listener.stop();
|
||||
}
|
||||
|
||||
await navigated;
|
||||
try {
|
||||
await navigated;
|
||||
} catch (e) {
|
||||
// Ignore any error if the initial navigation failed.
|
||||
lazy.logger.debug(
|
||||
lazy.truncate`[${browsingContext.id}] Initial Navigation to ${listener.currentURI?.spec} failed: ${e}`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
currentURI: listener.currentURI,
|
||||
|
|
@ -126,6 +139,7 @@ export class ProgressListener {
|
|||
#webProgress;
|
||||
|
||||
#deferredNavigation;
|
||||
#errorName;
|
||||
#seenStartFlag;
|
||||
#targetURI;
|
||||
#unloadTimerId;
|
||||
|
|
@ -171,6 +185,7 @@ export class ProgressListener {
|
|||
this.#webProgress = webProgress;
|
||||
|
||||
this.#deferredNavigation = null;
|
||||
this.#errorName = null;
|
||||
this.#seenStartFlag = false;
|
||||
this.#targetURI = null;
|
||||
this.#unloadTimerId = null;
|
||||
|
|
@ -188,6 +203,15 @@ export class ProgressListener {
|
|||
return this.#webProgress.browsingContext.currentURI;
|
||||
}
|
||||
|
||||
get documentURI() {
|
||||
return this.#webProgress.browsingContext.currentWindowGlobal.documentURI;
|
||||
}
|
||||
|
||||
get isInitialDocument() {
|
||||
return this.#webProgress.browsingContext.currentWindowGlobal
|
||||
.isInitialDocument;
|
||||
}
|
||||
|
||||
get isLoadingDocument() {
|
||||
return this.#webProgress.isLoadingDocument;
|
||||
}
|
||||
|
|
@ -196,6 +220,10 @@ export class ProgressListener {
|
|||
return !!this.#deferredNavigation;
|
||||
}
|
||||
|
||||
get loadType() {
|
||||
return this.#webProgress.loadType;
|
||||
}
|
||||
|
||||
get targetURI() {
|
||||
return this.#targetURI;
|
||||
}
|
||||
|
|
@ -203,13 +231,17 @@ export class ProgressListener {
|
|||
#checkLoadingState(request, options = {}) {
|
||||
const { isStart = false, isStop = false, status = 0 } = options;
|
||||
|
||||
this.#trace(`Check loading state: isStart=${isStart} isStop=${isStop}`);
|
||||
this.#trace(
|
||||
`Loading state: isStart=${isStart} isStop=${isStop} status=0x${status.toString(
|
||||
16
|
||||
)}, loadType=0x${this.loadType.toString(16)}`
|
||||
);
|
||||
if (isStart && !this.#seenStartFlag) {
|
||||
this.#seenStartFlag = true;
|
||||
|
||||
this.#targetURI = this.#getTargetURI(request);
|
||||
|
||||
this.#trace(`state=start: ${this.targetURI?.spec}`);
|
||||
this.#trace(lazy.truncate`Started loading ${this.targetURI?.spec}`);
|
||||
|
||||
if (this.#unloadTimerId !== null) {
|
||||
lazy.clearTimeout(this.#unloadTimerId);
|
||||
|
|
@ -231,29 +263,32 @@ export class ProgressListener {
|
|||
!Components.isSuccessCode(status) &&
|
||||
status != Cr.NS_ERROR_PARSED_DATA_CACHED
|
||||
) {
|
||||
if (
|
||||
status == Cr.NS_BINDING_ABORTED &&
|
||||
this.browsingContext.currentWindowGlobal.isInitialDocument
|
||||
) {
|
||||
const errorName = ChromeUtils.getXPCOMErrorName(status);
|
||||
|
||||
if (this.loadType & LOAD_FLAG_ERROR_PAGE) {
|
||||
// Wait for the next location change notification to ensure that the
|
||||
// real error page was loaded.
|
||||
this.#trace(`Error=${errorName}, wait for redirect to error page`);
|
||||
this.#errorName = errorName;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle an aborted navigation. While for an initial document another
|
||||
// navigation to the real document will happen it's not the case for
|
||||
// normal documents. Here we need to stop the listener immediately.
|
||||
if (status == Cr.NS_BINDING_ABORTED && this.isInitialDocument) {
|
||||
this.#trace(
|
||||
"Ignore aborted navigation error to the initial document, real document will be loaded."
|
||||
"Ignore aborted navigation error to the initial document."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// The navigation request caused an error.
|
||||
const errorName = ChromeUtils.getXPCOMErrorName(status);
|
||||
this.#trace(
|
||||
`state=stop: error=0x${status.toString(16)} (${errorName})`
|
||||
);
|
||||
this.stop({ error: new Error(errorName) });
|
||||
return;
|
||||
}
|
||||
|
||||
this.#trace(`state=stop: ${this.currentURI.spec}`);
|
||||
|
||||
// If a non initial page finished loading the navigation is done.
|
||||
if (!this.browsingContext.currentWindowGlobal.isInitialDocument) {
|
||||
if (!this.isInitialDocument) {
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
|
|
@ -267,6 +302,18 @@ export class ProgressListener {
|
|||
}
|
||||
}
|
||||
|
||||
#getErrorName(documentURI) {
|
||||
try {
|
||||
// Otherwise try to retrieve it from the document URI if it is an
|
||||
// error page like `about:neterror?e=contentEncodingError&u=http%3A//...`
|
||||
const regex = /about:.*error\?e=([^&]*)/;
|
||||
return documentURI.spec.match(regex)[1];
|
||||
} catch (e) {
|
||||
// Or return a generic name
|
||||
return "Address rejected";
|
||||
}
|
||||
}
|
||||
|
||||
#getTargetURI(request) {
|
||||
try {
|
||||
return request.QueryInterface(Ci.nsIChannel).originalURI;
|
||||
|
|
@ -296,8 +343,8 @@ export class ProgressListener {
|
|||
|
||||
onStateChange(progress, request, flag, status) {
|
||||
this.#checkLoadingState(request, {
|
||||
isStart: flag & Ci.nsIWebProgressListener.STATE_START,
|
||||
isStop: flag & Ci.nsIWebProgressListener.STATE_STOP,
|
||||
isStart: !!(flag & STATE_START),
|
||||
isStop: !!(flag & STATE_STOP),
|
||||
status,
|
||||
});
|
||||
}
|
||||
|
|
@ -305,15 +352,18 @@ export class ProgressListener {
|
|||
onLocationChange(progress, request, location, flag) {
|
||||
// If an error page has been loaded abort the navigation.
|
||||
if (flag & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
|
||||
this.#trace(`location=errorPage: ${location.spec}`);
|
||||
this.stop({ error: new Error("Address restricted") });
|
||||
const errorName = this.#errorName || this.#getErrorName(this.documentURI);
|
||||
this.#trace(
|
||||
lazy.truncate`Location=errorPage, error=${errorName}, url=${this.documentURI.spec}`
|
||||
);
|
||||
this.stop({ error: new Error(errorName) });
|
||||
return;
|
||||
}
|
||||
|
||||
// If location has changed in the same document the navigation is done.
|
||||
if (flag & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
|
||||
this.#targetURI = location;
|
||||
this.#trace(`location=sameDocument: ${this.targetURI?.spec}`);
|
||||
this.#trace(`Location=sameDocument: ${this.targetURI?.spec}`);
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
|
|
@ -383,7 +433,9 @@ export class ProgressListener {
|
|||
stop(options = {}) {
|
||||
const { error } = options;
|
||||
|
||||
this.#trace(`Stop: has error=${!!error}`);
|
||||
this.#trace(
|
||||
lazy.truncate`Stop: has error=${!!error} url=${this.currentURI.spec}`
|
||||
);
|
||||
|
||||
if (!this.#deferredNavigation) {
|
||||
throw new Error("Progress listener not yet started");
|
||||
|
|
|
|||
|
|
@ -42,24 +42,6 @@ permissions.getStorageAccessPermissionsType = function (uri) {
|
|||
return "3rdPartyFrameStorage^" + thirdPartyPrincipalSite;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the notification preference is set.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* Returns `true` if the notification preference is set.
|
||||
*
|
||||
* @throws {UnsupportedOperationError}
|
||||
* If the notification preference is not set.
|
||||
*/
|
||||
permissions.isNotificationPreferenceSet = function () {
|
||||
if (Services.prefs.getBoolPref("notification.prompt.testing", false)) {
|
||||
return true;
|
||||
}
|
||||
throw new lazy.error.UnsupportedOperationError(
|
||||
`Setting "descriptor.name" "notifications" expected "notification.prompt.testing" preference to be set`
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a permission given a permission descriptor, a permission state,
|
||||
* an origin.
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ const {
|
|||
"chrome://remote/content/shared/Navigate.sys.mjs"
|
||||
);
|
||||
|
||||
const LOAD_FLAG_ERROR_PAGE = 0x10000;
|
||||
|
||||
const CURRENT_URI = Services.io.newURI("http://foo.bar/");
|
||||
const INITIAL_URI = Services.io.newURI("about:blank");
|
||||
const TARGET_URI = Services.io.newURI("http://foo.cheese/");
|
||||
const TARGET_URI_IS_ERROR_PAGE = Services.io.newURI("doesnotexist://");
|
||||
const TARGET_URI_ERROR_PAGE = Services.io.newURI("doesnotexist://");
|
||||
const TARGET_URI_WITH_HASH = Services.io.newURI("http://foo.cheese/#foo");
|
||||
|
||||
function wait(time) {
|
||||
|
|
@ -43,6 +45,7 @@ class MockWebProgress {
|
|||
this.documentRequest = null;
|
||||
this.isLoadingDocument = false;
|
||||
this.listener = null;
|
||||
this.loadType = 0;
|
||||
this.progressListenerRemoved = false;
|
||||
}
|
||||
|
||||
|
|
@ -69,15 +72,15 @@ class MockWebProgress {
|
|||
this.documentRequest = null;
|
||||
|
||||
if (flag & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
|
||||
this.browsingContext.currentURI = TARGET_URI_WITH_HASH;
|
||||
this.browsingContext.updateURI(TARGET_URI_WITH_HASH);
|
||||
} else if (flag & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
|
||||
this.browsingContext.currentURI = TARGET_URI_IS_ERROR_PAGE;
|
||||
this.browsingContext.updateURI(TARGET_URI_ERROR_PAGE, true);
|
||||
}
|
||||
|
||||
this.listener?.onLocationChange(
|
||||
this,
|
||||
this.documentRequest,
|
||||
TARGET_URI_WITH_HASH,
|
||||
this.browsingContext.currentURI,
|
||||
flag
|
||||
);
|
||||
|
||||
|
|
@ -98,6 +101,7 @@ class MockWebProgress {
|
|||
this.browsingContext.currentWindowGlobal.isInitialDocument = isInitial;
|
||||
|
||||
this.isLoadingDocument = true;
|
||||
this.loadType = 0;
|
||||
const uri = isInitial ? INITIAL_URI : TARGET_URI;
|
||||
this.documentRequest = new MockRequest(uri);
|
||||
|
||||
|
|
@ -105,14 +109,14 @@ class MockWebProgress {
|
|||
this,
|
||||
this.documentRequest,
|
||||
Ci.nsIWebProgressListener.STATE_START,
|
||||
null
|
||||
0
|
||||
);
|
||||
|
||||
return new Promise(executeSoon);
|
||||
}
|
||||
|
||||
sendStopState(options = {}) {
|
||||
const { errorFlag = 0 } = options;
|
||||
const { flag = 0, loadType = 0 } = options;
|
||||
|
||||
this.browsingContext.currentURI = this.documentRequest.originalURI;
|
||||
|
||||
|
|
@ -123,9 +127,16 @@ class MockWebProgress {
|
|||
this,
|
||||
this.documentRequest,
|
||||
Ci.nsIWebProgressListener.STATE_STOP,
|
||||
errorFlag
|
||||
flag
|
||||
);
|
||||
|
||||
if (loadType & LOAD_FLAG_ERROR_PAGE) {
|
||||
this.loadType = 0x10000;
|
||||
return this.sendLocationChange({
|
||||
flag: Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE,
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise(executeSoon);
|
||||
}
|
||||
}
|
||||
|
|
@ -133,11 +144,23 @@ class MockWebProgress {
|
|||
class MockTopContext {
|
||||
constructor(webProgress = null) {
|
||||
this.currentURI = CURRENT_URI;
|
||||
this.currentWindowGlobal = { isInitialDocument: true };
|
||||
this.currentWindowGlobal = {
|
||||
isInitialDocument: true,
|
||||
documentURI: CURRENT_URI,
|
||||
};
|
||||
this.id = 7;
|
||||
this.top = this;
|
||||
this.webProgress = webProgress || new MockWebProgress(this);
|
||||
}
|
||||
|
||||
updateURI(uri, isError = false) {
|
||||
this.currentURI = uri;
|
||||
if (isError) {
|
||||
this.currentWindowGlobal.documentURI = "about:neterror?e=errorMessage";
|
||||
} else {
|
||||
this.currentWindowGlobal.documentURI = uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hasPromiseResolved = async function (promise) {
|
||||
|
|
@ -775,7 +798,7 @@ add_task(async function test_ProgressListener_ignoreCacheError() {
|
|||
|
||||
await webProgress.sendStartState();
|
||||
await webProgress.sendStopState({
|
||||
errorFlag: Cr.NS_ERROR_PARSED_DATA_CACHED,
|
||||
flag: Cr.NS_ERROR_PARSED_DATA_CACHED,
|
||||
});
|
||||
|
||||
ok(await hasPromiseResolved(navigated), "Listener has resolved");
|
||||
|
|
@ -803,23 +826,50 @@ add_task(async function test_ProgressListener_navigationRejectedOnErrorPage() {
|
|||
);
|
||||
});
|
||||
|
||||
add_task(async function test_ProgressListener_navigationRejectedOnStopState() {
|
||||
const browsingContext = new MockTopContext();
|
||||
const webProgress = browsingContext.webProgress;
|
||||
add_task(
|
||||
async function test_ProgressListener_navigationRejectedOnStopStateErrorPage() {
|
||||
const browsingContext = new MockTopContext();
|
||||
const webProgress = browsingContext.webProgress;
|
||||
|
||||
const progressListener = new ProgressListener(webProgress, {
|
||||
waitForExplicitStart: false,
|
||||
});
|
||||
const navigated = progressListener.start();
|
||||
const progressListener = new ProgressListener(webProgress, {
|
||||
waitForExplicitStart: false,
|
||||
});
|
||||
const navigated = progressListener.start();
|
||||
|
||||
await webProgress.sendStartState();
|
||||
await webProgress.sendStopState({ errorFlag: Cr.NS_BINDING_ABORTED });
|
||||
await webProgress.sendStartState();
|
||||
await webProgress.sendStopState({
|
||||
flag: Cr.NS_ERROR_MALWARE_URI,
|
||||
loadType: LOAD_FLAG_ERROR_PAGE,
|
||||
});
|
||||
|
||||
ok(
|
||||
await hasPromiseRejected(navigated),
|
||||
"Listener has rejected in stop state for erroneous navigation"
|
||||
);
|
||||
});
|
||||
ok(
|
||||
await hasPromiseRejected(navigated),
|
||||
"Listener has rejected in stop state for erroneous navigation"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function test_ProgressListener_navigationRejectedOnStopStateAndAborted() {
|
||||
const browsingContext = new MockTopContext();
|
||||
const webProgress = browsingContext.webProgress;
|
||||
|
||||
for (const flag of [Cr.NS_BINDING_ABORTED, Cr.NS_ERROR_ABORT]) {
|
||||
const progressListener = new ProgressListener(webProgress, {
|
||||
waitForExplicitStart: false,
|
||||
});
|
||||
const navigated = progressListener.start();
|
||||
|
||||
await webProgress.sendStartState();
|
||||
await webProgress.sendStopState({ flag });
|
||||
|
||||
ok(
|
||||
await hasPromiseRejected(navigated),
|
||||
"Listener has rejected in stop state for erroneous navigation"
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add_task(async function test_ProgressListener_stopIfStarted() {
|
||||
const browsingContext = new MockTopContext();
|
||||
|
|
|
|||
|
|
@ -63,15 +63,6 @@ class PermissionsModule extends Module {
|
|||
|
||||
const permissionName = descriptor.name;
|
||||
|
||||
// Bug 1878741: Allowing this permission causes timing related Android crash.
|
||||
if (
|
||||
permissionName === "notifications" &&
|
||||
lazy.permissions.isNotificationPreferenceSet()
|
||||
) {
|
||||
// Okay, do nothing. The notifications module will work without permission.
|
||||
return;
|
||||
}
|
||||
|
||||
if (permissionName === "storage-access") {
|
||||
// TODO: Bug 1895457. Add support for "storage-access" permission.
|
||||
throw new lazy.error.UnsupportedOperationError(
|
||||
|
|
|
|||
|
|
@ -980,6 +980,16 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
|
|||
criteria = "safe-to-deploy"
|
||||
delta = "1.4.0 -> 1.6.0"
|
||||
|
||||
[[audits.clang-sys]]
|
||||
who = "Erich Gubler <erichdongubler@gmail.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "1.6.0 -> 1.7.0"
|
||||
notes = """
|
||||
Adds several new symbols for Clang versions 11.0, 12.0, 16.0, and 17.0, conditionally enabled based
|
||||
on Cargo feature flags. Some other minor internal refactors were implemented that shouldn't change
|
||||
functionality otherwise.
|
||||
"""
|
||||
|
||||
[[audits.clap-verbosity-flag]]
|
||||
who = "Kershaw Chang <kershaw@mozilla.com>"
|
||||
criteria = "safe-to-run"
|
||||
|
|
@ -1278,6 +1288,11 @@ who = "Andreas Pehrson <apehrson@mozilla.com>"
|
|||
criteria = "safe-to-deploy"
|
||||
delta = "0.10.3 -> 0.12.0"
|
||||
|
||||
[[audits.cubeb]]
|
||||
who = "Andreas Pehrson <apehrson@mozilla.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.12.0 -> 0.13.0"
|
||||
|
||||
[[audits.cubeb-backend]]
|
||||
who = "Matthew Gregan <kinetik@flim.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
@ -1309,6 +1324,11 @@ who = "Andreas Pehrson <apehrson@mozilla.com>"
|
|||
criteria = "safe-to-deploy"
|
||||
delta = "0.10.7 -> 0.12.0"
|
||||
|
||||
[[audits.cubeb-backend]]
|
||||
who = "Andreas Pehrson <apehrson@mozilla.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.12.0 -> 0.13.0"
|
||||
|
||||
[[audits.cubeb-core]]
|
||||
who = "Matthew Gregan <kinetik@flim.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
@ -1345,6 +1365,11 @@ who = "Andreas Pehrson <apehrson@mozilla.com>"
|
|||
criteria = "safe-to-deploy"
|
||||
delta = "0.10.7 -> 0.12.0"
|
||||
|
||||
[[audits.cubeb-core]]
|
||||
who = "Andreas Pehrson <apehrson@mozilla.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.12.0 -> 0.13.0"
|
||||
|
||||
[[audits.cubeb-sys]]
|
||||
who = "Matthew Gregan <kinetik@flim.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
@ -1376,6 +1401,11 @@ who = "Andreas Pehrson <apehrson@mozilla.com>"
|
|||
criteria = "safe-to-deploy"
|
||||
delta = "0.10.7 -> 0.12.0"
|
||||
|
||||
[[audits.cubeb-sys]]
|
||||
who = "Andreas Pehrson <apehrson@mozilla.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.12.0 -> 0.13.0"
|
||||
|
||||
[[audits.d3d12]]
|
||||
who = "Jim Blandy <jimb@red-bean.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ firefox-ci:
|
|||
- '.taskcluster.yml'
|
||||
- 'taskcluster/kinds/**'
|
||||
- 'taskcluster/**/*.py'
|
||||
- 'third_party/python/taskcluster_taskgraph/**/*.py'
|
||||
- 'tools/tryselect/selectors/auto.py'
|
||||
|
||||
fog:
|
||||
|
|
@ -343,8 +344,9 @@ taskgraph-tests:
|
|||
subsuite: taskgraph
|
||||
when:
|
||||
files-changed:
|
||||
- 'taskcluster/**/*.py'
|
||||
- 'python/mach/**/*.py'
|
||||
- 'taskcluster/**/*.py'
|
||||
- 'third_party/python/taskcluster_taskgraph/**/*.py'
|
||||
|
||||
tryselect:
|
||||
description: tools/tryselect unit tests
|
||||
|
|
@ -362,6 +364,7 @@ tryselect:
|
|||
- 'taskcluster/config.yml'
|
||||
- 'taskcluster/kinds/test/**'
|
||||
- 'taskcluster/gecko_taskgraph/transforms/**'
|
||||
- 'third_party/python/taskcluster_taskgraph/**/*.py'
|
||||
- 'tools/tryselect/**'
|
||||
|
||||
mozbuild:
|
||||
|
|
|
|||
|
|
@ -40,3 +40,4 @@ diff:
|
|||
files-changed:
|
||||
- 'taskcluster/kinds/**'
|
||||
- 'taskcluster/**/*.py'
|
||||
- 'third_party/python/taskcluster_taskgraph/**/*.py'
|
||||
|
|
|
|||
|
|
@ -349,6 +349,15 @@ class DesktopUnittest(TestingMixin, MercurialScript, MozbaseMixin, CodeCoverageM
|
|||
"help": "Whether to use the Http2 server",
|
||||
},
|
||||
],
|
||||
[
|
||||
["--mochitest-flavor"],
|
||||
{
|
||||
"action": "store",
|
||||
"dest": "mochitest_flavor",
|
||||
"help": "Specify which mochitest flavor to run."
|
||||
"Examples: 'plain', 'browser'",
|
||||
},
|
||||
],
|
||||
]
|
||||
+ copy.deepcopy(testing_config_options)
|
||||
+ copy.deepcopy(code_coverage_config_options)
|
||||
|
|
@ -385,6 +394,7 @@ class DesktopUnittest(TestingMixin, MercurialScript, MozbaseMixin, CodeCoverageM
|
|||
self.binary_path = c.get("binary_path")
|
||||
self.abs_app_dir = None
|
||||
self.abs_res_dir = None
|
||||
self.mochitest_flavor = c.get("mochitest_flavor", None)
|
||||
|
||||
# Construct an identifier to be used to identify Perfherder data
|
||||
# for resource monitoring recording. This attempts to uniquely
|
||||
|
|
@ -642,6 +652,9 @@ class DesktopUnittest(TestingMixin, MercurialScript, MozbaseMixin, CodeCoverageM
|
|||
"gtest_dir": os.path.join(dirs["abs_test_install_dir"], "gtest"),
|
||||
}
|
||||
|
||||
if self.mochitest_flavor:
|
||||
str_format_values.update({"mochitest_flavor": self.mochitest_flavor})
|
||||
|
||||
# TestingMixin._download_and_extract_symbols() will set
|
||||
# self.symbols_path when downloading/extracting.
|
||||
if self.symbols_path:
|
||||
|
|
@ -701,6 +714,9 @@ class DesktopUnittest(TestingMixin, MercurialScript, MozbaseMixin, CodeCoverageM
|
|||
"mochitest."
|
||||
)
|
||||
|
||||
if c.get("mochitest_flavor", None):
|
||||
base_cmd.append("--flavor={}".format(c["mochitest_flavor"]))
|
||||
|
||||
if c["headless"]:
|
||||
base_cmd.append("--headless")
|
||||
|
||||
|
|
@ -1210,7 +1226,14 @@ class DesktopUnittest(TestingMixin, MercurialScript, MozbaseMixin, CodeCoverageM
|
|||
options_list = suites[suite]
|
||||
tests_list = []
|
||||
|
||||
flavor = self._query_try_flavor(suite_category, suite)
|
||||
flavor = (
|
||||
self.mochitest_flavor
|
||||
if self.mochitest_flavor
|
||||
else self._query_try_flavor(suite_category, suite)
|
||||
)
|
||||
if self.mochitest_flavor:
|
||||
replace_dict.update({"mochitest_flavor": flavor})
|
||||
|
||||
try_options, try_tests = self.try_args(flavor)
|
||||
|
||||
suite_name = suite_category + "-" + suite
|
||||
|
|
|
|||
|
|
@ -151,6 +151,15 @@ class MarionetteTest(TestingMixin, MercurialScript, TransferMixin, CodeCoverageM
|
|||
"help": "Run the browser without fission enabled",
|
||||
},
|
||||
],
|
||||
[
|
||||
["--subsuite"],
|
||||
{
|
||||
"action": "store",
|
||||
"dest": "subsuite",
|
||||
"default": "marionette",
|
||||
"help": "Selects test paths from test-manifests.active",
|
||||
},
|
||||
],
|
||||
]
|
||||
+ copy.deepcopy(testing_config_options)
|
||||
+ copy.deepcopy(code_coverage_config_options)
|
||||
|
|
@ -188,6 +197,7 @@ class MarionetteTest(TestingMixin, MercurialScript, TransferMixin, CodeCoverageM
|
|||
self.binary_path = c.get("binary_path")
|
||||
self.test_url = c.get("test_url")
|
||||
self.test_packages_url = c.get("test_packages_url")
|
||||
self.subsuite = c.get("subsuite")
|
||||
|
||||
self.test_suite = self._get_test_suite(c.get("emulator"))
|
||||
if self.test_suite not in self.config["suite_definitions"]:
|
||||
|
|
@ -357,7 +367,7 @@ class MarionetteTest(TestingMixin, MercurialScript, TransferMixin, CodeCoverageM
|
|||
test_paths = json.loads(os.environ.get("MOZHARNESS_TEST_PATHS", '""'))
|
||||
confirm_paths = json.loads(os.environ.get("MOZHARNESS_CONFIRM_PATHS", '""'))
|
||||
|
||||
suite = "marionette"
|
||||
suite = self.subsuite
|
||||
if test_paths and suite in test_paths:
|
||||
suite_test_paths = test_paths[suite]
|
||||
if confirm_paths and suite in confirm_paths and confirm_paths[suite]:
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
lsan-allowed: [Alloc, NS_GetXPTCallStub, NewPage, nsXPCWrappedJS::GetNewOrUsed]
|
||||
leak-threshold: [default:51200]
|
||||
prefs: [marionette.setpermission.enabled:true]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
prefs: [notification.prompt.testing:true]
|
||||
|
||||
[event-onclose.https.html]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1816427
|
||||
expected:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
prefs: [notification.prompt.testing:true]
|
||||
|
||||
[event-onshow.https.html]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1816427
|
||||
expected:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
prefs: [notification.prompt.testing:true, dom.webnotifications.loglevel:All]
|
||||
prefs: [dom.webnotifications.loglevel:All]
|
||||
[getnotifications-across-processes.https.window.html]
|
||||
[Get notification created from window]
|
||||
expected:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
prefs: [notification.prompt.testing:true]
|
||||
[instance.https.window.html]
|
||||
expected:
|
||||
if (os == "linux") and not debug and not asan and not tsan: [OK, TIMEOUT]
|
||||
|
|
|
|||
|
|
@ -1,113 +1,172 @@
|
|||
[lang.https.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
if os == "android": TIMEOUT
|
||||
|
||||
[Roundtripping lang "". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": TIMEOUT
|
||||
|
||||
[Roundtripping lang "en". Expecting "en".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "en-US-x-hixie". Expecting "en-US-x-hixie".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "de-DE". Expecting "de-DE".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "de-de". Expecting "de-de".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "de-De". Expecting "de-De".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "de-dE". Expecting "de-dE".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "de-DE-1996". Expecting "de-DE-1996".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "de-Latn-DE". Expecting "de-Latn-DE".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "de-Latf-DE". Expecting "de-Latf-DE".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "de-Latn-DE-1996". Expecting "de-Latn-DE-1996".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "de-CH". Expecting "de-CH".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "it-CH". Expecting "it-CH".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "fr-CH". Expecting "fr-CH".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "rm-CH". Expecting "rm-CH".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "es-CH". Expecting "es-CH".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
|
||||
[Roundtripping lang "Latn-de". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "Latf-de". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "tic-tac-tac-toe". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "cocoa-1-bar". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "cocoa-a-bar". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "en-". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "en--". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "foo--bar". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "id---Java". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "fr-x". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "fr-xenomorph". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "fr-x-xenomorph". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "a". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "a-fr-lang". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "b-fr-lang". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "es1-KK-aa-bb-cc-dd". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "es2-KL-aa-bb-cc-dd". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "es3-KM-aa-bb-cc-dd". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "fooÉ". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "foöÉ-bÁr". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
||||
[Roundtripping lang "foöÉbÁr". Expecting "".]
|
||||
expected: NOTRUN
|
||||
expected:
|
||||
if os == "android": NOTRUN
|
||||
FAIL
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
[permission.html]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1897999
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
if (os == "android"): CRASH
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
prefs: [notification.prompt.testing:true]
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
prefs: [notification.prompt.testing:true]
|
||||
[getnotifications-across-processes.https.window.html]
|
||||
[Get notification created from window]
|
||||
expected:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
prefs: [notification.prompt.testing:true]
|
||||
|
||||
[tag.https.html]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1891536
|
||||
expected:
|
||||
|
|
|
|||
|
|
@ -36,6 +36,15 @@ async def test_insecure_certificate(
|
|||
expected_error=True,
|
||||
)
|
||||
|
||||
# If the error page hasn't fully loaded yet, running a BiDi command
|
||||
# in most of the cases will trigger an error like:
|
||||
# `AbortError: Actor 'MessageHandlerFrame' destroyed before query`
|
||||
result = await bidi_session.browsing_context.locate_nodes(
|
||||
context=contexts[0]["context"],
|
||||
locator={"type": "css", "value": "body"},
|
||||
)
|
||||
assert len(result["nodes"]) > 0
|
||||
|
||||
|
||||
async def test_invalid_content_encoding(bidi_session, new_tab, inline):
|
||||
await navigate_and_assert(
|
||||
|
|
@ -44,3 +53,12 @@ async def test_invalid_content_encoding(bidi_session, new_tab, inline):
|
|||
f"{inline('<div>foo')}&pipe=header(Content-Encoding,gzip)",
|
||||
expected_error=True,
|
||||
)
|
||||
|
||||
# If the error page hasn't fully loaded yet, running a BiDi command
|
||||
# in most of the cases will trigger an error like:
|
||||
# `AbortError: Actor 'MessageHandlerFrame' destroyed before query`
|
||||
result = await bidi_session.browsing_context.locate_nodes(
|
||||
context=new_tab["context"],
|
||||
locator={"type": "css", "value": "body"},
|
||||
)
|
||||
assert len(result["nodes"]) > 0
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ idl_test(
|
|||
Notification: ['notification'],
|
||||
});
|
||||
self.notification = new Notification('title');
|
||||
self.notification.close();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
<link rel="author" title="Apple Inc." href="http://www.apple.com/">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/resources/testdriver.js"></script>
|
||||
<script src="/resources/testdriver-vendor.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
<script>
|
||||
setup({ explicit_done: true });
|
||||
|
||||
/* Validity and well-formedness was determined by using the BCP 47
|
||||
Validator at http://schneegans.de/lv/ */
|
||||
|
||||
|
|
@ -29,20 +30,20 @@ var invalid_langs = ["en-", "en-\-", "foo-\-bar", "id-\-\-Java", "fr-x",
|
|||
"es2-KL-aa-bb-cc-dd", "es3-KM-aa-bb-cc-dd", "fooÉ",
|
||||
"foöÉ-bÁr", "foöÉbÁr"];
|
||||
|
||||
|
||||
promise_setup(async () => {
|
||||
// No need to show actual notification
|
||||
await trySettingPermission("prompt");
|
||||
});
|
||||
|
||||
function test_lang(language, should_passthrough) {
|
||||
var expected = should_passthrough ? language : "";
|
||||
test(function() {
|
||||
if (Notification.permission != "granted") {
|
||||
this.force_timeout()
|
||||
this.set_status(this.NOTRUN, "You must allow notifications for this origin before running this test.")
|
||||
}
|
||||
promise_test(async () => {
|
||||
var notification = new Notification("This is a notification.", {
|
||||
lang: language
|
||||
});
|
||||
notification.close();
|
||||
assert_equals(notification.lang, expected, "notification.lang");
|
||||
notification.onshow = function() {
|
||||
notification.close();
|
||||
};
|
||||
},
|
||||
"Roundtripping lang \"" + language + "\". Expecting \"" + expected + "\".");
|
||||
}
|
||||
|
|
@ -58,6 +59,4 @@ for (var i=0; i<well_formed_langs.length; i++) {
|
|||
for (var i=0; i<invalid_langs.length; i++) {
|
||||
test_lang(invalid_langs[i], false);
|
||||
}
|
||||
|
||||
done();
|
||||
</script>
|
||||
|
|
|
|||
9
third_party/python/poetry.lock
generated
vendored
9
third_party/python/poetry.lock
generated
vendored
|
|
@ -1161,7 +1161,6 @@ files = [
|
|||
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
||||
|
|
@ -1376,14 +1375,14 @@ test = ["aiofiles", "coverage", "flake8", "httmock", "httptest", "hypothesis", "
|
|||
|
||||
[[package]]
|
||||
name = "taskcluster-taskgraph"
|
||||
version = "8.0.1"
|
||||
version = "8.2.0"
|
||||
description = "Build taskcluster taskgraphs"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "taskcluster-taskgraph-8.0.1.tar.gz", hash = "sha256:21387537bbebab2a7b1890d03e20e49379bdda65efd45ca7fb8d01f5c29e1797"},
|
||||
{file = "taskcluster_taskgraph-8.0.1-py3-none-any.whl", hash = "sha256:14500bc703f64eb002c0cd505caaf2d34ffc0ae66d109b108e738661da1ae09c"},
|
||||
{file = "taskcluster-taskgraph-8.2.0.tar.gz", hash = "sha256:af146323402c2d5f67c65e3c232eda953da1ce319e465069e4d5c7aeb212b66e"},
|
||||
{file = "taskcluster_taskgraph-8.2.0-py3-none-any.whl", hash = "sha256:410e9c9ef43eac1d0676f16867137de90f77eb0b4e0cbe746fe5512d1a626822"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -1625,4 +1624,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "a683c725217ca4a2d61b56b43b137217b5f8630ee0715e44637fd29599e0b0a2"
|
||||
content-hash = "401a5bde4f28e456dc5369de933b4382bfc9cbf12da2a9ccd7dbfa1a592f34b9"
|
||||
|
|
|
|||
2
third_party/python/requirements.in
vendored
2
third_party/python/requirements.in
vendored
|
|
@ -53,7 +53,7 @@ setuptools==68.0.0
|
|||
six==1.16.0
|
||||
slugid==2.0.0
|
||||
taskcluster==44.2.2
|
||||
taskcluster-taskgraph==8.0.1
|
||||
taskcluster-taskgraph==8.2.0
|
||||
taskcluster-urls==13.0.1
|
||||
toml==0.10.2
|
||||
tomlkit==0.12.3
|
||||
|
|
|
|||
7
third_party/python/requirements.txt
vendored
7
third_party/python/requirements.txt
vendored
|
|
@ -492,7 +492,6 @@ pyyaml==6.0.1 ; python_version >= "3.8" and python_version < "4.0" \
|
|||
--hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
|
||||
--hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
|
||||
--hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
|
||||
--hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
|
||||
--hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
|
||||
--hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
|
||||
--hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
|
||||
|
|
@ -540,9 +539,9 @@ six==1.16.0 ; python_version >= "3.8" and python_version < "4.0" \
|
|||
slugid==2.0.0 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:a950d98b72691178bdd4d6c52743c4a2aa039207cf7a97d71060a111ff9ba297 \
|
||||
--hash=sha256:aec8b0e01c4ad32e38e12d609eab3ec912fd129aaf6b2ded0199b56a5f8fd67c
|
||||
taskcluster-taskgraph==8.0.1 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:14500bc703f64eb002c0cd505caaf2d34ffc0ae66d109b108e738661da1ae09c \
|
||||
--hash=sha256:21387537bbebab2a7b1890d03e20e49379bdda65efd45ca7fb8d01f5c29e1797
|
||||
taskcluster-taskgraph==8.2.0 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:410e9c9ef43eac1d0676f16867137de90f77eb0b4e0cbe746fe5512d1a626822 \
|
||||
--hash=sha256:af146323402c2d5f67c65e3c232eda953da1ce319e465069e4d5c7aeb212b66e
|
||||
taskcluster-urls==13.0.1 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:5e25e7e6818e8877178b175ff43d2e6548afad72694aa125f404a7329ece0973 \
|
||||
--hash=sha256:b25e122ecec249c4299ac7b20b08db76e3e2025bdaeb699a9d444556de5fd367 \
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: taskcluster-taskgraph
|
||||
Version: 8.0.1
|
||||
Version: 8.2.0
|
||||
Summary: Build taskcluster taskgraphs
|
||||
Home-page: https://github.com/taskcluster/taskgraph
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
taskgraph/__init__.py,sha256=hCl3NLzC-cVXlKhuzf0-_0wd0gYmNA3oshXfTaa9DNQ,729
|
||||
taskgraph/__init__.py,sha256=Y7AMSO_xkN6zeyK0gagjJ7_kp0ra84C6-RPuLB6FH_A,729
|
||||
taskgraph/config.py,sha256=8vntWUrPwGds22mFKYAgcsD4Mr8hoONTv2ssGBcClLw,5108
|
||||
taskgraph/create.py,sha256=_zokjSM3ZaO04l2LiMhenE8qXDZVfYvueIIu5hGUhzc,5185
|
||||
taskgraph/decision.py,sha256=sG0CIj9OSOdfN65LSt6dRYFWbns9_JraVC5fQU1_7oc,13012
|
||||
taskgraph/docker.py,sha256=rk-tAMycHnapFyR2Q-XJXzC2A4uv0i-VykLZfwl-pRo,8417
|
||||
taskgraph/decision.py,sha256=gIvVLfMTd6KtnrTFkmFTrky93mknB9dxtL7_aZwEtoA,13088
|
||||
taskgraph/docker.py,sha256=Tw2L4A3Mb3P4BdSkVilhSf8Ob38j15xIYYxtUXSDT9s,8415
|
||||
taskgraph/filter_tasks.py,sha256=R7tYXiaVPGIkQ6O1c9-QJrKZ59m9pFXCloUlPraVnZU,866
|
||||
taskgraph/generator.py,sha256=zrH1zfy-8akksKTSOf6e4FEsdOd5y7-h1Jne_2Jabcc,15703
|
||||
taskgraph/graph.py,sha256=bHUsv2pPa2SSaWgBY-ItIj7REPd0o4fFYrwoQbwFKTY,4680
|
||||
taskgraph/main.py,sha256=tgfAEcNUJfmADteL24yJR5u7tzU4v3mzmxiogVSCK8Y,29072
|
||||
taskgraph/main.py,sha256=n4p2LAN10Oo2yVv1G-cnWxK0FV2KcB9Q4H5m0K0qmw0,29171
|
||||
taskgraph/morph.py,sha256=bwkaSGdTZLcK_rhF2st2mCGv9EHN5WdbnDeuZcqp9UA,9208
|
||||
taskgraph/parameters.py,sha256=hrwUHHu4PS79w-fQ3qNnLSyjRto1EDlidE8e1GzIy8U,12272
|
||||
taskgraph/target_tasks.py,sha256=9_v66bzmQFELPsfIDGITXrqzsmEiLq1EeuJFhycKL0M,3356
|
||||
|
|
@ -24,8 +24,8 @@ taskgraph/loader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|||
taskgraph/loader/default.py,sha256=_bBJG6l04v44Jm5HSIEnVndC05NpNmq5L28QfJHk0wo,1185
|
||||
taskgraph/loader/transform.py,sha256=olUBPjxk3eEIg25sduxlcyqhjoig4ts5kPlT_zs6g9g,2147
|
||||
taskgraph/optimize/__init__.py,sha256=Oqpq1RW8QzOcu7zaMlNQ3BHT9ws9e_93FWfCqzNcQps,123
|
||||
taskgraph/optimize/base.py,sha256=wTViUwVmY9sZvlzSuGwkVrETCo0v2OfyNxFFgzJrDNc,18982
|
||||
taskgraph/optimize/strategies.py,sha256=UryFI5TizzEF_2NO8MyuKwqVektHfJeG_t0_zZwxEds,2577
|
||||
taskgraph/optimize/base.py,sha256=ckr0C2qzYTyp036oDInMDRaGmieAH7t93kOy-1hXPbg,20107
|
||||
taskgraph/optimize/strategies.py,sha256=KTX9PJ846Z8Bpy7z2_JGlJtyIbpt8Di8qrM9oGcGElA,3241
|
||||
taskgraph/run-task/fetch-content,sha256=G1aAvZlTg0yWHqxhSxi4RvfxW-KBJ5JwnGtWRqfH_bg,29990
|
||||
taskgraph/run-task/hgrc,sha256=BybWLDR89bWi3pE5T05UqmDHs02CbLypE-omLZWU6Uk,896
|
||||
taskgraph/run-task/robustcheckout.py,sha256=vPKvHb3fIIJli9ZVZG88XYoa8Sohy2JrpmH6pDgBDHI,30813
|
||||
|
|
@ -62,7 +62,7 @@ taskgraph/util/readonlydict.py,sha256=XzTG-gqGqWVlSkDxSyOL6Ur7Z0ONhIJ9DVLWV3q4q1
|
|||
taskgraph/util/schema.py,sha256=HmbbJ_i5uxZZHZSJ8sVWaD-VMhZI4ymx0STNcjO5t2M,8260
|
||||
taskgraph/util/set_name.py,sha256=cha9awo2nMQ9jfSEcbyNkZkCq_1Yg_kKJTfvDzabHSc,1134
|
||||
taskgraph/util/shell.py,sha256=nf__ly0Ikhj92AiEBCQtvyyckm8UfO_3DSgz0SU-7QA,1321
|
||||
taskgraph/util/taskcluster.py,sha256=LScpZknMycOOneIcRMf236rCTMRHHGxFTc9Lh7mRKaI,13057
|
||||
taskgraph/util/taskcluster.py,sha256=-BlQqkxxH5S2BbZ4X2c0lNd1msU2xLM1S5rr8qrLwkE,15961
|
||||
taskgraph/util/taskgraph.py,sha256=ecKEvTfmLVvEKLPO_0g34CqVvc0iCzuNMh3064BZNrE,1969
|
||||
taskgraph/util/templates.py,sha256=HGTaIKCpAwEzBDHq0cDai1HJjPJrdnHsjJz6N4LVpKI,2139
|
||||
taskgraph/util/time.py,sha256=XauJ0DbU0fyFvHLzJLG4ehHv9KaKixxETro89GPC1yk,3350
|
||||
|
|
@ -71,9 +71,9 @@ taskgraph/util/vcs.py,sha256=FjS82fiTsoQ_ArjTCDOtDGfNdVUp_8zvVKB9SoAG3Rs,18019
|
|||
taskgraph/util/verify.py,sha256=htrNX7aXMMDzxymsFVcs0kaO5gErFHd62g9cQsZI_WE,8518
|
||||
taskgraph/util/workertypes.py,sha256=1wgM6vLrlgtyv8854anVIs0Bx11kV8JJJaKcOHJc2j0,2498
|
||||
taskgraph/util/yaml.py,sha256=-LaIf3RROuaSWckOOGN5Iviu-DHWxIChgHn9a7n6ec4,1059
|
||||
taskcluster_taskgraph-8.0.1.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
||||
taskcluster_taskgraph-8.0.1.dist-info/METADATA,sha256=qg-m62f4BGLh2jBAr_-OQZhraOSciTrv5EyNY0Wwq8I,4688
|
||||
taskcluster_taskgraph-8.0.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
||||
taskcluster_taskgraph-8.0.1.dist-info/entry_points.txt,sha256=2hxDzE3qq_sHh-J3ROqwpxgQgxO-196phWAQREl2-XA,50
|
||||
taskcluster_taskgraph-8.0.1.dist-info/top_level.txt,sha256=3JNeYn_hNiNXC7DrdH_vcv-WYSE7QdgGjdvUYvSjVp0,10
|
||||
taskcluster_taskgraph-8.0.1.dist-info/RECORD,,
|
||||
taskcluster_taskgraph-8.2.0.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
||||
taskcluster_taskgraph-8.2.0.dist-info/METADATA,sha256=minv1wMCm1M-KJtSo85Cj_tUPkQEdc3OuqHt-HT4tjE,4688
|
||||
taskcluster_taskgraph-8.2.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
||||
taskcluster_taskgraph-8.2.0.dist-info/entry_points.txt,sha256=2hxDzE3qq_sHh-J3ROqwpxgQgxO-196phWAQREl2-XA,50
|
||||
taskcluster_taskgraph-8.2.0.dist-info/top_level.txt,sha256=3JNeYn_hNiNXC7DrdH_vcv-WYSE7QdgGjdvUYvSjVp0,10
|
||||
taskcluster_taskgraph-8.2.0.dist-info/RECORD,,
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
# 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/.
|
||||
|
||||
__version__ = "8.0.1"
|
||||
__version__ = "8.2.0"
|
||||
|
||||
# Maximum number of dependencies a single task can have
|
||||
# https://docs.taskcluster.net/reference/platform/taskcluster-queue/references/api#createTask
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ def taskgraph_decision(options, parameters=None):
|
|||
* generating a set of artifacts to memorialize the graph
|
||||
* calling TaskCluster APIs to create the graph
|
||||
"""
|
||||
if options.get("verbose"):
|
||||
logging.root.setLevel(logging.DEBUG)
|
||||
|
||||
parameters = parameters or (
|
||||
lambda graph_config: get_decision_parameters(graph_config, options)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ except ImportError as e:
|
|||
zstd = e
|
||||
|
||||
from taskgraph.util import docker
|
||||
from taskgraph.util.taskcluster import get_artifact_url, get_session
|
||||
from taskgraph.util.taskcluster import (
|
||||
get_artifact_url,
|
||||
get_session,
|
||||
)
|
||||
|
||||
DEPLOY_WARNING = """
|
||||
*****************************************************************
|
||||
|
|
@ -59,10 +62,9 @@ def load_image_by_name(image_name, tag=None):
|
|||
)
|
||||
tasks = load_tasks_for_kind(params, "docker-image")
|
||||
task = tasks[f"build-docker-image-{image_name}"]
|
||||
deadline = None
|
||||
task_id = IndexSearch().should_replace_task(
|
||||
task, {}, deadline, task.optimization.get("index-search", [])
|
||||
)
|
||||
|
||||
indexes = task.optimization.get("index-search", [])
|
||||
task_id = IndexSearch().should_replace_task(task, {}, None, indexes)
|
||||
|
||||
if task_id in (True, False):
|
||||
print(
|
||||
|
|
|
|||
|
|
@ -697,6 +697,9 @@ def image_digest(args):
|
|||
"--tasks-for", required=True, help="the tasks_for value used to generate this task"
|
||||
)
|
||||
@argument("--try-task-config-file", help="path to try task configuration file")
|
||||
@argument(
|
||||
"--verbose", "-v", action="store_true", help="include debug-level logging output"
|
||||
)
|
||||
def decision(options):
|
||||
from taskgraph.decision import taskgraph_decision
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from taskgraph.graph import Graph
|
|||
from taskgraph.taskgraph import TaskGraph
|
||||
from taskgraph.util.parameterization import resolve_task_references, resolve_timestamps
|
||||
from taskgraph.util.python_path import import_sibling_modules
|
||||
from taskgraph.util.taskcluster import find_task_id_batched, status_task_batched
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
registry = {}
|
||||
|
|
@ -51,6 +52,9 @@ def optimize_task_graph(
|
|||
Perform task optimization, returning a taskgraph and a map from label to
|
||||
assigned taskId, including replacement tasks.
|
||||
"""
|
||||
# avoid circular import
|
||||
from taskgraph.optimize.strategies import IndexSearch
|
||||
|
||||
label_to_taskid = {}
|
||||
if not existing_tasks:
|
||||
existing_tasks = {}
|
||||
|
|
@ -70,6 +74,23 @@ def optimize_task_graph(
|
|||
do_not_optimize=do_not_optimize,
|
||||
)
|
||||
|
||||
# Gather each relevant task's index
|
||||
indexes = set()
|
||||
for label in target_task_graph.graph.visit_postorder():
|
||||
if label in do_not_optimize:
|
||||
continue
|
||||
_, strategy, arg = optimizations(label)
|
||||
if isinstance(strategy, IndexSearch) and arg is not None:
|
||||
indexes.update(arg)
|
||||
|
||||
index_to_taskid = {}
|
||||
taskid_to_status = {}
|
||||
if indexes:
|
||||
# Find their respective status using TC index/queue batch APIs
|
||||
indexes = list(indexes)
|
||||
index_to_taskid = find_task_id_batched(indexes)
|
||||
taskid_to_status = status_task_batched(list(index_to_taskid.values()))
|
||||
|
||||
replaced_tasks = replace_tasks(
|
||||
target_task_graph=target_task_graph,
|
||||
optimizations=optimizations,
|
||||
|
|
@ -78,6 +99,8 @@ def optimize_task_graph(
|
|||
label_to_taskid=label_to_taskid,
|
||||
existing_tasks=existing_tasks,
|
||||
removed_tasks=removed_tasks,
|
||||
index_to_taskid=index_to_taskid,
|
||||
taskid_to_status=taskid_to_status,
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
@ -259,12 +282,17 @@ def replace_tasks(
|
|||
label_to_taskid,
|
||||
removed_tasks,
|
||||
existing_tasks,
|
||||
index_to_taskid,
|
||||
taskid_to_status,
|
||||
):
|
||||
"""
|
||||
Implement the "Replacing Tasks" phase, returning a set of task labels of
|
||||
all replaced tasks. The replacement taskIds are added to label_to_taskid as
|
||||
a side-effect.
|
||||
"""
|
||||
# avoid circular import
|
||||
from taskgraph.optimize.strategies import IndexSearch
|
||||
|
||||
opt_counts = defaultdict(int)
|
||||
replaced = set()
|
||||
dependents_of = target_task_graph.graph.reverse_links_dict()
|
||||
|
|
@ -307,6 +335,10 @@ def replace_tasks(
|
|||
deadline = max(
|
||||
resolve_timestamps(now, task.task["deadline"]) for task in dependents
|
||||
)
|
||||
|
||||
if isinstance(opt, IndexSearch):
|
||||
arg = arg, index_to_taskid, taskid_to_status
|
||||
|
||||
repl = opt.should_replace_task(task, params, deadline, arg)
|
||||
if repl:
|
||||
if repl is True:
|
||||
|
|
@ -316,7 +348,7 @@ def replace_tasks(
|
|||
removed_tasks.add(label)
|
||||
else:
|
||||
logger.debug(
|
||||
f"replace_tasks: {label} replaced by optimization strategy"
|
||||
f"replace_tasks: {label} replaced with {repl} by optimization strategy"
|
||||
)
|
||||
label_to_taskid[label] = repl
|
||||
replaced.add(label)
|
||||
|
|
|
|||
|
|
@ -22,12 +22,30 @@ class IndexSearch(OptimizationStrategy):
|
|||
|
||||
fmt = "%Y-%m-%dT%H:%M:%S.%fZ"
|
||||
|
||||
def should_replace_task(self, task, params, deadline, index_paths):
|
||||
def should_replace_task(self, task, params, deadline, arg):
|
||||
"Look for a task with one of the given index paths"
|
||||
batched = False
|
||||
# Appease static checker that doesn't understand that this is not needed
|
||||
label_to_taskid = {}
|
||||
taskid_to_status = {}
|
||||
|
||||
if isinstance(arg, tuple) and len(arg) == 3:
|
||||
# allow for a batched call optimization instead of two queries
|
||||
# per index path
|
||||
index_paths, label_to_taskid, taskid_to_status = arg
|
||||
batched = True
|
||||
else:
|
||||
index_paths = arg
|
||||
|
||||
for index_path in index_paths:
|
||||
try:
|
||||
task_id = find_task_id(index_path)
|
||||
status = status_task(task_id)
|
||||
if batched:
|
||||
task_id = label_to_taskid[index_path]
|
||||
status = taskid_to_status[task_id]
|
||||
else:
|
||||
# 404 is raised as `KeyError` also end up here
|
||||
task_id = find_task_id(index_path)
|
||||
status = status_task(task_id)
|
||||
# status can be `None` if we're in `testing` mode
|
||||
# (e.g. test-action-callback)
|
||||
if not status or status.get("state") in ("exception", "failed"):
|
||||
|
|
@ -40,7 +58,7 @@ class IndexSearch(OptimizationStrategy):
|
|||
|
||||
return task_id
|
||||
except KeyError:
|
||||
# 404 will end up here and go on to the next index path
|
||||
# go on to the next index path
|
||||
pass
|
||||
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -193,6 +193,48 @@ def find_task_id(index_path, use_proxy=False):
|
|||
return response.json()["taskId"]
|
||||
|
||||
|
||||
def find_task_id_batched(index_paths, use_proxy=False):
|
||||
"""Gets the task id of multiple tasks given their respective index.
|
||||
|
||||
Args:
|
||||
index_paths (List[str]): A list of task indexes.
|
||||
use_proxy (bool): Whether to use taskcluster-proxy (default: False)
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: A dictionary object mapping each valid index path
|
||||
to its respective task id.
|
||||
|
||||
See the endpoint here:
|
||||
https://docs.taskcluster.net/docs/reference/core/index/api#findTasksAtIndex
|
||||
"""
|
||||
endpoint = liburls.api(get_root_url(use_proxy), "index", "v1", "tasks/indexes")
|
||||
task_ids = {}
|
||||
continuation_token = None
|
||||
|
||||
while True:
|
||||
response = _do_request(
|
||||
endpoint,
|
||||
json={
|
||||
"indexes": index_paths,
|
||||
},
|
||||
params={"continuationToken": continuation_token},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
if not response_data["tasks"]:
|
||||
break
|
||||
response_tasks = response_data["tasks"]
|
||||
if (len(task_ids) + len(response_tasks)) > len(index_paths):
|
||||
# Sanity check
|
||||
raise ValueError("more task ids were returned than were asked for")
|
||||
task_ids.update((t["namespace"], t["taskId"]) for t in response_tasks)
|
||||
|
||||
continuationToken = response_data.get("continuationToken")
|
||||
if continuationToken is None:
|
||||
break
|
||||
return task_ids
|
||||
|
||||
|
||||
def get_artifact_from_index(index_path, artifact_path, use_proxy=False):
|
||||
full_path = index_path + "/artifacts/" + artifact_path
|
||||
response = _do_request(get_index_url(full_path, use_proxy))
|
||||
|
|
@ -271,6 +313,49 @@ def status_task(task_id, use_proxy=False):
|
|||
return status
|
||||
|
||||
|
||||
def status_task_batched(task_ids, use_proxy=False):
|
||||
"""Gets the status of multiple tasks given task_ids.
|
||||
|
||||
In testing mode, just logs that it would have retrieved statuses.
|
||||
|
||||
Args:
|
||||
task_id (List[str]): A list of task ids.
|
||||
use_proxy (bool): Whether to use taskcluster-proxy (default: False)
|
||||
|
||||
Returns:
|
||||
dict: A dictionary object as defined here:
|
||||
https://docs.taskcluster.net/docs/reference/platform/queue/api#statuses
|
||||
"""
|
||||
if testing:
|
||||
logger.info(f"Would have gotten status for {len(task_ids)} tasks.")
|
||||
return
|
||||
endpoint = liburls.api(get_root_url(use_proxy), "queue", "v1", "tasks/status")
|
||||
statuses = {}
|
||||
continuation_token = None
|
||||
|
||||
while True:
|
||||
response = _do_request(
|
||||
endpoint,
|
||||
json={
|
||||
"taskIds": task_ids,
|
||||
},
|
||||
params={
|
||||
"continuationToken": continuation_token,
|
||||
},
|
||||
)
|
||||
response_data = response.json()
|
||||
if not response_data["statuses"]:
|
||||
break
|
||||
response_tasks = response_data["statuses"]
|
||||
if (len(statuses) + len(response_tasks)) > len(task_ids):
|
||||
raise ValueError("more task statuses were returned than were asked for")
|
||||
statuses.update((t["taskId"], t["status"]) for t in response_tasks)
|
||||
continuationToken = response_data.get("continuationToken")
|
||||
if continuationToken is None:
|
||||
break
|
||||
return statuses
|
||||
|
||||
|
||||
def state_task(task_id, use_proxy=False):
|
||||
"""Gets the state of a task given a task_id.
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"e94c46bbd290f02adccc7ae932285416d7e021bfde80abb2fb31a2c05426e732","cbindgen.toml":"fb6abe1671497f432a06e40b1db7ed7cd2cceecbd9a2382193ad7534e8855e34","src/context.rs":"a0559e92b554ef3156ab2bf2f1424555c8ef4a7977b9f43ac8500a9f399f8d99","src/lib.rs":"c87d9d57a16a9286cde730978db692df0fbc70cc69dd4f4677198d6843031fd8","src/send_recv.rs":"859abe75b521eb4297c84b30423814b5b87f3c7741ad16fe72189212e123e1ac","src/stream.rs":"90dc6a85552f3569ab1847de4247a46bcff2f5aef0c4d43fa2376589df015b25"},"package":null}
|
||||
{"files":{"Cargo.toml":"b4fad65749eb0988ce4e6b6a2aae51e58ae22eca97cf61dfb011e951a0909f0e","cbindgen.toml":"fb6abe1671497f432a06e40b1db7ed7cd2cceecbd9a2382193ad7534e8855e34","src/context.rs":"a0559e92b554ef3156ab2bf2f1424555c8ef4a7977b9f43ac8500a9f399f8d99","src/lib.rs":"c87d9d57a16a9286cde730978db692df0fbc70cc69dd4f4677198d6843031fd8","src/send_recv.rs":"859abe75b521eb4297c84b30423814b5b87f3c7741ad16fe72189212e123e1ac","src/stream.rs":"90dc6a85552f3569ab1847de4247a46bcff2f5aef0c4d43fa2376589df015b25"},"package":null}
|
||||
2
third_party/rust/audioipc2-client/Cargo.toml
vendored
2
third_party/rust/audioipc2-client/Cargo.toml
vendored
|
|
@ -21,7 +21,7 @@ description = "Cubeb Backend for talking to remote cubeb server."
|
|||
license = "ISC"
|
||||
|
||||
[dependencies]
|
||||
cubeb-backend = "0.12"
|
||||
cubeb-backend = "0.13"
|
||||
log = "0.4"
|
||||
|
||||
[dependencies.audio_thread_priority]
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"77997660e305851d9c0e656aac7159b999452a36f3436d8b2f402edd36fef853","cbindgen.toml":"fb6abe1671497f432a06e40b1db7ed7cd2cceecbd9a2382193ad7534e8855e34","src/lib.rs":"d70079c66de72c3469504f1f0c9cf5e510644cac17f2d8300b8d12218740e07b","src/server.rs":"187e2236aa9f2fb6cc4a533d40714a71504afa5ef9d849ac28b7f26032859c29"},"package":null}
|
||||
{"files":{"Cargo.toml":"62eab883f31c0c088ff865fe2e4305d987b7b534f6cdfe1e5812072a2ec13f8b","cbindgen.toml":"fb6abe1671497f432a06e40b1db7ed7cd2cceecbd9a2382193ad7534e8855e34","src/lib.rs":"d70079c66de72c3469504f1f0c9cf5e510644cac17f2d8300b8d12218740e07b","src/server.rs":"187e2236aa9f2fb6cc4a533d40714a71504afa5ef9d849ac28b7f26032859c29"},"package":null}
|
||||
2
third_party/rust/audioipc2-server/Cargo.toml
vendored
2
third_party/rust/audioipc2-server/Cargo.toml
vendored
|
|
@ -21,7 +21,7 @@ description = "Remote cubeb server"
|
|||
license = "ISC"
|
||||
|
||||
[dependencies]
|
||||
cubeb-core = "0.12.0"
|
||||
cubeb-core = "0.13"
|
||||
log = "0.4"
|
||||
once_cell = "1.2.0"
|
||||
slab = "0.4"
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue