Update On Tue Aug 30 20:54:07 CEST 2022

This commit is contained in:
github-action[bot] 2022-08-30 20:54:08 +02:00
parent 88f9da8c3b
commit 432ab9058e
185 changed files with 4425 additions and 1780 deletions

View file

@ -31,6 +31,10 @@ class CacheDomain {
static constexpr uint64_t Viewport = ((uint64_t)0x1) << 14;
static constexpr uint64_t ARIA = ((uint64_t)0x1) << 15;
static constexpr uint64_t Relations = ((uint64_t)0x1) << 16;
#ifdef XP_WIN
// Used for MathML.
static constexpr uint64_t InnerHTML = ((uint64_t)0x1) << 17;
#endif
static constexpr uint64_t All = ~((uint64_t)0x0);
};

View file

@ -1367,6 +1367,10 @@ void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
SendCache(CacheDomain::Actions, CacheUpdateType::Update);
}
if (aAttribute == nsGkAtoms::href) {
mDoc->QueueCacheUpdate(this, CacheDomain::Value);
}
if (aAttribute == nsGkAtoms::aria_controls ||
aAttribute == nsGkAtoms::aria_flowto) {
mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
@ -2498,6 +2502,20 @@ void LocalAccessible::BindToParent(LocalAccessible* aParent,
// for the next tick before the cache update is sent.
mDoc->QueueCacheUpdate(aParent, CacheDomain::Table);
}
#if defined(XP_WIN)
if (StaticPrefs::accessibility_cache_enabled_AtStartup() &&
aParent->HasOwnContent() && aParent->mContent->IsMathMLElement()) {
// For any change in a MathML subtree, update the innerHTML cache on the
// root math element.
for (LocalAccessible* acc = aParent; acc; acc = acc->LocalParent()) {
if (acc->HasOwnContent() &&
acc->mContent->IsMathMLElement(nsGkAtoms::math)) {
mDoc->QueueCacheUpdate(acc, CacheDomain::InnerHTML);
}
}
}
#endif // defined(XP_WIN)
}
// LocalAccessible protected
@ -3149,6 +3167,7 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
// 1. Accessible is an HTML input type that holds a number.
// 2. Accessible has a numeric value and an aria-valuetext.
// 3. Accessible is an HTML input type that holds text.
// 4. Accessible is a link, in which case value is the target URL.
// ... for all other cases we divine the value remotely.
bool cacheValueText = false;
if (HasNumericValue()) {
@ -3161,7 +3180,7 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
mContent->AsElement()->HasAttr(
kNameSpaceID_None, nsGkAtoms::aria_valuetext));
} else {
cacheValueText = IsTextField();
cacheValueText = IsTextField() || IsHTMLLink();
}
if (cacheValueText) {
@ -3593,6 +3612,15 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
}
}
#if defined(XP_WIN)
if (aCacheDomain & CacheDomain::InnerHTML && HasOwnContent() &&
mContent->IsMathMLElement(nsGkAtoms::math)) {
nsString innerHTML;
mContent->AsElement()->GetInnerHTML(innerHTML, IgnoreErrors());
fields->SetAttribute(nsGkAtoms::html, std::move(innerHTML));
}
#endif // defined(XP_WIN)
if (aUpdateType == CacheUpdateType::Initial) {
// Add fields which never change and thus only need to be included in the
// initial cache push.

View file

@ -276,6 +276,17 @@ void RemoteAccessibleBase<Derived>::Value(nsString& aValue) const {
if (option) {
option->Name(aValue);
}
return;
}
if (IsTextLeaf() || IsImage()) {
if (const Accessible* actionAcc = ActionAncestor()) {
if (const_cast<Accessible*>(actionAcc)->State() & states::LINKED) {
// Text and image descendants of links expose the link URL as the
// value.
return actionAcc->Value(aValue);
}
}
}
}
}

View file

@ -408,6 +408,9 @@ class RemoteAccessibleBase : public Accessible, public HyperTextAccessibleBase {
friend HyperTextAccessibleBase;
friend class xpcAccessible;
friend class CachedTableCellAccessible;
#ifdef XP_WIN
friend class sdnAccessible;
#endif
nsTArray<Derived*> mChildren;
DocAccessibleParent* mDoc;

View file

@ -21,6 +21,8 @@ skip-if =
[browser_caching_attributes.js]
[browser_caching_description.js]
[browser_caching_document_props.js]
[browser_caching_innerHTML.js]
skip-if = os != 'win'
[browser_caching_name.js]
skip-if = (os == "linux" && bits == 64) || (debug && os == "mac") || (debug && os == "win") #Bug 1388256
[browser_caching_relations.js]

View file

@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Test caching of innerHTML on math elements for Windows clients.
*/
addAccessibleTask(
`
<p id="p">test</p>
<math id="math"><mfrac><mi>x</mi><mi>y</mi></mfrac></math>
`,
async function(browser, docAcc) {
if (!isCacheEnabled) {
// Stop the harness from complaining that this file is empty when run with
// the cache disabled.
todo(false, "Cache disabled for a cache only test");
return;
}
const p = findAccessibleChildByID(docAcc, "p");
let hasHtml;
try {
p.cache.getStringProperty("html");
hasHtml = true;
} catch (e) {
hasHtml = false;
}
ok(!hasHtml, "p doesn't have cached html");
const math = findAccessibleChildByID(docAcc, "math");
is(
math.cache.getStringProperty("html"),
"<mfrac><mi>x</mi><mi>y</mi></mfrac>",
"math cached html is correct"
);
info("Mutating math");
await invokeContentTask(browser, [], () => {
content.document.querySelectorAll("mi")[1].textContent = "z";
});
await untilCacheIs(
() => math.cache.getStringProperty("html"),
"<mfrac><mi>x</mi><mi>z</mi></mfrac>",
"math cached html is correct after mutation"
);
},
{
topLevel: true,
iframe: isCacheEnabled,
remoteIframe: isCacheEnabled,
}
);

View file

@ -212,3 +212,37 @@ addAccessibleTask(
},
{ iframe: true, remoteIframe: true }
);
/**
* Test caching of link URL values.
*/
addAccessibleTask(
`<a id="link" href="https://example.com/">Test</a>`,
async function(browser, docAcc) {
const link = findAccessibleChildByID(docAcc, "link");
is(link.value, "https://example.com/", "link initial value correct");
const textLeaf = link.firstChild;
is(textLeaf.value, "https://example.com/", "link initial value correct");
info("Changing link href");
await invokeSetAttribute(browser, "link", "href", "https://example.net/");
await untilCacheIs(
() => link.value,
"https://example.net/",
"link value correct after change"
);
info("Removing link href");
await invokeSetAttribute(browser, "link", "href");
await untilCacheIs(() => link.value, "", "link value empty after removal");
info("Setting link href");
await invokeSetAttribute(browser, "link", "href", "https://example.com/");
await untilCacheIs(
() => link.value,
"https://example.com/",
"link value correct after change"
);
},
{ chrome: true, topLevel: true, iframe: true, remoteIframe: true }
);

View file

