diff --git a/accessible/base/CacheConstants.h b/accessible/base/CacheConstants.h index 6bfd5ff9962..4c8b5513e36 100644 --- a/accessible/base/CacheConstants.h +++ b/accessible/base/CacheConstants.h @@ -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); }; diff --git a/accessible/generic/LocalAccessible.cpp b/accessible/generic/LocalAccessible.cpp index 0b3acb7ddeb..73582de5cff 100644 --- a/accessible/generic/LocalAccessible.cpp +++ b/accessible/generic/LocalAccessible.cpp @@ -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 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 LocalAccessible::BundleFieldsForCache( mContent->AsElement()->HasAttr( kNameSpaceID_None, nsGkAtoms::aria_valuetext)); } else { - cacheValueText = IsTextField(); + cacheValueText = IsTextField() || IsHTMLLink(); } if (cacheValueText) { @@ -3593,6 +3612,15 @@ already_AddRefed 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. diff --git a/accessible/ipc/RemoteAccessibleBase.cpp b/accessible/ipc/RemoteAccessibleBase.cpp index cc939a13583..1e9ecc5fe0d 100644 --- a/accessible/ipc/RemoteAccessibleBase.cpp +++ b/accessible/ipc/RemoteAccessibleBase.cpp @@ -276,6 +276,17 @@ void RemoteAccessibleBase::Value(nsString& aValue) const { if (option) { option->Name(aValue); } + return; + } + + if (IsTextLeaf() || IsImage()) { + if (const Accessible* actionAcc = ActionAncestor()) { + if (const_cast(actionAcc)->State() & states::LINKED) { + // Text and image descendants of links expose the link URL as the + // value. + return actionAcc->Value(aValue); + } + } } } } diff --git a/accessible/ipc/RemoteAccessibleBase.h b/accessible/ipc/RemoteAccessibleBase.h index 139e796b689..7f4cd31ec47 100644 --- a/accessible/ipc/RemoteAccessibleBase.h +++ b/accessible/ipc/RemoteAccessibleBase.h @@ -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 mChildren; DocAccessibleParent* mDoc; diff --git a/accessible/tests/browser/e10s/browser.ini b/accessible/tests/browser/e10s/browser.ini index 2c85d9cae79..df88eee067e 100644 --- a/accessible/tests/browser/e10s/browser.ini +++ b/accessible/tests/browser/e10s/browser.ini @@ -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] diff --git a/accessible/tests/browser/e10s/browser_caching_innerHTML.js b/accessible/tests/browser/e10s/browser_caching_innerHTML.js new file mode 100644 index 00000000000..be7469d55e0 --- /dev/null +++ b/accessible/tests/browser/e10s/browser_caching_innerHTML.js @@ -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( + ` +

test

+xy + `, + 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"), + "xy", + "math cached html is correct" + ); + + info("Mutating math"); + await invokeContentTask(browser, [], () => { + content.document.querySelectorAll("mi")[1].textContent = "z"; + }); + await untilCacheIs( + () => math.cache.getStringProperty("html"), + "xz", + "math cached html is correct after mutation" + ); + }, + { + topLevel: true, + iframe: isCacheEnabled, + remoteIframe: isCacheEnabled, + } +); diff --git a/accessible/tests/browser/e10s/browser_caching_value.js b/accessible/tests/browser/e10s/browser_caching_value.js index 135d9bdaa29..948bbdc7f26 100644 --- a/accessible/tests/browser/e10s/browser_caching_value.js +++ b/accessible/tests/browser/e10s/browser_caching_value.js @@ -212,3 +212,37 @@ addAccessibleTask( }, { iframe: true, remoteIframe: true } ); + +/** + * Test caching of link URL values. + */ +addAccessibleTask( + `Test`, + 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 } +); diff --git a/accessible/windows/msaa/MsaaAccessible.cpp b/accessible/windows/msaa/MsaaAccessible.cpp index a78a6c25b7f..9efeb820612 100644 --- a/accessible/windows/msaa/MsaaAccessible.cpp +++ b/accessible/windows/msaa/MsaaAccessible.cpp @@ -862,8 +862,8 @@ MsaaAccessible::QueryInterface(REFIID iid, void** ppv) { return E_NOINTERFACE; } *ppv = static_cast(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; } diff --git a/accessible/windows/sdn/sdnAccessible-inl.h b/accessible/windows/sdn/sdnAccessible-inl.h index 35e9069ad04..76f8796f42f 100644 --- a/accessible/windows/sdn/sdnAccessible-inl.h +++ b/accessible/windows/sdn/sdnAccessible-inl.h @@ -16,6 +16,7 @@ namespace mozilla { namespace a11y { inline DocAccessible* sdnAccessible::GetDocument() const { + MOZ_ASSERT(mNode); return GetExistingDocAccessible(mNode->OwnerDoc()); } diff --git a/accessible/windows/sdn/sdnAccessible.cpp b/accessible/windows/sdn/sdnAccessible.cpp index c56950262d1..69a12f404d0 100644 --- a/accessible/windows/sdn/sdnAccessible.cpp +++ b/accessible/windows/sdn/sdnAccessible.cpp @@ -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(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()) diff --git a/accessible/windows/sdn/sdnAccessible.h b/accessible/windows/sdn/sdnAccessible.h index d5673d07068..610a4ca5de0 100644 --- a/accessible/windows/sdn/sdnAccessible.h +++ b/accessible/windows/sdn/sdnAccessible.h @@ -25,18 +25,32 @@ class sdnAccessible final : public ISimpleDOMNode { if (!mNode) MOZ_CRASH(); } - explicit sdnAccessible(NotNull aMsaa) - : mNode(aMsaa->LocalAcc()->GetNode()), mMsaa(aMsaa) {} + explicit sdnAccessible(NotNull 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 mNode; RefPtr mMsaa; Maybe mUniqueId; diff --git a/browser/base/content/browser-allTabsMenu.js b/browser/base/content/browser-allTabsMenu.js index 851e0bfbac3..bc1c07b5f72 100644 --- a/browser/base/content/browser-allTabsMenu.js +++ b/browser/base/content/browser-allTabsMenu.js @@ -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( diff --git a/browser/base/content/browser-sidebar.js b/browser/base/content/browser-sidebar.js index 78afa499b4a..f09e37fc42f 100644 --- a/browser/base/content/browser-sidebar.js +++ b/browser/base/content/browser-sidebar.js @@ -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; } } diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml index 335526ee642..f3ca1616f88 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -92,6 +92,7 @@ diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index 8cba1881d61..30711f1c759 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -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", { diff --git a/browser/base/content/test/sidebar/browser_sidebar_app_locale_changed.js b/browser/base/content/test/sidebar/browser_sidebar_app_locale_changed.js index 7562a290b61..e8a99b27366 100644 --- a/browser/base/content/test/sidebar/browser_sidebar_app_locale_changed.js +++ b/browser/base/content/test/sidebar/browser_sidebar_app_locale_changed.js @@ -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": ` + + + + + + A Test Sidebar + + `, + "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(); +}); diff --git a/browser/base/content/test/tabs/browser.ini b/browser/base/content/test/tabs/browser.ini index aabd7bfd8f9..66831c88b8e 100644 --- a/browser/base/content/test/tabs/browser.ini +++ b/browser/base/content/test/tabs/browser.ini @@ -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] diff --git a/browser/base/content/test/tabs/browser_tab_manager_keyboard_access.js b/browser/base/content/test/tabs/browser_tab_manager_keyboard_access.js new file mode 100644 index 00000000000..039e49fa5bf --- /dev/null +++ b/browser/base/content/test/tabs/browser_tab_manager_keyboard_access.js @@ -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); +}); diff --git a/browser/components/downloads/test/browser/browser_downloads_panel_context_menu.js b/browser/components/downloads/test/browser/browser_downloads_panel_context_menu.js index 80c7ccf84a8..c5deb992a9d 100644 --- a/browser/components/downloads/test/browser/browser_downloads_panel_context_menu.js +++ b/browser/components/downloads/test/browser/browser_downloads_panel_context_menu.js @@ -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; diff --git a/browser/components/extensions/test/browser/browser_ext_urlbar.js b/browser/components/extensions/test/browser/browser_ext_urlbar.js index 3cd3644da86..253f686f22e 100644 --- a/browser/components/extensions/test/browser/browser_ext_urlbar.js +++ b/browser/components/extensions/test/browser/browser_ext_urlbar.js @@ -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"); diff --git a/browser/components/firefoxview/firefoxview.css b/browser/components/firefoxview/firefoxview.css index e12e80ae360..42142cfaf0e 100644 --- a/browser/components/firefoxview/firefoxview.css +++ b/browser/components/firefoxview/firefoxview.css @@ -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; } diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js index 8139b32a9b3..4881ab4ac1a 100644 --- a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js +++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js @@ -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: { diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.css b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css index f911e8625aa..799b4d7115b 100644 --- a/browser/components/newtab/aboutwelcome/content/aboutwelcome.css +++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css @@ -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"); diff --git a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss index 5c13f5a7473..e9534c50d9f 100644 --- a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss +++ b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss @@ -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'); diff --git a/browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx b/browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx index ffda30448ba..47c73692f7b 100644 --- a/browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx +++ b/browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx @@ -200,7 +200,7 @@ export function LanguageSwitcher(props) { + +
+ + diff --git a/dom/gamepad/cocoa/CocoaGamepad.cpp b/dom/gamepad/cocoa/CocoaGamepad.cpp index f9f0feb7e59..8a813b24542 100644 --- a/dom/gamepad/cocoa/CocoaGamepad.cpp +++ b/dom/gamepad/cocoa/CocoaGamepad.cpp @@ -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 mRemapper; - std::vector mInputReport; + nsTArray mInputReport; + UniquePtr 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 mGamepads; + nsTArray mGamepads; nsCOMPtr mMonitorThread; nsCOMPtr 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( + 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(context)->ReportChanged(report, report_length); + auto reportContext = static_cast(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)) { diff --git a/dom/media/ipc/RemoteDecoderManagerChild.cpp b/dom/media/ipc/RemoteDecoderManagerChild.cpp index 1ccabdc9deb..6709dff5c3c 100644 --- a/dom/media/ipc/RemoteDecoderManagerChild.cpp +++ b/dom/media/ipc/RemoteDecoderManagerChild.cpp @@ -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 diff --git a/dom/res/hiddenWindow.html b/dom/res/hiddenWindow.html index e1a8ac20d8f..2c8f29181d5 100644 --- a/dom/res/hiddenWindow.html +++ b/dom/res/hiddenWindow.html @@ -1,5 +1,8 @@ - - + + + + + diff --git a/dom/webidl/TestInterfaceJSMaplikeSetlikeIterable.webidl b/dom/webidl/TestInterfaceJSMaplikeSetlikeIterable.webidl index 7fc53e38fd6..f2570fc5db3 100644 --- a/dom/webidl/TestInterfaceJSMaplikeSetlikeIterable.webidl +++ b/dom/webidl/TestInterfaceJSMaplikeSetlikeIterable.webidl @@ -96,11 +96,15 @@ interface TestInterfaceIterableDoubleUnion { iterable; }; +dictionary TestInterfaceAsyncIterableSingleOptions { + boolean failToInit = false; +}; + [Pref="dom.expose_test_interfaces", Exposed=Window] interface TestInterfaceAsyncIterableSingle { [Throws] - constructor(); + constructor(optional TestInterfaceAsyncIterableSingleOptions options = {}); async iterable; }; diff --git a/gfx/thebes/gfxMacPlatformFontList.mm b/gfx/thebes/gfxMacPlatformFontList.mm index 0694c744525..98f187f9346 100644 --- a/gfx/thebes/gfxMacPlatformFontList.mm +++ b/gfx/thebes/gfxMacPlatformFontList.mm @@ -729,6 +729,10 @@ bool MacOSFontEntry::SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTa if (!cgFont) { return mHasAATSmallCaps; } + + CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName, + FamilyName()); + AutoCFRelease ctFont = CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr); if (ctFont) { AutoCFRelease 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 provider = ::CGDataProviderCreateWithData(nullptr, aFontData, aLength, &ReleaseData); AutoCFRelease 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]; diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index a8e7be3ff4c..a10aad915c6 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -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, diff --git a/js/src/configure b/js/src/configure new file mode 100755 index 00000000000..e2c672e40d1 --- /dev/null +++ b/js/src/configure @@ -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" "$@" diff --git a/js/src/configure.in b/js/src/configure.in deleted file mode 100644 index eab7cce639d..00000000000 --- a/js/src/configure.in +++ /dev/null @@ -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" "$@" diff --git a/js/src/make-source-package.py b/js/src/make-source-package.py index 4e5eeeeb2f1..954c3d7a1da 100755 --- a/js/src/make-source-package.py +++ b/js/src/make-source-package.py @@ -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() diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index 2b98de76706..c0cdadbc5e4 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -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 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()); +} diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h index 9b9d8edd533..08afafe9439 100644 --- a/layout/base/PresShell.h +++ b/layout/base/PresShell.h @@ -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 mPendingScrollAnchorAdjustment; nsTHashSet mPendingScrollResnap; + nsTHashSet mHiddenContentInForcedLayout; + nsCallbackEventRequest* mFirstCallbackEventRequest = nullptr; nsCallbackEventRequest* mLastCallbackEventRequest = nullptr; diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 06158915445..50e6c0ca042 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -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; } diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 1563653823c..0bf7939f6bb 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -754,9 +754,13 @@ class nsLayoutUtils { * @return Null, if there is no popup frame at the point, otherwise, * returns top-most popup frame at the point. */ + enum class GetPopupFrameForPointFlags : uint8_t { + OnlyReturnFramesWithWidgets = 0x1, + }; static nsIFrame* GetPopupFrameForPoint( nsPresContext* aRootPresContext, nsIWidget* aWidget, - const mozilla::LayoutDeviceIntPoint& aPoint); + const mozilla::LayoutDeviceIntPoint& aPoint, + GetPopupFrameForPointFlags aFlags = GetPopupFrameForPointFlags(0)); /** * Get container and offset if aEvent collapses Selection. @@ -3052,6 +3056,7 @@ class nsLayoutUtils { }; MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsLayoutUtils::PaintFrameFlags) +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsLayoutUtils::GetPopupFrameForPointFlags) template /* static */ bool nsLayoutUtils::PointIsCloserToRect( diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp index 1d80143f0ac..329b7cbb4fb 100644 --- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -1264,6 +1264,11 @@ static bool IsLineClampItem(const ReflowInput& aReflowInput) { void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, const ReflowInput& aReflowInput, nsReflowStatus& aStatus) { + if (GetInFlowParent() && GetInFlowParent()->IsContentHiddenForLayout()) { + FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay); + return; + } + MarkInReflow(); DO_GLOBAL_REFLOW_COUNT("nsBlockFrame"); DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus); @@ -2533,6 +2538,10 @@ static bool LinesAreEmpty(const nsLineList& aList) { } void nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) { + if (IsContentHiddenForLayout()) { + return; + } + bool keepGoing = true; bool repositionViews = false; // should we really need this? bool foundAnyClears = aState.mFloatBreakType != StyleClear::None; diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index 9c271e57726..503c65a7f7a 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -4552,6 +4552,10 @@ void nsFlexContainerFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aReflowOutput, const ReflowInput& aReflowInput, nsReflowStatus& aStatus) { + if (GetInFlowParent() && GetInFlowParent()->IsContentHiddenForLayout()) { + return; + } + MarkInReflow(); DO_GLOBAL_REFLOW_COUNT("nsFlexContainerFrame"); DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus); @@ -5296,6 +5300,10 @@ std::tuple nsFlexContainerFrame::ReflowChildren( const nscoord aSumOfPrevInFlowsChildrenBlockSize, const FlexboxAxisTracker& aAxisTracker, bool aHasLineClampEllipsis, FlexLayoutResult& aFlr) { + if (IsContentHiddenForLayout()) { + return {0, false}; + } + // Before giving each child a final reflow, calculate the origin of the // flex container's content box (with respect to its border-box), so that // we can compute our flex item's final positions. @@ -5514,11 +5522,11 @@ void nsFlexContainerFrame::PopulateReflowOutput( if (aFlexContainerAscent == nscoord_MIN) { // Still don't have our baseline set -- this happens if we have no - // children (or if our children are huge enough that they have nscoord_MIN - // as their baseline... in which case, we'll use the wrong baseline, but no - // big deal) + // children, if our children are huge enough that they have nscoord_MIN + // as their baseline, or our content is hidden in which case, we'll use the + // wrong baseline (but no big deal). NS_WARNING_ASSERTION( - aLines[0].IsEmpty(), + IsContentHidden() || aLines[0].IsEmpty(), "Have flex items but didn't get an ascent - that's odd (or there are " "just gigantic sizes involved)"); // Per spec, synthesize baseline from the flex container's content box diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index de0e91232f9..8004fdc071b 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -8426,6 +8426,12 @@ nscoord nsGridContainerFrame::ReflowChildren(GridReflowInput& aState, const nsSize& aContainerSize, ReflowOutput& aDesiredSize, nsReflowStatus& aStatus) { + WritingMode wm = aState.mReflowInput->GetWritingMode(); + nscoord bSize = aContentArea.BSize(wm); + if (IsContentHiddenForLayout()) { + return bSize; + } + MOZ_ASSERT(aState.mReflowInput); MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); @@ -8437,8 +8443,6 @@ nscoord nsGridContainerFrame::ReflowChildren(GridReflowInput& aState, ocStatus, MergeSortedFrameListsFor); } - WritingMode wm = aState.mReflowInput->GetWritingMode(); - nscoord bSize = aContentArea.BSize(wm); Maybe fragmentainer = GetNearestFragmentainer(aState); // MasonryLayout() can only handle fragmentation in the masonry-axis, // so we let ReflowInFragmentainer() deal with grid-axis fragmentation @@ -8532,6 +8536,10 @@ void nsGridContainerFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput, nsReflowStatus& aStatus) { + if (GetInFlowParent() && GetInFlowParent()->IsContentHiddenForLayout()) { + return; + } + MarkInReflow(); DO_GLOBAL_REFLOW_COUNT("nsGridContainerFrame"); DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); diff --git a/layout/generic/nsIFrame.cpp b/layout/generic/nsIFrame.cpp index 3393114ec66..246f1416b0b 100644 --- a/layout/generic/nsIFrame.cpp +++ b/layout/generic/nsIFrame.cpp @@ -6747,6 +6747,10 @@ void nsIFrame::DidReflow(nsPresContext* aPresContext, const ReflowInput* aReflowInput) { NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsIFrame::DidReflow")); + if (GetInFlowParent() && GetInFlowParent()->IsContentHiddenForLayout()) { + return; + } + SVGObserverUtils::InvalidateDirectRenderingObservers( this, SVGObserverUtils::INVALIDATE_REFLOW); @@ -6859,6 +6863,11 @@ bool nsIFrame::IsContentHidden() const { return IsFrameOfType(nsIFrame::eReplaced) || !StyleDisplay()->IsInlineFlow(); } +bool nsIFrame::IsContentHiddenForLayout() const { + return IsContentHidden() && + !PresShell()->IsForcingLayoutForHiddenContent(this); +} + bool nsIFrame::AncestorHidesContent() const { if (!StaticPrefs::layout_css_content_visibility_enabled()) { return false; diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index a198d8edd70..d132336ed2d 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -3150,6 +3150,13 @@ class nsIFrame : public nsQueryFrame { */ bool IsContentHidden() const; + /** + * Whether the content is hidden via the `content-visibilty` property for + * layout. Hidden content might not be hidden for layout when forcing layout + * for size queries. + */ + bool IsContentHiddenForLayout() const; + /** * Returns true if this frame is entirely hidden due the `content-visibility` * property on an ancestor. diff --git a/layout/tools/layout-debug/ui/content/layoutdebug.ftl b/layout/tools/layout-debug/ui/content/layoutdebug.ftl new file mode 100644 index 00000000000..98c6fa3b92d --- /dev/null +++ b/layout/tools/layout-debug/ui/content/layoutdebug.ftl @@ -0,0 +1,81 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +### This file is not in a locales directory to prevent it from being +### translated as the layout debugger is only available in debug builds. + +layoutdebug-main-window = + .title = Layout Debugger + +layoutdebug-back-button = + .label = Back +layoutdebug-forward-button = + .label = Forward +layoutdebug-reload-button = + .label = Reload +layoutdebug-stop-button = + .label = Stop + +## Toggle Menu + +layoutdebug-toggle-menu = + .label = Toggle + .accesskey = T +layoutdebug-paint-dumping = + .label = Paint Dumping + .accesskey = P +layoutdebug-invalidate-dumping = + .label = Invalidate Dumping + .accesskey = I +layoutdebug-event-dumping = + .label = Event Dumping + .accesskey = E +layoutdebug-motion-event-dumping = + .label = Motion Event Dumping + .accesskey = M +layoutdebug-crossing-event-dumping = + .label = Crossing Event Dumping + .accesskey = C +layoutdebug-reflow-counts = + .label = Reflow Counts + .accesskey = R +layoutdebug-paged-mode = + .label = Paged Mode + .accesskey = g + +## Dump Menu + +layoutdebug-dump-menu = + .label = Dump + .accesskey = D +layoutdebug-dump-content = + .label = Content + .accesskey = C +layoutdebug-dump-frames = + .label = Frames (app units) + .accesskey = F +layoutdebug-dump-frames-in-css-pixels = + .label = Frames (CSS pixels) + .accesskey = p +layoutdebug-dump-text-runs = + .label = Text Runs + .accesskey = T +layoutdebug-dump-views = + .label = Views and Widgets + .accesskey = V +layoutdebug-dump-counter-manager = + .label = CSS Counters + .accesskey = n +layoutdebug-dump-style-sheets = + .label = Style Sheets + .accesskey = S +layoutdebug-dump-matched-rules = + .label = Matched CSS Rules + .accesskey = M +layoutdebug-dump-computed-styles = + .label = Style Contexts + .accesskey = x +layoutdebug-dump-reflow-stats = + .label = Reflow Statistics + .accesskey = R diff --git a/layout/tools/layout-debug/ui/content/layoutdebug.xhtml b/layout/tools/layout-debug/ui/content/layoutdebug.xhtml index f25ee714e3b..a39d4609eb8 100644 --- a/layout/tools/layout-debug/ui/content/layoutdebug.xhtml +++ b/layout/tools/layout-debug/ui/content/layoutdebug.xhtml @@ -6,7 +6,7 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - + @@ -23,9 +23,7 @@ xmlns:html="http://www.w3.org/1999/xhtml" id="main-window" align="stretch" - title="&ldb.MainWindow.title;" - titlemodifier="&ldb.MainWindow.title;" - titlemenuseparator=" — " + data-l10n-id="layoutdebug-main-window" windowtype="mozapp:layoutdebug" onload="OnLDBLoad();" onclose="OnLDBBeforeUnload(event);" @@ -34,6 +32,10 @@ screenX="4" screenY="4" > + + + + + + + + +
+ + + diff --git a/testing/web-platform/tests/css/css-contain/content-visibility/content-visibility-081.html b/testing/web-platform/tests/css/css-contain/content-visibility/content-visibility-081.html index ddd6593dc2e..b5f10cb6c89 100644 --- a/testing/web-platform/tests/css/css-contain/content-visibility/content-visibility-081.html +++ b/testing/web-platform/tests/css/css-contain/content-visibility/content-visibility-081.html @@ -5,6 +5,7 @@ + diff --git a/testing/web-platform/tests/css/css-contain/content-visibility/content-visibility-forced-layout-client-rects.html b/testing/web-platform/tests/css/css-contain/content-visibility/content-visibility-forced-layout-client-rects.html new file mode 100644 index 00000000000..60e68498924 --- /dev/null +++ b/testing/web-platform/tests/css/css-contain/content-visibility/content-visibility-forced-layout-client-rects.html @@ -0,0 +1,105 @@ + + + +Content Visibility: nested forced layouts + + + + + + + + + +
+ +
+
A line of a certain length...
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/testing/xpcshell/example/unit/xpcshell.ini b/testing/xpcshell/example/unit/xpcshell.ini index 0644db68ccf..869875435bb 100644 --- a/testing/xpcshell/example/unit/xpcshell.ini +++ b/testing/xpcshell/example/unit/xpcshell.ini @@ -17,10 +17,10 @@ support-files = [test_add_setup.js] [test_check_nsIException.js] -skip-if = os == "win" && debug +skip-if = os == 'win' && debug [test_check_nsIException_failing.js] fail-if = true -skip-if = os == "win" && debug +skip-if = os == 'win' && debug [test_do_get_tempdir.js] [test_execute_soon.js] @@ -46,11 +46,11 @@ fail-if = true skip-if = true [test_do_check_null.js] -skip-if = os == "win" && debug +skip-if = os == 'win' && debug [test_do_check_null_failing.js] fail-if = true -skip-if = os == "win" && debug +skip-if = os == 'win' && debug [test_do_check_matches.js] [test_do_check_matches_failing.js] diff --git a/toolkit/actors/ClipboardReadTextPasteChild.jsm b/toolkit/actors/ClipboardReadPasteChild.jsm similarity index 61% rename from toolkit/actors/ClipboardReadTextPasteChild.jsm rename to toolkit/actors/ClipboardReadPasteChild.jsm index 2e69b7197eb..d857a2d0578 100644 --- a/toolkit/actors/ClipboardReadTextPasteChild.jsm +++ b/toolkit/actors/ClipboardReadPasteChild.jsm @@ -3,35 +3,35 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -var EXPORTED_SYMBOLS = ["ClipboardReadTextPasteChild"]; +var EXPORTED_SYMBOLS = ["ClipboardReadPasteChild"]; /** - * Propagates "MozClipboardReadTextPaste" events from a content process to the + * Propagates "MozClipboardReadPaste" events from a content process to the * chrome process. * Receives messages from the chrome process. */ -class ClipboardReadTextPasteChild extends JSWindowActorChild { +class ClipboardReadPasteChild extends JSWindowActorChild { constructor() { super(); } // EventListener interface. handleEvent(aEvent) { - if (aEvent.type == "MozClipboardReadTextPaste" && aEvent.isTrusted) { - this.sendAsyncMessage("ClipboardReadTextPaste:ShowMenupopup", {}); + if (aEvent.type == "MozClipboardReadPaste" && aEvent.isTrusted) { + this.sendAsyncMessage("ClipboardReadPaste:ShowMenupopup", {}); } } // For JSWindowActorChild. receiveMessage(value) { switch (value.name) { - case "ClipboardReadTextPaste:PasteMenuItemClicked": { + case "ClipboardReadPaste:PasteMenuItemClicked": { this.contentWindow.navigator.clipboard.onUserReactedToPasteMenuPopup( true ); break; } - case "ClipboardReadTextPaste:PasteMenuItemDismissed": { + case "ClipboardReadPaste:PasteMenuItemDismissed": { this.contentWindow.navigator.clipboard.onUserReactedToPasteMenuPopup( false ); diff --git a/toolkit/actors/ClipboardReadTextPasteParent.jsm b/toolkit/actors/ClipboardReadPasteParent.jsm similarity index 77% rename from toolkit/actors/ClipboardReadTextPasteParent.jsm rename to toolkit/actors/ClipboardReadPasteParent.jsm index 405ddfb3141..95bcabbd7b1 100644 --- a/toolkit/actors/ClipboardReadTextPasteParent.jsm +++ b/toolkit/actors/ClipboardReadPasteParent.jsm @@ -3,16 +3,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -var EXPORTED_SYMBOLS = ["ClipboardReadTextPasteParent"]; +var EXPORTED_SYMBOLS = ["ClipboardReadPasteParent"]; const kPasteMenuItemClickedEventType = "PasteMenuItemClicked"; const kPasteMenupopupHidingEventType = "PasteMenupopupHiding"; -const kMenuPopupId = "clipboardReadTextPasteMenuPopup"; +const kMenuPopupId = "clipboardReadPasteMenuPopup"; // Exchanges messages with the child actor and handles events from the // pasteMenuHandler. -class ClipboardReadTextPasteParent extends JSWindowActorParent { +class ClipboardReadPasteParent extends JSWindowActorParent { constructor() { super(); @@ -25,7 +25,7 @@ class ClipboardReadTextPasteParent extends JSWindowActorParent { switch (aEvent.type) { case kPasteMenuItemClickedEventType: { this._pasteMenuItemClicked = true; - this.sendAsyncMessage("ClipboardReadTextPaste:PasteMenuItemClicked"); + this.sendAsyncMessage("ClipboardReadPaste:PasteMenuItemClicked"); break; } case kPasteMenupopupHidingEventType: { @@ -38,9 +38,7 @@ class ClipboardReadTextPasteParent extends JSWindowActorParent { // click/dismiss events properly. this._pasteMenuItemClicked = false; } else { - this.sendAsyncMessage( - "ClipboardReadTextPaste:PasteMenuItemDismissed" - ); + this.sendAsyncMessage("ClipboardReadPaste:PasteMenuItemDismissed"); } break; } @@ -49,7 +47,7 @@ class ClipboardReadTextPasteParent extends JSWindowActorParent { // For JSWindowActorParent. receiveMessage(value) { - if (value.name == "ClipboardReadTextPaste:ShowMenupopup") { + if (value.name == "ClipboardReadPaste:ShowMenupopup") { if (!this._menupopup) { this._menupopup = this._getMenupopup(); } @@ -68,15 +66,15 @@ class ClipboardReadTextPasteParent extends JSWindowActorParent { ); // `openPopup` is a no-op if the popup is already opened. - // That property is used when `navigator.clipboard.readText()` is called - // from two different frames, e.g. an iframe and the top level frame. - // In that scenario, the two frames correspond to different - // `navigator.clipboard` instances. When `readText()` is called from both - // frames, an actor pair is instantiated for each of them. Both actor - // parents will call `openPopup` on the same `_menupopup` object. If the - // popup is already open, `openPopup` is a no-op. When the popup is - // clicked or dismissed both actor parents will receive the corresponding - // event. + // That property is used when `navigator.clipboard.readText()` or + // `navigator.clipboard.read()`is called from two different frames, e.g. + // an iframe and the top level frame. In that scenario, the two frames + // correspond to different `navigator.clipboard` instances. When + // `readText()` or `read()` is called from both frames, an actor pair is + // instantiated for each of them. Both actor parents will call `openPopup` + // on the same `_menupopup` object. If the popup is already open, + // `openPopup` is a no-op. When the popup is clicked or dismissed both + // actor parents will receive the corresponding event. this._menupopup.openPopup( null, "overlap" /* options */, @@ -99,7 +97,7 @@ class ClipboardReadTextPasteParent extends JSWindowActorParent { _createMenupopup(aChromeDoc) { let menuitem = aChromeDoc.createXULElement("menuitem"); - menuitem.id = "clipboardReadTextPasteMenuItem"; + menuitem.id = "clipboardReadPasteMenuItem"; menuitem.setAttribute("data-l10n-id", "text-action-paste"); menuitem.setAttribute( "oncommand", diff --git a/toolkit/actors/moz.build b/toolkit/actors/moz.build index 874bd25577f..69706274972 100644 --- a/toolkit/actors/moz.build +++ b/toolkit/actors/moz.build @@ -46,8 +46,8 @@ FINAL_TARGET_FILES.actors += [ "BackgroundThumbnailsChild.jsm", "BrowserElementChild.jsm", "BrowserElementParent.jsm", - "ClipboardReadTextPasteChild.jsm", - "ClipboardReadTextPasteParent.jsm", + "ClipboardReadPasteChild.jsm", + "ClipboardReadPasteParent.jsm", "ContentMetaChild.jsm", "ContentMetaParent.jsm", "ControllersChild.jsm", diff --git a/toolkit/components/cookiebanners/CookieBannerListService.jsm b/toolkit/components/cookiebanners/CookieBannerListService.jsm index 4cb88847b11..8d7c2e80e1d 100644 --- a/toolkit/components/cookiebanners/CookieBannerListService.jsm +++ b/toolkit/components/cookiebanners/CookieBannerListService.jsm @@ -7,6 +7,9 @@ const lazy = {}; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); ChromeUtils.defineModuleGetter( lazy, @@ -14,6 +17,12 @@ ChromeUtils.defineModuleGetter( "resource://services-settings/remote-settings.js" ); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "DEFAULT_EXPIRY_RELATIVE", + "cookiebanners.cookieInjector.defaultExpiryRelative" +); + // Name of the RemoteSettings collection containing the rules. const COLLECTION_NAME = "cookie-banner-rules-list"; @@ -131,19 +140,24 @@ class CookieBannerListService { let isOptOut = category == "optOut"; for (let c of cookies[category]) { + let { expiryRelative } = c; + if (expiryRelative == null || expiryRelative <= 0) { + expiryRelative = lazy.DEFAULT_EXPIRY_RELATIVE; + } + rule.addCookie( isOptOut, - c.host, c.name, c.value, // The following fields are optional and may not be defined by the - // rule. They will fall back to defaults. - c.path, - c.expiryRelative, + // rule. + c.host || `.${rule.domain}`, + c.path || "/", + expiryRelative, c.unsetValue, c.isSecure, c.isHTTPOnly, - c.isSession, + c.isSession ?? true, c.sameSite, c.schemeMap ); diff --git a/toolkit/components/cookiebanners/nsCookieBannerRule.cpp b/toolkit/components/cookiebanners/nsCookieBannerRule.cpp index 87e973f4402..41741a655f0 100644 --- a/toolkit/components/cookiebanners/nsCookieBannerRule.cpp +++ b/toolkit/components/cookiebanners/nsCookieBannerRule.cpp @@ -24,9 +24,8 @@ nsCookieBannerRule::ClearCookies() { } NS_IMETHODIMP -nsCookieBannerRule::AddCookie(bool aIsOptOut, const nsACString& aHost, - const nsACString& aName, const nsACString& aValue, - // Optional +nsCookieBannerRule::AddCookie(bool aIsOptOut, const nsACString& aName, + const nsACString& aValue, const nsACString& aHost, const nsACString& aPath, int64_t aExpiryRelative, const nsACString& aUnsetValue, bool aIsSecure, bool aIsHttpOnly, bool aIsSession, @@ -37,16 +36,9 @@ nsCookieBannerRule::AddCookie(bool aIsOptOut, const nsACString& aHost, mDomain.get(), aIsOptOut, nsPromiseFlatCString(aHost).get(), nsPromiseFlatCString(aName).get())); - // Default cookie host to . - nsAutoCString host(aHost); - if (host.IsEmpty()) { - host.AppendLiteral("."); - host.Append(mDomain); - } - // Create and insert cookie rule. nsCOMPtr cookieRule = new nsCookieRule( - aIsOptOut, host, aName, aValue, aPath, aExpiryRelative, aUnsetValue, + aIsOptOut, aName, aValue, aHost, aPath, aExpiryRelative, aUnsetValue, aIsSecure, aIsHttpOnly, aIsSession, aSameSite, aSchemeMap); Cookies(aIsOptOut).AppendElement(cookieRule); diff --git a/toolkit/components/cookiebanners/nsCookieRule.cpp b/toolkit/components/cookiebanners/nsCookieRule.cpp index 558ca9128e5..be22a0b6145 100644 --- a/toolkit/components/cookiebanners/nsCookieRule.cpp +++ b/toolkit/components/cookiebanners/nsCookieRule.cpp @@ -16,30 +16,19 @@ namespace mozilla { NS_IMPL_ISUPPORTS(nsCookieRule, nsICookieRule) -nsCookieRule::nsCookieRule(bool aIsOptOut, const nsACString& aHost, - const nsACString& aName, const nsACString& aValue, - // Optional +nsCookieRule::nsCookieRule(bool aIsOptOut, const nsACString& aName, + const nsACString& aValue, const nsACString& aHost, const nsACString& aPath, int64_t aExpiryRelative, const nsACString& aUnsetValue, bool aIsSecure, bool aIsHttpOnly, bool aIsSession, int32_t aSameSite, nsICookie::schemeType aSchemeMap) { - // Default expiry time is defined by pref. - if (aExpiryRelative <= 0) { - aExpiryRelative = - StaticPrefs::cookiebanners_cookieInjector_defaultExpiryRelative(); - } mExpiryRelative = aExpiryRelative; - - nsCString path(aPath); - if (path.IsEmpty()) { - path.AssignLiteral("/"); - } - mUnsetValue = aUnsetValue; - net::CookieStruct cookieData( - nsCString(aName), nsCString(aValue), nsCString(aHost), path, 0, 0, 0, - aIsHttpOnly, aIsSession, aIsSecure, aSameSite, aSameSite, aSchemeMap); + net::CookieStruct cookieData(nsCString(aName), nsCString(aValue), + nsCString(aHost), nsCString(aPath), 0, 0, 0, + aIsHttpOnly, aIsSession, aIsSecure, aSameSite, + aSameSite, aSchemeMap); OriginAttributes attrs; mCookie = net::Cookie::Create(cookieData, attrs); diff --git a/toolkit/components/cookiebanners/nsCookieRule.h b/toolkit/components/cookiebanners/nsCookieRule.h index 4793bec2ab3..c989742d0d0 100644 --- a/toolkit/components/cookiebanners/nsCookieRule.h +++ b/toolkit/components/cookiebanners/nsCookieRule.h @@ -19,15 +19,12 @@ class nsCookieRule final : public nsICookieRule { public: nsCookieRule() = default; - explicit nsCookieRule( - bool aIsOptOut, const nsACString& aHost, const nsACString& aName, - const nsACString& aValue, const nsACString& aPath = "/"_ns, - int64_t aExpiryRelative = - StaticPrefs::cookiebanners_cookieInjector_defaultExpiryRelative(), - const nsACString& aUnsetValue = ""_ns, bool aIsSecure = true, - bool aIsHttpOnly = false, bool aIsSession = false, - int32_t aSameSite = nsICookie::SAMESITE_LAX, - nsICookie::schemeType aSchemeMap = nsICookie::SCHEME_HTTPS); + explicit nsCookieRule(bool aIsOptOut, const nsACString& aName, + const nsACString& aValue, const nsACString& aHost, + const nsACString& aPath, int64_t aExpiryRelative, + const nsACString& aUnsetValue, bool aIsSecure, + bool aIsHttpOnly, bool aIsSession, int32_t aSameSite, + nsICookie::schemeType aSchemeMap); private: ~nsCookieRule() = default; diff --git a/toolkit/components/cookiebanners/nsICookieBannerRule.idl b/toolkit/components/cookiebanners/nsICookieBannerRule.idl index 9f871102506..9734d49d225 100644 --- a/toolkit/components/cookiebanners/nsICookieBannerRule.idl +++ b/toolkit/components/cookiebanners/nsICookieBannerRule.idl @@ -37,17 +37,17 @@ interface nsICookieBannerRule : nsISupports { * For a description of the other fields see nsICookieManager#addNative. */ void addCookie(in boolean aIsOptOut, - in AUTF8String aHost, in ACString aName, in AUTF8String aValue, - [optional] in AUTF8String aPath, - [optional] in int64_t aExpiryRelative, - [optional] in AUTF8String aUnsetValue, - [optional] in boolean aIsSecure, - [optional] in boolean aIsHttpOnly, - [optional] in boolean aIsSession, - [optional] in int32_t aSameSite, - [optional] in nsICookie_schemeType aSchemeMap); + in AUTF8String aHost, + in AUTF8String aPath, + in int64_t aExpiryRelative, + in AUTF8String aUnsetValue, + in boolean aIsSecure, + in boolean aIsHttpOnly, + in boolean aIsSession, + in int32_t aSameSite, + in nsICookie_schemeType aSchemeMap); // The clicking rule that associates with this rule. The banner auto // clicking will use this rule to detect and click the banner. diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice.js b/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice.js index e0569f54bf1..90ca277f451 100644 --- a/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice.js +++ b/toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice.js @@ -163,9 +163,48 @@ add_task(async function test_insertAndGetRule() { rule.clearCookies(); info("Adding cookies to the rule for example.com."); - rule.addCookie(true, "example.com", "foo", "bar"); - rule.addCookie(true, "example.com", "foobar", "barfoo"); - rule.addCookie(false, "foo.example.com", "foo", "bar", "/myPath", 3600); + rule.addCookie( + true, + "foo", + "bar", + "example.com", + "/", + 3600, + "", + false, + false, + false, + 0, + 0 + ); + rule.addCookie( + true, + "foobar", + "barfoo", + "example.com", + "/", + 3600, + "", + false, + false, + false, + 0, + 0 + ); + rule.addCookie( + false, + "foo", + "bar", + "foo.example.com", + "/myPath", + 3600, + "", + false, + false, + true, + 0, + 0 + ); info("Adding a click rule to the rule for example.com."); rule.addClickRule("div#presence", "div#hide", "div#optOut", "div#optIn"); @@ -189,7 +228,20 @@ add_task(async function test_insertAndGetRule() { rule2.clearCookies(); info("Adding a cookie to the rule for example.org."); - rule2.addCookie(false, "example.org", "foo2", "bar2"); + rule2.addCookie( + false, + "foo2", + "bar2", + "example.org", + "/", + 0, + "", + false, + false, + false, + 0, + 0 + ); info("Adding a click rule to the rule for example.org."); rule2.addClickRule("div#presence", null, null, "div#optIn"); @@ -384,7 +436,20 @@ add_task(async function test_overwriteRule() { rule.domain = "example.com"; info("Adding a cookie so we can detect if the rule updates."); - rule.addCookie(true, "", "foo", "original"); + rule.addCookie( + true, + "foo", + "original", + "example.com", + "/", + 3600, + "", + false, + false, + false, + 0, + 0 + ); info("Adding a click rule so we can detect if the rule updates."); rule.addClickRule("div#original"); @@ -403,7 +468,20 @@ add_task(async function test_overwriteRule() { ); ruleNew.domain = "example.com"; - ruleNew.addCookie(true, "", "foo", "new"); + ruleNew.addCookie( + true, + "foo", + "new", + "example.com", + "/", + 3600, + "", + false, + false, + false, + 0, + 0 + ); ruleNew.addClickRule("div#new"); diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookieinjector.js b/toolkit/components/cookiebanners/test/browser/browser_cookieinjector.js index 489e1f7d362..3d3556521ca 100644 --- a/toolkit/components/cookiebanners/test/browser/browser_cookieinjector.js +++ b/toolkit/components/cookiebanners/test/browser/browser_cookieinjector.js @@ -37,15 +37,31 @@ function insertTestRules() { Services.cookieBanners.insertRule(ruleA); ruleA.addCookie( true, - "." + DOMAIN_A, `cookieConsent_${DOMAIN_A}_1`, - "optOut1" + "optOut1", + "." + DOMAIN_A, + "/", + 3600, + "", + false, + false, + false, + 0, + 0 ); ruleA.addCookie( true, - "." + DOMAIN_A, `cookieConsent_${DOMAIN_A}_2`, - "optOut2" + "optOut2", + DOMAIN_A, + "/", + 3600, + "", + false, + false, + false, + 0, + 0 ); // An opt-in cookie rule for DOMAIN_B. @@ -57,12 +73,17 @@ function insertTestRules() { Services.cookieBanners.insertRule(ruleB); ruleB.addCookie( false, - null, // This should result in . `cookieConsent_${DOMAIN_B}_1`, "optIn1", + DOMAIN_B, "/", + 3600, + "UNSET", + false, + false, + true, 0, - "UNSET" + 0 ); } /** diff --git a/toolkit/components/cookiebanners/test/unit/test_cookiebannerlistservice.js b/toolkit/components/cookiebanners/test/unit/test_cookiebannerlistservice.js index dc3cc072ee4..9b0cd7ce30b 100644 --- a/toolkit/components/cookiebanners/test/unit/test_cookiebannerlistservice.js +++ b/toolkit/components/cookiebanners/test/unit/test_cookiebannerlistservice.js @@ -28,6 +28,7 @@ const RULE_A_ORIGINAL = { name: "doOptOut", value: "true", isSecure: true, + isSession: false, }, ], }, @@ -61,6 +62,8 @@ const RULE_C = { { name: "gdpr", value: "1", + path: "/myPath", + host: "foo.example.net", }, ], }, @@ -160,13 +163,45 @@ add_task(async function test_initial_import() { Assert.equal(rulesInserted.length, 2, "Two inserted rules after init."); Assert.equal(rulesRemoved.length, 0, "No removed rules after init."); - Assert.ok( - rulesInserted.some(rule => rule.domain == RULE_A_ORIGINAL.domain), - "Has rule A." + let ruleA = rulesInserted.find(rule => rule.domain == RULE_A_UPDATED.domain); + let cookieRuleA = ruleA.cookiesOptOut[0].cookie; + let ruleC = rulesInserted.find(rule => rule.domain == RULE_C.domain); + let cookieRuleC = ruleC.cookiesOptIn[0].cookie; + + Assert.ok(ruleA, "Has rule A."); + // Test the defaults which CookieBannerListService sets when the rule does + // not. + Assert.equal( + cookieRuleA.isSession, + false, + "Cookie for rule A should not be a session cookie." ); - Assert.ok( - rulesInserted.some(rule => rule.domain == RULE_C.domain), - "Has rule C." + Assert.equal( + cookieRuleA.host, + ".example.com", + "Cookie host for rule A should be default." + ); + Assert.equal( + cookieRuleA.path, + "/", + "Cookie path for rule A should be default." + ); + + Assert.ok(ruleC, "Has rule C."); + Assert.equal( + ruleC.cookiesOptIn[0].cookie.isSession, + true, + "Cookie for rule C should should be a session cookie." + ); + Assert.equal( + cookieRuleC.host, + "foo.example.net", + "Cookie host for rule C should be custom." + ); + Assert.equal( + cookieRuleC.path, + "/myPath", + "Cookie path for rule C should be custom." ); // Cleanup diff --git a/toolkit/components/glean/ipc/Support.cpp b/toolkit/components/glean/ipc/Support.cpp index 9d8f203da09..479b9b42b0e 100644 --- a/toolkit/components/glean/ipc/Support.cpp +++ b/toolkit/components/glean/ipc/Support.cpp @@ -57,6 +57,12 @@ int FOG_GetProcessType() { return XRE_GetProcessType(); } * We should probably flush before we reach the max IPC message size. */ void FOG_IPCPayloadFull() { + // NS_DispatchToMainThread can leak the runnable (bug 1787589), so let's be + // sure not to create it too late in shutdown. + // We choose XPCOMShutdown to match gFOG->Shutdown(). + if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdown)) { + return; + } // FOG IPC must happen on the main thread until bug 1641989. // If there is no main thread (too early in startup or too late in shutdown), // there's nothing we can do but log. diff --git a/toolkit/components/glean/tests/xpcshell/test_GIFFT.js b/toolkit/components/glean/tests/xpcshell/test_GIFFT.js index 3fa32200586..b7a8f15c7a6 100644 --- a/toolkit/components/glean/tests/xpcshell/test_GIFFT.js +++ b/toolkit/components/glean/tests/xpcshell/test_GIFFT.js @@ -167,7 +167,8 @@ add_task(async function test_gifft_timing_dist() { const EPSILON = 40000; // Variance in timing makes getting the sum impossible to know. - Assert.greater(data.sum, 15 * NANOS_IN_MILLIS - EPSILON); + // 10 and 5 input value can be trunacted to 4. + 9. >= 13. from cast + Assert.greater(data.sum, 13 * NANOS_IN_MILLIS - EPSILON); // No guarantees from timers means no guarantees on buckets. // But we can guarantee it's only two samples. diff --git a/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js b/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js index aedb06e2d10..c9d18ad6b07 100644 --- a/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js +++ b/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js @@ -295,7 +295,9 @@ add_task( ) ); const timingHist = histSnapshot.content.TELEMETRY_TEST_EXPONENTIAL; - Assert.greaterOrEqual(timingHist.sum, 15, "Histogram's in milliseconds."); + Assert.greaterOrEqual(timingHist.sum, 13, "Histogram's in milliseconds."); + // Both values, 10 and 5, are truncated by a cast in AccumulateTimeDelta + // Minimally downcast 9. + 4. could realistically result in 13. Assert.equal( 2, Object.entries(timingHist.values).reduce( diff --git a/toolkit/components/printing/content/print.css b/toolkit/components/printing/content/print.css index 2f88fb3461c..4fce748dffa 100644 --- a/toolkit/components/printing/content/print.css +++ b/toolkit/components/printing/content/print.css @@ -162,6 +162,7 @@ input[type="text"] { #open-dialog-link::after { display: block; + flex-shrink: 0; content: ""; background: url(chrome://global/skin/icons/open-in-new.svg) no-repeat center; background-size: 12px; diff --git a/toolkit/components/search/docs/SearchEngines.rst b/toolkit/components/search/docs/SearchEngines.rst index 1940a8ab3af..36255d08219 100644 --- a/toolkit/components/search/docs/SearchEngines.rst +++ b/toolkit/components/search/docs/SearchEngines.rst @@ -6,12 +6,12 @@ user, enabling users to search the internet with different search providers. The three main ways Firefox serves search engines to the users is through: - Add-on Search Engines -- Open Search Engines +- OpenSearch Engines - Enterprise Policy Engines An example of a search provider is Google, which is one of the Add-on Search Engines described in the first section below. Another example of a search -provider is Bugzilla, which is an Open Search Engines described in the second +provider is Bugzilla, which is an OpenSearch Engine described in the second section below. Lastly, there are Enterprise Policy Search Engines, which will be the third section described in this documentation. @@ -20,7 +20,7 @@ Add-on Search Engines Add-ons are additional functionality that third-party developers provide for users to install into Firefox. The add-on mechanism is also used by Firefox to ship the search engines provided by the application. To define Add-on Search -Engines developers use the `WebExtensions API`_. Since the WebExtensions API +Engines, developers use the `WebExtensions API`_. Since the WebExtensions API technology is used, developers interchangeably used the term WebExtension Search Engines when referring to Add-ons Search Engines. @@ -30,10 +30,10 @@ Engines when referring to Add-ons Search Engines. The list of Add-on Search Engines provided by Firefox and their extension files can be found in `mozilla-central/browser/components/search/extensions `__. -Within each Add-on Search Engine folder, there is a manifest.json file and a -`chrome_settings_overrides` object that describes how to construct the url, -images, strings, icon, and etc. Here’s an example of how the search provider is -set within chrome_settings_overrides: +Within each Add-on Search Engine folder, there is a manifest.json file. One of the +keys in that manifest.json file is `chrome_settings_overrides`, whose value is an object +that describes how to construct the url, images, strings, icon, etc. Here’s an example of +how the search provider is set within `chrome_settings_overrides`: .. code-block:: js @@ -54,7 +54,7 @@ manifest.json/chrome_settings_overrides>`__ In Practice ----------- -All versions of Firefox support add-ons. Firefox switched over from Open Search +All versions of Firefox support add-ons. Firefox switched over from OpenSearch to Add-on Search Engines internally in Firefox version 78. Add-on Search engines allows Firefox developers to have more flexibility and control in the modification of formatting search engines as we support different search @@ -62,38 +62,37 @@ providers. We maintain these Add-on Search Engines through a search configuration file that is bundled and configured via Remote Settings. As of this writing, June 2022, we -use Remote settings for managing search engines only for Firefox Desktop but not +use Remote Settings for managing search engines only for Firefox Desktop but not outside of Firefox Desktop. -Open Search Engines +OpenSearch Engines =================== -Open Search is a Plugin, a software installed on Firefox to enhance capabilities -for searching. Open Search has a collection of formats that describe how to -construct the url, images, strings, icon, and etc. These formats provided by -Open Search allow Firefox to make a search over the internet with a specific +OpenSearch is a plugin, software installed on Firefox to enhance capabilities +for searching. OpenSearch has a collection of formats that describe how to +construct the url, images, strings, icon, etc. These formats provided by +OpenSearch allow Firefox to make a search over the internet with a specific search provider that is not an application provided search engine on Firefox. -The purpose of Open Search is to provide more convenient ways of searching and +The purpose of OpenSearch is to provide more convenient ways of searching and different ways of searching. -Open Search allows users to search with a vast variety of search providers which -do not come installed with Firefox out of the box. The main benefit of Open -Search is it allows site owners to easily provide users with a way to search a -site. +OpenSearch allows users to search with a vast variety of search providers which +do not come installed with Firefox out of the box. The main benefit of OpenSearch +is it allows site owners to easily provide users with a way to search a site. History ------- -Prior to Open Search, search Plugins were first created by the `Mycroft Project +Prior to OpenSearch, search plugins were first created by the `Mycroft Project `__ and based off of `Sherlock `__, a file and web search tool created by Apple. -The Open Search Protocol was created and launched by A9.com in 2005. Open search +The OpenSearch Protocol was created and launched by A9.com in 2005. OpenSearch was added to Firefox version 2 in the year 2006. As of today in 2022, OpenSearch is a collection of formats for sharing of search results. The code is stable but unchanged for many years. -See the `Open Search Documentation `__ for -more information on the Open Search formats. +See the `OpenSearch Documentation `__ for +more information on the OpenSearch formats. Autodiscovery ------------- @@ -102,11 +101,11 @@ the webpage they visited has a search plugin. Here is an example of Autodiscovery from Bugzilla. You can visit https://bugzilla.mozilla.org and Firefox will automatically detect that the -website has a provided search plugin. In the result dropdown, you can look at +website has a provided search plugin. In the results dropdown, you can look at the search engine shortcuts section at the bottom and it will show a green plus sign over the Bugzilla search icon. The green plus sign indicates that the user -can add Bugzilla as an Open Search Engine. After the user adds Bugzilla as an -Open Search Engine the green plus icon disappears. The user can now click the +can add Bugzilla as an OpenSearch Engine. After the user adds Bugzilla as an +OpenSearch Engine, the green plus icon disappears. The user can now click the Bugzilla icon to make a search directly on bugzilla.mozilla.org. .. figure:: assets/bugzilla-open-search1.png @@ -125,14 +124,14 @@ Autodiscovery. Enterprise Policy Engines ========================= -Enterprise Policies are customizable configurations for the Firefox Browser set +Enterprise Policies are customizable configurations for the Firefox browser set by enterprises or companies who want to distribute configuration for their -users. The idea of policy is to allow companies to customize Firefox and how -their users can or cannot change the usage of Firefox based on predefined +users. The idea of Enterprise Policies is to allow companies to customize Firefox +and how their users can or cannot change the usage of Firefox based on predefined configuration that was set in place. Enterprise Policy Engines are search engines that a company has added as search -engines on Firefox for their users by setting the enterprise policy. In this +engines on Firefox for their users by setting the Enterprise Policy. In this `Enterprise Policy Documentation `__, @@ -165,7 +164,7 @@ turns them into settings that Firefox can consume. A Hypothetical Use of Enterprise Policy --------------------------------------- -A company that is in the banking industry which requires tighter security over +A company that is in the banking industry and requires tighter security over their users may not want their users to do something on Firefox without the company's knowledge. It may make sense for the company to disable private browsing for Firefox. @@ -174,26 +173,25 @@ Within a specific company, the employees of the finance department could use the Firefox ESR version. In this situation, we think of the finance department as the Firefox user rather than the individual employees as Firefox users. The department makes choices for the individuals that use the Firefox browser -through the enterprise policy. +through the Enterprise Policy. Features On Enterprise Policy ----------------------------- -All Firefox versions have to honor the enterprise policy, but the enterprise -policy may not have effect on an individual who is not using Firefox ESR at a +All Firefox versions have to honor the Enterprise Policy, but the Enterprise +Policy may not have effect on an individual who is not using Firefox ESR at a company. There are features that are enterprise specific that are only available in ESR. These features allow search engines to be configured, allowing for unsigned extensions, installing search engines, and setting a default search engine. -How To Set Up and Use Policy for Firefox ----------------------------------------- -Install the ESR version of Firefox since policy is not supported on rapid -release. Then, create the JSON file that is located in the read.me within +How To Set Up and Use an Enterprise Policy for Firefox +------------------------------------------------------ +Install the ESR version of Firefox since Enterprise Policies are not supported on +rapid release. Then, create the JSON file that is located in the README.md within https://github.com/mozilla/policy-templates. There are instructions there on how to configure and use the policy. Once the JSON is created with the appropriate -settings, drop the JSON file in the directory outlined by the read.me and +settings, drop the JSON file in the directory outlined by the README.md and Firefox will find it and Firefox will open and run with the policy. Common formatting mistakes are often made when creating the JSON file. The JSON -file can be validated using a JSON validator such as visiting -https://jsonlint.com/. +file can be validated using a JSON validator such as https://jsonlint.com/. diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 217246385fa..5bda546a99b 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -17077,6 +17077,42 @@ "alert_emails": ["necko@mozilla.com", "mwoodrow@mozilla.com"], "description": "Time spent waiting on the main-thread to be available to run AsyncOpen, for image preload requests" }, + "TEXT_RECOGNITION_API_PERFORMANCE": { + "record_in_processes": ["main"], + "products": ["firefox"], + "releaseChannelCollection": "opt-out", + "alert_emails": ["gtatum@mozilla.com", "nordzilla@mozilla.com"], + "expires_in_version": "109", + "kind": "exponential", + "high": 120000, + "n_buckets": 20, + "bug_numbers": [1783261], + "description": "The milliseconds of time the text recognition results took to display, including the UI time and OS response time." + }, + "TEXT_RECOGNITION_INTERACTION_TIMING": { + "record_in_processes": ["main"], + "products": ["firefox"], + "releaseChannelCollection": "opt-out", + "alert_emails": ["gtatum@mozilla.com", "nordzilla@mozilla.com"], + "expires_in_version": "109", + "kind": "exponential", + "high": 120000, + "n_buckets": 20, + "bug_numbers": [1783261], + "description": "The milliseconds of time that a user viewed the text results." + }, + "TEXT_RECOGNITION_TEXT_LENGTH": { + "record_in_processes": ["main"], + "products": ["firefox"], + "releaseChannelCollection": "opt-out", + "alert_emails": ["gtatum@mozilla.com", "nordzilla@mozilla.com"], + "expires_in_version": "109", + "kind": "exponential", + "high": 50000, + "n_buckets": 20, + "bug_numbers": [1783261], + "description": "Measures the length of the text that was recognized, in code units." + }, "DOWNLOADS_USER_ACTION_ON_BLOCKED_DOWNLOAD": { "record_in_processes": ["main"], "products": ["firefox"], diff --git a/toolkit/components/telemetry/Scalars.yaml b/toolkit/components/telemetry/Scalars.yaml index 968105a9e23..06c37d77e5e 100644 --- a/toolkit/components/telemetry/Scalars.yaml +++ b/toolkit/components/telemetry/Scalars.yaml @@ -1153,6 +1153,22 @@ browser.ui.interaction: record_in_processes: - 'main' + textrecognition_error: + bug_numbers: + - 1783261 + description: > + Recorded when text recognition in images fails for some unknown reason. + notification_emails: + - gtatum@mozilla.com + - nordzilla@mozilla.com + expires: never + kind: uint + release_channel_collection: opt-out + products: + - 'firefox' + record_in_processes: + - 'main' + gecko: version: bug_numbers: diff --git a/toolkit/components/telemetry/core/Telemetry.h b/toolkit/components/telemetry/core/Telemetry.h index 863d17a31ba..285516bdec7 100644 --- a/toolkit/components/telemetry/core/Telemetry.h +++ b/toolkit/components/telemetry/core/Telemetry.h @@ -22,7 +22,7 @@ * points and gives access to the data. * * For documentation on how to add and use new Telemetry probes, see: - * https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Adding_a_new_Telemetry_probe + * https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/start/adding-a-new-probe.html * * For more general information on Telemetry see: * https://wiki.mozilla.org/Telemetry diff --git a/toolkit/components/telemetry/docs/internals/tests.rst b/toolkit/components/telemetry/docs/internals/tests.rst index 062809ce39e..e2d2cdae62f 100644 --- a/toolkit/components/telemetry/docs/internals/tests.rst +++ b/toolkit/components/telemetry/docs/internals/tests.rst @@ -15,7 +15,7 @@ Mochitest --------- :Location: ``t/c/t/t/browser/`` :Language: Javascript - (`mochitest `__) + (`mochitest `__) This test harness runs nearly the entire Firefox and gives access to multiple tabs and browser chrome APIs. It requires window focus to complete correctly, @@ -68,7 +68,7 @@ xpcshell -------- :Location: ``t/c/t/t/unit`` :Language: Javascript - (`xpcshell `__) + (`xpcshell `__) This test harness uses a stripped-down shell of the Firefox browser to run privileged Javascript. It should be used to write unit tests for the Javascript API and app-level logic of Firefox Telemetry. @@ -86,8 +86,8 @@ Instrumentation Tests --------------------- :Location: Various :Language: Usually Javascript - (`xpcshell `__ or - `mochitest `__) + (`xpcshell `__ or + `mochitest `__) In addition to the tests of Firefox Telemetry, other code owners have written tests that ensure that their code records appropriate values to Telemetry. diff --git a/toolkit/components/telemetry/tests/marionette/tests/client/test_fog_deletion_request_ping.py b/toolkit/components/telemetry/tests/marionette/tests/client/test_fog_deletion_request_ping.py index 133a04e31f1..5b4fc89e604 100644 --- a/toolkit/components/telemetry/tests/marionette/tests/client/test_fog_deletion_request_ping.py +++ b/toolkit/components/telemetry/tests/marionette/tests/client/test_fog_deletion_request_ping.py @@ -31,7 +31,11 @@ class TestDeletionRequestPing(FOGTestCase): self.restart_browser() - self.assertEqual(self.fog_ping_server.pings[-1], ping1) + # We'd like to assert that a "deletion-request" is the last ping we + # ever receive, but it's possible there's another ping on another + # thread that gets sent after the sync-sent "deletion-request". + # (This is fine, it'll be deleted within 28 days on the server.) + # self.assertEqual(self.fog_ping_server.pings[-1], ping1) self.enable_telemetry() self.restart_browser() diff --git a/toolkit/locales/jar.mn b/toolkit/locales/jar.mn index 6d5964b3383..00f6827e945 100644 --- a/toolkit/locales/jar.mn +++ b/toolkit/locales/jar.mn @@ -6,6 +6,9 @@ [localization] @AB_CD@.jar: crashreporter (%crashreporter/**/*.ftl) toolkit (%toolkit/**/*.ftl) +#ifdef MOZ_LAYOUT_DEBUGGER + layoutdebug/layoutdebug.ftl (../../layout/tools/layout-debug/ui/content/layoutdebug.ftl) +#endif @AB_CD@.jar: % locale global @AB_CD@ %locale/@AB_CD@/global/ diff --git a/toolkit/modules/ActorManagerParent.jsm b/toolkit/modules/ActorManagerParent.jsm index bd562cdc376..c34523081d5 100644 --- a/toolkit/modules/ActorManagerParent.jsm +++ b/toolkit/modules/ActorManagerParent.jsm @@ -513,15 +513,15 @@ if (!Services.prefs.getBoolPref("browser.pagedata.enabled", false)) { if (AppConstants.platform != "android") { // For GeckoView support see bug 1776829. - JSWINDOWACTORS.ClipboardReadTextPaste = { + JSWINDOWACTORS.ClipboardReadPaste = { parent: { - moduleURI: "resource://gre/actors/ClipboardReadTextPasteParent.jsm", + moduleURI: "resource://gre/actors/ClipboardReadPasteParent.jsm", }, child: { - moduleURI: "resource://gre/actors/ClipboardReadTextPasteChild.jsm", + moduleURI: "resource://gre/actors/ClipboardReadPasteChild.jsm", events: { - MozClipboardReadTextPaste: {}, + MozClipboardReadPaste: {}, }, }, diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 4b8d3257036..33c5c3acb35 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -708,7 +708,7 @@ var AddonManagerInternal = { CATEGORY_PROVIDER_MODULE )) { try { - ChromeUtils.import(url); + ChromeUtils.importESModule(url); logger.debug(`Loaded provider scope for ${url}`); } catch (e) { AddonManagerPrivate.recordException( diff --git a/toolkit/mozapps/extensions/Blocklist.jsm b/toolkit/mozapps/extensions/Blocklist.jsm index 63041316236..bab95038a34 100644 --- a/toolkit/mozapps/extensions/Blocklist.jsm +++ b/toolkit/mozapps/extensions/Blocklist.jsm @@ -89,7 +89,7 @@ const kRegExpRemovalRegExp = /^\/\^\(\(?|\\|\)\)?\$\/$/g; // providers that differ from the existing types. XPCOMUtils.defineLazyGetter(lazy, "kXPIAddonTypes", () => { // In practice, this result is equivalent to ALL_XPI_TYPES in XPIProvider.jsm. - // "plugin" (from GMPProvider.jsm) is intentionally omitted, as we decided to + // "plugin" (from GMPProvider.sys.mjs) is intentionally omitted, as we decided to // not support blocklisting of GMP plugins in bug 1086668. return lazy.AddonManagerPrivate.getAddonTypesByProvider("XPIProvider"); }); diff --git a/toolkit/mozapps/extensions/content/aboutaddons.css b/toolkit/mozapps/extensions/content/aboutaddons.css index c8cd2385eaf..605eb6160fc 100644 --- a/toolkit/mozapps/extensions/content/aboutaddons.css +++ b/toolkit/mozapps/extensions/content/aboutaddons.css @@ -76,7 +76,7 @@ body { display: inline-block; min-width: 20px; background-color: var(--in-content-accent-color); - color: #fff; + color: var(--in-content-primary-button-text-color); font-weight: bold; /* Use a large border-radius to get semi-circles on the sides. */ border-radius: 1000px; diff --git a/toolkit/mozapps/extensions/extensions.manifest b/toolkit/mozapps/extensions/extensions.manifest index 7ece67529a3..f0ec991030f 100644 --- a/toolkit/mozapps/extensions/extensions.manifest +++ b/toolkit/mozapps/extensions/extensions.manifest @@ -3,6 +3,6 @@ category update-timer addonManager @mozilla.org/addons/integration;1,getService, #endif #ifndef MOZ_THUNDERBIRD #ifndef MOZ_WIDGET_ANDROID -category addon-provider-module GMPProvider resource://gre/modules/addons/GMPProvider.jsm +category addon-provider-module GMPProvider resource://gre/modules/addons/GMPProvider.sys.mjs #endif #endif diff --git a/toolkit/mozapps/extensions/internal/GMPProvider.jsm b/toolkit/mozapps/extensions/internal/GMPProvider.sys.mjs similarity index 99% rename from toolkit/mozapps/extensions/internal/GMPProvider.jsm rename to toolkit/mozapps/extensions/internal/GMPProvider.sys.mjs index 56074268ea1..ed87f38f60f 100644 --- a/toolkit/mozapps/extensions/internal/GMPProvider.jsm +++ b/toolkit/mozapps/extensions/internal/GMPProvider.sys.mjs @@ -4,11 +4,8 @@ "use strict"; -var EXPORTED_SYMBOLS = ["GMPTestUtils"]; +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; -const { XPCOMUtils } = ChromeUtils.importESModule( - "resource://gre/modules/XPCOMUtils.sys.mjs" -); const { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ); @@ -880,7 +877,7 @@ var GMPProvider = { GMPProvider.addObserver(); // For test use only. -const GMPTestUtils = { +export const GMPTestUtils = { /** * Used to override the GMP service with a mock. * diff --git a/toolkit/mozapps/extensions/internal/moz.build b/toolkit/mozapps/extensions/internal/moz.build index 6d34f51c35f..6586d7327e3 100644 --- a/toolkit/mozapps/extensions/internal/moz.build +++ b/toolkit/mozapps/extensions/internal/moz.build @@ -16,7 +16,7 @@ EXTRA_JS_MODULES.addons += [ if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": EXTRA_JS_MODULES.addons += [ - "GMPProvider.jsm", + "GMPProvider.sys.mjs", ] TESTING_JS_MODULES += [ diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js b/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js index 3ee5eb21647..afd11c4f9d3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js @@ -3,8 +3,8 @@ "use strict"; -const { GMPTestUtils } = ChromeUtils.import( - "resource://gre/modules/addons/GMPProvider.jsm" +const { GMPTestUtils } = ChromeUtils.importESModule( + "resource://gre/modules/addons/GMPProvider.sys.mjs" ); const { GMPInstallManager } = ChromeUtils.import( "resource://gre/modules/GMPInstallManager.jsm" diff --git a/tools/lint/codespell.yml b/tools/lint/codespell.yml index a7ce7ae3d7d..137fc3d34cc 100644 --- a/tools/lint/codespell.yml +++ b/tools/lint/codespell.yml @@ -24,7 +24,7 @@ codespell: - intl/docs/ - intl/locales/en-US/ - js/src/doc/ - - layout/tools/layout-debug/ui/locale/en-US/ + - layout/tools/layout-debug/ui/content/layoutdebug.ftl - mobile/android/branding/ - mobile/android/docs/ - mobile/android/locales/en-US/ diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-addtask-setup.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-addtask-setup.js index d3b678203f9..62846933f21 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-addtask-setup.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-addtask-setup.js @@ -9,9 +9,9 @@ "use strict"; -// ----------------------------------------------------------------------------- -// Rule Definition -// ----------------------------------------------------------------------------- +function isNamedLikeSetup(name) { + return name.toLowerCase() === "setup"; +} module.exports = { meta: { @@ -27,7 +27,7 @@ module.exports = { if ( arg.type !== "FunctionExpression" || !arg.id || - arg.id.name !== "setup" + !isNamedLikeSetup(arg.id.name) ) { return; } diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/no-addtask-setup.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-addtask-setup.js index 7ffe458b89e..7a43a826d94 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/tests/no-addtask-setup.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-addtask-setup.js @@ -63,5 +63,12 @@ ruleTester.run("no-addtask-setup", rule, { "Do not use add_task() for setup, use add_setup() instead." ), }, + { + code: "add_task(async function setUp() {});", + output: "add_setup(async function() {});", + errors: callError( + "Do not use add_task() for setup, use add_setup() instead." + ), + }, ], }); diff --git a/widget/GfxInfoBase.cpp b/widget/GfxInfoBase.cpp index c88d5502bbf..a8a119b5f4c 100644 --- a/widget/GfxInfoBase.cpp +++ b/widget/GfxInfoBase.cpp @@ -1263,7 +1263,9 @@ bool GfxInfoBase::DoesDriverVendorMatch(const nsAString& aBlocklistVendor, bool GfxInfoBase::IsFeatureAllowlisted(int32_t aFeature) const { return aFeature == nsIGfxInfo::FEATURE_WEBRENDER || - aFeature == nsIGfxInfo::FEATURE_VIDEO_OVERLAY; + aFeature == nsIGfxInfo::FEATURE_VIDEO_OVERLAY || + aFeature == nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY || + aFeature == nsIGfxInfo::FEATURE_REUSE_DECODER_DEVICE; } nsresult GfxInfoBase::GetFeatureStatusImpl( diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp index e69f6ba8248..d969a7578b0 100644 --- a/widget/gtk/nsWindow.cpp +++ b/widget/gtk/nsWindow.cpp @@ -930,15 +930,17 @@ void nsWindow::Show(bool aState) { void nsWindow::ResizeInt(const Maybe& aMove, LayoutDeviceIntSize aSize) { LOG("nsWindow::ResizeInt w:%d h:%d\n", aSize.width, aSize.height); - if (aMove) { - mBounds.x = aMove->x; - mBounds.y = aMove->y; + const bool moved = aMove && *aMove != mBounds.TopLeft(); + if (moved) { + mBounds.MoveTo(*aMove); LOG(" with move to left:%d top:%d", aMove->x, aMove->y); } ConstrainSize(&aSize.width, &aSize.height); LOG(" ConstrainSize: w:%d h;%d\n", aSize.width, aSize.height); + const bool resized = aSize != mLastSizeRequest; + // For top-level windows, aSize should possibly be // interpreted as frame bounds, but NativeMoveResize treats these as window // bounds (Bug 581866). @@ -959,7 +961,11 @@ void nsWindow::ResizeInt(const Maybe& aMove, return; } - NativeMoveResize(aMove.isSome(), true); + if (!moved && !resized) { + return; + } + + NativeMoveResize(moved, resized); // We optimistically assume size changes immediately in two cases: // 1. Override-redirect window: Size is controlled by only us. diff --git a/widget/windows/CompositorWidgetParent.cpp b/widget/windows/CompositorWidgetParent.cpp index 7f41b070273..5854dd3a8cb 100644 --- a/widget/windows/CompositorWidgetParent.cpp +++ b/widget/windows/CompositorWidgetParent.cpp @@ -219,7 +219,8 @@ void CompositorWidgetParent::UpdateCompositorWnd(const HWND aCompositorWnd, ->Then( layers::CompositorThread(), __func__, [self](const bool& aSuccess) { - if (aSuccess && self->mRootLayerTreeID.isSome()) { + if (aSuccess && self->mRootLayerTreeID.isSome() && + layers::CompositorThreadHolder::IsActive()) { self->mSetParentCompleted = true; // Schedule composition after ::SetParent() call in parent // process. diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp index 4de785dd982..8813d99f5ae 100644 --- a/widget/windows/GfxInfo.cpp +++ b/widget/windows/GfxInfo.cpp @@ -1792,6 +1792,44 @@ const nsTArray& GfxInfo::GetGfxDriverInfo() { nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN, V(26, 20, 15000, 37), "FEATURE_FAILURE_BUG_1767212"); + //////////////////////////////////// + // FEATURE_HW_DECODED_VIDEO_ZERO_COPY - ALLOWLIST +#ifdef EARLY_BETA_OR_EARLIER + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, DeviceFamily::All, + nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY, + nsIGfxInfo::FEATURE_ALLOW_ALWAYS, + DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0), + "FEATURE_ROLLOUT_ALL"); +#else + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::IntelAll, + nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY, + nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED, + V(0, 0, 0, 0), "FEATURE_ROLLOUT_ALL"); +#endif + + //////////////////////////////////// + // FEATURE_REUSE_DECODER_DEVICE - ALLOWLIST +#ifdef EARLY_BETA_OR_EARLIER + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, DeviceFamily::All, + nsIGfxInfo::FEATURE_REUSE_DECODER_DEVICE, + nsIGfxInfo::FEATURE_ALLOW_ALWAYS, + DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0), + "FEATURE_ROLLOUT_ALL"); +#else + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::IntelAll, + nsIGfxInfo::FEATURE_REUSE_DECODER_DEVICE, + nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED, + V(0, 0, 0, 0), "FEATURE_ROLLOUT_INTEL"); + // ATI/AMD always requires reuse decoder device. + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_REUSE_DECODER_DEVICE, + nsIGfxInfo::FEATURE_ALLOW_ALWAYS, + DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0), + "FEATURE_ROLLOUT_INTEL"); +#endif + //////////////////////////////////// // FEATURE_WEBRENDER // Block 8.56.1.15/16