@ -862,8 +862,8 @@ MsaaAccessible::QueryInterface(REFIID iid, void** ppv) {
return E_NOINTERFACE;
}
*ppv = static_cast<IEnumVARIANT*>(new ChildrenEnumVariant(this));
} else if (IID_ISimpleDOMNode == iid && localAcc) {
if (!localAcc->HasOwnContent() && !localAcc->IsDoc()) {
} else if (IID_ISimpleDOMNode == iid) {
if (mAcc->IsDoc() || (localAcc && !localAcc->HasOwnContent())) {
return E_NOINTERFACE;
}

View file

@ -16,6 +16,7 @@ namespace mozilla {
namespace a11y {
inline DocAccessible* sdnAccessible::GetDocument() const {
MOZ_ASSERT(mNode);
return GetExistingDocAccessible(mNode->OwnerDoc());
}

View file

@ -85,6 +85,10 @@ sdnAccessible::get_nodeInfo(BSTR __RPC_FAR* aNodeName,
*aNodeType = 0;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
uint16_t nodeType = mNode->NodeType();
*aNodeType = static_cast<unsigned short>(nodeType);
@ -133,6 +137,10 @@ sdnAccessible::get_attributes(unsigned short aMaxAttribs,
*aNumAttribs = 0;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
if (!mNode->IsElement()) return S_FALSE;
@ -167,6 +175,13 @@ sdnAccessible::get_attributesForNames(unsigned short aMaxAttribs,
if (!aAttribNames || !aNameSpaceID || !aAttribValues) return E_INVALIDARG;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
// NVDA expects this to succeed for MathML and won't call innerHTML if this
// fails. Therefore, return S_FALSE here instead of E_NOTIMPL, indicating
// that the attributes aren't present.
return S_FALSE;
}
if (!mNode->IsElement()) return S_FALSE;
@ -205,6 +220,10 @@ sdnAccessible::get_computedStyle(
return E_INVALIDARG;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
*aNumStyleProperties = 0;
@ -248,6 +267,10 @@ sdnAccessible::get_computedStyleForProperties(
if (!aStyleProperties || !aStyleValues) return E_INVALIDARG;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
if (mNode->IsDocument()) return S_FALSE;
@ -271,10 +294,16 @@ sdnAccessible::get_computedStyleForProperties(
// XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294.
MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP
sdnAccessible::scrollTo(boolean aScrollTopLeft) {
DocAccessible* document = GetDocument();
if (!document) // that's IsDefunct check
if (IsDefunct()) {
return CO_E_OBJNOTCONNECTED;
}
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
DocAccessible* document = GetDocument();
MOZ_ASSERT(document);
if (!mNode->IsContent()) return S_FALSE;
uint32_t scrollType = aScrollTopLeft
@ -293,6 +322,10 @@ sdnAccessible::get_parentNode(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) {
*aNode = nullptr;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
nsINode* resultNode = mNode->GetParentNode();
if (resultNode) {
@ -309,6 +342,10 @@ sdnAccessible::get_firstChild(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) {
*aNode = nullptr;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
nsINode* resultNode = mNode->GetFirstChild();
if (resultNode) {
@ -325,6 +362,10 @@ sdnAccessible::get_lastChild(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) {
*aNode = nullptr;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
nsINode* resultNode = mNode->GetLastChild();
if (resultNode) {
@ -341,6 +382,10 @@ sdnAccessible::get_previousSibling(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) {
*aNode = nullptr;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
nsINode* resultNode = mNode->GetPreviousSibling();
if (resultNode) {
@ -357,6 +402,10 @@ sdnAccessible::get_nextSibling(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) {
*aNode = nullptr;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
nsINode* resultNode = mNode->GetNextSibling();
if (resultNode) {
@ -374,6 +423,10 @@ sdnAccessible::get_childAt(unsigned aChildIndex,
*aNode = nullptr;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
nsINode* resultNode = mNode->GetChildAt_Deprecated(aChildIndex);
if (resultNode) {
@ -391,10 +444,21 @@ sdnAccessible::get_innerHTML(BSTR __RPC_FAR* aInnerHTML) {
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode->IsElement()) return S_FALSE;
nsAutoString innerHTML;
mNode->AsElement()->GetInnerHTML(innerHTML, IgnoreErrors());
if (!mNode) {
RemoteAccessible* remoteAcc = mMsaa->Acc()->AsRemote();
MOZ_ASSERT(remoteAcc);
if (!remoteAcc->mCachedFields) {
return S_FALSE;
}
remoteAcc->mCachedFields->GetAttribute(nsGkAtoms::html, innerHTML);
} else {
if (!mNode->IsElement()) {
return S_FALSE;
}
mNode->AsElement()->GetInnerHTML(innerHTML, IgnoreErrors());
}
if (innerHTML.IsEmpty()) return S_FALSE;
*aInnerHTML = ::SysAllocStringLen(innerHTML.get(), innerHTML.Length());
@ -422,6 +486,10 @@ sdnAccessible::get_language(BSTR __RPC_FAR* aLanguage) {
*aLanguage = nullptr;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
nsAutoString language;
if (mNode->IsContent())

View file

@ -25,18 +25,32 @@ class sdnAccessible final : public ISimpleDOMNode {
if (!mNode) MOZ_CRASH();
}
explicit sdnAccessible(NotNull<MsaaAccessible*> aMsaa)
: mNode(aMsaa->LocalAcc()->GetNode()), mMsaa(aMsaa) {}
explicit sdnAccessible(NotNull<MsaaAccessible*> aMsaa) : mMsaa(aMsaa) {
Accessible* acc = aMsaa->Acc();
MOZ_ASSERT(acc);
if (LocalAccessible* localAcc = acc->AsLocal()) {
mNode = localAcc->GetNode();
}
}
~sdnAccessible();
/**
* Return if the object is defunct.
*/
bool IsDefunct() const { return !GetDocument(); }
bool IsDefunct() const {
if (mMsaa && !mMsaa->Acc()) {
return true;
}
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return false;
}
return !GetDocument();
}
/**
* Return a document accessible it belongs to if any.
* Return a local document accessible it belongs to if any.
*/
DocAccessible* GetDocument() const;
@ -122,6 +136,8 @@ class sdnAccessible final : public ISimpleDOMNode {
/* [out][retval] */ BSTR __RPC_FAR* aLanguage);
private:
// mNode will be null for a RemoteAccessible. In that case, we only partially
// implement this interface using data from the RemoteAccessible cache.
nsCOMPtr<nsINode> mNode;
RefPtr<MsaaAccessible> mMsaa;
Maybe<uint32_t> mUniqueId;

View file

@ -139,6 +139,12 @@ var gTabsPanel = {
},
showAllTabsPanel(event) {
// Note that event may be null.
// Only space and enter should open the popup, ignore other keypresses:
if (event?.type == "keypress" && event.key != "Enter" && event.key != " ") {
return;
}
this.init();
if (this.canOpen) {
PanelUI.showSubView(

View file

@ -167,7 +167,7 @@ var SidebarUI = {
// support live switching the app locale. Reload the entire sidebar to
// invalidate any old text.
this.hide();
this._show(this.lastOpenedId);
this.showInitially(this.lastOpenedId);
break;
}
}

View file

@ -92,6 +92,7 @@
<toolbarbutton id="alltabs-button"
class="toolbarbutton-1 chromeclass-toolbar-additional tabs-alltabs-button"
badged="true"
onkeypress="gTabsPanel.showAllTabsPanel(event);"
onmousedown="gTabsPanel.showAllTabsPanel(event);"
data-l10n-id="tabs-toolbar-list-all-tabs"
removable="false"/>

View file

@ -2324,6 +2324,10 @@ class nsContextMenu {
getImageText() {
let dialogBox = gBrowser.getTabDialogBox(this.browser);
const imageTextResult = this.actor.getImageText(this.targetIdentifier);
TelemetryStopwatch.start(
"TEXT_RECOGNITION_API_PERFORMANCE",
imageTextResult
);
const { dialog } = dialogBox.open(
"chrome://browser/content/textrecognition/textrecognition.html",
{

View file

@ -54,3 +54,58 @@ add_task(async function test_bookmarks_sidebar() {
add_task(async function test_history_sidebar() {
await testLiveReloading("viewHistorySidebar");
});
add_task(async function test_ext_sidebar_panel_reloaded_on_locale_changes() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
sidebar_action: {
default_panel: "sidebar.html",
},
},
useAddonManager: "temporary",
files: {
"sidebar.html": `<html>
<head>
<meta charset="utf-8"/>
<script src="sidebar.js"></script>
</head>
<body>
A Test Sidebar
</body>
</html>`,
"sidebar.js": function() {
const { browser } = this;
window.onload = () => {
browser.test.sendMessage("sidebar");
};
},
},
});
await extension.startup();
// Test sidebar is opened on install
await extension.awaitMessage("sidebar");
// Test sidebar is opened on simulated locale changes.
info("Switch browser to bidi and expect the sidebar panel to be reloaded");
await SpecialPowers.pushPrefEnv({
set: [["intl.l10n.pseudo", "bidi"]],
});
await extension.awaitMessage("sidebar");
is(
window.document.documentElement.getAttribute("dir"),
"rtl",
"browser window changed direction to rtl as expected"
);
await SpecialPowers.popPrefEnv();
await extension.awaitMessage("sidebar");
is(
window.document.documentElement.getAttribute("dir"),
"ltr",
"browser window changed direction to ltr as expected"
);
await extension.unload();
});

View file

@ -59,6 +59,7 @@ support-files = tab_that_closes.html
[browser_multiselect_tabs_move_to_another_window_drag.js]
[browser_multiselect_tabs_move_to_new_window_contextmenu.js]
https_first_disabled = true
[browser_tab_manager_keyboard_access.js]
[browser_tab_play.js]
[browser_multiselect_tabs_move.js]
[browser_multiselect_tabs_mute_unmute.js]

View file

@ -0,0 +1,37 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Check we can open the tab manager using the keyboard.
* Note that navigation to buttons in the toolbar is covered
* by other tests.
*/
add_task(async function test_open_tabmanager_keyboard() {
await SpecialPowers.pushPrefEnv({
set: [["browser.tabs.tabmanager.enabled", true]],
});
let newWindow = await BrowserTestUtils.openNewWindowWithFlushedCacheForMozSupports();
let elem = newWindow.document.getElementById("alltabs-button");
// Borrowed from forceFocus() in the keyboard directory head.js
elem.setAttribute("tabindex", "-1");
elem.focus();
elem.removeAttribute("tabindex");
let focused = BrowserTestUtils.waitForEvent(newWindow, "focus", true);
EventUtils.synthesizeKey(" ", {}, newWindow);
let event = await focused;
ok(
event.originalTarget.closest("#allTabsMenu-allTabsView"),
"Focus inside all tabs menu after toolbar button pressed"
);
let hidden = BrowserTestUtils.waitForEvent(
event.target.closest("panel"),
"popuphidden"
);
EventUtils.synthesizeKey("KEY_Escape", { shiftKey: false }, newWindow);
await hidden;
await BrowserTestUtils.closeWindow(newWindow);
});

View file

@ -345,7 +345,7 @@ const TestCasesMultipleFiles = [
},
];
add_task(async function test_setUp() {
add_setup(async function() {
// remove download files, empty out collections
let downloadList = await Downloads.getList(Downloads.ALL);
let downloadCount = (await downloadList.getAll()).length;

View file

@ -89,7 +89,7 @@ async function updateTopSites(condition, searchShortcuts = false) {
}, "Waiting for top sites to be updated");
}
add_task(async function setUp() {
add_setup(async function() {
Services.prefs.setBoolPref("browser.urlbar.suggest.quickactions", false);
registerCleanupFunction(async () => {
Services.prefs.clearUserPref("browser.urlbar.suggest.quickactions");

View file

@ -649,25 +649,26 @@ button.close {
grid-template-columns: min-content repeat(2, 1fr) minmax(min-content, auto);
grid-template-rows: auto;
grid-template-areas:
"favicon title title title"
"favicon domain domain domain"
"favicon device device time"
"favicon title title title"
"favicon domain domain domain"
"favicon device device time"
}
.synced-tab-a:hover {
box-shadow: 0px 2px 14px var(--fxview-contrast-border);
}
.synced-tab-a:not(:first-child) {
.synced-tab-li:not(:first-child) > .synced-tab-a {
align-content: center;
}
@media only screen and (max-width: 60rem) {
.synced-tab-li {
.synced-tab-li > .synced-tab-a,
.synced-tab-li-placeholder {
grid-template-areas:
"favicon title title title"
"favicon domain domain domain"
"favicon device device device"
"favicon title title title"
"favicon domain domain domain"
"favicon device device device"
}
.synced-tab-li:not(:first-child) > .synced-tab-a > .synced-tab-li-time {
display: none;
@ -693,7 +694,7 @@ button.close {
}
.li-placeholder-domain {
grid-area: url;
grid-area: domain;
height: 8px;
width: 100%;
}
@ -715,10 +716,10 @@ button.close {
grid-template-columns: repeat(4, 1fr);
grid-template-rows: auto;
grid-template-areas:
"favicon favicon badge badge"
"title title title title"
"domain domain domain domain"
"device device device time";
"favicon favicon badge badge"
"title title title title"
"domain domain domain domain"
"device device device time";
}
.synced-tab-li:nth-child(2) {
@ -738,14 +739,11 @@ button.close {
.synced-tab-li-url {
text-decoration-line: underline;
}
.synced-tab-li:first-child > .synced-tab-a > .synced-tab-li-url {
align-self: end;
grid-area: domain;
align-self: end;
}
.synced-tab-li:not(:first-child) > .synced-tab-li-url {
.synced-tab-li:not(:first-child) > .synced-tab-a > .synced-tab-li-url {
margin-top: 4px;
}

View file

@ -671,14 +671,20 @@ const MultiStageProtonScreen = props => {
});
};
const ProtonScreenActionButtons = props => {
var _content$primary_butt, _content$primary_butt2;
var _content$checkbox, _content$primary_butt, _content$primary_butt2;
const {
content
} = props;
const [isChecked, setIsChecked] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
const defaultValue = (_content$checkbox = content.checkbox) === null || _content$checkbox === void 0 ? void 0 : _content$checkbox.defaultValue;
const [isChecked, setIsChecked] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(defaultValue || false);
if (!content.primary_button && !content.secondary_button) {
return null;
}
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "action-buttons"
className: `action-buttons ${content.dual_action_buttons ? "dual-action-buttons" : ""}`
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, {
text: (_content$primary_butt = content.primary_button) === null || _content$primary_butt === void 0 ? void 0 : _content$primary_butt.label
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", {
@ -695,6 +701,7 @@ const ProtonScreenActionButtons = props => {
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", {
type: "checkbox",
id: "action-checkbox",
checked: isChecked,
onChange: () => {
setIsChecked(!isChecked);
}
@ -1367,7 +1374,7 @@ function LanguageSwitcher(props) {
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", {
value: "decline_waiting",
type: "button",
className: "secondary text-link",
className: "secondary text-link arrow-icon",
onClick: handleAction
})))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
style: {

View file

@ -415,6 +415,19 @@ body[lwt-newtab-brighttext] {
.onboardingContainer .screen[pos=split] .section-main .main-content .action-buttons .checkbox-container label {
vertical-align: middle;
}
.onboardingContainer .screen[pos=split] .section-main .main-content .action-buttons.dual-action-buttons button {
padding: 8px 16px;
}
.onboardingContainer .screen[pos=split] .section-main .main-content .action-buttons.dual-action-buttons .secondary-cta {
display: block;
position: relative;
bottom: unset;
margin-block: 5px 0;
}
.onboardingContainer .screen[pos=split] .section-main .main-content .action-buttons.dual-action-buttons .secondary-cta .secondary {
margin-inline: 0;
font-size: 13px;
}
.onboardingContainer .screen[pos=split] .section-main .main-content .action-buttons .secondary-cta {
position: absolute;
bottom: -30px;
@ -436,6 +449,8 @@ body[lwt-newtab-brighttext] {
background: url("chrome://browser/skin/forward.svg") no-repeat right 8px center;
background-size: 12px;
padding-inline-end: 24px;
-moz-context-properties: fill;
fill: currentColor;
}
.onboardingContainer .screen[pos=split] .section-main .main-content .action-buttons .secondary-cta .arrow-icon:dir(rtl) {
background-image: url("chrome://browser/skin/back.svg");

View file

@ -321,6 +321,25 @@ body {
}
}
&.dual-action-buttons {
button {
padding: 8px 16px;
}
.secondary-cta {
display: block;
position: relative;
bottom: unset;
margin-block: 5px 0;
.secondary {
margin-inline: 0;
font-size: 13px;
}
}
}
.secondary-cta {
position: absolute;
bottom: -30px;
@ -346,6 +365,8 @@ body {
background: url('chrome://browser/skin/forward.svg') no-repeat right 8px center;
background-size: 12px;
padding-inline-end: 24px;
-moz-context-properties: fill;
fill: currentColor;
&:dir(rtl) {
background-image: url('chrome://browser/skin/back.svg');

View file

@ -200,7 +200,7 @@ export function LanguageSwitcher(props) {
<button
value="decline_waiting"
type="button"
className="secondary text-link"
className="secondary text-link arrow-icon"
onClick={handleAction}
/>
</Localized>

View file

@ -54,11 +54,20 @@ export const MultiStageProtonScreen = props => {
export const ProtonScreenActionButtons = props => {
const { content } = props;
const defaultValue = content.checkbox?.defaultValue;
const [isChecked, setIsChecked] = useState(false);
const [isChecked, setIsChecked] = useState(defaultValue || false);
if (!content.primary_button && !content.secondary_button) {
return null;
}
return (
<div className="action-buttons">
<div
className={`action-buttons ${
content.dual_action_buttons ? "dual-action-buttons" : ""
}`}
>
<Localized text={content.primary_button?.label}>
<button
className="primary"
@ -76,6 +85,7 @@ export const ProtonScreenActionButtons = props => {
<input
type="checkbox"
id="action-checkbox"
checked={isChecked}
onChange={() => {
setIsChecked(!isChecked);
}}

View file

@ -277,6 +277,12 @@ const QueryCache = {
FRECENT_SITES_UPDATE_INTERVAL,
ShellService
),
isDefaultBrowser: new CachedTargetingGetter(
"isDefaultBrowser",
null,
FRECENT_SITES_UPDATE_INTERVAL,
ShellService
),
currentThemes: new CachedTargetingGetter(
"getAddonsByTypes",
["theme"],
@ -506,10 +512,7 @@ const TargetingGetters = {
});
},
get isDefaultBrowser() {
try {
return ShellService.isDefaultBrowser();
} catch (e) {}
return null;
return QueryCache.getters.isDefaultBrowser.get().catch(() => null);
},
get devToolsOpenedCount() {
return lazy.devtoolsSelfXSSCount;
@ -774,7 +777,7 @@ const TargetingGetters = {
}
return QueryCache.getters.currentThemes.get().then(themes => {
let themeId = themes.find(theme => theme.isActive)?.id;
return (
return !!(
themeId && lazy.BuiltInThemes.isColorwayFromCurrentCollection(themeId)
);
});

View file

@ -96,6 +96,29 @@ const ONBOARDING_MESSAGES = () => [
type: "PIN_FIREFOX_TO_TASKBAR",
},
},
checkbox: {
label: {
string_id: "mr2022-onboarding-existing-pin-checkbox-label",
},
defaultValue: true,
action: {
type: "MULTI_ACTION",
navigate: true,
data: {
actions: [
{
type: "PIN_FIREFOX_TO_TASKBAR",
data: {
privatePin: true,
},
},
{
type: "PIN_FIREFOX_TO_TASKBAR",
},
],
},
},
},
secondary_button: {
label: {
string_id: "mr2022-onboarding-secondary-skip-button-label",
@ -411,6 +434,60 @@ const ONBOARDING_MESSAGES = () => [
},
},
},
{
id: "UPGRADE_PRIVACY_SEGMENTATION",
content: {
position: "split",
progress_bar: "true",
dual_action_buttons: true,
background:
"url('chrome://activity-stream/content/data/content/assets/mr-pintaskbar.svg') var(--mr-secondary-position) no-repeat, var(--in-content-page-background) radial-gradient(83.12% 83.12% at 80.59% 16.88%, rgba(103, 51, 205, 0.75) 0%, rgba(0, 108, 207, 0.75) 54.51%, rgba(128, 199, 247, 0.75) 100%)",
logo: {},
title: {
string_id: "mr2022-onboarding-privacy-segmentation-title",
},
subtitle: {
string_id: "mr2022-onboarding-privacy-segmentation-subtitle",
},
cta_paragraph: {
text: {
string_id: "mr2022-onboarding-privacy-segmentation-text-cta",
},
},
primary_button: {
label: {
string_id:
"mr2022-onboarding-privacy-segmentation-button-primary-label",
},
action: {
type: "SET_PREF",
data: {
pref: {
name: "browser.privacySegmentation.enabled",
value: true,
},
},
navigate: true,
},
},
secondary_button: {
label: {
string_id:
"mr2022-onboarding-privacy-segmentation-button-secondary-label",
},
action: {
type: "SET_PREF",
data: {
pref: {
name: "browser.privacySegmentation.enabled",
value: false,
},
},
navigate: true,
},
},
},
},
{
id: "UPGRADE_GRATITUDE",
content: {
@ -895,6 +972,9 @@ const OnboardingMessageProvider = {
let removeDefault = !needDefault;
// If user doesn't need pin, update screen to set "default" or "get started" configuration
if (!needPin && pinScreen) {
// don't need to show the checkbox
delete pinScreen.content.checkbox;
removeDefault = true;
let primary = pinScreen.content.primary_button;
if (needDefault) {
@ -919,6 +999,22 @@ const OnboardingMessageProvider = {
}
}
// If a user has Firefox private pinned remove pin private window screen
// We also remove standalone pin private window screen if a user doesn't have
// Firefox pinned in which case the option is shown as checkbox with UPGRADE_PIN_FIREFOX screen
if (!needPrivatePin || needPin) {
removeScreens(screen =>
screen.id?.startsWith("UPGRADE_PIN_PRIVATE_WINDOW")
);
}
//If privatePin, remove checkbox from pinscreen
if (!needPrivatePin) {
delete content.screens?.find(
screen => screen.id === "UPGRADE_PIN_FIREFOX"
)?.content?.checkbox;
}
if (removeDefault) {
removeScreens(screen => screen.id?.startsWith("UPGRADE_SET_DEFAULT"));
}
@ -938,15 +1034,6 @@ const OnboardingMessageProvider = {
};
}
// If a user has Firefox private pinned remove pin private window screen
// We also remove standalone pin private window screen if a user doesn't have
// Firefox pinned in which case the option is shown as checkbox with UPGRADE_PIN_FIREFOX screen
if (!needPrivatePin || needPin) {
removeScreens(screen =>
screen.id?.startsWith("UPGRADE_PIN_PRIVATE_WINDOW")
);
}
// Remove colorways screen if there is no active colorways collection
const hasActiveColorways = !!lazy.BuiltInThemes.findActiveColorwayCollection?.();
if (!hasActiveColorways) {

View file

@ -32,6 +32,8 @@ add_setup(async () => {
.stub(OnboardingMessageProvider, "_doesAppNeedDefault")
.resolves(false);
sandbox.stub(SpecialMessageActions, "pinFirefoxToTaskbar").resolves();
registerCleanupFunction(async () => {
await popPrefs();
sandbox.restore();
@ -240,3 +242,109 @@ add_task(
await popPrefs();
}
);
/*
*Test checkbox if needPrivatePin is true
*/
add_task(async function test_aboutwelcome_upgrade_mr_private_pin() {
OnboardingMessageProvider._doesAppNeedPin.resolves(true);
let browser = await openMRUpgradeWelcome(["UPGRADE_PIN_FIREFOX"]);
await test_upgrade_screen_content(
browser,
//Expected selectors:
["main.UPGRADE_PIN_FIREFOX", "input#action-checkbox"],
//Unexpected selectors:
["main.UPGRADE_COLORWAY"]
);
await clickVisibleButton(browser, ".action-buttons button.primary");
await BrowserTestUtils.waitForDialogClose(
browser.top.document.getElementById("window-modal-dialog")
);
const pinStub = SpecialMessageActions.pinFirefoxToTaskbar;
Assert.equal(
pinStub.callCount,
2,
"pinFirefoxToTaskbar should have been called twice"
);
Assert.ok(
// eslint-disable-next-line eqeqeq
pinStub.firstCall.lastArg != pinStub.secondCall.lastArg,
"pinFirefoxToTaskbar should have been called once for private, once not"
);
});
/*
*Test checkbox shouldn't be shown in get started screen
*/
add_task(async function test_aboutwelcome_upgrade_mr_private_pin_get_started() {
OnboardingMessageProvider._doesAppNeedPin.resolves(false);
let browser = await openMRUpgradeWelcome(["UPGRADE_GET_STARTED"]);
await test_upgrade_screen_content(
browser,
//Expected selectors
["main.UPGRADE_GET_STARTED"],
//Unexpected selectors:
["input#action-checkbox"]
);
await clickVisibleButton(browser, ".action-buttons button.secondary");
await BrowserTestUtils.waitForDialogClose(
browser.top.document.getElementById("window-modal-dialog")
);
});
/*
*Test checkbox shouldn't be shown if needPrivatePin is false
*/
add_task(async function test_aboutwelcome_upgrade_mr_private_pin_not_needed() {
OnboardingMessageProvider._doesAppNeedPin
.resolves(true)
.withArgs(true)
.resolves(false);
let browser = await openMRUpgradeWelcome(["UPGRADE_PIN_FIREFOX"]);
await test_upgrade_screen_content(
browser,
//Expected selectors
["main.UPGRADE_PIN_FIREFOX"],
//Unexpected selectors:
["input#action-checkbox"]
);
await clickVisibleButton(browser, ".action-buttons button.secondary");
await BrowserTestUtils.waitForDialogClose(
browser.top.document.getElementById("window-modal-dialog")
);
});
/*
* Make sure we don't get an extraneous checkbox here.
*/
add_task(
async function test_aboutwelcome_upgrade_mr_pin_not_needed_default_needed() {
OnboardingMessageProvider._doesAppNeedPin.resolves(false);
OnboardingMessageProvider._doesAppNeedDefault.resolves(false);
let browser = await openMRUpgradeWelcome(["UPGRADE_GET_STARTED"]);
await test_upgrade_screen_content(
browser,
//Expected selectors
["main.UPGRADE_GET_STARTED"],
//Unexpected selectors:
["input#action-checkbox"]
);
await clickVisibleButton(browser, ".action-buttons button.secondary");
await BrowserTestUtils.waitForDialogClose(
browser.top.document.getElementById("window-modal-dialog")
);
}
);

View file

@ -355,7 +355,7 @@ add_task(async function checksearchEngines() {
add_task(async function checkisDefaultBrowser() {
const expected = ShellService.isDefaultBrowser();
const result = ASRouterTargeting.Environment.isDefaultBrowser;
const result = await ASRouterTargeting.Environment.isDefaultBrowser;
is(typeof result, "boolean", "isDefaultBrowser should be a boolean value");
is(
result,

View file

@ -103,6 +103,25 @@ describe("MultiStageAboutWelcomeProton module", () => {
assert.equal(wrapper.find(".welcome-text h1").text(), "test title");
assert.equal(wrapper.find("main").prop("pos"), "center");
});
it("should render action buttons container with dual-action-buttons class", () => {
const SCREEN_PROPS = {
content: {
position: "split",
title: "test title",
dual_action_buttons: true,
primary_button: {
label: "test primary button",
},
secondary_button: {
label: "test secondary button",
},
},
};
const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
assert.ok(wrapper.exists());
assert.ok(wrapper.find(".dual-action-buttons").text());
});
});
describe("AboutWelcomeDefaults for proton", () => {
@ -335,5 +354,17 @@ describe("MultiStageAboutWelcomeProton module", () => {
{ id: "world" },
]);
});
it("should not render action buttons if a primary and secondary button does not exist", async () => {
const SCREEN_PROPS = {
content: {
title: "test title",
subtitle: "test subtitle",
},
};
const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
assert.ok(wrapper.exists());
assert.equal(wrapper.find(".action-buttons").exists(), false);
});
});
});

View file

@ -437,16 +437,6 @@ var PlacesOrganizer = {
? [view.selectedNode]
: view.selectedNodes;
this._fillDetailsPane(selectedNodes);
window
.promiseDocumentFlushed(() => {})
.then(() => {
if (view.selectedNode && ContentArea.currentView.view) {
let row = ContentArea.currentView.view.treeIndexForNode(
view.selectedNode
);
ContentTree.view.ensureRowIsVisible(row);
}
});
}
},

View file

@ -115,7 +115,6 @@ skip-if = os == "linux" && (tsan || asan) # Bug 1714384
[browser_library_open_all.js]
[browser_library_open_bookmark.js]
[browser_library_panel_leak.js]
[browser_library_row_is_visible.js]
[browser_library_search.js]
[browser_library_tree_leak.js]
[browser_library_views_liveupdate.js]

View file

@ -1,118 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/**
* Checks whether or not a row is visible when a user clicks on
* row that's close to the bottom of the Library window, as the
* DetailsPane can grow after the user has clicked on a bookmark.
*
*/
"use strict";
let library;
let PlacesOrganizer;
function assertSelectedRowIsVisible(selectedRow, msg) {
let firstRow = library.ContentTree.view.getFirstVisibleRow();
let lastRow = library.ContentTree.view.getLastVisibleRow();
Assert.ok(firstRow <= selectedRow && lastRow >= selectedRow, msg);
}
/**
* Add a bunch of bookmarks so that the Library view needs to be
* scrolled in order to see all the bookmarks.
*/
add_setup(async function() {
await PlacesUtils.bookmarks.eraseEverything();
library = await promiseLibrary("UnfiledBookmarks");
let baseUrl = "https://www.example.com/";
// Enough bookmarks that should go beyond the initial screen
let nBookmarks = library.ContentTree.view.getLastVisibleRow() + 5;
let bookmarks = new Array(nBookmarks);
// Hack to make it so that when the list of bookmarks is
// first loaded, a bookmark folder is first selected so that
// the details pane is small
bookmarks[0] = {
title: "Test Folder",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
};
for (let index = 1; index < nBookmarks; ++index) {
bookmarks[index] = {
title: `Example Bookmark ${index + 10}`,
url: baseUrl + index + 10,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
};
}
await PlacesUtils.bookmarks.insertTree({
guid: PlacesUtils.bookmarks.unfiledGuid,
children: bookmarks,
});
await promiseLibraryClosed(library);
registerCleanupFunction(async () => {
await PlacesUtils.bookmarks.eraseEverything();
await promiseLibraryClosed(library);
});
});
/**
* Click on a bookmark that should be hidden when the details
* panel is expanded, and so the library panel should scroll.
*/
add_task(async function test_click_bookmark() {
library = await promiseLibrary("UnfiledBookmarks");
let selectedRow = library.ContentTree.view.getLastVisibleRow();
let node = library.ContentTree.view.view.nodeForTreeIndex(selectedRow);
library.ContentTree.view.selectNode(node);
synthesizeClickOnSelectedTreeCell(library.ContentTree.view);
// TODO Bug 1769312: Have to wait till the row is scrolled to
// which unfortunately is not easy to know at the current moment.
// Should be replaced with an event.
/* eslint-disable mozilla/no-arbitrary-setTimeout */
await new Promise(resolve => {
setTimeout(resolve, 500);
});
assertSelectedRowIsVisible(selectedRow, "Selected row is visible");
await promiseLibraryClosed(library);
});
/**
* Sort a bookmarks list by one of the columns and check if
* clicking on a bookmark will show the bookmark.
*/
add_task(async function test_click_bookmark_on_sort() {
library = await promiseLibrary("UnfiledBookmarks");
let selectedRow = library.ContentTree.view.getLastVisibleRow();
// Re-sort by name
library.ViewMenu.setSortColumn(0, "descending");
let node = library.ContentTree.view.view.nodeForTreeIndex(selectedRow);
library.ContentTree.view.selectNode(node);
synthesizeClickOnSelectedTreeCell(library.ContentTree.view);
// TODO Bug 1769312: Have to wait till the row is scrolled to
// which unfortunately is not easy to know at the current moment.
// Should be replaced with an event.
/* eslint-disable mozilla/no-arbitrary-setTimeout */
await new Promise(resolve => {
setTimeout(resolve, 500);
});
assertSelectedRowIsVisible(selectedRow, "Selected row is visible after sort");
});

View file

@ -10,9 +10,22 @@ add_task(async function() {
set: [["dom.text-recognition.enabled", true]],
});
clearTelemetry();
await BrowserTestUtils.withNewTab(URL_IMG, async function(browser) {
setClipboardText("");
is(getTextFromClipboard(), "", "The copied text is empty.");
ok(
!getTelemetryScalars()["browser.ui.interaction.content_context"],
"No telemetry has been recorded yet."
);
is(
Services.telemetry
.getHistogramById("TEXT_RECOGNITION_API_PERFORMANCE")
.snapshot().sum,
0,
"No histogram timing was recorded."
);
info("Right click image to show context menu.");
let popupShownPromise = BrowserTestUtils.waitForEvent(
@ -43,6 +56,17 @@ add_task(async function() {
document.querySelector(".textRecognitionDialogFrame")
);
{
info("Check the scalar telemetry.");
const scalars = await BrowserTestUtils.waitForCondition(() =>
getTelemetryScalars()
);
const contentContext = scalars["browser.ui.interaction.content_context"];
ok(contentContext, "Opening the context menu was recorded.");
is(contentContext["context-imagetext"], 1, "Telemetry has been recorded");
}
info("Waiting for text results.");
const resultsHeader = contentDocument.querySelector(
"#text-recognition-header-results"
@ -51,29 +75,57 @@ add_task(async function() {
return resultsHeader.style.display !== "none";
});
info("Checking the text results.");
const text = contentDocument.querySelector(".textRecognitionText");
is(text.children.length, 2, "Two piece of text were found");
const [p1, p2] = text.children;
is(p1.tagName, "P", "The children are paragraph tags.");
is(p2.tagName, "P", "The children are paragraph tags.");
is(p1.innerText, "Mozilla\n", "The first piece of text matches.");
is(p2.innerText, "Firefox\n", "The second piece of text matches.");
{
info("Check the text results.");
const text = contentDocument.querySelector(".textRecognitionText");
is(text.children.length, 2, "Two piece of text were found");
const [p1, p2] = text.children;
is(p1.tagName, "P", "The children are paragraph tags.");
is(p2.tagName, "P", "The children are paragraph tags.");
is(p1.innerText, "Mozilla\n", "The first piece of text matches.");
is(p2.innerText, "Firefox\n", "The second piece of text matches.");
}
ok(
Services.telemetry
.getHistogramById("TEXT_RECOGNITION_API_PERFORMANCE")
.snapshot().sum > 0,
"Text recognition API performance was recorded."
);
info("Close the dialog box.");
const close = contentDocument.querySelector("#text-recognition-close");
close.click();
const expectedResultText = "Mozilla\nFirefox\n";
is(getTextFromClipboard(), expectedResultText, "The copied text matches.");
is(
getTextFromClipboard(),
"Mozilla\nFirefox\n",
"The copied text matches."
Services.telemetry
.getHistogramById("TEXT_RECOGNITION_TEXT_LENGTH")
.snapshot().sum,
expectedResultText.length,
"The length of the text was recorded."
);
info("Waiting for the dialog frame to close.");
await BrowserTestUtils.waitForCondition(
() => !document.querySelector(".textRecognitionDialogFrame")
);
info("Check for interaction telemetry.");
const timing = await BrowserTestUtils.waitForCondition(() => {
const { sum } = Services.telemetry
.getHistogramById("TEXT_RECOGNITION_INTERACTION_TIMING")
.snapshot();
if (sum > 0) {
return sum;
}
return false;
});
ok(timing > 0, "Interaction timing was measured.");
setClipboardText("");
clearTelemetry();
});
});

View file

@ -10,6 +10,8 @@ add_task(async function() {
set: [["dom.text-recognition.enabled", true]],
});
clearTelemetry();
await BrowserTestUtils.withNewTab(url, async function(browser) {
setClipboardText("");
is(getTextFromClipboard(), "", "The copied text is empty.");
@ -51,9 +53,35 @@ add_task(async function() {
return noResultsHeader.style.display !== "none";
});
{
info("Check the scalar telemetry.");
const scalars = await BrowserTestUtils.waitForCondition(() =>
getTelemetryScalars()
);
const contentContext = scalars["browser.ui.interaction.content_context"];
ok(contentContext, "Opening the context menu was recorded.");
is(contentContext["context-imagetext"], 1, "Telemetry has been recorded");
}
const text = contentDocument.querySelector(".textRecognitionText");
is(text.children.length, 0, "No results are listed.");
ok(
Services.telemetry
.getHistogramById("TEXT_RECOGNITION_API_PERFORMANCE")
.snapshot().sum > 0,
"Histogram timing was recorded even though there were no results."
);
is(
Services.telemetry
.getHistogramById("TEXT_RECOGNITION_INTERACTION_TIMING")
.snapshot().sum,
0,
"No interaction timing has been measured yet."
);
info("Close the dialog box.");
const close = contentDocument.querySelector("#text-recognition-close");
close.click();
@ -65,4 +93,6 @@ add_task(async function() {
is(getTextFromClipboard(), "", "The copied text is still empty.");
});
clearTelemetry();
});

View file

@ -26,3 +26,30 @@ function getTextFromClipboard() {
transferable.getTransferData("text/unicode", results);
return results.value.QueryInterface(Ci.nsISupportsString)?.data ?? "";
}
/**
* Returns events specifically for text recognition.
*/
function getTelemetryScalars() {
const snapshot = Services.telemetry.getSnapshotForKeyedScalars(
"main",
true /* clear events */
);
if (!snapshot.parent) {
return {};
}
return snapshot.parent;
}
function clearTelemetry() {
Services.telemetry.clearScalars();
Services.telemetry
.getHistogramById("TEXT_RECOGNITION_API_PERFORMANCE")
.clear();
Services.telemetry
.getHistogramById("TEXT_RECOGNITION_INTERACTION_TIMING")
.clear();
Services.telemetry.getHistogramById("TEXT_RECOGNITION_TEXT_LENGTH").clear();
}

View file

@ -46,6 +46,11 @@ class TextRecognitionModal {
if (results.length === 0) {
// Update the UI to indicate that there were no results.
this.showHeaderByID("text-recognition-header-no-results");
// It's still worth recording telemetry times, as the API was still invoked.
TelemetryStopwatch.finish(
"TEXT_RECOGNITION_API_PERFORMANCE",
resultsPromise
);
return;
}
@ -53,16 +58,71 @@ class TextRecognitionModal {
// the results to the UI.
this.runClusteringAndUpdateUI(results, direction);
this.showHeaderByID("text-recognition-header-results");
TelemetryStopwatch.finish(
"TEXT_RECOGNITION_API_PERFORMANCE",
resultsPromise
);
TextRecognitionModal.recordInteractionTime();
},
error => {
// There was an error in the text recognition call. Treat this as the same
// as if there were no results, but report the error to the console.
console.error(error);
// as if there were no results, but report the error to the console and telemetry.
this.showHeaderByID("text-recognition-header-no-results");
console.error(
"There was an error recognizing the text from an image.",
error
);
Services.telemetry.scalarAdd(
"browser.ui.interaction.textrecognition_error",
1
);
TelemetryStopwatch.cancel(
"TEXT_RECOGNITION_API_PERFORMANCE",
resultsPromise
);
}
);
}
/**
* After the results are shown, measure how long a user interacts with the modal.
*/
static recordInteractionTime() {
TelemetryStopwatch.start(
"TEXT_RECOGNITION_INTERACTION_TIMING",
// Pass the instance of the window in case multiple tabs are doing text recognition
// and there is a race condition.
window
);
const finish = () => {
TelemetryStopwatch.finish("TEXT_RECOGNITION_INTERACTION_TIMING", window);
window.removeEventListener("blur", finish);
window.removeEventListener("unload", finish);
};
// The user's focus went away, record this as the total interaction, even if they
// go back and interact with it more. This can be triggered by doing actions like
// clicking the URL bar, or by switching tabs.
window.addEventListener("blur", finish);
// The modal is closed.
window.addEventListener("unload", finish);
}
/**
* After the results are shown, measure how long a user interacts with the modal.
* @param {number} length
*/
static recordTextLengthTelemetry(length) {
const histogram = Services.telemetry.getHistogramById(
"TEXT_RECOGNITION_TEXT_LENGTH"
);
histogram.add(length);
}
setupCloseHandler() {
document
.querySelector("#text-recognition-close")
@ -166,6 +226,7 @@ class TextRecognitionModal {
this.textEl.style.display = "block";
TextRecognitionModal.copy(text);
TextRecognitionModal.recordTextLengthTelemetry(text.length);
}
}

View file

@ -27,7 +27,7 @@ async function checkOpensOnFocus(win = window) {
});
}
add_task(async function setUp() {
add_setup(async function() {
// Add some history for the empty panel.
await PlacesTestUtils.addVisits([
{

View file

@ -69,7 +69,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "25ffad05ab62ef2a0f749182e2382572cb980d78"
"revision": "e4efbb1ae8c9460d91697cb19ca399391516c6a1"
},
"ast": {
"pin": false,
@ -357,7 +357,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "9e11d30d9d1e59de209db1601d33afeb5f64f16c"
"revision": "10e0d35aa0c0fe14bdfca7003998b021d3112067"
},
"de": {
"pin": false,
@ -375,7 +375,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "7008190a3c810b10d03174708111652787885eb5"
"revision": "081cb3579df43cc693b9ec190ee0080834b077b9"
},
"dsb": {
"pin": false,
@ -483,7 +483,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "1c0769caf37db56f48b38a60527e9ddaae558207"
"revision": "5792a213755f0522cfa517a248ac4c749abcb906"
},
"es-CL": {
"pin": false,
@ -501,7 +501,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "2027b3e3a0503374d8a42b3505d448fef6d96a34"
"revision": "717f385cee05e39f757d9eb2df86c26210d0a35f"
},
"es-ES": {
"pin": false,
@ -663,7 +663,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "8e24d6455abf245be8c9f7653d8a70e2bb9392b4"
"revision": "43d90376f8c879819a9690e79a46207045fda723"
},
"ga-IE": {
"pin": false,
@ -771,7 +771,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "54f3fd42e75f97931f4b8bc4381269e145123ba3"
"revision": "9bfd43614204af81d29411dd2db524d0c79eaab2"
},
"hi-IN": {
"pin": false,
@ -1317,7 +1317,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "2510a6117777124d4b65572ae4b01cbc03c4ddfe"
"revision": "5ae8284dce3f664674a05e48b39f565f7cc1ecd4"
},
"nn-NO": {
"pin": false,
@ -1353,7 +1353,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "00c0c22d756eb512e952534155971c79b6b930bf"
"revision": "859a2e7f699c47e49a33e6fb7e1ac2ed72aa2290"
},
"pa-IN": {
"pin": false,
@ -1569,7 +1569,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "788aa6ec2d7c12368f80ab818919103beeaf513a"
"revision": "fadee9d67986216412cc618106e2caa4029778c6"
},
"sk": {
"pin": false,
@ -1587,7 +1587,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "2d30d7600d5883569e4f7ad680ceac5310e185df"
"revision": "13f02831c35a98b6dd6c013cf65b3b9facd2e638"
},
"sl": {
"pin": false,
@ -1749,7 +1749,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "ce90e151998747f4d3b5acec66cd6d6b42ee949e"
"revision": "684694f3c6fb8be0c709a8c5203f3a6792d352f5"
},
"th": {
"pin": false,
@ -1839,7 +1839,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "0994db68103ff00e69b4b4477c5324076c37fc07"
"revision": "9ab9d08a4e6c8a432f1d7ff699840d685fd48020"
},
"ur": {
"pin": false,
@ -1965,6 +1965,6 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "2824c9de66684379c0cafa1250646fdd744a52fd"
"revision": "e46566eabf0f5d4915c1088cc94b0e2254b538c3"
}
}

View file

@ -52,12 +52,19 @@ with only_when(target_is_osx):
"--enable-macos-target",
env="MACOSX_DEPLOYMENT_TARGET",
nargs=1,
default=depends(target)(lambda t: "11.0" if t.cpu == "aarch64" else "10.12"),
default=depends(target, developer_options)
# We continue to target 10.12 on Intel, but can target 11.0 for
# aarch64 since the earliest hardware was released alongside 11.0.
# For local builds, we want to target 10.12 regardless of the
# underlying platform to catch any errors or warnings that wouldn't
# show up when targeting 11.0, since these would later show up on
# CI for Intel builds.
(lambda t, d: "11.0" if (t.cpu == "aarch64" and not d) else "10.12"),
help="Set the minimum MacOS version needed at runtime{|}",
)
@depends_if("--enable-macos-target")
def macos_target(value):
@depends_if("--enable-macos-target", developer_options)
def macos_target(value, _):
return value[0]

View file

@ -143,18 +143,6 @@ with only_when(depends(target)(lambda t: t.kernel == "Darwin")):
# catch ObjC method parameter type not matching super class method
check_and_add_warning("-Wsuper-class-method-mismatch")
# catches expressions used as a null pointer constant
# XXX: at the time of writing, the version of clang used on the OS X test
# machines has a bug that causes it to reject some valid files if both
# -Wnon-literal-null-conversion and -Wsometimes-uninitialized are
# specified. We work around this by instead using
# -Werror=non-literal-null-conversion, but we only do that when
# --enable-warnings-as-errors is specified so that no unexpected fatal
# warnings are produced.
check_and_add_warning(
"-Werror=non-literal-null-conversion", when="--enable-warnings-as-errors"
)
# catches string literals used in boolean expressions
check_and_add_warning("-Wstring-conversion")
@ -172,25 +160,12 @@ check_and_add_warning("-Wno-error=deprecated-declarations")
# false positives depending on optimization
check_and_add_warning("-Wno-error=array-bounds")
# can't get rid of those PGO warnings
check_and_add_warning("-Wno-error=coverage-mismatch")
# -Wbackend-plugin warnings from Android PGO profile-use builds:
# error: /builds/worker/workspace/build/src/mozglue/misc/AutoProfilerLabel.cpp:
# Function control flow change detected (hash mismatch)
# _ZN7mozilla17AutoProfilerLabelD2Ev [-Werror,-Wbackend-plugin]
check_and_add_warning("-Wno-error=backend-plugin")
# false positives depending on optimizations
check_and_add_warning("-Wno-error=free-nonheap-object")
# Would be a pain to fix all occurrences, for very little gain
check_and_add_warning("-Wno-multistatement-macros")
# Disable the -Werror for return-std-move because of a false positive
# on nsTAutoStringN: https://bugs.llvm.org/show_bug.cgi?id=37249
check_and_add_warning("-Wno-error=return-std-move")
# Disable the -Werror for -Wclass-memaccess as we have a long
# tail of issues to fix
check_and_add_warning("-Wno-error=class-memaccess")
@ -199,10 +174,6 @@ check_and_add_warning("-Wno-error=class-memaccess")
# https://bugs.llvm.org/show_bug.cgi?id=38593
check_and_add_warning("-Wno-error=atomic-alignment")
# New warning with gcc 9. Not useful
# https://bugzilla.mozilla.org/show_bug.cgi?id=1515356
check_and_add_warning("-Wno-error=deprecated-copy")
# New warning with clang 15. Catches uses of deprecated builtins in abseil-cpp.
# https://bugzilla.mozilla.org/show_bug.cgi?id=1779528
check_and_add_warning("-Wno-error=deprecated-builtins")
@ -308,9 +279,6 @@ with only_when(target_is_windows):
# build, but we're not sure why.
check_and_add_warning("-Wno-enum-compare")
# We hit this all over the place with the gtest INSTANTIATE_TEST_CASE_P macro
check_and_add_warning("-Wno-gnu-zero-variadic-macro-arguments")
# Make it an error to be missing function declarations for C code.
check_and_add_warning("-Werror=implicit-function-declaration", c_compiler)
@ -332,6 +300,9 @@ check_and_add_warning(
),
)
# Warn if APIs are used without available() checks on macOS.
check_and_add_warning("-Werror=unguarded-availability-new", when=target_is_osx)
# Please keep these last in this file
add_old_configure_assignment("_WARNINGS_CFLAGS", warnings_flags.cflags)
add_old_configure_assignment("_WARNINGS_CXXFLAGS", warnings_flags.cxxflags)

10
configure vendored Executable file
View file

@ -0,0 +1,10 @@
#!/bin/sh
# 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/.
SRCDIR=$(dirname $0)
TOPSRCDIR="$SRCDIR"
PYTHON3="${PYTHON3:-python3}"
exec "$PYTHON3" "$TOPSRCDIR/configure.py" "$@"

View file

@ -1,19 +0,0 @@
#!/bin/sh
# 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/.
#
# Because adding a configure file in the tree is going to conflict with
# existing configure files in people's (and automation) work trees, and
# because some automation jobs are still running autoconf and configure
# "manually", this file is actually an m4 file that is processed by
# autoconf, but doesn't call any autoconf macros. The `divert` line
# below ensures the script that follows is output by autoconf.
: "divert(0)dnl"
#!/bin/sh
SRCDIR=$(dirname $0)
TOPSRCDIR="$SRCDIR"
PYTHON3="${PYTHON3:-python3}"
exec "$PYTHON3" "$TOPSRCDIR/configure.py" "$@"

View file

@ -40,12 +40,11 @@ add_task(async function testWebExtensionToolboxReload() {
document
);
const { devtoolsTab, devtoolsWindow } = await openAboutDevtoolsToolbox(
document,
tab,
window,
ADDON_NAME
);
const {
devtoolsDocument,
devtoolsTab,
devtoolsWindow,
} = await openAboutDevtoolsToolbox(document, tab, window, ADDON_NAME);
const toolbox = getToolbox(devtoolsWindow);
const webconsole = await toolbox.selectTool("webconsole");
@ -64,13 +63,35 @@ add_task(async function testWebExtensionToolboxReload() {
synthesizeKeyShortcut(L10N.getStr("toolbox.reload.key"), toolbox.win);
info("Wait until a new background log message is logged");
const secondMessage = await waitFor(() => {
const newMessage = findMessagesByType(
hud,
"background script executed",
".console-api"
);
if (newMessage && newMessage !== initialMessage) {
return newMessage;
}
return false;
});
await waitForLoadedPanelsReload();
info("Reload via the debug target info bar button");
clickReload(devtoolsDocument);
info("Wait until yet another background log message is logged");
await waitFor(() => {
const newMessage = findMessagesByType(
hud,
"background script executed",
".console-api"
);
return newMessage && newMessage !== initialMessage;
return (
newMessage &&
newMessage !== initialMessage &&
newMessage !== secondMessage
);
});
await waitForLoadedPanelsReload();
@ -79,3 +100,7 @@ add_task(async function testWebExtensionToolboxReload() {
await removeTemporaryExtension(ADDON_NAME, document);
await removeTab(tab);
});
function clickReload(devtoolsDocument) {
devtoolsDocument.querySelector(".qa-reload-button").click();
}

View file

@ -289,13 +289,20 @@ class DebugTargetInfo extends PureComponent {
const { debugTargetData } = this.props;
const { targetType } = debugTargetData;
if (targetType !== DEBUG_TARGET_TYPES.TAB) {
if (
targetType !== DEBUG_TARGET_TYPES.TAB &&
targetType !== DEBUG_TARGET_TYPES.EXTENSION
) {
return null;
}
const items = [];
if (this.props.toolbox.target.getTrait("navigation")) {
// There is little value in exposing back/forward for WebExtensions
if (
this.props.toolbox.target.getTrait("navigation") &&
targetType === DEBUG_TARGET_TYPES.TAB
) {
items.push(
this.renderNavigationButton({
className: "qa-back-button",

View file

@ -91,6 +91,7 @@ skip-if = (verify && !debug && os == 'win')
[browser_rules_computed-lists_02.js]
[browser_rules_computed-lists_03.js]
[browser_rules_completion-popup-hidden-after-navigation.js]
[browser_rules_container-queries.js]
[browser_rules_content_01.js]
[browser_rules_content_02.js]
[browser_rules_variables-in-pseudo-element_01.js]

View file

@ -0,0 +1,111 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the rule-view content is correct when the page defines container queries.
const TEST_URI = `
<style type="text/css">
body {
container: mycontainer / inline-size;
}
@container (width > 0px) {
h1, [test-hint="nocontainername"]{
outline-color: chartreuse;
}
}
@container unknowncontainer (min-width: 2vw) {
h1, [test-hint="unknowncontainer"] {
border-color: salmon;
}
}
@container mycontainer (1px < width < 10000px) {
h1, [test-hint="container"] {
color: tomato;
}
}
@layer mylayer {
@container mycontainer (min-width: 3em) {
@media screen {
@container mycontainer (min-width: 4rem) {
h1, [test-hint="nested"] {
background: gold;
}
}
}
}
}
</style>
<h1>Hello @container!</h1>
`;
add_task(async function() {
await pushPref("layout.css.container-queries.enabled", true);
await addTab(
"https://example.com/document-builder.sjs?html=" +
encodeURIComponent(TEST_URI)
);
const { inspector, view } = await openRuleView();
await selectNode("h1", inspector);
const expectedRules = [
{ selector: "element", ancestorRulesData: null },
{
selector: `h1, [test-hint="container"]`,
ancestorRulesData: ["@container mycontainer (1px < width < 10000px)"],
},
{
selector: `h1, [test-hint="unknowncontainer"]`,
ancestorRulesData: ["@container unknowncontainer (min-width: 2vw)"],
},
{
selector: `h1, [test-hint="nocontainername"]`,
ancestorRulesData: ["@container (width > 0px)"],
},
{
selector: `h1, [test-hint="nested"]`,
ancestorRulesData: [
`@layer mylayer`,
`@container mycontainer (min-width: 3em)`,
`@media screen`,
`@container mycontainer (min-width: 4rem)`,
],
},
];
const rulesInView = Array.from(view.element.children);
is(
rulesInView.length,
expectedRules.length,
"All expected rules are displayed"
);
for (let i = 0; i < expectedRules.length; i++) {
const expectedRule = expectedRules[i];
info(`Checking rule #${i}: ${expectedRule.selector}`);
const selector = rulesInView[i].querySelector(".ruleview-selectorcontainer")
.innerText;
is(selector, expectedRule.selector, `Expected selector for ${selector}`);
if (expectedRule.ancestorRulesData == null) {
is(
getRuleViewAncestorRulesDataElementByIndex(view, i),
null,
`No ancestor rules data displayed for ${selector}`
);
} else {
is(
getRuleViewAncestorRulesDataTextByIndex(view, i),
expectedRule.ancestorRulesData.join("\n"),
`Expected ancestor rules data displayed for ${selector}`
);
}
}
});

View file

@ -137,14 +137,22 @@ RuleEditor.prototype = {
if (this.rule.domRule.ancestorData.length) {
const parts = this.rule.domRule.ancestorData.map(ancestorData => {
if (ancestorData.type == "container") {
const containerQueryParts = [
"@container",
ancestorData.containerName,
ancestorData.containerQuery,
].filter(p => !!p);
return containerQueryParts.join(" ");
}
if (ancestorData.type == "layer") {
return `@layer${ancestorData.value ? " " + ancestorData.value : ""}`;
}
if (ancestorData.type == "media") {
return `@media ${ancestorData.value}`;
}
// We shouldn't get here as `type` can only be set to "layer" or "media", but just
// in case, let's return an empty string.
// We shouldn't get here as `type` can only be set to "container", "layer" or "media",
// but just in case, let's return an empty string.
console.warn("Unknown ancestor data type:", ancestorData.type);
return ``;
});

View file

@ -396,6 +396,16 @@ pieChart.loading=Loading
# no data available, even after loading it.
pieChart.unavailable=Empty
# LOCALIZATION NOTE (pieChart.ariaLabel): This is the text used for the aria-label attribute
# for SVG pie charts (e.g., in the performance analysis view).
pieChart.ariaLabel=Pie chart representing the size of each type of request in proportion to each other
# LOCALIZATION NOTE (pieChart.sliceAriaLabel): This is the text used for the aria-label attribute
# for SVG pie charts slices (e.g., in the performance analysis view).
# %1$S is the slice label (e.g. "html")
# %2$S is the percentage (e.g. "33.23%").
pieChart.sliceAriaLabel=%1$S: %2$S
# LOCALIZATION NOTE (tableChart.loading): This is the label displayed
# for table charts (e.g., in the performance analysis view) when there is
# no data available yet.
@ -458,6 +468,11 @@ charts.totalCached=Cached responses: %S
# in the performance analysis view for total requests.
charts.totalCount=Total requests: %S
# LOCALIZATION NOTE (charts.requestsNumber): This is the label for the header column in
# the performance analysis view for the number of requests. The label is not visible on screen,
# but is set in the DOM for accessibility sake.
charts.requestsNumber=Number of requests
# LOCALIZATION NOTE (charts.size): This is the label displayed
# in the header column in the performance analysis view for size of the request.
charts.size=Size

View file

@ -65,60 +65,52 @@
}
.statistics-panel .table-chart-container {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-width: 240px;
margin-inline-start: 1vw;
margin-inline-end: 3vw;
}
.chart-colored-blob[name=html] {
fill: var(--theme-highlight-bluegrey);
background: var(--theme-highlight-bluegrey);
tr[data-statistic-name] td:first-of-type {
border-inline-start: 15px solid var(--stat-color);
}
.chart-colored-blob[name=css] {
fill: var(--theme-highlight-blue);
background: var(--theme-highlight-blue);
path[data-statistic-name] {
fill: var(--stat-color);
}
.chart-colored-blob[name=js] {
fill: var(--theme-highlight-lightorange);
background: var(--theme-highlight-lightorange);
[data-statistic-name=html] {
--stat-color: var(--theme-highlight-bluegrey);
}
.chart-colored-blob[name=xhr] {
fill: var(--theme-highlight-orange);
background: var(--theme-highlight-orange);
[data-statistic-name=css] {
--stat-color: var(--theme-highlight-blue);
}
.chart-colored-blob[name=fonts] {
fill: var(--theme-highlight-purple);
background: var(--theme-highlight-purple);
[data-statistic-name=js] {
--stat-color: var(--theme-highlight-lightorange);
}
.chart-colored-blob[name=images] {
fill: var(--theme-highlight-pink);
background: var(--theme-highlight-pink);
[data-statistic-name=xhr] {
--stat-color: var(--theme-highlight-orange);
}
.chart-colored-blob[name=media] {
fill: var(--theme-highlight-green);
background: var(--theme-highlight-green);
[data-statistic-name=fonts] {
--stat-color: var(--theme-highlight-purple);
}
.chart-colored-blob[name=flash] {
fill: var(--theme-highlight-red);
background: var(--theme-highlight-red);
[data-statistic-name=images] {
--stat-color: var(--theme-highlight-pink);
}
.table-chart-row {
display: flex;
[data-statistic-name=media] {
--stat-color: var(--theme-highlight-green);
}
.table-chart-row-label[name=cached] {
display: none;
/*
* Align cell text to the center by default.
*/
.table-chart-row-label {
text-align: center;
}
.table-chart-row-label[name=count] {
@ -169,3 +161,11 @@
margin-bottom: 2rem;
}
}
.offscreen{
position: absolute!important;
font-size: 0px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
clip-path: polygon(0 0,0 0,0 0,0 0);
}

View file

@ -173,8 +173,7 @@ class StatisticsPanel extends Component {
diameter: NETWORK_ANALYSIS_PIE_CHART_DIAMETER,
title,
header: {
cached: "",
count: "",
count: L10N.getStr("charts.requestsNumber"),
label: L10N.getStr("charts.type"),
size: L10N.getStr("charts.size"),
transferredSize: L10N.getStr("charts.transferred"),

View file

@ -264,7 +264,6 @@ skip-if = true #Bug 1667115
[browser_net_statistics-01.js]
skip-if = true # Bug 1373558
[browser_net_statistics-02.js]
fail-if = a11y_checks # bug 1687789 interactive char is not accessible
[browser_net_status-bar-transferred-size.js]
[browser_net_status-bar.js]
skip-if = win10_2004 # Bug 1723573
@ -282,7 +281,7 @@ skip-if = true # TODO: fix the test
[browser_net_timing-division.js]
[browser_net_tracking-resources.js]
[browser_net_truncate-post-data.js]
skip-if =
skip-if =
os == 'win' && os_version == '6.1' && !debug # Bug 1547150
socketprocess_networking # Bug 1772211
[browser_net_truncate.js]

View file

@ -41,7 +41,8 @@ add_task(async function() {
});
const { node } = pie;
const slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
const slicesContainer = node.querySelectorAll(".pie-chart-slice-container");
const slices = node.querySelectorAll(".pie-chart-slice");
const labels = node.querySelectorAll(".pie-chart-label");
ok(
@ -49,6 +50,17 @@ add_task(async function() {
node.classList.contains("generic-chart-container"),
"A pie chart container was created successfully."
);
is(
node.getAttribute("aria-label"),
"Pie chart representing the size of each type of request in proportion to each other",
"pie chart container has expected aria-label"
);
is(
slicesContainer.length,
3,
"There should be 3 pie chart slices container created."
);
is(slices.length, 3, "There should be 3 pie chart slices created.");
ok(
@ -76,6 +88,22 @@ add_task(async function() {
"The third slice has the correct data."
);
is(
slicesContainer[0].getAttribute("aria-label"),
"baz: 50%",
"First slice container has expected aria-label"
);
is(
slicesContainer[1].getAttribute("aria-label"),
"bar: 33.33%",
"Second slice container has expected aria-label"
);
is(
slicesContainer[2].getAttribute("aria-label"),
"foo: 16.67%",
"Third slice container has expected aria-label"
);
ok(
slices[0].hasAttribute("largest"),
"The first slice should be the largest one."
@ -86,17 +114,17 @@ add_task(async function() {
);
is(
slices[0].getAttribute("name"),
slices[0].getAttribute("data-statistic-name"),
"baz",
"The first slice's name is correct."
);
is(
slices[1].getAttribute("name"),
slices[1].getAttribute("data-statistic-name"),
"bar",
"The first slice's name is correct."
);
is(
slices[2].getAttribute("name"),
slices[2].getAttribute("data-statistic-name"),
"foo",
"The first slice's name is correct."
);
@ -105,6 +133,21 @@ add_task(async function() {
is(labels[0].textContent, "baz", "The first label's text is correct.");
is(labels[1].textContent, "bar", "The first label's text is correct.");
is(labels[2].textContent, "foo", "The first label's text is correct.");
is(
labels[0].getAttribute("aria-hidden"),
"true",
"The first label has aria-hidden."
);
is(
labels[1].getAttribute("aria-hidden"),
"true",
"The first label has aria-hidden."
);
is(
labels[2].getAttribute("aria-hidden"),
"true",
"The first label has aria-hidden."
);
await teardown(monitor);
});

View file

@ -30,7 +30,7 @@ add_task(async function() {
});
const { node } = pie;
const slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
const slices = node.querySelectorAll(".pie-chart-slice");
const labels = node.querySelectorAll(".pie-chart-label");
ok(
@ -58,7 +58,7 @@ add_task(async function() {
"The first slice should also be the smallest one."
);
is(
slices[0].getAttribute("name"),
slices[0].getAttribute("data-statistic-name"),
L10N.getStr("pieChart.loading"),
"The first slice's name is correct."
);

View file

@ -78,97 +78,85 @@ add_task(async function() {
);
is(
rows[0].querySelectorAll("span")[0].getAttribute("name"),
rows[0].querySelectorAll(".table-chart-row-label")[0].getAttribute("name"),
"label1",
"The first column of the header exists."
);
is(
rows[0].querySelectorAll("span")[1].getAttribute("name"),
rows[0].querySelectorAll(".table-chart-row-label")[1].getAttribute("name"),
"label2",
"The second column of the header exists."
);
is(
rows[0].querySelectorAll("span")[0].textContent,
rows[0].querySelectorAll(".table-chart-row-label")[0].textContent,
"label1header",
"The first column of the header displays the correct text."
);
is(
rows[0].querySelectorAll("span")[1].textContent,
rows[0].querySelectorAll(".table-chart-row-label")[1].textContent,
"label2header",
"The second column of the header displays the correct text."
);
ok(
rows[1].querySelector(".table-chart-row-box.chart-colored-blob"),
"A colored blob exists for the first row."
);
is(
rows[1].querySelectorAll("span")[0].getAttribute("name"),
rows[1].querySelectorAll(".table-chart-row-label")[0].getAttribute("name"),
"label1",
"The first column of the first row exists."
);
is(
rows[1].querySelectorAll("span")[1].getAttribute("name"),
rows[1].querySelectorAll(".table-chart-row-label")[1].getAttribute("name"),
"label2",
"The second column of the first row exists."
);
is(
rows[1].querySelectorAll("span")[0].textContent,
rows[1].querySelectorAll(".table-chart-row-label")[0].textContent,
"1",
"The first column of the first row displays the correct text."
);
is(
rows[1].querySelectorAll("span")[1].textContent,
rows[1].querySelectorAll(".table-chart-row-label")[1].textContent,
"11.1foo",
"The second column of the first row displays the correct text."
);
ok(
rows[2].querySelector(".table-chart-row-box.chart-colored-blob"),
"A colored blob exists for the second row."
);
is(
rows[2].querySelectorAll("span")[0].getAttribute("name"),
rows[2].querySelectorAll(".table-chart-row-label")[0].getAttribute("name"),
"label1",
"The first column of the second row exists."
);
is(
rows[2].querySelectorAll("span")[1].getAttribute("name"),
rows[2].querySelectorAll(".table-chart-row-label")[1].getAttribute("name"),
"label2",
"The second column of the second row exists."
);
is(
rows[2].querySelectorAll("span")[0].textContent,
rows[2].querySelectorAll(".table-chart-row-label")[0].textContent,
"2",
"The first column of the second row displays the correct text."
);
is(
rows[2].querySelectorAll("span")[1].textContent,
rows[2].querySelectorAll(".table-chart-row-label")[1].textContent,
"12.2bar",
"The second column of the first row displays the correct text."
);
ok(
rows[3].querySelector(".table-chart-row-box.chart-colored-blob"),
"A colored blob exists for the third row."
);
is(
rows[3].querySelectorAll("span")[0].getAttribute("name"),
rows[3].querySelectorAll(".table-chart-row-label")[0].getAttribute("name"),
"label1",
"The first column of the third row exists."
);
is(
rows[3].querySelectorAll("span")[1].getAttribute("name"),
rows[3].querySelectorAll(".table-chart-row-label")[1].getAttribute("name"),
"label2",
"The second column of the third row exists."
);
is(
rows[3].querySelectorAll("span")[0].textContent,
rows[3].querySelectorAll(".table-chart-row-label")[0].textContent,
"3",
"The first column of the third row displays the correct text."
);
is(
rows[3].querySelectorAll("span")[1].textContent,
rows[3].querySelectorAll(".table-chart-row-label")[1].textContent,
"13.3baz",
"The second column of the third row displays the correct text."
);

View file

@ -62,27 +62,23 @@ add_task(async function() {
"There should be 1 table chart row and a 1 header created."
);
ok(
rows[1].querySelector(".table-chart-row-box.chart-colored-blob"),
"A colored blob exists for the first row."
);
is(
rows[1].querySelectorAll("span")[0].getAttribute("name"),
rows[1].querySelectorAll(".table-chart-row-label")[0].getAttribute("name"),
"size",
"The first column of the first row exists."
);
is(
rows[1].querySelectorAll("span")[1].getAttribute("name"),
rows[1].querySelectorAll(".table-chart-row-label")[1].getAttribute("name"),
"label",
"The second column of the first row exists."
);
is(
rows[1].querySelectorAll("span")[0].textContent,
rows[1].querySelectorAll(".table-chart-row-label")[0].textContent,
"",
"The first column of the first row displays the correct text."
);
is(
rows[1].querySelectorAll("span")[1].textContent,
rows[1].querySelectorAll(".table-chart-row-label")[1].textContent,
L10N.getStr("tableChart.loading"),
"The second column of the first row displays the correct text."
);

View file

@ -29,7 +29,7 @@ add_task(async function() {
});
const { node } = pie;
const slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
const slices = node.querySelectorAll(".pie-chart-slice");
const labels = node.querySelectorAll(".pie-chart-label");
is(slices.length, 1, "There should be 1 pie chart slice created.");
@ -48,7 +48,7 @@ add_task(async function() {
"The slice should also be the smallest one."
);
is(
slices[0].getAttribute("name"),
slices[0].getAttribute("data-statistic-name"),
L10N.getStr("pieChart.unavailable"),
"The slice's name is correct."
);

View file

@ -42,27 +42,23 @@ add_task(async function() {
is(rows.length, 2, "There should be 1 table chart row and 1 header created.");
ok(
rows[1].querySelector(".table-chart-row-box.chart-colored-blob"),
"A colored blob exists for the first row."
);
is(
rows[1].querySelectorAll("span")[0].getAttribute("name"),
rows[1].querySelectorAll(".table-chart-row-label")[0].getAttribute("name"),
"size",
"The first column of the first row exists."
);
is(
rows[1].querySelectorAll("span")[1].getAttribute("name"),
rows[1].querySelectorAll(".table-chart-row-label")[1].getAttribute("name"),
"label",
"The second column of the first row exists."
);
is(
rows[1].querySelectorAll("span")[0].textContent,
rows[1].querySelectorAll(".table-chart-row-label")[0].textContent,
"",
"The first column of the first row displays the correct text."
);
is(
rows[1].querySelectorAll("span")[1].textContent,
rows[1].querySelectorAll(".table-chart-row-label")[1].textContent,
L10N.getStr("tableChart.unavailable"),
"The second column of the first row displays the correct text."
);

View file

@ -57,7 +57,7 @@ add_task(async function() {
EventUtils.sendMouseEvent(
{ type: "click" },
document.querySelector(".pie-chart-slice")
document.querySelector(".pie-chart-slice-container")
);
ok(

View file

@ -69,20 +69,9 @@ define(function(require, exports, module) {
);
}
function createGripMapEntry(key, value) {
return {
type: "mapEntry",
preview: {
key,
value,
},
};
}
// Exports from this module
module.exports = {
rep: wrapRender(GripEntry),
createGripMapEntry,
supportsObject,
};
});

View file

@ -14,7 +14,9 @@ const {
const { shouldLoadItemIndexedProperties } = Utils.loadProperties;
const GripEntryRep = require("devtools/client/shared/components/reps/reps/grip-entry");
const {
createGripMapEntry,
} = require("devtools/client/shared/components/test/node/components/reps/test-helpers");
const accessorStubs = require("devtools/client/shared/components/test/node/stubs/reps/accessor");
const gripMapStubs = require("devtools/client/shared/components/test/node/stubs/reps/grip-map");
const gripArrayStubs = require("devtools/client/shared/components/test/node/stubs/reps/grip-array");
@ -189,7 +191,7 @@ describe("shouldLoadItemIndexedProperties", () => {
});
it("returns false for a MapEntry node", () => {
const node = GripEntryRep.createGripMapEntry("key", "value");
const node = createGripMapEntry("key", "value");
expect(shouldLoadItemIndexedProperties(node)).toBeFalsy();
});

View file

@ -14,7 +14,9 @@ const {
const { shouldLoadItemNonIndexedProperties } = Utils.loadProperties;
const GripEntryRep = require("devtools/client/shared/components/reps/reps/grip-entry");
const {
createGripMapEntry,
} = require("devtools/client/shared/components/test/node/components/reps/test-helpers");
const accessorStubs = require("devtools/client/shared/components/test/node/stubs/reps/accessor");
const gripMapStubs = require("devtools/client/shared/components/test/node/stubs/reps/grip-map");
const gripArrayStubs = require("devtools/client/shared/components/test/node/stubs/reps/grip-array");
@ -152,7 +154,7 @@ describe("shouldLoadItemNonIndexedProperties", () => {
});
it("returns false for a MapEntry node", () => {
const node = GripEntryRep.createGripMapEntry("key", "value");
const node = createGripMapEntry("key", "value");
expect(shouldLoadItemNonIndexedProperties(node)).toBeFalsy();
});

View file

@ -14,7 +14,9 @@ const {
const { shouldLoadItemPrototype } = Utils.loadProperties;
const GripEntryRep = require("devtools/client/shared/components/reps/reps/grip-entry");
const {
createGripMapEntry,
} = require("devtools/client/shared/components/test/node/components/reps/test-helpers");
const accessorStubs = require("devtools/client/shared/components/test/node/stubs/reps/accessor");
const gripMapStubs = require("devtools/client/shared/components/test/node/stubs/reps/grip-map");
const gripArrayStubs = require("devtools/client/shared/components/test/node/stubs/reps/grip-array");
@ -148,7 +150,7 @@ describe("shouldLoadItemPrototype", () => {
});
it("returns false for a MapEntry node", () => {
const node = GripEntryRep.createGripMapEntry("key", "value");
const node = createGripMapEntry("key", "value");
expect(shouldLoadItemPrototype(node)).toBeFalsy();
});

View file

@ -14,7 +14,9 @@ const {
const { shouldLoadItemSymbols } = Utils.loadProperties;
const GripEntryRep = require("devtools/client/shared/components/reps/reps/grip-entry");
const {
createGripMapEntry,
} = require("devtools/client/shared/components/test/node/components/reps/test-helpers");
const accessorStubs = require("devtools/client/shared/components/test/node/stubs/reps/accessor");
const gripMapStubs = require("devtools/client/shared/components/test/node/stubs/reps/grip-map");
const gripArrayStubs = require("devtools/client/shared/components/test/node/stubs/reps/grip-array");
@ -148,7 +150,7 @@ describe("shouldLoadItemSymbols", () => {
});
it("returns false for a MapEntry node", () => {
const node = GripEntryRep.createGripMapEntry("key", "value");
const node = createGripMapEntry("key", "value");
expect(shouldLoadItemSymbols(node)).toBeFalsy();
});

View file

@ -13,11 +13,11 @@ const {
} = require("devtools/client/shared/components/reps/reps/rep");
const { GripEntry } = REPS;
const { createGripMapEntry } = GripEntry;
const {
MODE,
} = require("devtools/client/shared/components/reps/reps/constants");
const {
createGripMapEntry,
getGripLengthBubbleText,
} = require("devtools/client/shared/components/test/node/components/reps/test-helpers");

View file

@ -97,7 +97,18 @@ function getMapLengthBubbleText(object, props) {
});
}
function createGripMapEntry(key, value) {
return {
type: "mapEntry",
preview: {
key,
value,
},
};
}
module.exports = {
createGripMapEntry,
expectActorAttribute,
getSelectableInInspectorGrips,
getGripLengthBubbleText,

View file

@ -197,9 +197,6 @@ function createPieChart(
radius = radius || (width + height) / 4;
let isPlaceholder = false;
// Filter out very small sizes, as they'll just render invisible slices.
data = data ? data.filter(e => e.size > EPSILON) : null;
// If there's no data available, display an empty placeholder.
if (!data) {
data = loadingPieChartData();
@ -215,13 +212,18 @@ function createPieChart(
"class",
"generic-chart-container pie-chart-container"
);
container.setAttribute("pack", "center");
container.setAttribute("flex", "1");
container.setAttribute("width", width);
container.setAttribute("height", height);
container.setAttribute("viewBox", "0 0 " + width + " " + height);
container.setAttribute("slices", data.length);
container.setAttribute("placeholder", isPlaceholder);
container.setAttribute("role", "group");
container.setAttribute("aria-label", L10N.getStr("pieChart.ariaLabel"));
const slicesGroup = document.createElementNS(SVG_NS, "g");
slicesGroup.setAttribute("role", "list");
container.append(slicesGroup);
const proxy = new PieChart(container);
@ -240,10 +242,38 @@ function createPieChart(
for (let i = data.length - 1; i >= 0; i--) {
const sliceInfo = data[i];
const sliceAngle = angles[i];
if (!sliceInfo.size || sliceAngle < EPSILON) {
continue;
const sliceNode = document.createElementNS(SVG_NS, "g");
sliceNode.setAttribute("role", "listitem");
slicesGroup.append(sliceNode);
const interactiveNodeId = `${sliceInfo.label}-slice`;
const textNodeId = `${sliceInfo.label}-slice-label`;
// The only way to make this keyboard accessible is to have a link
const interactiveNode = document.createElementNS(SVG_NS, "a");
interactiveNode.setAttribute("id", interactiveNodeId);
interactiveNode.setAttribute("xlink:href", `#${interactiveNodeId}`);
interactiveNode.setAttribute("tabindex", `0`);
interactiveNode.setAttribute("role", `button`);
interactiveNode.classList.add("pie-chart-slice-container");
if (!isPlaceholder) {
interactiveNode.setAttribute(
"aria-label",
L10N.getFormatStr(
"pieChart.sliceAriaLabel",
sliceInfo.label,
new Intl.NumberFormat(undefined, {
style: "unit",
unit: "percent",
maximumFractionDigits: 2,
}).format((sliceInfo.size / total) * 100)
)
);
}
sliceNode.append(interactiveNode);
endAngle = startAngle - sliceAngle;
midAngle = (startAngle + endAngle) / 2;
@ -254,8 +284,8 @@ function createPieChart(
const largeArcFlag = Math.abs(startAngle - endAngle) > PI ? 1 : 0;
const pathNode = document.createElementNS(SVG_NS, "path");
pathNode.setAttribute("class", "pie-chart-slice chart-colored-blob");
pathNode.setAttribute("name", sliceInfo.label);
pathNode.classList.add("pie-chart-slice");
pathNode.setAttribute("data-statistic-name", sliceInfo.label);
pathNode.setAttribute(
"d",
" M " +
@ -293,19 +323,30 @@ function createPieChart(
pathNode.setAttribute("style", data.length > 1 ? hoverTransform : "");
proxy.slices.set(sliceInfo, pathNode);
delegate(proxy, ["click", "mouseover", "mouseout"], pathNode, sliceInfo);
container.appendChild(pathNode);
delegate(
proxy,
["click", "mouseover", "mouseout", "focus"],
interactiveNode,
sliceInfo
);
interactiveNode.appendChild(pathNode);
if (sliceInfo.label && sliceAngle > NAMED_SLICE_MIN_ANGLE) {
const textX = centerX + textDistance * Math.sin(midAngle);
const textY = centerY - textDistance * Math.cos(midAngle);
const textX = centerX + textDistance * Math.sin(midAngle);
const textY = centerY - textDistance * Math.cos(midAngle);
// Don't add the label if the slice isn't large enough so it doesn't look cramped.
if (sliceAngle >= NAMED_SLICE_MIN_ANGLE) {
const label = document.createElementNS(SVG_NS, "text");
label.appendChild(document.createTextNode(sliceInfo.label));
label.setAttribute("id", textNodeId);
// A label is already set on `interactiveNode`, so hide this from the accessibility tree
// to avoid duplicating text.
label.setAttribute("aria-hidden", "true");
label.setAttribute("class", "pie-chart-label");
label.setAttribute("style", data.length > 1 ? hoverTransform : "");
label.setAttribute("x", data.length > 1 ? textX : centerX);
label.setAttribute("y", data.length > 1 ? textY : centerY);
container.appendChild(label);
interactiveNode.append(label);
}
startAngle = endAngle;
@ -376,11 +417,7 @@ function createTableChart(document, { title, data, strings, totals, header }) {
const container = document.createElement("div");
container.className = "generic-chart-container table-chart-container";
container.setAttribute("pack", "center");
container.setAttribute("flex", "1");
container.setAttribute("rows", data.length);
container.setAttribute("placeholder", isPlaceholder);
container.setAttribute("style", "-moz-box-orient: vertical");
const proxy = new TableChart(container);
@ -389,43 +426,46 @@ function createTableChart(document, { title, data, strings, totals, header }) {
titleNode.textContent = title;
container.appendChild(titleNode);
const tableNode = document.createElement("div");
const tableNode = document.createElement("table");
tableNode.className = "plain table-chart-grid";
tableNode.setAttribute("style", "-moz-box-orient: vertical");
container.appendChild(tableNode);
const headerNode = document.createElement("div");
const headerNode = document.createElement("thead");
headerNode.className = "table-chart-row";
const headerBoxNode = document.createElement("div");
const bodyNode = document.createElement("tbody");
const headerBoxNode = document.createElement("tr");
headerBoxNode.className = "table-chart-row-box";
headerNode.appendChild(headerBoxNode);
for (const [key, value] of Object.entries(header)) {
const headerLabelNode = document.createElement("span");
const headerLabelNode = document.createElement("th");
headerLabelNode.className = "plain table-chart-row-label";
headerLabelNode.setAttribute("name", key);
headerLabelNode.textContent = value;
headerNode.appendChild(headerLabelNode);
if (key == "count") {
headerLabelNode.classList.add("offscreen");
}
headerBoxNode.appendChild(headerLabelNode);
}
tableNode.appendChild(headerNode);
tableNode.append(headerNode, bodyNode);
for (const rowInfo of data) {
const rowNode = document.createElement("div");
const rowNode = document.createElement("tr");
rowNode.className = "table-chart-row";
rowNode.setAttribute("align", "center");
const boxNode = document.createElement("div");
boxNode.className = "table-chart-row-box chart-colored-blob";
boxNode.setAttribute("name", rowInfo.label);
rowNode.appendChild(boxNode);
rowNode.setAttribute("data-statistic-name", rowInfo.label);
for (const [key, value] of Object.entries(rowInfo)) {
// Don't render the "cached" column. We only have it in here so it can be displayed
// in the `totals` section.
if (key == "cached") {
continue;
}
const index = data.indexOf(rowInfo);
const stringified = strings[key] ? strings[key](value, index) : value;
const labelNode = document.createElement("span");
const labelNode = document.createElement("td");
labelNode.className = "plain table-chart-row-label";
labelNode.setAttribute("name", key);
labelNode.textContent = stringified;
@ -434,12 +474,11 @@ function createTableChart(document, { title, data, strings, totals, header }) {
proxy.rows.set(rowInfo, rowNode);
delegate(proxy, ["click", "mouseover", "mouseout"], rowNode, rowInfo);
tableNode.appendChild(rowNode);
bodyNode.appendChild(rowNode);
}
const totalsNode = document.createElement("div");
totalsNode.className = "table-chart-totals";
totalsNode.setAttribute("style", "-moz-box-orient: vertical");
for (const [key, value] of Object.entries(totals)) {
const total = data.reduce((acc, e) => acc + e[key], 0);

View file

@ -87,16 +87,16 @@
cursor: pointer;
}
.table-chart-grid:hover > .table-chart-row {
.table-chart-grid:hover .table-chart-row {
transition: opacity 0.1s ease-in-out;
}
.table-chart-grid:not(:hover) > .table-chart-row {
.table-chart-grid:not(:hover) .table-chart-row {
transition: opacity 0.2s ease-in-out;
}
.generic-chart-container:hover > .table-chart-grid:hover > .table-chart-row:not(:hover),
.generic-chart-container:hover ~ .table-chart-container > .table-chart-grid > .table-chart-row:not([focused]) {
.generic-chart-container:hover > .table-chart-grid:hover .table-chart-row:not(:hover),
.generic-chart-container:hover ~ .table-chart-container > .table-chart-grid .table-chart-row:not([focused]) {
opacity: 0.4;
}

View file

@ -333,6 +333,16 @@ const StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
type: "layer",
value: ancestorRule.rawRule.name,
});
} else if (
ChromeUtils.getClassName(ancestorRule.rawRule) === "CSSContainerRule"
) {
form.ancestorData.push({
type: "container",
// Send containerName and containerQuery separately (instead of conditionText)
// so the client has more flexibility to display the information.
containerName: ancestorRule.rawRule.containerName,
containerQuery: ancestorRule.rawRule.containerQuery,
});
}
}

View file

@ -253,7 +253,17 @@ nsIFrame* nsIContent::GetPrimaryFrame(mozilla::FlushType aType) {
doc->FlushPendingNotifications(aType);
}
return GetPrimaryFrame();
auto* frame = GetPrimaryFrame();
if (!frame) {
return nullptr;
}
if (aType == mozilla::FlushType::Layout) {
frame->PresShell()->EnsureReflowIfFrameHasHiddenContent(frame);
frame = GetPrimaryFrame();
}
return frame;
}
namespace mozilla::dom {

View file

@ -60,7 +60,9 @@ DOMHighResTimeStamp IdleDeadline::TimeRemaining() {
return 0.0;
}
return std::max(mDeadline - performance->Now(), 0.0);
// The web API doesn't expect deadlines > 50ms, but conversion from the
// internal API may lead to some rounding errors.
return std::min(std::max(mDeadline - performance->Now(), 0.0), 50.0);
}
// If there's no window, we're in a system scope, and can just use

View file

@ -22142,38 +22142,42 @@ class CGIterableMethodGenerator(CGGeneric):
),
)
return
if descriptor.interface.isIterable():
CGGeneric.__init__(
self,
fill(
"""
typedef ${iterClass} itrType;
RefPtr<itrType> result(new itrType(self,
itrType::IteratorType::${itrMethod},
&${ifaceName}Iterator_Binding::Wrap));
""",
iterClass=iteratorNativeType(descriptor),
ifaceName=descriptor.interface.identifier.name,
itrMethod=methodName.title(),
),
)
binding = descriptor.interface.identifier.name + "Iterator_Binding"
init = ""
else:
assert descriptor.interface.isAsyncIterable()
CGGeneric.__init__(
self,
fill(
"""
binding = descriptor.interface.identifier.name + "AsyncIterator_Binding"
init = fill(
"""
{
ErrorResult initError;
self->InitAsyncIterator(result.get(), initError);
if (initError.MaybeSetPendingException(cx, "Asynchronous iterator initialization steps for ${ifaceName} failed")) {
return false;
}
}
""",
ifaceName=descriptor.interface.identifier.name,
)
CGGeneric.__init__(
self,
fill(
"""
typedef ${iterClass} itrType;
RefPtr<itrType> result(new itrType(self,
itrType::IteratorType::${itrMethod},
&${ifaceName}AsyncIterator_Binding::Wrap));
self->InitAsyncIterator(result.get());
&${binding}::Wrap));
$*{init}
""",
iterClass=iteratorNativeType(descriptor),
ifaceName=descriptor.interface.identifier.name,
itrMethod=methodName.title(),
),
)
iterClass=iteratorNativeType(descriptor),
itrMethod=methodName.title(),
binding=binding,
init=init,
),
)
def getObservableArrayBackingObject(descriptor, attr, errorReturn="return false;\n"):

View file

@ -54,7 +54,8 @@ nsPIDOMWindowInner* TestInterfaceAsyncIterableDouble::GetParentObject() const {
return mParent;
}
void TestInterfaceAsyncIterableDouble::InitAsyncIterator(Iterator* aIterator) {
void TestInterfaceAsyncIterableDouble::InitAsyncIterator(Iterator* aIterator,
ErrorResult& aError) {
UniquePtr<IteratorData> data(new IteratorData(0));
aIterator->SetData((void*)data.release());
}

View file

@ -39,7 +39,7 @@ class TestInterfaceAsyncIterableDouble final : public nsISupports,
const GlobalObject& aGlobal, ErrorResult& rv);
using Iterator = AsyncIterableIterator<TestInterfaceAsyncIterableDouble>;
void InitAsyncIterator(Iterator* aIterator);
void InitAsyncIterator(Iterator* aIterator, ErrorResult& aError);
void DestroyAsyncIterator(Iterator* aIterator);
already_AddRefed<Promise> GetNextPromise(JSContext* aCx, Iterator* aIterator,
ErrorResult& aRv);

View file

@ -61,7 +61,7 @@ nsPIDOMWindowInner* TestInterfaceAsyncIterableDoubleUnion::GetParentObject()
}
void TestInterfaceAsyncIterableDoubleUnion::InitAsyncIterator(
Iterator* aIterator) {
Iterator* aIterator, ErrorResult& aError) {
UniquePtr<IteratorData> data(new IteratorData(0));
aIterator->SetData((void*)data.release());
}

View file

@ -39,7 +39,7 @@ class TestInterfaceAsyncIterableDoubleUnion final : public nsISupports,
const GlobalObject& aGlobal, ErrorResult& rv);
using Iterator = AsyncIterableIterator<TestInterfaceAsyncIterableDoubleUnion>;
void InitAsyncIterator(Iterator* aIterator);
void InitAsyncIterator(Iterator* aIterator, ErrorResult& aError);
void DestroyAsyncIterator(Iterator* aIterator);
already_AddRefed<Promise> GetNextPromise(JSContext* aCx, Iterator* aIterator,
ErrorResult& aRv);

View file

@ -23,13 +23,14 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceAsyncIterableSingle)
NS_INTERFACE_MAP_END
TestInterfaceAsyncIterableSingle::TestInterfaceAsyncIterableSingle(
nsPIDOMWindowInner* aParent)
: mParent(aParent) {}
nsPIDOMWindowInner* aParent, bool aFailToInit)
: mParent(aParent), mFailToInit(aFailToInit) {}
// static
already_AddRefed<TestInterfaceAsyncIterableSingle>
TestInterfaceAsyncIterableSingle::Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv) {
TestInterfaceAsyncIterableSingle::Constructor(
const GlobalObject& aGlobal,
const TestInterfaceAsyncIterableSingleOptions& aOptions, ErrorResult& aRv) {
nsCOMPtr<nsPIDOMWindowInner> window =
do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
@ -38,7 +39,7 @@ TestInterfaceAsyncIterableSingle::Constructor(const GlobalObject& aGlobal,
}
RefPtr<TestInterfaceAsyncIterableSingle> r =
new TestInterfaceAsyncIterableSingle(window);
new TestInterfaceAsyncIterableSingle(window, aOptions.mFailToInit);
return r.forget();
}
@ -51,7 +52,13 @@ nsPIDOMWindowInner* TestInterfaceAsyncIterableSingle::GetParentObject() const {
return mParent;
}
void TestInterfaceAsyncIterableSingle::InitAsyncIterator(Iterator* aIterator) {
void TestInterfaceAsyncIterableSingle::InitAsyncIterator(Iterator* aIterator,
ErrorResult& aError) {
if (mFailToInit) {
aError.ThrowTypeError("Caller asked us to fail");
return;
}
UniquePtr<IteratorData> data(new IteratorData(0));
aIterator->SetData((void*)data.release());
}

View file

@ -21,6 +21,7 @@ class ErrorResult;
namespace dom {
class GlobalObject;
struct TestInterfaceAsyncIterableSingleOptions;
// Implementation of test binding for webidl iterable interfaces, using
// primitives for value type
@ -30,15 +31,17 @@ class TestInterfaceAsyncIterableSingle final : public nsISupports,
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceAsyncIterableSingle)
explicit TestInterfaceAsyncIterableSingle(nsPIDOMWindowInner* aParent);
TestInterfaceAsyncIterableSingle(nsPIDOMWindowInner* aParent,
bool aFailToInit);
nsPIDOMWindowInner* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<TestInterfaceAsyncIterableSingle> Constructor(
const GlobalObject& aGlobal, ErrorResult& rv);
const GlobalObject& aGlobal,
const TestInterfaceAsyncIterableSingleOptions& aOptions, ErrorResult& rv);
using Iterator = AsyncIterableIterator<TestInterfaceAsyncIterableSingle>;
void InitAsyncIterator(Iterator* aIterator);
void InitAsyncIterator(Iterator* aIterator, ErrorResult& aError);
void DestroyAsyncIterator(Iterator* aIterator);
already_AddRefed<Promise> GetNextPromise(JSContext* aCx, Iterator* aIterator,
ErrorResult& aRv);
@ -59,6 +62,7 @@ class TestInterfaceAsyncIterableSingle final : public nsISupports,
void ResolvePromise(IteratorData* aData);
nsCOMPtr<nsPIDOMWindowInner> mParent;
bool mFailToInit;
};
} // namespace dom

View file

@ -18,7 +18,20 @@ async function test_data_single() {
info(`AsyncIterableSingle: Testing simple iterable creation and functionality`);
// eslint-disable-next-line no-undef
let itr = new TestInterfaceAsyncIterableSingle();
let itr = new TestInterfaceAsyncIterableSingle({ failToInit: true });
let initFailed = false;
try {
itr.values();
} catch (e) {
initFailed = true;
}
ok(initFailed,
"AsyncIterableSingle: A failure in asynchronous iterator initialization " +
"steps should propagate to the caller of the asynchronous iterator's " +
"constructor.");
// eslint-disable-next-line no-undef
itr = new TestInterfaceAsyncIterableSingle();
is(itr.values, itr[Symbol.asyncIterator],
`AsyncIterableSingle: Should be using @@asyncIterator for 'values'`);

View file

@ -1088,6 +1088,9 @@ bool DrawTargetWebgl::SharedContext::SupportsPattern(const Pattern& aPattern) {
return true;
case PatternType::SURFACE: {
auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
if (surfacePattern.mExtendMode != ExtendMode::CLAMP) {
return false;
}
if (surfacePattern.mSurface) {
IntSize size = surfacePattern.mSurface->GetSize();
// The maximum size a surface can be before triggering a fallback to

View file

@ -59,7 +59,7 @@ bool Clipboard::IsTestingPrefEnabledOrHasReadPermission(
}
// @return true iff the event was dispatched successfully.
static bool MaybeCreateAndDispatchMozClipboardReadTextPasteEvent(
static bool MaybeCreateAndDispatchMozClipboardReadPasteEvent(
nsPIDOMWindowInner& aOwner) {
RefPtr<Document> document = aOwner.GetDoc();
@ -70,187 +70,28 @@ static bool MaybeCreateAndDispatchMozClipboardReadTextPasteEvent(
return false;
}
// Conceptionally, `ClipboardReadTextPasteChild` is the target of the event.
// Conceptionally, `ClipboardReadPasteChild` is the target of the event.
// It ensures to receive the event by declaring the event in
// <BrowserGlue.jsm>.
return !NS_WARN_IF(NS_FAILED(nsContentUtils::DispatchChromeEvent(
document, ToSupports(document), u"MozClipboardReadTextPaste"_ns,
document, ToSupports(document), u"MozClipboardReadPaste"_ns,
CanBubble::eNo, Cancelable::eNo)));
}
already_AddRefed<nsIRunnable> Clipboard::ReadTextRequest::Answer() {
return NS_NewRunnableFunction(
"Clipboard::ReadText", [p = std::move(mPromise)]() {
nsresult rv;
nsCOMPtr<nsIClipboard> clipboardService(
do_GetService("@mozilla.org/widget/clipboard;1", &rv));
if (NS_FAILED(rv)) {
p->MaybeReject(NS_ERROR_UNEXPECTED);
return;
}
void Clipboard::ReadRequest::Answer() {
RefPtr<Promise> p(std::move(mPromise));
RefPtr<nsPIDOMWindowInner> owner(std::move(mOwner));
nsCOMPtr<nsITransferable> trans =
do_CreateInstance("@mozilla.org/widget/transferable;1");
if (NS_WARN_IF(!trans)) {
p->MaybeReject(NS_ERROR_UNEXPECTED);
return;
}
trans->Init(nullptr);
trans->AddDataFlavor(kUnicodeMime);
clipboardService->AsyncGetData(trans, nsIClipboard::kGlobalClipboard)
->Then(
GetMainThreadSerialEventTarget(), __func__,
/* resolve */
[trans, p]() {
nsCOMPtr<nsISupports> data;
nsresult rv = trans->GetTransferData(kUnicodeMime,
getter_AddRefs(data));
nsAutoString str;
if (!NS_WARN_IF(NS_FAILED(rv))) {
nsCOMPtr<nsISupportsString> supportsstr =
do_QueryInterface(data);
MOZ_ASSERT(supportsstr);
if (supportsstr) {
supportsstr->GetData(str);
}
}
p->MaybeResolve(str);
},
/* reject */
[p](nsresult rv) { p->MaybeReject(rv); });
});
}
static bool IsReadTextExposedToContent() {
return StaticPrefs::dom_events_asyncClipboard_readText_DoNotUseDirectly();
}
already_AddRefed<nsIRunnable>
Clipboard::CheckReadTextPermissionAndHandleRequest(
Promise& aPromise, nsIPrincipal& aSubjectPrincipal) {
if (IsTestingPrefEnabledOrHasReadPermission(aSubjectPrincipal)) {
MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
("%s: testing pref enabled or has read permission", __FUNCTION__));
return ReadTextRequest{aPromise}.Answer();
nsresult rv;
nsCOMPtr<nsIClipboard> clipboardService(
do_GetService("@mozilla.org/widget/clipboard;1", &rv));
if (NS_FAILED(rv)) {
p->MaybeReject(NS_ERROR_UNEXPECTED);
return;
}
if (aSubjectPrincipal.GetIsAddonOrExpandedAddonPrincipal()) {
// TODO: enable showing the "Paste" button in this case; see bug 1773681.
MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
("%s: Addon without read permssion.", __FUNCTION__));
aPromise.MaybeRejectWithUndefined();
return nullptr;
}
return HandleReadTextRequestWhichRequiresPasteButton(aPromise);
}
already_AddRefed<nsIRunnable>
Clipboard::HandleReadTextRequestWhichRequiresPasteButton(Promise& aPromise) {
RefPtr<nsIRunnable> runnable;
nsPIDOMWindowInner* owner = GetOwner();
WindowContext* windowContext = owner ? owner->GetWindowContext() : nullptr;
if (!windowContext) {
MOZ_ASSERT_UNREACHABLE("There should be a WindowContext.");
aPromise.MaybeRejectWithUndefined();
return runnable.forget();
}
// If no transient user activation, reject the promise and return.
if (!windowContext->HasValidTransientUserGestureActivation()) {
aPromise.MaybeRejectWithNotAllowedError(
"`navigator.clipboard.readText()` was blocked due to lack of "
"user activation.");
return runnable.forget();
}
// TODO: when a user activation stems from a contextmenu event
// (https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event),
// forbid pasting (bug 1767941).
switch (mTransientUserPasteState.RefreshAndGet(*windowContext)) {
case TransientUserPasteState::Value::Initial: {
MOZ_ASSERT(mReadTextRequests.IsEmpty());
if (MaybeCreateAndDispatchMozClipboardReadTextPasteEvent(*owner)) {
mTransientUserPasteState.OnStartWaitingForUserReactionToPasteMenuPopup(
windowContext->GetUserGestureStart());
mReadTextRequests.AppendElement(MakeUnique<ReadTextRequest>(aPromise));
} else {
// This shouldn't happen but let's handle this case.
aPromise.MaybeRejectWithUndefined();
}
break;
}
case TransientUserPasteState::Value::
WaitingForUserReactionToPasteMenuPopup: {
MOZ_ASSERT(!mReadTextRequests.IsEmpty());
mReadTextRequests.AppendElement(MakeUnique<ReadTextRequest>(aPromise));
break;
}
case TransientUserPasteState::Value::TransientlyForbiddenByUser: {
aPromise.MaybeRejectWithNotAllowedError(
"`navigator.clipboard.readText()` was blocked due to the user "
"dismissing the 'Paste' button.");
break;
}
case TransientUserPasteState::Value::TransientlyAllowedByUser: {
runnable = ReadTextRequest{aPromise}.Answer();
break;
}
}
return runnable.forget();
}
already_AddRefed<Promise> Clipboard::ReadHelper(
nsIPrincipal& aSubjectPrincipal, ClipboardReadType aClipboardReadType,
ErrorResult& aRv) {
// Create a new promise
RefPtr<Promise> p = dom::Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
switch (aClipboardReadType) {
case eReadText: {
RefPtr<nsIRunnable> runnable =
CheckReadTextPermissionAndHandleRequest(*p, aSubjectPrincipal);
if (runnable) {
GetParentObject()->Dispatch(TaskCategory::Other, runnable.forget());
}
break;
}
case eRead: {
// We want to disable security check for automated tests that have the
// pref
// dom.events.testing.asyncClipboard set to true
const bool isTestingPrefEnabledOrHasReadPermission =
IsTestingPrefEnabledOrHasReadPermission(aSubjectPrincipal);
if (!isTestingPrefEnabledOrHasReadPermission) {
MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
("Clipboard, ReadHelper, "
"Don't have permissions for reading\n"));
p->MaybeRejectWithUndefined();
return p.forget();
}
nsresult rv;
nsCOMPtr<nsIClipboard> clipboardService(
do_GetService("@mozilla.org/widget/clipboard;1", &rv));
if (NS_FAILED(rv)) {
p->MaybeReject(rv);
return p.forget();
}
switch (mType) {
case ReadRequestType::eRead: {
clipboardService
->AsyncHasDataMatchingFlavors(
// Mandatory data types defined in
@ -262,9 +103,8 @@ already_AddRefed<Promise> Clipboard::ReadHelper(
->Then(
GetMainThreadSerialEventTarget(), __func__,
/* resolve */
[self = RefPtr{this}, p](nsTArray<nsCString> formats) {
nsCOMPtr<nsIGlobalObject> global =
do_QueryInterface(self->GetOwner());
[owner, p](nsTArray<nsCString> formats) {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(owner);
if (NS_WARN_IF(!global)) {
p->MaybeReject(NS_ERROR_UNEXPECTED);
return;
@ -303,8 +143,146 @@ already_AddRefed<Promise> Clipboard::ReadHelper(
[p](nsresult rv) { p->MaybeReject(rv); });
break;
}
case ReadRequestType::eReadText: {
nsCOMPtr<nsITransferable> trans =
do_CreateInstance("@mozilla.org/widget/transferable;1");
if (NS_WARN_IF(!trans)) {
p->MaybeReject(NS_ERROR_UNEXPECTED);
return;
}
trans->Init(nullptr);
trans->AddDataFlavor(kUnicodeMime);
clipboardService->AsyncGetData(trans, nsIClipboard::kGlobalClipboard)
->Then(
GetMainThreadSerialEventTarget(), __func__,
/* resolve */
[trans, p]() {
nsCOMPtr<nsISupports> data;
nsresult rv =
trans->GetTransferData(kUnicodeMime, getter_AddRefs(data));
nsAutoString str;
if (!NS_WARN_IF(NS_FAILED(rv))) {
nsCOMPtr<nsISupportsString> supportsstr =
do_QueryInterface(data);
MOZ_ASSERT(supportsstr);
if (supportsstr) {
supportsstr->GetData(str);
}
}
p->MaybeResolve(str);
},
/* reject */
[p](nsresult rv) { p->MaybeReject(rv); });
break;
}
default: {
MOZ_ASSERT_UNREACHABLE("Unknown read type");
break;
}
}
}
static bool IsReadTextExposedToContent() {
return StaticPrefs::dom_events_asyncClipboard_readText_DoNotUseDirectly();
}
void Clipboard::CheckReadPermissionAndHandleRequest(
Promise& aPromise, nsIPrincipal& aSubjectPrincipal, ReadRequestType aType) {
if (IsTestingPrefEnabledOrHasReadPermission(aSubjectPrincipal)) {
MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
("%s: testing pref enabled or has read permission", __FUNCTION__));
nsPIDOMWindowInner* owner = GetOwner();
if (!owner) {
aPromise.MaybeRejectWithUndefined();
return;
}
ReadRequest{aPromise, aType, *owner}.Answer();
return;
}
if (aSubjectPrincipal.GetIsAddonOrExpandedAddonPrincipal()) {
// TODO: enable showing the "Paste" button in this case; see bug 1773681.
MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
("%s: Addon without read permssion.", __FUNCTION__));
aPromise.MaybeRejectWithUndefined();
return;
}
HandleReadRequestWhichRequiresPasteButton(aPromise, aType);
}
void Clipboard::HandleReadRequestWhichRequiresPasteButton(
Promise& aPromise, ReadRequestType aType) {
nsPIDOMWindowInner* owner = GetOwner();
WindowContext* windowContext = owner ? owner->GetWindowContext() : nullptr;
if (!windowContext) {
MOZ_ASSERT_UNREACHABLE("There should be a WindowContext.");
aPromise.MaybeRejectWithUndefined();
return;
}
// If no transient user activation, reject the promise and return.
if (!windowContext->HasValidTransientUserGestureActivation()) {
aPromise.MaybeRejectWithNotAllowedError(
"Clipboard read request was blocked due to lack of "
"user activation.");
return;
}
// TODO: when a user activation stems from a contextmenu event
// (https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event),
// forbid pasting (bug 1767941).
switch (mTransientUserPasteState.RefreshAndGet(*windowContext)) {
case TransientUserPasteState::Value::Initial: {
MOZ_ASSERT(mReadRequests.IsEmpty());
if (MaybeCreateAndDispatchMozClipboardReadPasteEvent(*owner)) {
mTransientUserPasteState.OnStartWaitingForUserReactionToPasteMenuPopup(
windowContext->GetUserGestureStart());
mReadRequests.AppendElement(
MakeUnique<ReadRequest>(aPromise, aType, *owner));
} else {
// This shouldn't happen but let's handle this case.
aPromise.MaybeRejectWithUndefined();
}
break;
}
case TransientUserPasteState::Value::
WaitingForUserReactionToPasteMenuPopup: {
MOZ_ASSERT(!mReadRequests.IsEmpty());
mReadRequests.AppendElement(
MakeUnique<ReadRequest>(aPromise, aType, *owner));
break;
}
case TransientUserPasteState::Value::TransientlyForbiddenByUser: {
aPromise.MaybeRejectWithNotAllowedError(
"`Clipboard read request was blocked due to the user "
"dismissing the 'Paste' button.");
break;
}
case TransientUserPasteState::Value::TransientlyAllowedByUser: {
ReadRequest{aPromise, aType, *owner}.Answer();
break;
}
}
}
already_AddRefed<Promise> Clipboard::ReadHelper(nsIPrincipal& aSubjectPrincipal,
ReadRequestType aType,
ErrorResult& aRv) {
// Create a new promise
RefPtr<Promise> p = dom::Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
CheckReadPermissionAndHandleRequest(*p, aSubjectPrincipal, aType);
return p.forget();
}
@ -360,12 +338,12 @@ void Clipboard::TransientUserPasteState::OnUserReactedToPasteMenuPopup(
already_AddRefed<Promise> Clipboard::Read(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv) {
return ReadHelper(aSubjectPrincipal, eRead, aRv);
return ReadHelper(aSubjectPrincipal, ReadRequestType::eRead, aRv);
}
already_AddRefed<Promise> Clipboard::ReadText(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv) {
return ReadHelper(aSubjectPrincipal, eReadText, aRv);
return ReadHelper(aSubjectPrincipal, ReadRequestType::eReadText, aRv);
}
namespace {
@ -714,7 +692,7 @@ already_AddRefed<Promise> Clipboard::WriteText(const nsAString& aData,
return Write(std::move(sequence), aSubjectPrincipal, aRv);
}
void Clipboard::ReadTextRequest::MaybeRejectWithNotAllowedError(
void Clipboard::ReadRequest::MaybeRejectWithNotAllowedError(
const nsACString& aMessage) {
mPromise->MaybeRejectWithNotAllowedError(aMessage);
}
@ -724,18 +702,18 @@ void Clipboard::OnUserReactedToPasteMenuPopup(const bool aAllowed) {
mTransientUserPasteState.OnUserReactedToPasteMenuPopup(aAllowed);
MOZ_ASSERT(!mReadTextRequests.IsEmpty());
MOZ_ASSERT(!mReadRequests.IsEmpty());
for (UniquePtr<ReadTextRequest>& request : mReadTextRequests) {
for (UniquePtr<ReadRequest>& request : mReadRequests) {
if (aAllowed) {
GetParentObject()->Dispatch(TaskCategory::Other, request->Answer());
request->Answer();
} else {
request->MaybeRejectWithNotAllowedError(
"The user dismissed the 'Paste' button."_ns);
}
}
mReadTextRequests.Clear();
mReadRequests.Clear();
}
JSObject* Clipboard::WrapObject(JSContext* aCx,

View file

@ -17,11 +17,6 @@
namespace mozilla::dom {
enum ClipboardReadType {
eRead,
eReadText,
};
class Promise;
class ClipboardItem;
@ -62,6 +57,11 @@ class Clipboard : public DOMEventTargetHelper {
JS::Handle<JSObject*> aGivenProto) override;
private:
enum class ReadRequestType {
eRead,
eReadText,
};
// Checks if dom.events.testing.asyncClipboard pref is enabled.
// The aforementioned pref allows automated tests to bypass the security
// checks when writing to
@ -71,36 +71,38 @@ class Clipboard : public DOMEventTargetHelper {
static bool IsTestingPrefEnabledOrHasReadPermission(
nsIPrincipal& aSubjectPrincipal);
// @return the remaining work to fill aPromise.
already_AddRefed<nsIRunnable> CheckReadTextPermissionAndHandleRequest(
Promise& aPromise, nsIPrincipal& aSubjectPrincipal);
void CheckReadPermissionAndHandleRequest(Promise& aPromise,
nsIPrincipal& aSubjectPrincipal,
ReadRequestType aType);
// @return the remaining work to fill aPromise.
already_AddRefed<nsIRunnable> HandleReadTextRequestWhichRequiresPasteButton(
Promise& aPromise);
void HandleReadRequestWhichRequiresPasteButton(Promise& aPromise,
ReadRequestType aType);
already_AddRefed<Promise> ReadHelper(nsIPrincipal& aSubjectPrincipal,
ClipboardReadType aClipboardReadType,
ErrorResult& aRv);
ReadRequestType aType, ErrorResult& aRv);
~Clipboard();
class ReadTextRequest final {
class ReadRequest final {
public:
explicit ReadTextRequest(Promise& aPromise) : mPromise{&aPromise} {}
ReadRequest(Promise& aPromise, ReadRequestType aType,
nsPIDOMWindowInner& aOwner)
: mType(aType), mPromise(&aPromise), mOwner(&aOwner) {}
// Clears the request too.
already_AddRefed<nsIRunnable> Answer();
void Answer();
void MaybeRejectWithNotAllowedError(const nsACString& aMessage);
private:
ReadRequestType mType;
// Not cycle-collected, because it's nulled when the request is answered or
// destructed.
RefPtr<Promise> mPromise;
RefPtr<nsPIDOMWindowInner> mOwner;
};
AutoTArray<UniquePtr<ReadTextRequest>, 1> mReadTextRequests;
AutoTArray<UniquePtr<ReadRequest>, 1> mReadRequests;
class TransientUserPasteState final {
public:

View file

@ -7,6 +7,9 @@ support-files =
[browser_navigator_clipboard_readText.js]
support-files =
simple_navigator_clipboard_readText.html
[browser_navigator_clipboard_read.js]
support-files =
simple_navigator_clipboard_read.html
[browser_navigator_clipboard_touch.js]
support-files =
simple_navigator_clipboard_readText.html

View file

@ -0,0 +1,200 @@
/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* import-globals-from head.js */
const kBaseUrlForContent = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content",
"https://example.com"
);
const kContentFileName = "simple_navigator_clipboard_read.html";
const kContentFileUrl = kBaseUrlForContent + kContentFileName;
const kApzTestNativeEventUtilsUrl =
"chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js";
Services.scriptloader.loadSubScript(kApzTestNativeEventUtilsUrl, this);
// @param aBrowser browser object of the content tab.
// @param aMultipleReadTextCalls if false, exactly one call is made, two
// otherwise.
function promiseClickContentToTriggerClipboardRead(
aBrowser,
aMultipleReadTextCalls
) {
return promiseClickContentElement(
aBrowser,
aMultipleReadTextCalls ? "invokeReadTwiceId" : "invokeReadOnceId"
);
}
// @param aBrowser browser object of the content tab.
function promiseMutatedReadResultFromContentElement(aBrowser) {
return promiseMutatedTextContentFromContentElement(aBrowser, "readResultId");
}
add_task(async function init() {
await SpecialPowers.pushPrefEnv({
set: [
["dom.events.asyncClipboard.clipboardItem", true],
["test.events.async.enabled", true],
],
});
});
add_task(async function test_paste_button_position() {
// Ensure there's text on the clipboard.
await promiseWritingRandomTextToClipboard();
await BrowserTestUtils.withNewTab(kContentFileUrl, async function(browser) {
const pasteButtonIsShown = promisePasteButtonIsShown();
const coordsOfClickInContentRelativeToScreenInDevicePixels = await promiseClickContentToTriggerClipboardRead(
browser,
false
);
info(
"coordsOfClickInContentRelativeToScreenInDevicePixels: " +
coordsOfClickInContentRelativeToScreenInDevicePixels.x +
", " +
coordsOfClickInContentRelativeToScreenInDevicePixels.y
);
const pasteButtonCoordsRelativeToScreenInDevicePixels = await pasteButtonIsShown;
info(
"pasteButtonCoordsRelativeToScreenInDevicePixels: " +
pasteButtonCoordsRelativeToScreenInDevicePixels.x +
", " +
pasteButtonCoordsRelativeToScreenInDevicePixels.y
);
const mouseCoordsRelativeToScreenInDevicePixels = getMouseCoordsRelativeToScreenInDevicePixels();
info(
"mouseCoordsRelativeToScreenInDevicePixels: " +
mouseCoordsRelativeToScreenInDevicePixels.x +
", " +
mouseCoordsRelativeToScreenInDevicePixels.y
);
// Asserting not overlapping is important; otherwise, when the
// "Paste" button is shown via a `mousedown` event, the following
// `mouseup` event could accept the "Paste" button unnoticed by the
// user.
ok(
isCloselyLeftOnTopOf(
mouseCoordsRelativeToScreenInDevicePixels,
pasteButtonCoordsRelativeToScreenInDevicePixels
),
"'Paste' button is closely left on top of the mouse pointer."
);
ok(
isCloselyLeftOnTopOf(
coordsOfClickInContentRelativeToScreenInDevicePixels,
pasteButtonCoordsRelativeToScreenInDevicePixels
),
"Coords of click in content are closely left on top of the 'Paste' button."
);
// To avoid disturbing subsequent tests.
const pasteButtonIsHidden = promisePasteButtonIsHidden();
await promiseClickPasteButton();
await pasteButtonIsHidden;
});
});
add_task(async function test_accepting_paste_button() {
// Randomized text to avoid overlappings with other tests.
const clipboardText = await promiseWritingRandomTextToClipboard();
await BrowserTestUtils.withNewTab(kContentFileUrl, async function(browser) {
const pasteButtonIsShown = promisePasteButtonIsShown();
await promiseClickContentToTriggerClipboardRead(browser, false);
await pasteButtonIsShown;
const pasteButtonIsHidden = promisePasteButtonIsHidden();
const mutatedReadResultFromContentElement = promiseMutatedReadResultFromContentElement(
browser
);
await promiseClickPasteButton();
await pasteButtonIsHidden;
await mutatedReadResultFromContentElement.then(value => {
is(
value,
"Resolved: " + clipboardText,
"Text returned from `navigator.clipboard.read()` is as expected."
);
});
});
});
add_task(async function test_dismissing_paste_button() {
await BrowserTestUtils.withNewTab(kContentFileUrl, async function(browser) {
const pasteButtonIsShown = promisePasteButtonIsShown();
await promiseClickContentToTriggerClipboardRead(browser, false);
await pasteButtonIsShown;
const pasteButtonIsHidden = promisePasteButtonIsHidden();
const mutatedReadResultFromContentElement = promiseMutatedReadResultFromContentElement(
browser
);
await promiseDismissPasteButton();
await pasteButtonIsHidden;
await mutatedReadResultFromContentElement.then(value => {
is(
value,
"Rejected: The user dismissed the 'Paste' button.",
"`navigator.clipboard.read()` rejected after dismissing the 'Paste' button"
);
});
});
});
add_task(
async function test_multiple_read_invocations_for_same_user_activation() {
// Randomized text to avoid overlappings with other tests.
const clipboardText = await promiseWritingRandomTextToClipboard();
await BrowserTestUtils.withNewTab(kContentFileUrl, async function(browser) {
const pasteButtonIsShown = promisePasteButtonIsShown();
await promiseClickContentToTriggerClipboardRead(browser, true);
await pasteButtonIsShown;
const mutatedReadResultFromContentElement = promiseMutatedReadResultFromContentElement(
browser
);
const pasteButtonIsHidden = promisePasteButtonIsHidden();
await promiseClickPasteButton();
await mutatedReadResultFromContentElement.then(value => {
is(
value,
"Resolved 1: " + clipboardText + "; Resolved 2: " + clipboardText,
"Two calls of `navigator.clipboard.read()` both resolved with the expected text."
);
});
// To avoid disturbing subsequent tests.
await pasteButtonIsHidden;
});
}
);
add_task(async function test_new_user_activation_shows_paste_button_again() {
await BrowserTestUtils.withNewTab(kContentFileUrl, async function(browser) {
// Ensure there's text on the clipboard.
await promiseWritingRandomTextToClipboard();
for (let i = 0; i < 2; ++i) {
const pasteButtonIsShown = promisePasteButtonIsShown();
// A click initiates a new user activation.
await promiseClickContentToTriggerClipboardRead(browser, false);
await pasteButtonIsShown;
const pasteButtonIsHidden = promisePasteButtonIsHidden();
await promiseClickPasteButton();
await pasteButtonIsHidden;
}
});
});

View file

@ -22,8 +22,6 @@ const kApzTestNativeEventUtilsUrl =
Services.scriptloader.loadSubScript(kApzTestNativeEventUtilsUrl, this);
const chromeDoc = window.document;
// @param aBrowser browser object of the content tab.
// @param aMultipleReadTextCalls if false, exactly one call is made, two
// otherwise.
@ -31,75 +29,18 @@ function promiseClickContentToTriggerClipboardReadText(
aBrowser,
aMultipleReadTextCalls
) {
const contentButtonId = aMultipleReadTextCalls
? "invokeReadTextTwiceId"
: "invokeReadTextOnceId";
// `aBrowser.contentDocument` is null, therefore use `SpecialPowers.spawn`.
return SpecialPowers.spawn(
return promiseClickContentElement(
aBrowser,
[contentButtonId],
async _contentButtonId => {
const contentButton = content.document.getElementById(_contentButtonId);
let promise = new Promise(resolve => {
contentButton.addEventListener(
"click",
function(e) {
resolve({ x: e.screenX, y: e.screenY });
},
{ once: true }
);
});
EventUtils.synthesizeMouseAtCenter(contentButton, {}, content.window);
return promise;
}
aMultipleReadTextCalls ? "invokeReadTextTwiceId" : "invokeReadTextOnceId"
);
}
// @param aBrowser browser object of the content tab.
function promiseMutatedReadTextResultFromContentElement(aBrowser) {
return SpecialPowers.spawn(aBrowser, [], async () => {
const readTextResultElement = content.document.getElementById(
"readTextResultId"
);
const promiseReadTextResult = new Promise(resolve => {
const mutationObserver = new content.MutationObserver(
(aMutationRecord, aMutationObserver) => {
info("Observed mutation.");
aMutationObserver.disconnect();
resolve(readTextResultElement.textContent);
}
);
mutationObserver.observe(readTextResultElement, {
childList: true,
});
});
return await promiseReadTextResult;
});
}
function promiseWritingRandomTextToClipboard() {
const clipboardText = "X" + Math.random();
return navigator.clipboard.writeText(clipboardText).then(() => {
return clipboardText;
});
}
function promiseDismissPasteButton() {
// nsXULPopupManager rollup is handled in widget code, so we have to
// synthesize native mouse events.
return EventUtils.promiseNativeMouseEvent({
type: "click",
target: chromeDoc.body,
// Relies on the assumption that the center of chrome document doesn't
// overlay with the paste button showed for clipboard readText request.
atCenter: true,
});
return promiseMutatedTextContentFromContentElement(
aBrowser,
"readTextResultId"
);
}
add_task(async function init() {

View file

@ -5,8 +5,8 @@
"use strict";
const kPasteMenuPopupId = "clipboardReadTextPasteMenuPopup";
const kPasteMenuItemId = "clipboardReadTextPasteMenuItem";
const kPasteMenuPopupId = "clipboardReadPasteMenuPopup";
const kPasteMenuItemId = "clipboardReadPasteMenuItem";
function promiseWritingRandomTextToClipboard() {
const clipboardText = "X" + Math.random();
@ -89,3 +89,71 @@ function isCloselyLeftOnTopOf(aCoordsP1, aCoordsP2, aDelta = 10) {
Math.abs(aCoordsP2.y - aCoordsP1.y) < aDelta
);
}
function promiseDismissPasteButton() {
// nsXULPopupManager rollup is handled in widget code, so we have to
// synthesize native mouse events.
return EventUtils.promiseNativeMouseEvent({
type: "click",
target: document.body,
// Relies on the assumption that the center of chrome document doesn't
// overlay with the paste button showed for clipboard readText request.
atCenter: true,
});
}
// @param aBrowser browser object of the content tab.
// @param aContentElementId the ID of the element to be clicked.
function promiseClickContentElement(aBrowser, aContentElementId) {
return SpecialPowers.spawn(
aBrowser,
[aContentElementId],
async _contentElementId => {
const contentElement = content.document.getElementById(_contentElementId);
let promise = new Promise(resolve => {
contentElement.addEventListener(
"click",
function(e) {
resolve({ x: e.screenX, y: e.screenY });
},
{ once: true }
);
});
EventUtils.synthesizeMouseAtCenter(contentElement, {}, content.window);
return promise;
}
);
}
// @param aBrowser browser object of the content tab.
// @param aContentElementId the ID of the element to observe.
function promiseMutatedTextContentFromContentElement(
aBrowser,
aContentElementId
) {
return SpecialPowers.spawn(
aBrowser,
[aContentElementId],
async _contentElementId => {
const contentElement = content.document.getElementById(_contentElementId);
const promiseTextContentResult = new Promise(resolve => {
const mutationObserver = new content.MutationObserver(
(aMutationRecord, aMutationObserver) => {
info("Observed mutation.");
aMutationObserver.disconnect();
resolve(contentElement.textContent);
}
);
mutationObserver.observe(contentElement, {
childList: true,
});
});
return await promiseTextContentResult;
}
);
}

View file

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- Required by the .js part of the test. In a more ideal world, the script
could be loaded in the .js part; however, currently, that causes other
problems, which would require other changes in test framework code. -->
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"></script>
<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
<script>
function onLoad() {
const readResult = document.getElementById("readResultId");
async function getClipboardText() {
let items = await navigator.clipboard.read();
if (items.length != 1) {
throw Error(`incorrect number of clipboard item (${items.length})`);
return;
}
let item = items[0];
for (let type of item.types) {
if (type == "text/plain") {
let blob = await item.getType(type);
return await blob.text();
}
}
throw Error("no text/plain type");
}
const b1 = document.getElementById("invokeReadOnceId");
b1.addEventListener("click", async () => {
getClipboardText().then(text => {
readResult.textContent = `Resolved: ${text}`;
}, (e) => { readResult.textContent = `Rejected: ${e.message}`});
});
const b2 = document.getElementById("invokeReadTwiceId");
b2.addEventListener("click", async () => {
const t1 = getClipboardText();
const t2 = getClipboardText();
const r1 = await t1.then(text => {
return `Resolved 1: ${text}`;
}, (e) => { return `Rejected 1: ${e.message}`;});
const r2 = await t2.then(text => {
return "Resolved 2: " + text;
}, (e) => { return `Rejected 2: ${e.message}`;});
readResult.textContent = r1 + "; " + r2;
});
}
</script>
</head>
<body onload="onLoad()">
<button id="invokeReadOnceId">1</button>
<button id="invokeReadTwiceId">2</button>
<div id="readResultId"/>
</body>
</html>

View file

@ -28,7 +28,6 @@ namespace {
using namespace mozilla;
using namespace mozilla::dom;
using std::vector;
class DarwinGamepadService;
DarwinGamepadService* gService = nullptr;
@ -80,6 +79,11 @@ const unsigned kBackUsage = 0x224;
// 50ms is arbitrarily chosen.
const uint32_t kDarwinGamepadPollInterval = 50;
struct GamepadInputReportContext {
DarwinGamepadService* service;
size_t gamepadSlot;
};
class Gamepad {
private:
IOHIDDeviceRef mDevice;
@ -120,7 +124,8 @@ class Gamepad {
GamepadHandle mHandle;
RefPtr<GamepadRemapper> mRemapper;
std::vector<uint8_t> mInputReport;
nsTArray<uint8_t> mInputReport;
UniquePtr<GamepadInputReportContext> mInputReportContext;
};
void Gamepad::init(IOHIDDeviceRef aDevice, bool aDefaultRemapper) {
@ -179,7 +184,7 @@ void Gamepad::init(IOHIDDeviceRef aDevice, bool aDefaultRemapper) {
class DarwinGamepadService {
private:
IOHIDManagerRef mManager;
vector<Gamepad> mGamepads;
nsTArray<Gamepad> mGamepads;
nsCOMPtr<nsIThread> mMonitorThread;
nsCOMPtr<nsIThread> mBackgroundThread;
@ -272,14 +277,14 @@ void DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device) {
}
size_t slot = size_t(-1);
for (size_t i = 0; i < mGamepads.size(); i++) {
for (size_t i = 0; i < mGamepads.Length(); i++) {
if (mGamepads[i] == device) return;
if (slot == size_t(-1) && mGamepads[i].empty()) slot = i;
}
if (slot == size_t(-1)) {
slot = mGamepads.size();
mGamepads.push_back(Gamepad());
slot = mGamepads.Length();
mGamepads.AppendElement(Gamepad());
}
// Gather some identifying information
@ -322,13 +327,15 @@ void DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device) {
}
mGamepads[slot].mHandle = handle;
mGamepads[slot].mInputReport.resize(remapper->GetMaxInputReportLength());
mGamepads[slot].mInputReport.SetLength(remapper->GetMaxInputReportLength());
mGamepads[slot].mInputReportContext = UniquePtr<GamepadInputReportContext>(
new GamepadInputReportContext{this, slot});
mGamepads[slot].mRemapper = remapper.forget();
IOHIDDeviceRegisterInputReportCallback(
device, mGamepads[slot].mInputReport.data(),
mGamepads[slot].mInputReport.size(), ReportChangedCallback,
&mGamepads[slot]);
device, mGamepads[slot].mInputReport.Elements(),
mGamepads[slot].mInputReport.Length(), ReportChangedCallback,
mGamepads[slot].mInputReportContext.get());
}
void DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device) {
@ -337,13 +344,16 @@ void DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device) {
if (!service) {
return;
}
for (size_t i = 0; i < mGamepads.size(); i++) {
if (mGamepads[i] == device) {
for (Gamepad& gamepad : mGamepads) {
if (gamepad == device) {
IOHIDDeviceRegisterInputReportCallback(
device, mGamepads[i].mInputReport.data(), 0, NULL, &mGamepads[i]);
device, gamepad.mInputReport.Elements(), 0, NULL,
gamepad.mInputReportContext.get());
service->RemoveGamepad(mGamepads[i].mHandle);
mGamepads[i].clear();
gamepad.mInputReportContext.reset();
service->RemoveGamepad(gamepad.mHandle);
gamepad.clear();
return;
}
}
@ -354,7 +364,10 @@ void DarwinGamepadService::ReportChangedCallback(
void* context, IOReturn result, void* sender, IOHIDReportType report_type,
uint32_t report_id, uint8_t* report, CFIndex report_length) {
if (context && report_type == kIOHIDReportTypeInput && report_length) {
reinterpret_cast<Gamepad*>(context)->ReportChanged(report, report_length);
auto reportContext = static_cast<GamepadInputReportContext*>(context);
DarwinGamepadService* service = reportContext->service;
service->mGamepads[reportContext->gamepadSlot].ReportChanged(report,
report_length);
}
}
@ -388,8 +401,7 @@ void DarwinGamepadService::InputValueChanged(IOHIDValueRef value) {
IOHIDElementRef element = IOHIDValueGetElement(value);
IOHIDDeviceRef device = IOHIDElementGetDevice(element);
for (unsigned i = 0; i < mGamepads.size(); i++) {
Gamepad& gamepad = mGamepads[i];
for (Gamepad& gamepad : mGamepads) {
if (gamepad == device) {
// Axis elements represent axes and d-pads.
if (Axis* axis = gamepad.lookupAxis(element)) {

View file

@ -224,8 +224,7 @@ bool RemoteDecoderManagerChild::Supports(
}
if (!supported) {
// We haven't received the correct information yet from either the GPU or
// the RDD process nor the Utility process; assume it is supported to
// prevent false negative.
// the RDD process nor the Utility process.
if (aLocation == RemoteDecodeIn::UtilityProcess) {
LaunchUtilityProcessIfNeeded();
}
@ -234,7 +233,20 @@ bool RemoteDecoderManagerChild::Supports(
// TODO: This can be removed once bug 1684991 is fixed.
LaunchRDDProcessIfNeeded();
}
return true;
// Assume the format is supported to prevent false negative, if the remote
// process supports that specific track type.
const bool isVideo = aParams.mConfig.IsVideo();
const bool isAudio = aParams.mConfig.IsAudio();
const auto trackSupport = GetTrackSupport(aLocation);
if (isVideo) {
return trackSupport.contains(TrackSupport::Video);
}
if (isAudio) {
return trackSupport.contains(TrackSupport::Audio);
}
MOZ_ASSERT_UNREACHABLE("Not audio and video?!");
return false;
}
// We can ignore the SupportDecoderParams argument for now as creation of the

View file

@ -1,5 +1,8 @@
<!-- 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/. -->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><meta charset="utf-8"><title></title></head><body></body></html>
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title></title></head>
<body></body>
</html>

View file

@ -96,11 +96,15 @@ interface TestInterfaceIterableDoubleUnion {
iterable<DOMString, (DOMString or long)>;
};
dictionary TestInterfaceAsyncIterableSingleOptions {
boolean failToInit = false;
};
[Pref="dom.expose_test_interfaces",
Exposed=Window]
interface TestInterfaceAsyncIterableSingle {
[Throws]
constructor();
constructor(optional TestInterfaceAsyncIterableSingleOptions options = {});
async iterable<long>;
};

View file

@ -729,6 +729,10 @@ bool MacOSFontEntry::SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTa
if (!cgFont) {
return mHasAATSmallCaps;
}
CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName,
FamilyName());
AutoCFRelease<CTFontRef> ctFont = CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr);
if (ctFont) {
AutoCFRelease<CFArrayRef> features = CTFontCopyFeatures(ctFont);
@ -1717,6 +1721,9 @@ gfxFontEntry* gfxMacPlatformFontList::LookupLocalFont(nsPresContext* aPresContex
nsAutoreleasePool localPool;
CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName,
aFontName);
NSString* faceName = GetNSStringForString(NS_ConvertUTF8toUTF16(aFontName));
// lookup face based on postscript or full name
@ -1782,6 +1789,9 @@ gfxFontEntry* gfxMacPlatformFontList::MakePlatformFont(const nsACString& aFontNa
return nullptr;
}
CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName,
aFontName);
AutoCFRelease<CGDataProviderRef> provider =
::CGDataProviderCreateWithData(nullptr, aFontData, aLength, &ReleaseData);
AutoCFRelease<CGFontRef> fontRef = ::CGFontCreateWithDataProvider(provider);
@ -2029,8 +2039,10 @@ void gfxMacPlatformFontList::GetFacesInitDataForFamily(const fontlist::Family* a
bool aLoadCmaps) const {
nsAutoreleasePool localPool;
NS_ConvertUTF8toUTF16 name(aFamily->Key().AsString(SharedFontList()));
NSString* family = GetNSStringForString(name);
auto name = aFamily->Key().AsString(SharedFontList());
NSString* family = GetNSStringForString(NS_ConvertUTF8toUTF16(name));
CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName, name);
// returns an array of [psname, style name, weight, traits] elements, goofy api
NSArray* fontfaces = [sFontManager availableMembersOfFontFamily:family];

View file

@ -2759,7 +2759,7 @@ void gfxPlatform::InitWebRenderConfig() {
"FEATURE_FAILURE_WR_NO_GFX_INFO"_ns);
useHwVideoZeroCopy = false;
} else {
if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
if (status != nsIGfxInfo::FEATURE_ALLOW_ALWAYS) {
FeatureState& feature =
gfxConfig::GetFeature(Feature::HW_DECODED_VIDEO_ZERO_COPY);
feature.DisableByDefault(FeatureStatus::Blocked,
@ -2796,7 +2796,7 @@ void gfxPlatform::InitWebRenderConfig() {
"FEATURE_FAILURE_WR_NO_GFX_INFO"_ns);
reuseDecoderDevice = false;
} else {
if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
if (status != nsIGfxInfo::FEATURE_ALLOW_ALWAYS) {
FeatureState& feature =
gfxConfig::GetFeature(Feature::REUSE_DECODER_DEVICE);
feature.DisableByDefault(FeatureStatus::Blocked,

13
js/src/configure vendored Executable file
View file

@ -0,0 +1,13 @@
#!/bin/sh
# 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/.
SRCDIR=$(dirname $0)
TOPSRCDIR="$SRCDIR"/../..
PYTHON3="${PYTHON3:-python3}"
export OLD_CONFIGURE="$SRCDIR"/old-configure
set -- "$@" --enable-project=js
exec "$PYTHON3" "$TOPSRCDIR/configure.py" "$@"

View file

@ -1,22 +0,0 @@
#!/bin/sh
# 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/.
#
# Because adding a configure file in the tree is going to conflict with
# existing configure files in people's (and automation) work trees, and
# because some automation jobs are still running autoconf and configure
# "manually", this file is actually an m4 file that is processed by
# autoconf, but doesn't call any autoconf macros. The `divert` line
# below ensures the script that follows is output by autoconf.
: "divert(0)dnl"
#!/bin/sh
SRCDIR=$(dirname $0)
TOPSRCDIR="$SRCDIR"/../..
PYTHON3="${PYTHON3:-python3}"
export OLD_CONFIGURE="$SRCDIR"/old-configure
set -- "$@" --enable-project=js
exec "$PYTHON3" "$TOPSRCDIR/configure.py" "$@"

View file

@ -8,7 +8,6 @@ import enum
import logging
import os
import shutil
import stat
import subprocess
import sys
from pathlib import Path
@ -375,17 +374,9 @@ def copy_cargo_toml():
def generate_configure():
"""Generate configure files to avoid build dependency on autoconf-2.13"""
src_configure_in_file = topsrc_dir / "js" / "src" / "configure.in"
src_old_configure_in_file = topsrc_dir / "js" / "src" / "old-configure.in"
dest_configure_file = target_dir / "js" / "src" / "configure"
dest_old_configure_file = target_dir / "js" / "src" / "old-configure"
shutil.copy2(
str(src_configure_in_file), str(dest_configure_file), follow_symlinks=False
)
st = dest_configure_file.stat()
dest_configure_file.chmod(st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
js_src_dir = topsrc_dir / "js" / "src"
env = os.environ.copy()

View file

@ -5642,8 +5642,9 @@ void PresShell::SynthesizeMouseMove(bool aFromScroll) {
static nsView* FindFloatingViewContaining(nsPresContext* aRootPresContext,
nsIWidget* aRootWidget,
const LayoutDeviceIntPoint& aPt) {
nsIFrame* popupFrame =
nsLayoutUtils::GetPopupFrameForPoint(aRootPresContext, aRootWidget, aPt);
nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForPoint(
aRootPresContext, aRootWidget, aPt,
nsLayoutUtils::GetPopupFrameForPointFlags::OnlyReturnFramesWithWidgets);
return popupFrame ? popupFrame->GetView() : nullptr;
}
@ -9409,6 +9410,8 @@ void PresShell::WillDoReflow() {
}
void PresShell::DidDoReflow(bool aInterruptible) {
mHiddenContentInForcedLayout.Clear();
HandlePostedReflowCallbacks(aInterruptible);
if (mIsDestroying) {
return;
@ -11821,3 +11824,41 @@ void PresShell::PingPerTickTelemetry(FlushType aFlushType) {
bool PresShell::GetZoomableByAPZ() const {
return mZoomConstraintsClient && mZoomConstraintsClient->GetAllowZoom();
}
void PresShell::EnsureReflowIfFrameHasHiddenContent(nsIFrame* aFrame) {
if (!aFrame || !aFrame->IsSubtreeDirty() ||
!StaticPrefs::layout_css_content_visibility_enabled()) {
return;
}
// Flushing notifications below might trigger more layouts, which might,
// in turn, trigger layout of other hidden content. We keep a local set
// of hidden content we are laying out to handle recursive calls.
nsTHashSet<nsIContent*> hiddenContentInForcedLayout;
MOZ_ASSERT(mHiddenContentInForcedLayout.IsEmpty());
nsIFrame* topmostFrameWithContentHidden = nullptr;
for (nsIFrame* cur = aFrame->GetInFlowParent(); cur;
cur = cur->GetInFlowParent()) {
if (cur->IsContentHidden()) {
topmostFrameWithContentHidden = cur;
mHiddenContentInForcedLayout.Insert(cur->GetContent());
}
}
if (mHiddenContentInForcedLayout.IsEmpty()) {
return;
}
// Queue and immediately flush a reflow for this node.
MOZ_ASSERT(topmostFrameWithContentHidden);
FrameNeedsReflow(topmostFrameWithContentHidden, IntrinsicDirty::Resize,
NS_FRAME_IS_DIRTY);
mDocument->FlushPendingNotifications(FlushType::Layout);
mHiddenContentInForcedLayout.Clear();
}
bool PresShell::IsForcingLayoutForHiddenContent(const nsIFrame* aFrame) const {
return mHiddenContentInForcedLayout.Contains(aFrame->GetContent());
}

View file

@ -1739,6 +1739,18 @@ class PresShell final : public nsStubDocumentObserver,
bool GetZoomableByAPZ() const;
/**
* If this frame has content hidden via `content-visibilty` that has a pending
* reflow, force the content to reflow immediately.
*/
void EnsureReflowIfFrameHasHiddenContent(nsIFrame*);
/**
* Whether or not this presshell is is forcing a reflow of hidden content in
* this frame via EnsureReflowIfFrameHasHiddenContent().
*/
bool IsForcingLayoutForHiddenContent(const nsIFrame*) const;
private:
~PresShell();
@ -2998,6 +3010,8 @@ class PresShell final : public nsStubDocumentObserver,
nsTHashSet<nsIScrollableFrame*> mPendingScrollAnchorAdjustment;
nsTHashSet<nsIScrollableFrame*> mPendingScrollResnap;
nsTHashSet<nsIContent*> mHiddenContentInForcedLayout;
nsCallbackEventRequest* mFirstCallbackEventRequest = nullptr;
nsCallbackEventRequest* mLastCallbackEventRequest = nullptr;

View file

@ -1774,7 +1774,8 @@ nsIFrame* nsLayoutUtils::GetPopupFrameForEventCoordinates(
nsIFrame* nsLayoutUtils::GetPopupFrameForPoint(
nsPresContext* aRootPresContext, nsIWidget* aWidget,
const mozilla::LayoutDeviceIntPoint& aPoint) {
const mozilla::LayoutDeviceIntPoint& aPoint,
GetPopupFrameForPointFlags aFlags /* = GetPopupFrameForPointFlags(0) */) {
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (!pm) {
return nullptr;
@ -1783,11 +1784,19 @@ nsIFrame* nsLayoutUtils::GetPopupFrameForPoint(
pm->GetVisiblePopups(popups);
// Search from top to bottom
for (nsIFrame* popup : popups) {
if (popup->PresContext()->GetRootPresContext() == aRootPresContext &&
popup->ScrollableOverflowRect().Contains(GetEventCoordinatesRelativeTo(
aWidget, aPoint, RelativeTo{popup}))) {
return popup;
if (popup->PresContext()->GetRootPresContext() != aRootPresContext) {
continue;
}
if (!popup->ScrollableOverflowRect().Contains(GetEventCoordinatesRelativeTo(
aWidget, aPoint, RelativeTo{popup}))) {
continue;
}
if (aFlags & GetPopupFrameForPointFlags::OnlyReturnFramesWithWidgets) {
if (!popup->HasView() || !popup->GetView()->HasWidget()) {
continue;
}
}
return popup;
}
return nullptr;
}

Some files were not shown because too many files have changed in this diff Show more