author | Norisz Fay <nfay@mozilla.com> |
Thu, 21 Oct 2021 12:35:33 +0300 | |
changeset 596543 | f12b7ea34395623b0480b78451e5a0fb6eeb7707 |
parent 596462 | 54672b7d3f45ce1c72f9fb48c0f6d8594a3c6bdb (current diff) |
parent 596542 | d822837fdcba33ff70def737ef49e7ccfec0861d (diff) |
child 596555 | 632c998505e6520ffd5b67688207c8b648c5db61 |
push id | 38900 |
push user | nfay@mozilla.com |
push date | Thu, 21 Oct 2021 09:36:31 +0000 |
treeherder | mozilla-central@f12b7ea34395 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 95.0a1 |
first release with | nightly linux32
f12b7ea34395
/
95.0a1
/
20211021093631
/
files
nightly linux64
f12b7ea34395
/
95.0a1
/
20211021093631
/
files
nightly mac
f12b7ea34395
/
95.0a1
/
20211021093631
/
files
nightly win32
f12b7ea34395
/
95.0a1
/
20211021093631
/
files
nightly win64
f12b7ea34395
/
95.0a1
/
20211021093631
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
95.0a1
/
20211021093631
/
pushlog to previous
nightly linux64
95.0a1
/
20211021093631
/
pushlog to previous
nightly mac
95.0a1
/
20211021093631
/
pushlog to previous
nightly win32
95.0a1
/
20211021093631
/
pushlog to previous
nightly win64
95.0a1
/
20211021093631
/
pushlog to previous
|
--- a/accessible/generic/LocalAccessible.cpp +++ b/accessible/generic/LocalAccessible.cpp @@ -641,22 +641,50 @@ nsRect LocalAccessible::ParentRelativeBo if (context->GetHitRegionRect(mContent->AsElement(), bounds)) { return bounds; } } } } } - if (mParent) { - boundingFrame = mParent->GetFrame(); + // We need to find a frame to make our bounds relative to. We'll store this + // in `boundingFrame`. Ultimately, we'll create screen-relative coordinates + // by summing the x, y offsets of our ancestors' bounds in + // RemoteAccessibleBase::Bounds(), so it is important that our bounding + // frame have a corresponding accessible. + if (IsDoc() && + nsCoreUtils::IsTopLevelContentDocInProcess(AsDoc()->DocumentNode())) { + // Tab documents and OOP iframe docs won't have ancestor accessibles with + // frames. We'll use their presshell root frame instead. + // XXX bug 1736635: Should DocAccessibles return their presShell frame on + // GetFrame()? + boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame); + } + + // Iterate through ancestors to find one with a frame. + LocalAccessible* parent = mParent; + while (parent && !boundingFrame) { + if (parent->IsDoc()) { + // If we find a doc accessible, use its presshell's root frame + // (since doc accessibles themselves don't have frames). + boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame); + break; + } + + if ((boundingFrame = parent->GetFrame())) { + // Otherwise, if the parent has a frame, use that + break; + } + + parent = parent->LocalParent(); } if (!boundingFrame) { - // if we can't get the bounding frame, use the pres shell root + MOZ_ASSERT_UNREACHABLE("No ancestor with frame?"); boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame); } nsRect unionRect = nsLayoutUtils::GetAllInFlowRectsUnion( frame, boundingFrame, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS); if (unionRect.IsEmpty()) { // If we end up with a 0x0 rect from above (or one with negative
--- a/accessible/tests/browser/bounds/browser.ini +++ b/accessible/tests/browser/bounds/browser.ini @@ -9,8 +9,9 @@ support-files = [browser_test_resolution.js] skip-if = e10s && os == 'win' # bug 1372296 [browser_test_zoom.js] skip-if = webrender # Bug 1734271 [browser_test_zoom_text.js] https_first_disabled = true skip-if = e10s && os == 'win' # bug 1372296 [browser_zero_area.js] +[browser_test_display_contents.js]
new file mode 100644 --- /dev/null +++ b/accessible/tests/browser/bounds/browser_test_display_contents.js @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* import-globals-from ../../mochitest/layout.js */ + +async function testContentBounds(browser, acc) { + let [ + expectedX, + expectedY, + expectedWidth, + expectedHeight, + ] = await getContentBoundsForDOMElm(browser, getAccessibleDOMNodeID(acc)); + + let contentDPR = await getContentDPR(browser); + let [x, y, width, height] = getBounds(acc, contentDPR); + let prettyAccName = prettyName(acc); + is(x, expectedX, "Wrong x coordinate of " + prettyAccName); + is(y, expectedY, "Wrong y coordinate of " + prettyAccName); + is(width, expectedWidth, "Wrong width of " + prettyAccName); + ok(height >= expectedHeight, "Wrong height of " + prettyAccName); +} + +async function runTests(browser, accDoc) { + let p = findAccessibleChildByID(accDoc, "div"); + let p2 = findAccessibleChildByID(accDoc, "p"); + + await testContentBounds(browser, p); + await testContentBounds(browser, p2); +} + +/** + * Test accessible bounds for accs with display:contents + */ +addAccessibleTask( + ` + <div id="div">before + <ul id="ul" style="display: contents;"> + <li id="li" style="display: contents;"> + <p id="p">item</p> + </li> + </ul> + </div>`, + runTests, + { iframe: true, remoteIframe: true } +);
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -676,16 +676,50 @@ pref("browser.tabs.tooltipsShowPidAndAct pref("security.allow_eval_with_system_principal", false); pref("security.allow_eval_in_parent_process", false); pref("security.allow_parent_unrestricted_js_loads", false); // Unload tabs when available memory is running low pref("browser.tabs.unloadOnLowMemory", true); +#if defined(XP_MACOSX) + // During low memory periods, poll with this frequency (milliseconds) + // until memory is no longer low. Changes to the pref take effect immediately. + // Browser restart not required. Chosen to be consistent with the windows + // implementation, but otherwise the 10s value is arbitrary. + pref("browser.lowMemoryPollingIntervalMS", 10000); + + // Pref to control the reponse taken on macOS when the OS is under memory + // pressure. Changes to the pref take effect immediately. Browser restart not + // required. The pref value is a bitmask: + // 0x0: No response (other than recording for telemetry, crash reporting) + // 0x1: Use the tab unloading feature to reduce memory use. Requires that + // the above "browser.tabs.unloadOnLowMemory" pref be set to true for tab + // unloading to occur. + // 0x2: Issue the internal "memory-pressure" notification to reduce memory use + // 0x3: Both 0x1 and 0x2. + #if defined(NIGHTLY_BUILD) + pref("browser.lowMemoryResponseMask", 3); + #else + pref("browser.lowMemoryResponseMask", 0); + #endif + + // Controls which macOS memory-pressure level triggers the browser low memory + // response. Changes to the pref take effect immediately. Browser restart not + // required. By default, use the "critical" level as that occurs after "warn" + // and we only want to trigger the low memory reponse when necessary. + // The macOS system memory-pressure level is either none, "warn", or + // "critical". The OS notifies the browser when the level changes. A false + // value for the pref indicates the low memory response should occur when + // reaching the "critical" level. A true value indicates the response should + // occur when reaching the "warn" level. + pref("browser.lowMemoryResponseOnWarn", false); +#endif + pref("browser.ctrlTab.sortByRecentlyUsed", false); // By default, do not export HTML at shutdown. // If true, at shutdown the bookmarks in your menu and toolbar will // be exported as HTML to the bookmarks.html file. pref("browser.bookmarks.autoExportHTML", false); // The maximum number of daily bookmark backups to
--- a/browser/base/content/browser-fullScreenAndPointerLock.js +++ b/browser/base/content/browser-fullScreenAndPointerLock.js @@ -370,27 +370,22 @@ var FullScreen = { Cu.reportError("Tried to shift the toolbar by a non-numeric distance."); return; } // shiftSize is sent from Cocoa widget code as a very precise double. We // don't need that kind of precision in our CSS. shiftSize = shiftSize.toFixed(2); let toolbox = document.getElementById("navigator-toolbox"); - let browserEl = document.getElementById("browser"); if (shiftSize > 0) { toolbox.style.setProperty("transform", `translateY(${shiftSize}px)`); toolbox.style.setProperty("z-index", "2"); - toolbox.style.setProperty("position", "relative"); - browserEl.style.setProperty("position", "relative"); } else { toolbox.style.removeProperty("transform"); toolbox.style.removeProperty("z-index"); - toolbox.style.removeProperty("position"); - browserEl.style.removeProperty("position"); } }, handleEvent(event) { switch (event.type) { case "willenterfullscreen": this.willToggle(true); break;
--- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -231,17 +231,17 @@ panelview[mainview] > .panel-header { @supports -moz-bool-pref("browser.tabs.hideThrobber") { .tab-throbber { display: none !important; } } %endif #tabbrowser-tabs[positionpinnedtabs] > #tabbrowser-arrowscrollbox > .tabbrowser-tab[pinned] { - position: fixed !important; + position: absolute !important; display: block; } #tabbrowser-tabs[movingtab] > #tabbrowser-arrowscrollbox > .tabbrowser-tab[selected], #tabbrowser-tabs[movingtab] > #tabbrowser-arrowscrollbox > .tabbrowser-tab[multiselected] { position: relative; z-index: 2; pointer-events: none; /* avoid blocking dragover events on scroll buttons */
--- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -4098,16 +4098,17 @@ BrowserGlue.prototype = { win.document.l10n.setAttributes( message, "restore-session-startup-suggestion-message" ); const buttons = [ { "l10n-id": "restore-session-startup-suggestion-button", + primary: true, callback: () => { win.PanelUI.selectAndMarkItem([ "appMenu-history-button", "appMenu-restoreSession", ]); }, }, ];
--- a/browser/components/newtab/content-src/components/TopSites/_TopSites.scss +++ b/browser/components/newtab/content-src/components/TopSites/_TopSites.scss @@ -292,16 +292,20 @@ .title { visibility: hidden; } } } .edit-topsites-wrapper { + .top-site-inner > .top-site-button > .tile { + border: 1px solid var(--newtab-border-color); + } + .modal { box-shadow: $shadow-secondary; left: 0; margin: 0 auto; max-height: calc(100% - 40px); position: fixed; right: 0; top: 40px;
--- a/browser/components/newtab/css/activity-stream-linux.css +++ b/browser/components/newtab/css/activity-stream-linux.css @@ -755,16 +755,19 @@ main.has-snippet { } .top-site-outer.dragged .tile *, .top-site-outer.dragged .tile::before { display: none; } .top-site-outer.dragged .title { visibility: hidden; } +.edit-topsites-wrapper .top-site-inner > .top-site-button > .tile { + border: 1px solid var(--newtab-border-color); +} .edit-topsites-wrapper .modal { box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2); left: 0; margin: 0 auto; max-height: calc(100% - 40px); position: fixed; right: 0; top: 40px;
--- a/browser/components/newtab/css/activity-stream-mac.css +++ b/browser/components/newtab/css/activity-stream-mac.css @@ -759,16 +759,19 @@ main.has-snippet { } .top-site-outer.dragged .tile *, .top-site-outer.dragged .tile::before { display: none; } .top-site-outer.dragged .title { visibility: hidden; } +.edit-topsites-wrapper .top-site-inner > .top-site-button > .tile { + border: 1px solid var(--newtab-border-color); +} .edit-topsites-wrapper .modal { box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2); left: 0; margin: 0 auto; max-height: calc(100% - 40px); position: fixed; right: 0; top: 40px;
--- a/browser/components/newtab/css/activity-stream-windows.css +++ b/browser/components/newtab/css/activity-stream-windows.css @@ -755,16 +755,19 @@ main.has-snippet { } .top-site-outer.dragged .tile *, .top-site-outer.dragged .tile::before { display: none; } .top-site-outer.dragged .title { visibility: hidden; } +.edit-topsites-wrapper .top-site-inner > .top-site-button > .tile { + border: 1px solid var(--newtab-border-color); +} .edit-topsites-wrapper .modal { box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2); left: 0; margin: 0 auto; max-height: calc(100% - 40px); position: fixed; right: 0; top: 40px;
--- a/browser/components/search/BrowserSearchTelemetry.jsm +++ b/browser/components/search/BrowserSearchTelemetry.jsm @@ -29,16 +29,17 @@ XPCOMUtils.defineLazyModuleGetters(this, // browser.search.adclicks.* const KNOWN_SEARCH_SOURCES = new Map([ ["abouthome", "about_home"], ["contextmenu", "contextmenu"], ["newtab", "about_newtab"], ["searchbar", "searchbar"], ["system", "system"], ["urlbar", "urlbar"], + ["urlbar-handoff", "urlbar_handoff"], ["urlbar-searchmode", "urlbar_searchmode"], ["webextension", "webextension"], ]); /** * This class handles saving search telemetry related to the url bar, * search bar and other areas as per the sources above. */ @@ -199,16 +200,17 @@ class BrowserSearchTelemetryHandler { histogram.add(countIdSource); } // Dispatch the search signal to other handlers. switch (source) { case "urlbar": case "searchbar": case "urlbar-searchmode": + case "urlbar-handoff": this._handleSearchAndUrlbar(browser, engine, source, details); break; case "abouthome": case "newtab": this._recordSearch(browser, engine, details.url, source, "enter"); break; default: this._recordSearch(browser, engine, details.url, source);
--- a/browser/components/search/docs/telemetry.rst +++ b/browser/components/search/docs/telemetry.rst @@ -46,27 +46,29 @@ SEARCH_COUNTS - SAP usage keywords refer to bookmarks. We expect no results for this SAP in Firefox 83+, since urlbar-searchmode replaces it. - ``abouthome`` - ``contextmenu`` - ``newtab`` - ``searchbar`` - ``system`` - ``urlbar`` Except aliases and search mode. + - ``urlbar_handoff`` Used when searching from about:newtab. - ``urlbar-searchmode`` Used when the Urlbar is in search mode. - ``webextension`` browser.engagement.navigation.* These keyed scalars track search through different SAPs, for example the urlbar is tracked by ``browser.engagement.navigation.urlbar``. It counts loads triggered in a subsession from the specified SAP, broken down by the originating action. Possible SAPs are: - ``urlbar`` Except search mode. + - ``urlbar_handoff`` Used when searching from about:newtab. - ``urlbar_searchmode`` Used when the Urlbar is in search mode. - ``searchbar`` - ``about_home`` - ``about_newtab`` - ``contextmenu`` - ``webextension`` - ``system`` Indicates a search from the command line. @@ -110,16 +112,17 @@ browser.search.content.* These keyed scalar track counts of SERP page loads. The key format is ``<provider>:[tagged|tagged-follow-on|organic]:[<code>|none]``. These will eventually replace the SEARCH_COUNTS - SERP results. They are broken down by the originating SAP where known: - ``urlbar`` Except search mode. + - ``urlbar_handoff`` Used when searching from about:newtab. - ``urlbar_searchmode`` Used when the Urlbar is in search mode. - ``searchbar`` - ``about_home`` - ``about_newtab`` - ``contextmenu`` - ``webextension`` - ``system`` Indicates a search from the command line. - ``tabhistory`` Indicates a search was counted as a result of the user loading it from the tab history.
--- a/browser/components/search/test/browser/browser_search_telemetry_sources.js +++ b/browser/components/search/test/browser/browser_search_telemetry_sources.js @@ -67,17 +67,25 @@ async function waitForIdle() { SearchTestUtils.init(this); UrlbarTestUtils.init(this); add_task(async function setup() { SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); await waitForIdle(); await SpecialPowers.pushPrefEnv({ - set: [["browser.urlbar.suggest.searches", true]], + set: [ + ["browser.urlbar.suggest.searches", true], + [ + "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar", + true, + ], + // Ensure to add search suggestion telemetry as search_suggestion not search_formhistory. + ["browser.urlbar.maxHistoricalSearchSuggestions", 0], + ], }); // Enable local telemetry recording for the duration of the tests. let oldCanRecord = Services.telemetry.canRecordExtended; Services.telemetry.canRecordExtended = true; Services.prefs.setBoolPref("browser.search.log", true); let currentEngineName = (await Services.search.getDefault()).name; @@ -185,16 +193,60 @@ add_task(async function test_source_urlb return tab; }, async () => { BrowserTestUtils.removeTab(tab); } ); }); +add_task(async function test_source_urlbar_handoff() { + let tab; + await track_ad_click( + "urlbar-handoff", + "urlbar_handoff", + async () => { + tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + BrowserTestUtils.loadURI(tab.linkedBrowser, "about:newtab"); + await BrowserTestUtils.browserStopped(tab.linkedBrowser, "about:newtab"); + + info("Focus on search input in newtab content"); + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + const searchInput = content.document.querySelector(".fake-editable"); + searchInput.click(); + }); + + info("Get suggestions"); + for (const c of "searchSuggestion".split("")) { + EventUtils.synthesizeKey(c); + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + await new Promise(r => setTimeout(r, 50)); + } + await TestUtils.waitForCondition(async () => { + const index = await getFirstSuggestionIndex(); + return index >= 0; + }, "Wait until suggestions are ready"); + + let idx = await getFirstSuggestionIndex(); + Assert.greaterOrEqual(idx, 0, "there should be a first suggestion"); + const onLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + while (idx--) { + EventUtils.sendKey("down"); + } + EventUtils.sendKey("return"); + await onLoaded; + + return tab; + }, + async () => { + BrowserTestUtils.removeTab(tab); + } + ); +}); + add_task(async function test_source_searchbar() { let tab; await track_ad_click( "searchbar", "searchbar", async () => { tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
--- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -4203,16 +4203,23 @@ var SessionStoreInternal = { ); } // Because newClosedTabsData are put in first, we need to // copy also the _lastClosedTabGroupCount. this._windows[ aWindow.__SSi ]._lastClosedTabGroupCount = newLastClosedTabGroupCount; + if (!this._isWindowLoaded(aWindow)) { + // from now on, the data will come from the actual window + delete this._statesToRestore[WINDOW_RESTORE_IDS.get(aWindow)]; + WINDOW_RESTORE_IDS.delete(aWindow); + delete this._windows[aWindow.__SSi]._restoring; + } + // Restore tabs, if any. if (winData.tabs.length) { this.restoreTabs(aWindow, tabs, winData.tabs, selectTab); } // set smoothScroll back to the original value arrowScrollbox.smoothScroll = smoothScroll; @@ -4397,23 +4404,16 @@ var SessionStoreInternal = { * @param aSelectTab * Index of the tab to select. This is a 1-based index where "1" * indicates the first tab should be selected, and "0" indicates that * the currently selected tab will not be changed. */ restoreTabs(aWindow, aTabs, aTabData, aSelectTab) { var tabbrowser = aWindow.gBrowser; - if (!this._isWindowLoaded(aWindow)) { - // from now on, the data will come from the actual window - delete this._statesToRestore[WINDOW_RESTORE_IDS.get(aWindow)]; - WINDOW_RESTORE_IDS.delete(aWindow); - delete this._windows[aWindow.__SSi]._restoring; - } - let numTabsToRestore = aTabs.length; let numTabsInWindow = tabbrowser.tabs.length; let tabsDataArray = this._windows[aWindow.__SSi].tabs; // Update the window state in case we shut down without being notified. // Individual tab states will be taken care of by restoreTab() below. if (numTabsInWindow == numTabsToRestore) { // Remove all previous tab data.
--- a/browser/components/sessionstore/test/browser.ini +++ b/browser/components/sessionstore/test/browser.ini @@ -96,16 +96,17 @@ https_first_disabled = true https_first_disabled = true skip-if = !e10s || !crashreporter || verify [browser_formdata_max_size.js] [browser_multiple_select_after_load.js] [browser_pinned_tabs.js] skip-if = debug || ccov # Bug 1625525 [browser_restore_srcdoc.js] +[browser_restore_tabless_window.js] [browser_unrestored_crashedTabs.js] skip-if = !e10s || !crashreporter [browser_revive_crashed_bg_tabs.js] https_first_disabled = true skip-if = !e10s || !crashreporter [browser_dying_cache.js]
new file mode 100644 --- /dev/null +++ b/browser/components/sessionstore/test/browser_restore_tabless_window.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * It's possible for us to restore windows without tabs, + * when on Windows/Linux the user closes the last tab as + * a means of closing the last window. That last tab + * would appear as a closed tab in session state for the + * window, with no open tabs (so the state would be resumed + * as showing the homepage). See bug 490136 for context. + * This test checks that in this case, the resulting window + * is functional and indeed has the required previously + * closed tabs available. + */ +add_task(async function test_restoring_tabless_window() { + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let newState = { + windows: [ + { + tabs: [], + _closedTabs: [ + { + state: { entries: [{ url: "about:" }] }, + title: "About:", + }, + ], + }, + ], + }; + + await setWindowState(newWin, newState, true); + let tab = await BrowserTestUtils.openNewForegroundTab( + newWin.gBrowser, + "https://example.com" + ); + await TabStateFlusher.flush(tab.linkedBrowser); + let receivedState = SessionStore.getWindowState(newWin); + let { tabs, selected } = receivedState.windows[0]; + is(tabs.length, 2, "Should have two tabs"); + is(selected, 2, "Should have selected the new tab"); + ok( + tabs[1]?.entries.some(e => e.url == "https://example.com/"), + "Should have found the new URL" + ); + + let closedTabData = SessionStore.getClosedTabData(newWin); + is(closedTabData.length, 1, "Should have found 1 closed tab"); + is( + closedTabData[0]?.state.entries[0].url, + "about:", + "Should have found the right URL for the closed tab" + ); + await BrowserTestUtils.closeWindow(newWin); +});
--- a/browser/components/tabunloader/content/aboutUnloads.js +++ b/browser/components/tabunloader/content/aboutUnloads.js @@ -3,17 +3,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { TabUnloader } = ChromeUtils.import( "resource:///modules/TabUnloader.jsm" ); async function refreshData() { - const sortedTabs = await TabUnloader.getSortedTabs(); + const sortedTabs = await TabUnloader.getSortedTabs(null); const tabTable = document.querySelector(".tab-table > tbody"); const getHost = uri => { try { return uri?.host; } catch (e) { return uri?.spec; } }; @@ -109,17 +109,17 @@ async function refreshData() { tabTable.appendChild(fragmentRows); updateTimestamp(); } async function onLoad() { document .getElementById("button-unload") .addEventListener("click", async () => { - await TabUnloader.unloadLeastRecentlyUsedTab(); + await TabUnloader.unloadLeastRecentlyUsedTab(null); await refreshData(); }); await refreshData(); } try { document.addEventListener("DOMContentLoaded", onLoad, { once: true }); } catch (ex) {
--- a/browser/components/urlbar/UrlbarInput.jsm +++ b/browser/components/urlbar/UrlbarInput.jsm @@ -679,16 +679,17 @@ class UrlbarInput { * * @param {string} searchString * @param {nsISearchEngine} [searchEngine] * Optional. If included and the right prefs are set, we will enter search * mode when handing `searchString` from the fake input to the Urlbar. * */ handoff(searchString, searchEngine) { + this._isHandoffSession = true; if (UrlbarPrefs.get("shouldHandOffToSearchMode") && searchEngine) { this.search(searchString, { searchEngine, searchModeEntry: "handoff", }); } else { this.search(searchString); } @@ -2227,25 +2228,32 @@ class UrlbarInput { * @param {boolean} searchActionDetails.isFormHistory * True if this query was initiated from a form history result. * @param {string} searchActionDetails.url * The url this query was triggered with. */ _recordSearch(engine, event, searchActionDetails = {}) { const isOneOff = this.view.oneOffSearchButtons.eventTargetIsAOneOff(event); - BrowserSearchTelemetry.recordSearch( - this.window.gBrowser.selectedBrowser, - engine, + let source = "urlbar"; + if (this._isHandoffSession) { + source = "urlbar-handoff"; + } else if (this.searchMode && !isOneOff) { // Without checking !isOneOff, we might record the string // oneoff_urlbar-searchmode in the SEARCH_COUNTS probe (in addition to // oneoff_urlbar and oneoff_searchbar). The extra information is not // necessary; the intent is the same regardless of whether the user is // in search mode when they do a key-modified click/enter on a one-off. - this.searchMode && !isOneOff ? "urlbar-searchmode" : "urlbar", + source = "urlbar-searchmode"; + } + + BrowserSearchTelemetry.recordSearch( + this.window.gBrowser.selectedBrowser, + engine, + source, { ...searchActionDetails, isOneOff } ); } /** * Shortens the given value, usually by removing http:// and trailing slashes. * * @param {string} val @@ -2728,16 +2736,18 @@ class UrlbarInput { this.searchMode?.entry != "oneoff" ) { this.controller.engagementEvent.discard(); } } _on_blur(event) { this.focusedViaMousedown = false; + this._isHandoffSession = false; + // We cannot count every blur events after a missed engagement as abandoment // because the user may have clicked on some view element that executes // a command causing a focus change. For example opening preferences from // the oneoff settings button. // For now we detect that case by discarding the event on command, but we // may want to figure out a more robust way to detect abandonment. this.controller.engagementEvent.record(event, { searchString: this._lastSearchString,
--- a/browser/components/urlbar/tests/browser/browser.ini +++ b/browser/components/urlbar/tests/browser/browser.ini @@ -323,16 +323,18 @@ support-files = [browser_urlbar_event_telemetry_noEvent.js] [browser_urlbar_selection.js] skip-if = (os == 'mac') # bug 1570474 [browser_urlbar_telemetry_dynamic.js] support-files = urlbarTelemetryUrlbarDynamic.css [browser_urlbar_telemetry_extension.js] tags = search-telemetry +[browser_urlbar_telemetry_handoff.js] +tags = search-telemetry [browser_urlbar_telemetry_places.js] https_first_disabled = true tags = search-telemetry [browser_urlbar_telemetry_quicksuggest.js] tags = search-telemetry [browser_urlbar_telemetry_remotetab.js] tags = search-telemetry [browser_urlbar_telemetry_searchmode.js]
new file mode 100644 --- /dev/null +++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_handoff.js @@ -0,0 +1,150 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { SearchSERPTelemetry } = ChromeUtils.import( + "resource:///modules/SearchSERPTelemetry.jsm" +); + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: /^https:\/\/example.com\/browser\/browser\/components\/search\/test\/browser\/searchTelemetry(?:Ad)?.html/, + queryParamName: "s", + codeParamName: "abc", + codePrefixes: ["ff"], + followOnParamNames: ["a"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad2?/], + }, +]; + +function getPageUrl(useAdPage = false) { + let page = useAdPage ? "searchTelemetryAd.html" : "searchTelemetry.html"; + return `https://example.com/browser/browser/components/search/test/browser/${page}`; +} + +// sharedData messages are only passed to the child on idle. Therefore +// we wait for a few idles to try and ensure the messages have been able +// to be passed across and handled. +async function waitForIdle() { + for (let i = 0; i < 10; i++) { + await new Promise(resolve => Services.tm.idleDispatchToMainThread(resolve)); + } +} + +add_task(async function setup() { + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); + + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar", + true, + ], + ], + }); + + await SearchTestUtils.installSearchExtension({ + search_url: getPageUrl(true), + search_url_get_params: "s={searchTerms}&abc=ff", + suggest_url: + "https://example.com/browser/browser/components/search/test/browser/searchSuggestionEngine.sjs", + suggest_url_get_params: "query={searchTerms}", + }); + const testEngine = Services.search.getEngineByName("Example"); + const originalEngine = await Services.search.getDefault(); + await Services.search.setDefault(testEngine); + + const oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + Services.telemetry.setEventRecordingEnabled("navigation", true); + + registerCleanupFunction(async function() { + await Services.search.setDefault(originalEngine); + await PlacesUtils.history.clear(); + + Services.telemetry.canRecordExtended = oldCanRecord; + Services.telemetry.setEventRecordingEnabled("navigation", false); + + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + + Services.telemetry.clearScalars(); + Services.telemetry.clearEvents(); + }); +}); + +add_task(async function test_search() { + Services.telemetry.clearScalars(); + Services.telemetry.clearEvents(); + + const histogram = TelemetryTestUtils.getAndClearKeyedHistogram( + "SEARCH_COUNTS" + ); + + info("Load about:newtab in new window"); + const newtab = "about:newtab"; + const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + BrowserTestUtils.loadURI(tab.linkedBrowser, newtab); + await BrowserTestUtils.browserStopped(tab.linkedBrowser, newtab); + + info("Focus on search input in newtab content"); + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + const searchInput = content.document.querySelector(".fake-editable"); + searchInput.click(); + }); + + info("Search and wait the result"); + const onLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + EventUtils.synthesizeKey("q"); + EventUtils.synthesizeKey("VK_RETURN"); + await onLoaded; + + info("Check the telemetries"); + await assertScalars([ + ["browser.engagement.navigation.urlbar_handoff", "search_enter", 1], + ]); + await assertHistogram(histogram, [ + ["example.in-content:sap:ff", 1], + ["other-Example.urlbar-handoff", 1], + ]); + TelemetryTestUtils.assertEvents( + [ + [ + "navigation", + "search", + "urlbar_handoff", + "enter", + { engine: "other-Example" }, + ], + ], + { category: "navigation", method: "search" } + ); + + BrowserTestUtils.removeTab(tab); +}); + +async function assertHistogram(histogram, expectedResults) { + await TestUtils.waitForCondition(() => { + const snapshot = histogram.snapshot(); + return expectedResults.every(([key]) => key in snapshot); + }, "Wait until the histogram has expected keys"); + + for (const [key, value] of expectedResults) { + TelemetryTestUtils.assertKeyedHistogramSum(histogram, key, value); + } +} + +async function assertScalars(expectedResults) { + await TestUtils.waitForCondition(() => { + const scalars = TelemetryTestUtils.getProcessScalars("parent", true); + return expectedResults.every(([scalarName]) => scalarName in scalars); + }, "Wait until the scalars have expected keyed scalars"); + + const scalars = TelemetryTestUtils.getProcessScalars("parent", true); + + for (const [scalarName, key, value] of expectedResults) { + TelemetryTestUtils.assertKeyedScalar(scalars, scalarName, key, value); + } +}
--- a/browser/installer/windows/nsis/shared.nsh +++ b/browser/installer/windows/nsis/shared.nsh @@ -508,16 +508,17 @@ ${AddAssociationIfNoneExist} ".pdf" "FirefoxHTML$5" ${AddAssociationIfNoneExist} ".oga" "FirefoxHTML$5" ${AddAssociationIfNoneExist} ".ogg" "FirefoxHTML$5" ${AddAssociationIfNoneExist} ".ogv" "FirefoxHTML$5" ${AddAssociationIfNoneExist} ".pdf" "FirefoxHTML$5" ${AddAssociationIfNoneExist} ".webm" "FirefoxHTML$5" ${AddAssociationIfNoneExist} ".svg" "FirefoxHTML$5" ${AddAssociationIfNoneExist} ".webp" "FirefoxHTML$5" + ${AddAssociationIfNoneExist} ".avif" "FirefoxHTML$5" ; An empty string is used for the 5th param because FirefoxHTML is not a ; protocol handler ${AddDisabledDDEHandlerValues} "FirefoxHTML$5" "$2" "$8,1" \ "${AppRegName} HTML Document" "" ${AddDisabledDDEHandlerValues} "FirefoxURL$5" "$2" "$8,1" "${AppRegName} URL" \ "true" @@ -599,16 +600,17 @@ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".htm" "FirefoxHTML$2" WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".html" "FirefoxHTML$2" WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".pdf" "FirefoxHTML$2" WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".shtml" "FirefoxHTML$2" WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".xht" "FirefoxHTML$2" WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".xhtml" "FirefoxHTML$2" WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".svg" "FirefoxHTML$2" WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".webp" "FirefoxHTML$2" + WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".avif" "FirefoxHTML$2" WriteRegStr ${RegKey} "$0\Capabilities\StartMenu" "StartMenuInternet" "$1" ; In the past, we supported ftp. Since we don't delete and re-create the ; entire key, we need to remove any existing registration. DeleteRegValue ${RegKey} "$0\Capabilities\URLAssociations" "ftp" WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "http" "FirefoxURL$2" @@ -668,16 +670,17 @@ ${WriteApplicationsSupportedType} ${RegKey} ".pjpeg" ${WriteApplicationsSupportedType} ${RegKey} ".pjp" ${WriteApplicationsSupportedType} ${RegKey} ".png" ${WriteApplicationsSupportedType} ${RegKey} ".rdf" ${WriteApplicationsSupportedType} ${RegKey} ".shtml" ${WriteApplicationsSupportedType} ${RegKey} ".svg" ${WriteApplicationsSupportedType} ${RegKey} ".webm" ${WriteApplicationsSupportedType} ${RegKey} ".webp" + ${WriteApplicationsSupportedType} ${RegKey} ".avif" ${WriteApplicationsSupportedType} ${RegKey} ".xht" ${WriteApplicationsSupportedType} ${RegKey} ".xhtml" ${WriteApplicationsSupportedType} ${RegKey} ".xml" ${EndIf} !macroend !define SetStartMenuInternet "!insertmacro SetStartMenuInternet" ; Add registry keys to support the Firefox 32 bit to 64 bit migration. These @@ -1617,16 +1620,18 @@ Function SetAsDefaultAppUserHKCU AppAssocReg::SetAppAsDefault "$R9" ".htm" "file" Pop $0 AppAssocReg::SetAppAsDefault "$R9" ".html" "file" Pop $0 AppAssocReg::SetAppAsDefault "$R9" ".shtml" "file" Pop $0 AppAssocReg::SetAppAsDefault "$R9" ".webp" "file" Pop $0 + AppAssocReg::SetAppAsDefault "$R9" ".avif" "file" + Pop $0 AppAssocReg::SetAppAsDefault "$R9" ".xht" "file" Pop $0 AppAssocReg::SetAppAsDefault "$R9" ".xhtml" "file" Pop $0 AppAssocReg::SetAppAsDefault "$R9" "http" "protocol" Pop $0 AppAssocReg::SetAppAsDefault "$R9" "https" "protocol" Pop $0
--- a/browser/installer/windows/nsis/uninstaller.nsi +++ b/browser/installer/windows/nsis/uninstaller.nsi @@ -491,16 +491,17 @@ Section "Uninstall" ${un.RegCleanFileHandler} ".xhtml" "FirefoxHTML-$AppUserModelID" ${un.RegCleanFileHandler} ".oga" "FirefoxHTML-$AppUserModelID" ${un.RegCleanFileHandler} ".ogg" "FirefoxHTML-$AppUserModelID" ${un.RegCleanFileHandler} ".ogv" "FirefoxHTML-$AppUserModelID" ${un.RegCleanFileHandler} ".pdf" "FirefoxHTML-$AppUserModelID" ${un.RegCleanFileHandler} ".webm" "FirefoxHTML-$AppUserModelID" ${un.RegCleanFileHandler} ".svg" "FirefoxHTML-$AppUserModelID" ${un.RegCleanFileHandler} ".webp" "FirefoxHTML-$AppUserModelID" + ${un.RegCleanFileHandler} ".avif" "FirefoxHTML-$AppUserModelID" SetShellVarContext all ; Set SHCTX to HKLM ${un.GetSecondInstallPath} "Software\Mozilla" $R9 ${If} $R9 == "false" SetShellVarContext current ; Set SHCTX to HKCU ${un.GetSecondInstallPath} "Software\Mozilla" $R9 ${EndIf}
--- a/browser/modules/TabUnloader.jsm +++ b/browser/modules/TabUnloader.jsm @@ -24,16 +24,20 @@ ChromeUtils.defineModuleGetter( // If there are only this many or fewer tabs open, just sort by weight, and close // the lowest tab. Otherwise, do a more intensive compuation that determines the // tabs to close based on memory and process use. const MIN_TABS_COUNT = 10; // Weight for non-discardable tabs. const NEVER_DISCARD = 100000; +// Default minimum inactive duration. Tabs that were accessed in the last +// period of this duration are not unloaded. +const kMinInactiveDurationInMs = 600000; // ten minutes + let criteriaTypes = [ ["isNonDiscardable", NEVER_DISCARD], ["isLoading", 8], ["usingPictureInPicture", NEVER_DISCARD], ["playingMedia", NEVER_DISCARD], ["usingWebRTC", NEVER_DISCARD], ["isPinned", 2], ]; @@ -77,16 +81,20 @@ let DefaultTabUnloaderMethods = { usingWebRTC(tab, weight) { return webrtcUI.browserHasStreams(tab.linkedBrowser) ? weight : 0; }, getMinTabCount() { return MIN_TABS_COUNT; }, + getNow() { + return Date.now(); + }, + *iterateTabs() { for (let win of Services.wm.getEnumerator("navigator:browser")) { for (let tab of win.gBrowser.tabs) { yield { tab, gBrowser: win.gBrowser }; } } }, @@ -150,17 +158,17 @@ var TabUnloader = { isDiscardable(tab) { if (!("weight" in tab)) { return false; } return tab.weight < NEVER_DISCARD; }, // This method is exposed on nsITabUnloader - async unloadTabAsync() { + async unloadTabAsync(minInactiveDuration = kMinInactiveDurationInMs) { const watcher = Cc["@mozilla.org/xpcom/memory-watcher;1"].getService( Ci.nsIAvailableMemoryWatcherBase ); if (!Services.prefs.getBoolPref("browser.tabs.unloadOnLowMemory", true)) { watcher.onUnloadAttemptCompleted(Cr.NS_ERROR_NOT_AVAILABLE); return; } @@ -169,28 +177,34 @@ var TabUnloader = { // Don't post multiple unloading requests. The situation may be solved // when the active unloading task is completed. Services.console.logStringMessage("Unloading a tab is in progress."); watcher.onUnloadAttemptCompleted(Cr.NS_ERROR_ABORT); return; } this._isUnloading = true; - const isTabUnloaded = await this.unloadLeastRecentlyUsedTab(); + const isTabUnloaded = await this.unloadLeastRecentlyUsedTab( + minInactiveDuration + ); this._isUnloading = false; watcher.onUnloadAttemptCompleted( isTabUnloaded ? Cr.NS_OK : Cr.NS_ERROR_NOT_AVAILABLE ); }, /** * Get a list of tabs that can be discarded. This list includes all tabs in * all windows and is sorted based on a weighting described below. * + * @param minInactiveDuration If this value is a number, tabs that were accessed + * in the last |minInactiveDuration| msec are not unloaded even if they + * are least-recently-used. + * * @param tabMethods an helper object with methods called by this algorithm. * * The algorithm used is: * 1. Sort all of the tabs by a base weight. Tabs with a higher weight, such as * those that are pinned or playing audio, will appear at the end. When two * tabs have the same weight, sort by the order in which they were last. * recently accessed Tabs that have a weight of NEVER_DISCARD are included in * the list, but will not be discarded. @@ -205,21 +219,34 @@ var TabUnloader = { * tab uses. * 5. Combine these weights to produce a final tab discard order, and discard the * first tab. If this fails, then discard the next tab in the list until no more * non-discardable tabs are found. * * The tabMethods are used so that unit tests can use false tab objects and * override their behaviour. */ - async getSortedTabs(tabMethods = DefaultTabUnloaderMethods) { + async getSortedTabs( + minInactiveDuration = kMinInactiveDurationInMs, + tabMethods = DefaultTabUnloaderMethods + ) { let tabs = []; + const now = tabMethods.getNow(); + let lowestWeight = 1000; for (let tab of tabMethods.iterateTabs()) { + if ( + typeof minInactiveDuration == "number" && + now - tab.tab.lastAccessed < minInactiveDuration + ) { + // Skip "fresh" tabs, which were accessed within the specified duration. + continue; + } + let weight = determineTabBaseWeight(tab, tabMethods); // Don't add tabs that have a weight of -1. if (weight != -1) { tab.weight = weight; tabs.push(tab); if (weight < lowestWeight) { lowestWeight = weight; @@ -274,18 +301,20 @@ var TabUnloader = { return tabs; }, /** * Select and discard one tab. * @returns true if a tab was unloaded, otherwise false. */ - async unloadLeastRecentlyUsedTab() { - let sortedTabs = await this.getSortedTabs(); + async unloadLeastRecentlyUsedTab( + minInactiveDuration = kMinInactiveDurationInMs + ) { + const sortedTabs = await this.getSortedTabs(minInactiveDuration); for (let tabInfo of sortedTabs) { if (!this.isDiscardable(tabInfo)) { // Since |sortedTabs| is sorted, once we see an undiscardable tab // no need to continue the loop. return false; }
--- a/browser/modules/test/browser/browser_TabUnloader.js +++ b/browser/modules/test/browser/browser_TabUnloader.js @@ -81,17 +81,17 @@ async function addWebRTCTab(win = window } async function pressure() { let tabDiscarded = BrowserTestUtils.waitForEvent( document, "TabBrowserDiscarded", true ); - TabUnloader.unloadTabAsync(); + TabUnloader.unloadTabAsync(null); return tabDiscarded; } function pressureAndObserve(aExpectedTopic) { const promise = new Promise(resolve => { const observer = { QueryInterface: ChromeUtils.generateQI([ "nsIObserver", @@ -101,22 +101,22 @@ function pressureAndObserve(aExpectedTop if (aTopicInner == aExpectedTopic) { Services.obs.removeObserver(observer, aTopicInner); resolve(aData); } }, }; Services.obs.addObserver(observer, aExpectedTopic); }); - TabUnloader.unloadTabAsync(); + TabUnloader.unloadTabAsync(null); return promise; } async function compareTabOrder(expectedOrder) { - let tabInfo = await TabUnloader.getSortedTabs(); + let tabInfo = await TabUnloader.getSortedTabs(null); is( tabInfo.length, expectedOrder.length, "right number of tabs in discard sort list" ); for (let idx = 0; idx < expectedOrder.length; idx++) { is(tabInfo[idx].tab, expectedOrder[idx], "index " + idx + " is incorrect"); @@ -209,17 +209,17 @@ add_task(async function test() { await pressure(); ok(!pinnedTab.linkedPanel, "unloaded a pinned tab"); await compareTabOrder([soundTab, tab0, pinnedSoundTab]); ok(pinnedSoundTab.soundPlaying, "tab is still playing sound"); // There are no unloadable tabs. - TabUnloader.unloadTabAsync(); + TabUnloader.unloadTabAsync(null); ok(soundTab.linkedPanel, "a tab playing sound is never unloaded"); const histogram = TelemetryTestUtils.getAndClearHistogram( "TAB_UNLOAD_TO_RELOAD" ); // It's possible that we're already in the memory-pressure state // and we may receive the "ongoing" message.
--- a/browser/modules/test/unit/test_TabUnloader.js +++ b/browser/modules/test/unit/test_TabUnloader.js @@ -36,16 +36,20 @@ let TestTabUnloaderMethods = { return /\bwebrtc\b/.test(tab.keywords) ? weight : 0; }, getMinTabCount() { // Use a low number for testing. return 3; }, + getNow() { + return 100; + }, + *iterateProcesses(tab) { for (let process of tab.process.split(",")) { yield Number(process); } }, async calculateMemoryUsage(processMap, tabs) { let memory = tabs[0].memory; @@ -129,16 +133,23 @@ let unloadTests = [ "3", "4 media", "5 media pinned", "6 selected", ], result: "2,0,3,5,1,4", }, { + // Since TestTabUnloaderMethods.getNow() returns 100 and the test + // passes minInactiveDuration = 0 to TabUnloader.getSortedTabs(), + // tab 200 and 300 are excluded from the result. + tabs: ["300", "10", "50", "100", "200"], + result: "1,2,3", + }, + { tabs: ["1", "2", "3", "4", "5", "6"], process: ["1", "2", "1", "1", "1", "1"], result: "1,0,2,3,4,5", }, { tabs: ["1", "2 selected", "3", "4", "5", "6"], process: ["1", "2", "1", "1", "1", "1"], result: "0,2,3,4,5,1", @@ -402,17 +413,20 @@ add_task(async function doTests() { gBrowser: globalBrowser, }; yield tab; } } TestTabUnloaderMethods.iterateTabs = iterateTabs; let expectedOrder = ""; - let sortedTabs = await TabUnloader.getSortedTabs(TestTabUnloaderMethods); + const sortedTabs = await TabUnloader.getSortedTabs( + 0, + TestTabUnloaderMethods + ); for (let tab of sortedTabs) { if (expectedOrder) { expectedOrder += ","; } expectedOrder += tab.tab.originalIndex; } Assert.equal(expectedOrder, test.result);
--- a/browser/themes/osx/browser.css +++ b/browser/themes/osx/browser.css @@ -26,18 +26,25 @@ --chrome-content-separator-color: rgba(0,0,0,.3); } :root[lwt-popup-brighttext] { --panel-separator-color: rgba(249,249,250,.1); --arrowpanel-field-background: rgba(12,12,13,.3); } +#browser { + /* #browser and #navigator-toolbox must have relative positions so that the + latter can slide over the former in fullscreen mode. */ + position: relative; +} + #navigator-toolbox { appearance: none; + position: relative; } #navigator-toolbox:not(:-moz-lwtheme) { background-color: window; } @media not (prefers-contrast) { #navigator-toolbox:not(:-moz-lwtheme) {
--- a/browser/themes/osx/customizableui/panelUI.css +++ b/browser/themes/osx/customizableui/panelUI.css @@ -25,17 +25,18 @@ panel[type="arrow"][side="right"] { } #downloadsPanel, #widget-overflow, #appMenu-popup, #customizationui-widget-panel, #identity-popup, #permission-popup, -#protections-popup { +#protections-popup, +#appMenu-notification-popup { margin-top: -1px; /* Overrides value from panelUI.inc.css */ } #BMB_bookmarksPopup { margin-top: -4px; /* Overrides value from panelUI.inc.css */ } .panel-subview-body {
--- a/browser/themes/shared/browser.inc.css +++ b/browser/themes/shared/browser.inc.css @@ -50,16 +50,17 @@ --autocomplete-popup-separator-color: color-mix(in srgb, currentColor 14%, transparent); --identity-box-margin-inline: 4px; --urlbar-box-bgcolor: var(--button-bgcolor); --urlbar-box-focus-bgcolor: var(--button-bgcolor); --urlbar-box-hover-bgcolor: var(--button-hover-bgcolor); --urlbar-box-active-bgcolor: var(--button-active-bgcolor); --urlbar-box-text-color: inherit; + --urlbar-box-hover-text-color: var(--urlbar-box-text-color); --urlbar-min-height: 32px; --urlbar-icon-fill-opacity: 0.9; --urlbar-icon-padding: 6px; /* (32px - 2px border - 2px padding - 16px icon) / 2 */ /* This should be used for icons and chiclets inside the input field. It makes the gap around them more uniform when they are close to the field edges */ --urlbar-icon-border-radius: calc(var(--toolbarbutton-border-radius) - 1px); --urlbar-popup-url-color: -moz-nativehyperlinktext; @@ -69,16 +70,20 @@ --lwt-brighttext-url-color: #00ddff; } @media (prefers-contrast) { :root { --autocomplete-popup-separator-color: color-mix(in srgb, currentColor 86%, transparent); --urlbar-icon-fill-opacity: 1; --checkbox-checked-border-color: var(--checkbox-checked-bgcolor); + + --urlbar-box-hover-bgcolor: SelectedItem; + --urlbar-box-active-bgcolor: SelectedItem; + --urlbar-box-hover-text-color: SelectedItemText; } } :root[uidensity=compact] { --urlbar-min-height: 26px; --urlbar-icon-padding: 3px; /* (26px - 2px border - 2px padding - 16px icon) / 2 */ }
--- a/browser/themes/shared/controlcenter/panel.inc.css +++ b/browser/themes/shared/controlcenter/panel.inc.css @@ -457,25 +457,27 @@ description#identity-popup-content-verif border-radius: 2px; padding: 0 var(--horizontal-padding); } .protections-popup-category:-moz-focusring, .protections-popup-category:hover, .protections-popup-footer-button:-moz-focusring, .protections-popup-footer-button:hover, +#protections-popup-not-blocking-section-why:hover, #protections-popup-show-report-stack:hover > .protections-popup-footer-button { - background-color: var(--arrowpanel-dimmed); + background-color: var(--panel-item-hover-bgcolor); outline: none; } .protections-popup-category:hover:active, .protections-popup-footer-button:hover:active, +#protections-popup-not-blocking-section-why:hover:active, #protections-popup-show-report-stack:hover:active > .protections-popup-footer-button { - background-color: var(--arrowpanel-dimmed-further); + background-color: var(--panel-item-active-bgcolor); } /* This subview could get filled with a lot of trackers, set a maximum size * and allow it to scroll vertically.*/ #protections-popup-socialblockView, #protections-popup-cookiesView, #protections-popup-trackersView { max-height: calc(600px + var(--height-offset)); @@ -584,21 +586,24 @@ description#identity-popup-content-verif margin: 0; } #protections-popup-not-blocking-section-why { margin: 0; } #protections-popup-not-blocking-section-why:hover { - background-color: var(--arrowpanel-dimmed); - outline: 4px solid var(--arrowpanel-dimmed); + outline: 4px solid var(--panel-item-hover-bgcolor); text-decoration: none; } +#protections-popup-not-blocking-section-why:hover:active { + outline-color: var(--panel-item-active-bgcolor); +} + .protections-popup-category.notFound { color: var(--panel-description-color); fill: var(--panel-description-color); } .protections-popup-category.notFound:hover { background: none; } @@ -1000,28 +1005,16 @@ description#identity-popup-content-verif padding: var(--arrowpanel-menuitem-padding); margin: var(--arrowpanel-menuitem-margin); } #protections-popup-footer-protection-type-label { margin-inline-end: 0; } -.protections-popup-category:hover, -.protections-popup-footer-button:hover, -#protections-popup-show-report-stack:hover > .protections-popup-footer-button { - background-color: var(--panel-item-hover-bgcolor); -} - -.protections-popup-category:hover:active, -.protections-popup-footer-button:hover:active, -#protections-popup-show-report-stack:hover:active > .protections-popup-footer-button { - background-color: var(--panel-item-active-bgcolor); -} - .protections-popup-category:focus-visible, .protections-popup-footer-button:focus-visible { box-shadow: var(--panelview-toolbarbutton-focus-box-shadow); background: none; } #blocked-popup-indicator-item, #geo-access-indicator-item {
--- a/browser/themes/shared/customizableui/panelUI.inc.css +++ b/browser/themes/shared/customizableui/panelUI.inc.css @@ -159,17 +159,18 @@ panelmultiview[transitioning] > .panel-v } #downloadsPanel, #widget-overflow, #appMenu-popup, #customizationui-widget-panel, #identity-popup, #permission-popup, -#protections-popup { +#protections-popup, +#appMenu-notification-popup { margin-top: -4px; } #BMB_bookmarksPopup { margin-top: -8px; } .panel-subview-body {
--- a/browser/themes/shared/identity-block/identity-block.inc.css +++ b/browser/themes/shared/identity-block/identity-block.inc.css @@ -69,36 +69,38 @@ #identity-box[pageproxystate="valid"].extensionPage > .identity-box-button, #urlbar-label-box { background-color: var(--urlbar-box-bgcolor); color: var(--urlbar-box-text-color); padding-inline: 8px; border-radius: var(--urlbar-icon-border-radius); } -#urlbar[focused="true"] #identity-box[pageproxystate="valid"].notSecureText > .identity-box-button, -#urlbar[focused="true"] #identity-box[pageproxystate="valid"].chromeUI > .identity-box-button, -#urlbar[focused="true"] #identity-box[pageproxystate="valid"].extensionPage > .identity-box-button, +#urlbar[focused="true"] #identity-box[pageproxystate="valid"].notSecureText > .identity-box-button:not(:hover, [open=true]), +#urlbar[focused="true"] #identity-box[pageproxystate="valid"].chromeUI > .identity-box-button:not(:hover, [open=true]), +#urlbar[focused="true"] #identity-box[pageproxystate="valid"].extensionPage > .identity-box-button:not(:hover, [open=true]), #urlbar[focused="true"] #urlbar-label-box { background-color: var(--urlbar-box-focus-bgcolor); } #identity-box[pageproxystate="valid"].notSecureText > .identity-box-button:hover:not([open]), #identity-box[pageproxystate="valid"].chromeUI > .identity-box-button:hover:not([open]), #identity-box[pageproxystate="valid"].extensionPage > .identity-box-button:hover:not([open]) { background-color: var(--urlbar-box-hover-bgcolor); + color: var(--urlbar-box-hover-text-color); } #identity-box[pageproxystate="valid"].notSecureText > .identity-box-button:hover:active, #identity-box[pageproxystate="valid"].notSecureText > .identity-box-button[open=true], #identity-box[pageproxystate="valid"].chromeUI > .identity-box-button:hover:active, #identity-box[pageproxystate="valid"].chromeUI > .identity-box-button[open=true], #identity-box[pageproxystate="valid"].extensionPage > .identity-box-button:hover:active, #identity-box[pageproxystate="valid"].extensionPage > .identity-box-button[open=true] { background-color: var(--urlbar-box-active-bgcolor); + color: var(--urlbar-box-hover-text-color); } #urlbar[searchmode]:not([focused="true"]) > #urlbar-input-container > #urlbar-search-mode-indicator, #urlbar[searchmode]:not([focused="true"]) > #urlbar-input-container > #urlbar-label-box { pointer-events: none; } #urlbar[searchmode]:not([focused="true"]) > #urlbar-input-container > #urlbar-search-mode-indicator > #urlbar-search-mode-indicator-close {
--- a/browser/themes/shared/urlbar-searchbar.inc.css +++ b/browser/themes/shared/urlbar-searchbar.inc.css @@ -309,30 +309,25 @@ height: 16px; -moz-context-properties: fill, fill-opacity; fill-opacity: 0.6; fill: currentColor; border-radius: var(--urlbar-icon-border-radius); } #urlbar-search-mode-indicator-close:hover { - background-color: hsla(0,0%,70%,.2); + background-color: var(--urlbar-box-hover-bgcolor); + color: var(--urlbar-box-hover-text-color); } #urlbar-search-mode-indicator-close:hover:active { - background-color: hsla(0,0%,70%,.3); + background-color: var(--urlbar-box-active-bgcolor); + color: var(--urlbar-box-hover-text-color); } -/* Use system colors for low/high contrast mode */ @media (prefers-contrast) { - #urlbar-search-mode-indicator { - background-color: SelectedItem; - outline-color: SelectedItem; - color: SelectedItemText; - } - #urlbar-search-mode-indicator-close { fill-opacity: 1.0; } } #urlbar-search-mode-indicator-title { padding-inline: 0 3px; } @@ -401,24 +396,26 @@ :root[uidensity=compact] #urlbar-go-button, :root[uidensity=compact] .search-go-button { margin-inline: 1px; } .urlbar-page-action:not([disabled]):hover, #urlbar-go-button:hover, .search-go-button:hover { - background-color: hsla(0,0%,70%,.2); + background-color: var(--urlbar-box-hover-bgcolor); + color: var(--urlbar-box-hover-text-color); } .urlbar-page-action:not([disabled])[open], .urlbar-page-action:not([disabled]):hover:active, #urlbar-go-button:hover:active, .search-go-button:hover:active { - background-color: hsla(0,0%,70%,.3); + background-color: var(--urlbar-box-active-bgcolor); + color: var(--urlbar-box-hover-text-color); } .urlbar-page-action:-moz-focusring { outline: var(--toolbarbutton-focus-outline); outline-offset: -2px; } #urlbar-go-button, @@ -589,26 +586,28 @@ padding: 3px 7px; border-radius: var(--urlbar-icon-border-radius); background-color: var(--urlbar-box-bgcolor); color: var(--urlbar-box-text-color); margin-block: calc((var(--urlbar-min-height) - 20px) / 2 - 1px /* border */ - var(--urlbar-container-padding)); overflow: hidden; } -#urlbar[focused="true"] #urlbar-zoom-button { +#urlbar[focused="true"] #urlbar-zoom-button:not(:hover) { background-color: var(--urlbar-box-focus-bgcolor); } #urlbar-zoom-button:hover { background-color: var(--urlbar-box-hover-bgcolor); + color: var(--urlbar-box-hover-text-color); } #urlbar-zoom-button:hover:active { background-color: var(--urlbar-box-active-bgcolor); + color: var(--urlbar-box-hover-text-color); } @keyframes urlbar-zoom-reset-pulse { 0% { transform: scale(0); } 75% { transform: scale(1.5);
--- a/build/docs/visualstudio.rst +++ b/build/docs/visualstudio.rst @@ -1,34 +1,23 @@ .. _build_visualstudio: ====================== Visual Studio Projects ====================== -The build system contains alpha support for generating Visual Studio -project files to aid with development. +The build system automatically generates Visual Studio project files to aid +with development, as part of a normal ``mach build`` from the command line. -To generate Visual Studio project files, you'll need to have a configured tree:: - - mach configure - -(If you have built recently, your tree is already configured.) +You can find the solution file at ``$OBJDIR/msvs/mozilla.sln``. -Then, simply generate the Visual Studio build backend:: - - mach build-backend -b VisualStudio +If you want to generate the project files before/without doing a full build, +running ``./mach configure && ./mach build-backend -b VisualStudio`` will do +so. -If all goes well, the path to the generated Solution (``.sln``) file should be -printed. You should be able to open that solution with Visual Studio 2010 or -newer. - -Currently, output is hard-coded to the Visual Studio 2010 format. If you open -the solution in a newer Visual Studio release, you will be prompted to upgrade -projects. Simply click through the wizard to do that. Structure of Solution ===================== The Visual Studio solution consists of hundreds of projects spanning thousands of files. To help with organization, the solution is divided into the following trees/folders: @@ -54,30 +43,18 @@ Libraries ``dom_base`` project. These projects don't do anything when built. If you build a project here, the *binaries* build target project is built. Updating Project Files ====================== -As you pull and update the source tree, your Visual Studio files may fall out -of sync with the build configuration. The tree should still build fine from -within Visual Studio. But source files may be missing and IntelliSense may not -have the proper build configuration. - -To account for this, you'll want to periodically regenerate the Visual Studio -project files. You can do this within Visual Studio by building the -``Build Targets :: visual-studio`` project or by running -``mach build-backend -b VisualStudio`` from the command line. - -Currently, regeneration rewrites the original project files. **If you've made -any customizations to the solution or projects, they will likely get -overwritten.** We would like to improve this user experience in the -future. +Either re-running ``./mach build`` or ``./mach build-backend -b VisualStudio`` +will update the Visual Studio files after the tree changes. Moving Project Files Around =========================== The produced Visual Studio solution and project files should be portable. If you want to move them to a non-default directory, they should continue to work from wherever they are. If they don't, please file a bug.
--- a/config/makefiles/rust.mk +++ b/config/makefiles/rust.mk @@ -90,25 +90,32 @@ rustflags_neon += -C target_feature=+neo endif endif rustflags_sancov = ifdef LIBFUZZER ifndef MOZ_TSAN ifndef FUZZING_JS_FUZZILLI # These options should match what is implicitly enabled for `clang -fsanitize=fuzzer` -# here: https://github.com/llvm/llvm-project/blob/release/8.x/clang/lib/Driver/SanitizerArgs.cpp#L354 +# here: https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/Driver/SanitizerArgs.cpp#L422 # # -sanitizer-coverage-inline-8bit-counters Increments 8-bit counter for every edge. # -sanitizer-coverage-level=4 Enable coverage for all blocks, critical edges, and indirect calls. # -sanitizer-coverage-trace-compares Tracing of CMP and similar instructions. # -sanitizer-coverage-pc-table Create a static PC table. # # In TSan builds, we must not pass any of these, because sanitizer coverage is incompatible with TSan. -rustflags_sancov += -Cpasses=sancov -Cllvm-args=-sanitizer-coverage-inline-8bit-counters -Cllvm-args=-sanitizer-coverage-level=4 -Cllvm-args=-sanitizer-coverage-trace-compares -Cllvm-args=-sanitizer-coverage-pc-table +# +# sancov legacy pass was removed in rustc 1.57 and replaced by sancov-module +ifeq (1,$(words $(filter 1.53.% 1.54.% 1.55.% 1.56.%,$(RUSTC_VERSION)))) +rustflags_sancov += -Cpasses=sancov +else +rustflags_sancov += -Cpasses=sancov-module +endif +rustflags_sancov += -Cllvm-args=-sanitizer-coverage-inline-8bit-counters -Cllvm-args=-sanitizer-coverage-level=4 -Cllvm-args=-sanitizer-coverage-trace-compares -Cllvm-args=-sanitizer-coverage-pc-table endif endif endif rustflags_override = $(MOZ_RUST_DEFAULT_FLAGS) $(rustflags_neon) ifdef DEVELOPER_OPTIONS # By default the Rust compiler will perform a limited kind of ThinLTO on each
--- a/devtools/client/webconsole/test/browser/browser_webconsole_cors_errors.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_cors_errors.js @@ -38,41 +38,46 @@ add_task(async function() { makeFaultyCorsCall("CORSDisabled"); message = await onCorsMessage; await checkCorsMessage(message, "CORSDisabled"); await pushPref("content.cors.disable", false); info("Test CORSPreflightDidNotSucceed"); onCorsMessage = waitForMessage( hud, - `CORS preflight response did not succeed` + `(Reason: CORS preflight response did not succeed). Status code: ` ); makeFaultyCorsCall("CORSPreflightDidNotSucceed"); message = await onCorsMessage; await checkCorsMessage(message, "CORSPreflightDidNotSucceed"); info("Test CORS did not succeed"); - onCorsMessage = waitForMessage(hud, "Reason: CORS request did not succeed"); + onCorsMessage = waitForMessage( + hud, + "(Reason: CORS request did not succeed). Status code: " + ); makeFaultyCorsCall("CORSDidNotSucceed"); message = await onCorsMessage; await checkCorsMessage(message, "CORSDidNotSucceed"); info("Test CORSExternalRedirectNotAllowed"); onCorsMessage = waitForMessage( hud, "Reason: CORS request external redirect not allowed" ); makeFaultyCorsCall("CORSExternalRedirectNotAllowed"); message = await onCorsMessage; await checkCorsMessage(message, "CORSExternalRedirectNotAllowed"); info("Test CORSMissingAllowOrigin"); onCorsMessage = waitForMessage( hud, - `Reason: CORS header ${quote("Access-Control-Allow-Origin")} missing` + `(Reason: CORS header ${quote( + "Access-Control-Allow-Origin" + )} missing). Status code: ` ); makeFaultyCorsCall("CORSMissingAllowOrigin"); message = await onCorsMessage; await checkCorsMessage(message, "CORSMissingAllowOrigin"); info("Test CORSMultipleAllowOriginNotAllowed"); onCorsMessage = waitForMessage( hud,
--- a/devtools/server/actors/errordocs.js +++ b/devtools/server/actors/errordocs.js @@ -140,27 +140,27 @@ const ErrorCategories = { }; const baseCorsErrorUrl = "https://developer.mozilla.org/docs/Web/HTTP/CORS/Errors/"; const corsParams = "?utm_source=devtools&utm_medium=firefox-cors-errors&utm_campaign=default"; const CorsErrorDocs = { CORSDisabled: "CORSDisabled", - CORSDidNotSucceed: "CORSDidNotSucceed", + CORSDidNotSucceed2: "CORSDidNotSucceed", CORSOriginHeaderNotAdded: "CORSOriginHeaderNotAdded", CORSExternalRedirectNotAllowed: "CORSExternalRedirectNotAllowed", CORSRequestNotHttp: "CORSRequestNotHttp", - CORSMissingAllowOrigin: "CORSMissingAllowOrigin", + CORSMissingAllowOrigin2: "CORSMissingAllowOrigin", CORSMultipleAllowOriginNotAllowed: "CORSMultipleAllowOriginNotAllowed", CORSAllowOriginNotMatchingOrigin: "CORSAllowOriginNotMatchingOrigin", CORSNotSupportingCredentials: "CORSNotSupportingCredentials", CORSMethodNotFound: "CORSMethodNotFound", CORSMissingAllowCredentials: "CORSMissingAllowCredentials", - CORSPreflightDidNotSucceed2: "CORSPreflightDidNotSucceed", + CORSPreflightDidNotSucceed3: "CORSPreflightDidNotSucceed", CORSInvalidAllowMethod: "CORSInvalidAllowMethod", CORSInvalidAllowHeader: "CORSInvalidAllowHeader", CORSMissingAllowHeaderFromPreflight2: "CORSMissingAllowHeaderFromPreflight", }; const baseStorageAccessPolicyErrorUrl = "https://developer.mozilla.org/docs/Mozilla/Firefox/Privacy/Storage_access_policy/Errors/"; const storageAccessPolicyParams =
--- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -251,16 +251,17 @@ #include "nsAttrValue.h" #include "nsAttrValueInlines.h" #include "nsBaseHashtable.h" #include "nsBidiUtils.h" #include "nsCRT.h" #include "nsCSSPropertyID.h" #include "nsCSSProps.h" #include "nsCSSPseudoElements.h" +#include "nsCSSRendering.h" #include "nsCanvasFrame.h" #include "nsCaseTreatment.h" #include "nsCharsetSource.h" #include "nsCommandManager.h" #include "nsCommandParams.h" #include "nsComponentManagerUtils.h" #include "nsContentCreatorFunctions.h" #include "nsContentList.h" @@ -391,16 +392,17 @@ #include "nsQueryObject.h" #include "nsRange.h" #include "nsRect.h" #include "nsRefreshDriver.h" #include "nsSandboxFlags.h" #include "nsSerializationHelper.h" #include "nsServiceManagerUtils.h" #include "nsStringFlags.h" +#include "nsStyleUtil.h" #include "nsStringIterator.h" #include "nsStyleSheetService.h" #include "nsStyleStruct.h" #include "nsTextNode.h" #include "nsUnicharUtils.h" #include "nsWrapperCache.h" #include "nsWrapperCacheInlines.h" #include "nsXPCOMCID.h" @@ -14447,16 +14449,98 @@ Element* Document::TopLayerPop(FunctionR // The top element of the stack is now an in-doc element. Return here. break; } } return removedElement; } +void Document::GetWireframe(bool aIncludeNodes, + Nullable<Wireframe>& aWireframe) { + using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions; + using FrameForPointOption = nsLayoutUtils::FrameForPointOption; + FlushPendingNotifications(FlushType::Layout); + + PresShell* shell = GetPresShell(); + if (!shell) { + return; + } + + nsPresContext* pc = shell->GetPresContext(); + if (!pc) { + return; + } + + auto& wireframe = aWireframe.SetValue(); + nsStyleUtil::GetSerializedColorValue(shell->GetCanvasBackground(), + wireframe.mCanvasBackground.Construct()); + + FrameForPointOptions options; + options.mBits += FrameForPointOption::IgnoreCrossDoc; + options.mBits += FrameForPointOption::IgnorePaintSuppression; + options.mBits += FrameForPointOption::OnlyVisible; + + AutoTArray<nsIFrame*, 32> frames; + const RelativeTo relativeTo{shell->GetRootFrame(), + mozilla::ViewportType::Layout}; + nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames, + options); + + // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or + // something perhaps, but seems hard / like it'd involve at least some extra + // copying around, since they don't outlive GetFramesForArea. + auto& rects = wireframe.mRects.Construct(); + if (!rects.SetCapacity(frames.Length(), fallible)) { + return; + } + for (nsIFrame* frame : frames) { + // Can't really fail because SetCapacity succeeded. + auto& taggedRect = *rects.AppendElement(fallible); + const auto r = + CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor( + frame, frame->GetRectRelativeToSelf(), relativeTo)); + if (aIncludeNodes) { + if (nsIContent* c = frame->GetContent()) { + taggedRect.mNode.Construct(c); + } + } + taggedRect.mRect.Construct(MakeRefPtr<DOMRectReadOnly>( + ToSupports(this), r.x, r.y, r.width, r.height)); + taggedRect.mType.Construct() = [&] { + if (frame->IsTextFrame()) { + nsStyleUtil::GetSerializedColorValue( + frame->StyleText()->mWebkitTextFillColor.CalcColor(frame), + taggedRect.mColor.Construct()); + return WireframeRectType::Text; + } + if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) { + return WireframeRectType::Image; + } + if (frame->IsThemed()) { + return WireframeRectType::Background; + } + bool drawImage = false; + bool drawColor = false; + const nscolor color = nsCSSRendering::DetermineBackgroundColor( + pc, frame->Style(), frame, drawImage, drawColor); + if (drawImage && + !frame->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) { + return WireframeRectType::Image; + } + if (drawColor) { + nsStyleUtil::GetSerializedColorValue(color, + taggedRect.mColor.Construct()); + return WireframeRectType::Background; + } + return WireframeRectType::Unknown; + }(); + } +} + Element* Document::GetTopLayerTop() { if (mTopLayer.IsEmpty()) { return nullptr; } uint32_t last = mTopLayer.Length() - 1; nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last])); NS_ASSERTION(element, "Should have a top layer element!"); NS_ASSERTION(element->IsInComposedDoc(),
--- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -3397,16 +3397,19 @@ class Document : public nsINode, void MozSetImageElement(const nsAString& aImageElementId, Element* aElement); nsIURI* GetDocumentURIObject() const; // Not const because all the fullscreen goop is not const const char* GetFullscreenError(CallerType); bool FullscreenEnabled(CallerType aCallerType) { return !GetFullscreenError(aCallerType); } + MOZ_CAN_RUN_SCRIPT void GetWireframe(bool aIncludeNodes, + Nullable<Wireframe>&); + Element* GetTopLayerTop(); // Return the fullscreen element in the top layer Element* GetUnretargetedFullScreenElement() const; bool Fullscreen() const { return !!GetUnretargetedFullScreenElement(); } already_AddRefed<Promise> ExitFullscreen(ErrorResult&); void ExitPointerLock() { PointerLockManager::Unlock(this); } void GetFgColor(nsAString& aFgColor); void SetFgColor(const nsAString& aFgColor);
--- a/dom/base/Selection.cpp +++ b/dom/base/Selection.cpp @@ -4,16 +4,17 @@ * 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/. */ /* * Implementation of mozilla::dom::Selection */ #include "mozilla/dom/Selection.h" +#include "mozilla/intl/Bidi.h" #include "mozilla/AccessibleCaretEventHub.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/Attributes.h" #include "mozilla/AutoCopyListener.h" #include "mozilla/AutoRestore.h" #include "mozilla/BasePrincipal.h" #include "mozilla/ContentIterator.h" @@ -380,17 +381,19 @@ bool Selection::IsEditorSelection() cons Nullable<int16_t> Selection::GetCaretBidiLevel( mozilla::ErrorResult& aRv) const { MOZ_ASSERT(mSelectionType == SelectionType::eNormal); if (!mFrameSelection) { aRv.Throw(NS_ERROR_NOT_INITIALIZED); return Nullable<int16_t>(); } - nsBidiLevel caretBidiLevel = mFrameSelection->GetCaretBidiLevel(); + mozilla::intl::Bidi::EmbeddingLevel caretBidiLevel = + static_cast<mozilla::intl::Bidi::EmbeddingLevel>( + mFrameSelection->GetCaretBidiLevel()); return (caretBidiLevel & BIDI_LEVEL_UNDEFINED) ? Nullable<int16_t>() : Nullable<int16_t>(caretBidiLevel); } void Selection::SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel, mozilla::ErrorResult& aRv) { MOZ_ASSERT(mSelectionType == SelectionType::eNormal); @@ -398,17 +401,17 @@ void Selection::SetCaretBidiLevel(const if (!mFrameSelection) { aRv.Throw(NS_ERROR_NOT_INITIALIZED); return; } if (aCaretBidiLevel.IsNull()) { mFrameSelection->UndefineCaretBidiLevel(); } else { mFrameSelection->SetCaretBidiLevelAndMaybeSchedulePaint( - aCaretBidiLevel.Value()); + mozilla::intl::Bidi::EmbeddingLevel(aCaretBidiLevel.Value())); } } /** * Test whether the supplied range points to a single table element. * Result is one of the TableSelectionMode constants. "None" means * a table element isn't selected. */ @@ -1352,17 +1355,18 @@ nsIFrame* Selection::GetPrimaryOrCaretFr if (!mFrameSelection) { return nullptr; } CaretAssociationHint hint = mFrameSelection->GetHint(); if (aVisual) { - nsBidiLevel caretBidiLevel = mFrameSelection->GetCaretBidiLevel(); + mozilla::intl::Bidi::EmbeddingLevel caretBidiLevel = + mFrameSelection->GetCaretBidiLevel(); return nsCaret::GetCaretFrameForNodeOffset( mFrameSelection, aContent, aOffset, hint, caretBidiLevel, /* aReturnUnadjustedFrame = */ nullptr, aOffsetUsed); } return nsFrameSelection::GetFrameForNodeOffset(aContent, aOffset, hint, aOffsetUsed); @@ -3293,19 +3297,20 @@ void Selection::Modify(const nsAString& } uint32_t focusOffset = FocusOffset(); CollapseInLimiter(focusNode, focusOffset); } // If the paragraph direction of the focused frame is right-to-left, // we may have to swap the direction of movement. if (nsIFrame* frame = GetPrimaryFrameForFocusNode(visual)) { - nsBidiDirection paraDir = nsBidiPresUtils::ParagraphDirection(frame); - - if (paraDir == NSBIDI_RTL && visual) { + mozilla::intl::Bidi::Direction paraDir = + nsBidiPresUtils::ParagraphDirection(frame); + + if (paraDir == mozilla::intl::Bidi::Direction::RTL && visual) { if (amount == eSelectBeginLine) { amount = eSelectEndLine; forward = !forward; } else if (amount == eSelectEndLine) { amount = eSelectBeginLine; forward = !forward; } } @@ -3469,36 +3474,38 @@ void Selection::SetStartAndEndInternal(I nsresult Selection::SelectionLanguageChange(bool aLangRTL) { if (!mFrameSelection) { return NS_ERROR_NOT_INITIALIZED; } RefPtr<nsFrameSelection> frameSelection = mFrameSelection; // if the direction of the language hasn't changed, nothing to do - nsBidiLevel kbdBidiLevel = aLangRTL ? NSBIDI_RTL : NSBIDI_LTR; + mozilla::intl::Bidi::EmbeddingLevel kbdBidiLevel = + aLangRTL ? mozilla::intl::Bidi::EmbeddingLevel::RTL() + : mozilla::intl::Bidi::EmbeddingLevel::LTR(); if (kbdBidiLevel == frameSelection->mKbdBidiLevel) { return NS_OK; } frameSelection->mKbdBidiLevel = kbdBidiLevel; nsIFrame* focusFrame = GetPrimaryFrameForFocusNode(false); if (!focusFrame) { return NS_ERROR_FAILURE; } auto [frameStart, frameEnd] = focusFrame->GetOffsets(); RefPtr<nsPresContext> context = GetPresContext(); - nsBidiLevel levelBefore, levelAfter; + mozilla::intl::Bidi::EmbeddingLevel levelBefore, levelAfter; if (!context) { return NS_ERROR_FAILURE; } - nsBidiLevel level = focusFrame->GetEmbeddingLevel(); + mozilla::intl::Bidi::EmbeddingLevel level = focusFrame->GetEmbeddingLevel(); int32_t focusOffset = static_cast<int32_t>(FocusOffset()); if ((focusOffset != frameStart) && (focusOffset != frameEnd)) // the cursor is not at a frame boundary, so the level of both the // characters (logically) before and after the cursor is equal to the frame // level levelBefore = levelAfter = level; else { // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find @@ -3506,36 +3513,40 @@ nsresult Selection::SelectionLanguageCha nsCOMPtr<nsIContent> focusContent = do_QueryInterface(GetFocusNode()); nsPrevNextBidiLevels levels = frameSelection->GetPrevNextBidiLevels(focusContent, focusOffset, false); levelBefore = levels.mLevelBefore; levelAfter = levels.mLevelAfter; } - if (IS_SAME_DIRECTION(levelBefore, levelAfter)) { + if (levelBefore.IsSameDirection(levelAfter)) { // if cursor is between two characters with the same orientation, changing // the keyboard language must toggle the cursor level between the level of // the character with the lowest level (if the new language corresponds to // the orientation of that character) and this level plus 1 (if the new // language corresponds to the opposite orientation) - if ((level != levelBefore) && (level != levelAfter)) + if ((level != levelBefore) && (level != levelAfter)) { level = std::min(levelBefore, levelAfter); - if (IS_SAME_DIRECTION(level, kbdBidiLevel)) + } + if (level.IsSameDirection(kbdBidiLevel)) { frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(level); - else - frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(level + 1); + } else { + frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint( + mozilla::intl::Bidi::EmbeddingLevel(level + 1)); + } } else { // if cursor is between characters with opposite orientations, changing the // keyboard language must change the cursor level to that of the adjacent // character with the orientation corresponding to the new language. - if (IS_SAME_DIRECTION(levelBefore, kbdBidiLevel)) + if (levelBefore.IsSameDirection(kbdBidiLevel)) { frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(levelBefore); - else + } else { frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(levelAfter); + } } // The caret might have moved, so invalidate the desired position // for future usages of up-arrow or down-arrow frameSelection->InvalidateDesiredCaretPos(); return NS_OK; }
--- a/dom/base/test/test_warning_for_blocked_cross_site_request.html +++ b/dom/base/test/test_warning_for_blocked_cross_site_request.html @@ -33,28 +33,28 @@ var tests = { xhr : { uri_test : "http://invalid", result : null, category: "CORSAllowOriginNotMatchingOrigin" }, font : { uri_test : "font_bad", result : null, - category: "CORSMissingAllowOrigin", + category: "CORSMissingAllowOrigin2", }, shape_outside : { uri_test : "bad_shape_outside", result : null, - category: "CORSMissingAllowOrigin", + category: "CORSMissingAllowOrigin2", ignore_windowID: true, }, mask_image : { uri_test : "bad_mask_image", result : null, - category: "CORSMissingAllowOrigin", + category: "CORSMissingAllowOrigin2", ignore_windowID: true, }, } function testsComplete() { for (var testName in tests) { var test = tests[testName]; if (test.result == null) {
--- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -7,16 +7,17 @@ #include "mozilla/gfx/Helpers.h" #include "nsXULElement.h" #include "nsMathUtils.h" #include "nsContentUtils.h" +#include "mozilla/intl/Bidi.h" #include "mozilla/PresShell.h" #include "mozilla/PresShellInlines.h" #include "mozilla/SVGImageContext.h" #include "mozilla/SVGObserverUtils.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/dom/GeneratePlaceholderCanvasData.h" #include "nsPresContext.h" @@ -3498,21 +3499,21 @@ struct MOZ_STACK_CLASS CanvasBidiProcess if (mMissingFonts) { mMissingFonts->Flush(); } } using ContextState = CanvasRenderingContext2D::ContextState; virtual void SetText(const char16_t* aText, int32_t aLength, - nsBidiDirection aDirection) override { + mozilla::intl::Bidi::Direction aDirection) override { mFontgrp->UpdateUserFonts(); // ensure user font generation is current // adjust flags for current direction run gfx::ShapedTextFlags flags = mTextRunFlags; - if (aDirection == NSBIDI_RTL) { + if (aDirection == mozilla::intl::Bidi::Direction::RTL) { flags |= gfx::ShapedTextFlags::TEXT_IS_RTL; } else { flags &= ~gfx::ShapedTextFlags::TEXT_IS_RTL; } mTextRun = mFontgrp->MakeTextRun( aText, aLength, mDrawTarget, mAppUnitsPerDevPixel, flags, nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts, mMissingFonts.get()); @@ -3868,17 +3869,19 @@ TextMetrics* CanvasRenderingContext2D::D aOp == TextDrawOperation::MEASURE; processor.mFontgrp = currentFontStyle; nscoord totalWidthCoord; // calls bidi algo twice since it needs the full text width and the // bounding boxes before rendering anything aError = nsBidiPresUtils::ProcessText( - textToDraw.get(), textToDraw.Length(), isRTL ? NSBIDI_RTL : NSBIDI_LTR, + textToDraw.get(), textToDraw.Length(), + isRTL ? mozilla::intl::Bidi::EmbeddingLevel::RTL() + : mozilla::intl::Bidi::EmbeddingLevel::LTR(), presShell->GetPresContext(), processor, nsBidiPresUtils::MODE_MEASURE, nullptr, 0, &totalWidthCoord, &mBidiEngine); if (aError.Failed()) { return nullptr; } float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel; @@ -4009,17 +4012,19 @@ TextMetrics* CanvasRenderingContext2D::D // save the previous bounding box gfxRect boundingBox = processor.mBoundingBox; // don't ever need to measure the bounding box twice processor.mDoMeasureBoundingBox = false; aError = nsBidiPresUtils::ProcessText( - textToDraw.get(), textToDraw.Length(), isRTL ? NSBIDI_RTL : NSBIDI_LTR, + textToDraw.get(), textToDraw.Length(), + isRTL ? mozilla::intl::Bidi::EmbeddingLevel::RTL() + : mozilla::intl::Bidi::EmbeddingLevel::LTR(), presShell->GetPresContext(), processor, nsBidiPresUtils::MODE_DRAW, nullptr, 0, nullptr, &mBidiEngine); if (aError.Failed()) { return nullptr; } mTarget->SetTransform(oldTransform);
--- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -4,28 +4,29 @@ #ifndef CanvasRenderingContext2D_h #define CanvasRenderingContext2D_h #include <vector> #include "mozilla/dom/BasicRenderingContext2D.h" #include "mozilla/dom/CanvasRenderingContext2DBinding.h" #include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/intl/Bidi.h" #include "mozilla/gfx/Rect.h" #include "mozilla/gfx/2D.h" #include "mozilla/Attributes.h" #include "mozilla/EnumeratedArray.h" #include "mozilla/RefPtr.h" #include "mozilla/SurfaceFromElementResult.h" #include "mozilla/UniquePtr.h" #include "FilterDescription.h" #include "gfx2DGlue.h" #include "nsICanvasRenderingContextInternal.h" -#include "nsBidi.h" #include "nsColor.h" +#include "nsIFrame.h" class gfxFontGroup; class nsGlobalWindowInner; class nsXULElement; namespace mozilla { class ErrorResult; class PresShell; @@ -792,17 +793,17 @@ class CanvasRenderingContext2D final : p RefPtr<Element> mElement; // Path of the hit region in the 2d context coordinate space (not user // space) RefPtr<gfx::Path> mPath; }; nsTArray<RegionInfo> mHitRegionsOptions; - nsBidi mBidiEngine; + mozilla::intl::Bidi mBidiEngine; /** * Returns true if a shadow should be drawn along with a * drawing operation. */ bool NeedToDrawShadow() { const ContextState& state = CurrentState();
--- a/dom/chrome-webidl/NetDashboard.webidl +++ b/dom/chrome-webidl/NetDashboard.webidl @@ -62,16 +62,17 @@ dictionary WebSocketDict { dictionary DnsCacheEntry { DOMString hostname = ""; sequence<DOMString> hostaddr; DOMString family = ""; double expiration = 0; boolean trr = false; DOMString originAttributesSuffix = ""; + DOMString flags = ""; }; [GenerateConversionToJS] dictionary DNSCacheDict { sequence<DnsCacheEntry> entries; }; [GenerateConversionToJS]
--- a/dom/locales/en-US/chrome/security/security.properties +++ b/dom/locales/en-US/chrome/security/security.properties @@ -5,27 +5,27 @@ # Mixed Content Blocker # LOCALIZATION NOTE: "%1$S" is the URI of the blocked mixed content resource BlockMixedDisplayContent = Blocked loading mixed display content “%1$S” BlockMixedActiveContent = Blocked loading mixed active content “%1$S” # CORS # LOCALIZATION NOTE: Do not translate "Access-Control-Allow-Origin", Access-Control-Allow-Credentials, Access-Control-Allow-Methods, Access-Control-Allow-Headers CORSDisabled=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS disabled). -CORSDidNotSucceed=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS request did not succeed). +CORSDidNotSucceed2=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS request did not succeed). Status code: %2$S. CORSOriginHeaderNotAdded=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Origin’ cannot be added). CORSExternalRedirectNotAllowed=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS request external redirect not allowed). CORSRequestNotHttp=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS request not http). -CORSMissingAllowOrigin=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). +CORSMissingAllowOrigin2=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: %2$S. CORSMultipleAllowOriginNotAllowed=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: Multiple CORS header ‘Access-Control-Allow-Origin’ not allowed). CORSAllowOriginNotMatchingOrigin=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘%2$S’). CORSNotSupportingCredentials=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘%1$S’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’). CORSMethodNotFound=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: Did not find method in CORS header ‘Access-Control-Allow-Methods’). CORSMissingAllowCredentials=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’). -CORSPreflightDidNotSucceed2=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS preflight response did not succeed). +CORSPreflightDidNotSucceed3=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS preflight response did not succeed). Status code: %2$S. CORSInvalidAllowMethod=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: invalid token ‘%2$S’ in CORS header ‘Access-Control-Allow-Methods’). CORSInvalidAllowHeader=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: invalid token ‘%2$S’ in CORS header ‘Access-Control-Allow-Headers’). CORSMissingAllowHeaderFromPreflight2=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: header ‘%2$S’ is not allowed according to header ‘Access-Control-Allow-Headers’ from CORS preflight response). # LOCALIZATION NOTE: Do not translate "Strict-Transport-Security", "HSTS", "max-age" or "includeSubDomains" STSUnknownError=Strict-Transport-Security: An unknown error occurred processing the header specified by the site. STSUntrustworthyConnection=Strict-Transport-Security: The connection to the site is untrustworthy, so the specified header was ignored. STSCouldNotParseHeader=Strict-Transport-Security: The site specified a header that could not be parsed successfully.
--- a/dom/media/MediaData.h +++ b/dom/media/MediaData.h @@ -458,17 +458,17 @@ class VideoData : public MediaData { // Creates a new VideoData containing a deep copy of aBuffer. May use // aContainer to allocate an Image to hold the copied data. static already_AddRefed<VideoData> CreateAndCopyData( const VideoInfo& aInfo, ImageContainer* aContainer, int64_t aOffset, const media::TimeUnit& aTime, const media::TimeUnit& aDuration, const YCbCrBuffer& aBuffer, bool aKeyframe, const media::TimeUnit& aTimecode, const IntRect& aPicture, - layers::KnowsCompositor* aAllocator = nullptr); + layers::KnowsCompositor* aAllocator); static already_AddRefed<VideoData> CreateAndCopyData( const VideoInfo& aInfo, ImageContainer* aContainer, int64_t aOffset, const media::TimeUnit& aTime, const media::TimeUnit& aDuration, const YCbCrBuffer& aBuffer, const YCbCrBuffer::Plane& aAlphaPlane, bool aKeyframe, const media::TimeUnit& aTimecode, const IntRect& aPicture);
--- a/dom/media/gmp/ChromiumCDMParent.cpp +++ b/dom/media/gmp/ChromiumCDMParent.cpp @@ -965,17 +965,17 @@ already_AddRefed<VideoData> ChromiumCDMP b.mYUVColorSpace = DefaultColorSpace({aFrame.mImageWidth(), aFrame.mImageHeight()}); gfx::IntRect pictureRegion(0, 0, aFrame.mImageWidth(), aFrame.mImageHeight()); RefPtr<VideoData> v = VideoData::CreateAndCopyData( mVideoInfo, mImageContainer, mLastStreamOffset, media::TimeUnit::FromMicroseconds(aFrame.mTimestamp()), media::TimeUnit::FromMicroseconds(aFrame.mDuration()), b, false, - media::TimeUnit::FromMicroseconds(-1), pictureRegion); + media::TimeUnit::FromMicroseconds(-1), pictureRegion, mKnowsCompositor); return v.forget(); } ipc::IPCResult ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus) { MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodeFailed(this=%p status=%" PRIu32 ")", @@ -1032,17 +1032,18 @@ void ChromiumCDMParent::ActorDestroy(Act if (mAbnormalShutdown && callback) { callback->Terminated(); } MaybeDisconnect(mAbnormalShutdown); } RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMParent::InitializeVideoDecoder( const gmp::CDMVideoDecoderConfig& aConfig, const VideoInfo& aInfo, - RefPtr<layers::ImageContainer> aImageContainer) { + RefPtr<layers::ImageContainer> aImageContainer, + RefPtr<layers::KnowsCompositor> aKnowsCompositor) { MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); if (mIsShutdown) { return MediaDataDecoder::InitPromise::CreateAndReject( MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, RESULT_DETAIL("ChromiumCDMParent is shutdown")), __func__); } @@ -1079,16 +1080,17 @@ RefPtr<MediaDataDecoder::InitPromise> Ch mMaxRefFrames = (aConfig.mCodec() == cdm::VideoCodec::kCodecH264) ? H264::HasSPS(aInfo.mExtraData) ? H264::ComputeMaxRefFrames(aInfo.mExtraData) : 16 : 0; mVideoDecoderInitialized = true; mImageContainer = aImageContainer; + mKnowsCompositor = aKnowsCompositor; mVideoInfo = aInfo; mVideoFrameBufferSize = bufferSize; return mInitVideoDecoderPromise.Ensure(__func__); } ipc::IPCResult ChromiumCDMParent::RecvOnDecoderInitDone( const uint32_t& aStatus) {
--- a/dom/media/gmp/ChromiumCDMParent.h +++ b/dom/media/gmp/ChromiumCDMParent.h @@ -83,17 +83,18 @@ class ChromiumCDMParent final : public P const nsCString& aMinHdcpVersion); RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample); // TODO: Add functions for clients to send data to CDM, and // a Close() function. RefPtr<MediaDataDecoder::InitPromise> InitializeVideoDecoder( const gmp::CDMVideoDecoderConfig& aConfig, const VideoInfo& aInfo, - RefPtr<layers::ImageContainer> aImageContainer); + RefPtr<layers::ImageContainer> aImageContainer, + RefPtr<layers::KnowsCompositor> aKnowsCompositor); RefPtr<MediaDataDecoder::DecodePromise> DecryptAndDecodeFrame( MediaRawData* aSample); RefPtr<MediaDataDecoder::FlushPromise> FlushVideoDecoder(); RefPtr<MediaDataDecoder::DecodePromise> Drain(); @@ -176,16 +177,17 @@ class ChromiumCDMParent final : public P nsTArray<RefPtr<DecryptJob>> mDecrypts; MozPromiseHolder<InitPromise> mInitPromise; MozPromiseHolder<MediaDataDecoder::InitPromise> mInitVideoDecoderPromise; MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise; RefPtr<layers::ImageContainer> mImageContainer; + RefPtr<layers::KnowsCompositor> mKnowsCompositor; VideoInfo mVideoInfo; uint64_t mLastStreamOffset = 0; MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushDecoderPromise; size_t mVideoFrameBufferSize = 0; // Count of the number of shmems in the set used to return decoded video
--- a/dom/media/platforms/agnostic/AOMDecoder.cpp +++ b/dom/media/platforms/agnostic/AOMDecoder.cpp @@ -206,20 +206,20 @@ RefPtr<MediaDataDecoder::DecodePromise> default: b.mYUVColorSpace = DefaultColorSpace({img->d_w, img->d_h}); break; } b.mColorRange = img->range == AOM_CR_FULL_RANGE ? ColorRange::FULL : ColorRange::LIMITED; RefPtr<VideoData> v; - v = VideoData::CreateAndCopyData(mInfo, mImageContainer, aSample->mOffset, - aSample->mTime, aSample->mDuration, b, - aSample->mKeyframe, aSample->mTimecode, - mInfo.ScaledImageRect(img->d_w, img->d_h)); + v = VideoData::CreateAndCopyData( + mInfo, mImageContainer, aSample->mOffset, aSample->mTime, + aSample->mDuration, b, aSample->mKeyframe, aSample->mTimecode, + mInfo.ScaledImageRect(img->d_w, img->d_h), nullptr); if (!v) { LOG("Image allocation error source %ux%u display %ux%u picture %ux%u", img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height, mInfo.mImage.width, mInfo.mImage.height); return DecodePromise::CreateAndReject( MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__); }
--- a/dom/media/platforms/agnostic/BlankDecoderModule.cpp +++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp @@ -64,19 +64,20 @@ already_AddRefed<MediaData> BlankVideoDa buffer.mPlanes[2].mData = frame.get(); buffer.mPlanes[2].mStride = (mFrameWidth + 1) / 2; buffer.mPlanes[2].mHeight = (mFrameHeight + 1) / 2; buffer.mPlanes[2].mWidth = (mFrameWidth + 1) / 2; buffer.mPlanes[2].mSkip = 0; buffer.mYUVColorSpace = gfx::YUVColorSpace::BT601; - return VideoData::CreateAndCopyData( - mInfo, mImageContainer, aSample->mOffset, aSample->mTime, - aSample->mDuration, buffer, aSample->mKeyframe, aSample->mTime, mPicture); + return VideoData::CreateAndCopyData(mInfo, mImageContainer, aSample->mOffset, + aSample->mTime, aSample->mDuration, + buffer, aSample->mKeyframe, + aSample->mTime, mPicture, nullptr); } BlankAudioDataCreator::BlankAudioDataCreator(uint32_t aChannelCount, uint32_t aSampleRate) : mFrameSum(0), mChannelCount(aChannelCount), mSampleRate(aSampleRate) {} already_AddRefed<MediaData> BlankAudioDataCreator::Create( MediaRawData* aSample) {
--- a/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp +++ b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp @@ -15,17 +15,18 @@ namespace mozilla { ChromiumCDMVideoDecoder::ChromiumCDMVideoDecoder( const GMPVideoDecoderParams& aParams, CDMProxy* aCDMProxy) : mCDMParent(aCDMProxy->AsChromiumCDMProxy()->GetCDMParent()), mConfig(aParams.mConfig), mCrashHelper(aParams.mCrashHelper), mGMPThread(GetGMPThread()), - mImageContainer(aParams.mImageContainer) {} + mImageContainer(aParams.mImageContainer), + mKnowsCompositor(aParams.mKnowsCompositor) {} ChromiumCDMVideoDecoder::~ChromiumCDMVideoDecoder() = default; static uint32_t ToCDMH264Profile(uint8_t aProfile) { switch (aProfile) { case 66: return cdm::VideoCodecProfile::kH264ProfileBaseline; case 77: @@ -84,20 +85,22 @@ RefPtr<MediaDataDecoder::InitPromise> Ch default: MOZ_ASSERT_UNREACHABLE("Should not have unrecognized encryption type"); break; } RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent; VideoInfo info = mConfig; RefPtr<layers::ImageContainer> imageContainer = mImageContainer; - return InvokeAsync( - mGMPThread, __func__, [cdm, config, info, imageContainer]() { - return cdm->InitializeVideoDecoder(config, info, imageContainer); - }); + RefPtr<layers::KnowsCompositor> knowsCompositor = mKnowsCompositor; + return InvokeAsync(mGMPThread, __func__, + [cdm, config, info, imageContainer, knowsCompositor]() { + return cdm->InitializeVideoDecoder( + config, info, imageContainer, knowsCompositor); + }); } nsCString ChromiumCDMVideoDecoder::GetDescriptionName() const { return "chromium cdm video decoder"_ns; } MediaDataDecoder::ConversionRequired ChromiumCDMVideoDecoder::NeedsConversion() const {
--- a/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h +++ b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h @@ -4,16 +4,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ChromiumCDMVideoDecoder_h_ #define ChromiumCDMVideoDecoder_h_ #include "ChromiumCDMParent.h" #include "PlatformDecoderModule.h" +#include "mozilla/layers/KnowsCompositor.h" namespace mozilla { class CDMProxy; struct GMPVideoDecoderParams; DDLoggedTypeDeclNameAndBase(ChromiumCDMVideoDecoder, MediaDataDecoder); @@ -35,15 +36,16 @@ class ChromiumCDMVideoDecoder private: ~ChromiumCDMVideoDecoder(); RefPtr<gmp::ChromiumCDMParent> mCDMParent; const VideoInfo mConfig; RefPtr<GMPCrashHelper> mCrashHelper; nsCOMPtr<nsISerialEventTarget> mGMPThread; RefPtr<layers::ImageContainer> mImageContainer; + RefPtr<layers::KnowsCompositor> mKnowsCompositor; MozPromiseHolder<InitPromise> mInitPromise; bool mConvertToAnnexB = false; }; } // namespace mozilla #endif // ChromiumCDMVideoDecoder_h_
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp +++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp @@ -29,17 +29,18 @@ static bool IsOnGMPThread() { MOZ_ASSERT(NS_SUCCEEDED(rv) && gmpThread); return gmpThread->IsOnCurrentThread(); } #endif GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams) : mConfig(aParams.VideoConfig()), mImageContainer(aParams.mImageContainer), - mCrashHelper(aParams.mCrashHelper) {} + mCrashHelper(aParams.mCrashHelper), + mKnowsCompositor(aParams.mKnowsCompositor) {} void GMPVideoDecoder::Decoded(GMPVideoi420Frame* aDecodedFrame) { GMPUniquePtr<GMPVideoi420Frame> decodedFrame(aDecodedFrame); MOZ_ASSERT(IsOnGMPThread()); VideoData::YCbCrBuffer b; for (int i = 0; i < kGMPNumOfPlanes; ++i) { @@ -59,17 +60,17 @@ void GMPVideoDecoder::Decoded(GMPVideoi4 DefaultColorSpace({decodedFrame->Width(), decodedFrame->Height()}); gfx::IntRect pictureRegion(0, 0, decodedFrame->Width(), decodedFrame->Height()); RefPtr<VideoData> v = VideoData::CreateAndCopyData( mConfig, mImageContainer, mLastStreamOffset, media::TimeUnit::FromMicroseconds(decodedFrame->Timestamp()), media::TimeUnit::FromMicroseconds(decodedFrame->Duration()), b, false, - media::TimeUnit::FromMicroseconds(-1), pictureRegion); + media::TimeUnit::FromMicroseconds(-1), pictureRegion, mKnowsCompositor); RefPtr<GMPVideoDecoder> self = this; if (v) { mDecodedData.AppendElement(std::move(v)); } else { mDecodedData.Clear(); mDecodePromise.RejectIfExists( MediaResult(NS_ERROR_OUT_OF_MEMORY, RESULT_DETAIL("CallBack::CreateAndCopyData")), @@ -118,17 +119,18 @@ void GMPVideoDecoder::Terminated() { } GMPVideoDecoder::GMPVideoDecoder(const GMPVideoDecoderParams& aParams) : mConfig(aParams.mConfig), mGMP(nullptr), mHost(nullptr), mConvertNALUnitLengths(false), mCrashHelper(aParams.mCrashHelper), - mImageContainer(aParams.mImageContainer) {} + mImageContainer(aParams.mImageContainer), + mKnowsCompositor(aParams.mKnowsCompositor) {} void GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags) { if (MP4Decoder::IsH264(mConfig.mMimeType)) { aTags.AppendElement("h264"_ns); } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) { aTags.AppendElement("vp8"_ns); } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) { aTags.AppendElement("vp9"_ns);
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h +++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h @@ -1,14 +1,15 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mozilla/layers/KnowsCompositor.h" #if !defined(GMPVideoDecoder_h_) # define GMPVideoDecoder_h_ # include "GMPVideoDecoderProxy.h" # include "ImageContainer.h" # include "MediaDataDecoderProxy.h" # include "MediaInfo.h" # include "PlatformDecoderModule.h" @@ -17,16 +18,17 @@ namespace mozilla { struct MOZ_STACK_CLASS GMPVideoDecoderParams { explicit GMPVideoDecoderParams(const CreateDecoderParams& aParams); const VideoInfo& mConfig; layers::ImageContainer* mImageContainer; GMPCrashHelper* mCrashHelper; + layers::KnowsCompositor* mKnowsCompositor; }; DDLoggedTypeDeclNameAndBase(GMPVideoDecoder, MediaDataDecoder); class GMPVideoDecoder : public MediaDataDecoder, public GMPVideoDecoderCallbackProxy, public DecoderDoctorLifeLogger<GMPVideoDecoder> { public: @@ -82,16 +84,17 @@ class GMPVideoDecoder : public MediaData GMPVideoDecoderProxy* mGMP; GMPVideoHost* mHost; bool mConvertNALUnitLengths; MozPromiseHolder<InitPromise> mInitPromise; RefPtr<GMPCrashHelper> mCrashHelper; int64_t mLastStreamOffset = 0; RefPtr<layers::ImageContainer> mImageContainer; + RefPtr<layers::KnowsCompositor> mKnowsCompositor; MozPromiseHolder<DecodePromise> mDecodePromise; MozPromiseHolder<DecodePromise> mDrainPromise; MozPromiseHolder<FlushPromise> mFlushPromise; DecodedData mDecodedData; bool mConvertToAnnexB = false; };
--- a/dom/media/platforms/omx/OmxDataDecoder.cpp +++ b/dom/media/platforms/omx/OmxDataDecoder.cpp @@ -947,17 +947,17 @@ already_AddRefed<VideoData> MediaDataHel RefPtr<VideoData> data = VideoData::CreateAndCopyData( info, mImageContainer, 0, // Filled later by caller. media::TimeUnit::Zero(), // Filled later by caller. media::TimeUnit::FromMicroseconds(1), // We don't know the duration. b, 0, // Filled later by caller. - media::TimeUnit::FromMicroseconds(-1), info.ImageRect()); + media::TimeUnit::FromMicroseconds(-1), info.ImageRect(), nullptr); MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("YUV420 VideoData: disp width %d, height %d, pic width %d, height " "%d, time %lld", info.mDisplay.width, info.mDisplay.height, info.mImage.width, info.mImage.height, aBufferData->mBuffer->nTimeStamp)); return data.forget();
--- a/dom/vr/XRFrame.cpp +++ b/dom/vr/XRFrame.cpp @@ -43,18 +43,22 @@ already_AddRefed<XRViewerPose> XRFrame:: if (aReferenceSpace.GetSession() != mSession) { aRv.ThrowInvalidStateError( "The XRReferenceSpace passed to GetViewerPose must belong to the " "XRSession that GetViewerPose is called on."); return nullptr; } - // TODO (Bug 1616390) - Validate that poses may be reported: - // https://immersive-web.github.io/webxr/#poses-may-be-reported + if (!mSession->CanReportPoses()) { + aRv.ThrowSecurityError( + "The visibilityState of the XRSpace's XRSession " + "that is passed to GetViewerPose must be 'visible'."); + return nullptr; + } // TODO (Bug 1616393) - Check if poses must be limited: // https://immersive-web.github.io/webxr/#poses-must-be-limited bool emulatedPosition = aReferenceSpace.IsPositionEmulated(); XRRenderState* renderState = mSession->GetActiveRenderState(); float depthNear = (float)renderState->DepthNear(); @@ -140,21 +144,20 @@ already_AddRefed<XRPose> XRFrame::GetPos if (aSpace.GetSession() != mSession || aBaseSpace.GetSession() != mSession) { aRv.ThrowInvalidStateError( "The XRSpace passed to GetPose must belong to the " "XRSession that GetPose is called on."); return nullptr; } - // TODO (Bug 1616390) - Validate that poses may be reported: - // https://immersive-web.github.io/webxr/#poses-may-be-reported - if (aSpace.GetSession()->VisibilityState() != XRVisibilityState::Visible) { - aRv.ThrowInvalidStateError( - "An XRSpace ’s visibilityState in not 'visible'."); + if (!mSession->CanReportPoses()) { + aRv.ThrowSecurityError( + "The visibilityState of the XRSpace's XRSession " + "that is passed to GetPose must be 'visible'."); return nullptr; } // TODO (Bug 1616393) - Check if poses must be limited: // https://immersive-web.github.io/webxr/#poses-must-be-limited const bool emulatedPosition = aSpace.IsPositionEmulated(); gfx::Matrix4x4Double base;
--- a/dom/vr/XRSession.cpp +++ b/dom/vr/XRSession.cpp @@ -170,21 +170,29 @@ already_AddRefed<Promise> XRSession::End return promise.forget(); } bool XRSession::IsImmersive() const { // Only immersive sessions have a VRDisplayClient return mDisplayClient != nullptr; } -XRVisibilityState XRSession::VisibilityState() { +XRVisibilityState XRSession::VisibilityState() const { return XRVisibilityState::Visible; // TODO (Bug 1609771): Implement changing visibility state } +// https://immersive-web.github.io/webxr/#poses-may-be-reported +// Given that an XRSession cannot be requested without explicit consent +// by the user, the only necessary check is whether the XRSession's +// visiblityState is 'visible'. +bool XRSession::CanReportPoses() const { + return VisibilityState() == XRVisibilityState::Visible; +} + // https://immersive-web.github.io/webxr/#dom-xrsession-updaterenderstate void XRSession::UpdateRenderState(const XRRenderStateInit& aNewState, ErrorResult& aRv) { if (mEnded) { aRv.ThrowInvalidStateError( "UpdateRenderState can not be called on an XRSession that has ended."); return; }
--- a/dom/vr/XRSession.h +++ b/dom/vr/XRSession.h @@ -59,17 +59,17 @@ class XRSession final : public DOMEventT gfx::VRDisplayClient* aClient, uint32_t aPresentationGroup, const nsTArray<XRReferenceSpaceType>& aEnabledReferenceSpaceTypes); // WebIDL Boilerplate JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; // WebIDL Attributes - XRVisibilityState VisibilityState(); + XRVisibilityState VisibilityState() const; XRRenderState* RenderState(); XRInputSourceArray* InputSources(); // WebIDL Methods void UpdateRenderState(const XRRenderStateInit& aNewState, ErrorResult& aRv); already_AddRefed<Promise> RequestReferenceSpace( const XRReferenceSpaceType& aReferenceSpaceType, ErrorResult& aRv); int32_t RequestAnimationFrame(XRFrameRequestCallback& aCallback, @@ -93,16 +93,17 @@ class XRSession final : public DOMEventT XRRenderState* GetActiveRenderState() const; bool IsEnded() const; bool IsImmersive() const; MOZ_CAN_RUN_SCRIPT void StartFrame(); void ExitPresent(); RefPtr<XRViewerPose> PooledViewerPose(const gfx::Matrix4x4Double& aTransform, bool aEmulatedPosition); + bool CanReportPoses() const; // nsARefreshObserver MOZ_CAN_RUN_SCRIPT void WillRefresh(mozilla::TimeStamp aTime) override; protected: virtual ~XRSession(); void LastRelease() override;
--- a/dom/webidl/Document.webidl +++ b/dom/webidl/Document.webidl @@ -711,8 +711,30 @@ partial interface Document { void setNotifyFormOrPasswordRemoved(boolean aShouldNotify); }; // Extension to allow chrome code to detect initial about:blank documents. partial interface Document { [ChromeOnly] readonly attribute boolean isInitialDocument; }; + +// Extension to allow chrome code to get some wireframe-like structure. +enum WireframeRectType { + "image", + "background", + "text", + "unknown", +}; +dictionary WireframeTaggedRect { + DOMRectReadOnly rect; + DOMString? color; /* Only relevant for "background" rects */ + WireframeRectType type; + Node? node; +}; +dictionary Wireframe { + DOMString canvasBackground; + sequence<WireframeTaggedRect> rects; +}; +partial interface Document { + [ChromeOnly] + Wireframe? getWireframe(optional boolean aIncludeNodes = false); +};
--- a/dom/xul/nsXULElement.cpp +++ b/dom/xul/nsXULElement.cpp @@ -1573,21 +1573,20 @@ nsXULPrototypeScript::nsXULPrototypeScri : nsXULPrototypeNode(eType_Script), mLineNo(aLineNo), mSrcLoading(false), mOutOfLine(true), mSrcLoadWaiters(nullptr), mStencil(nullptr) {} static nsresult WriteStencil(nsIObjectOutputStream* aStream, JSContext* aCx, - const JS::ReadOnlyCompileOptions& aOptions, JS::Stencil* aStencil) { JS::TranscodeBuffer buffer; JS::TranscodeResult code; - code = JS::EncodeStencil(aCx, aOptions, aStencil, buffer); + code = JS::EncodeStencil(aCx, aStencil, buffer); if (code != JS::TranscodeResult::Ok) { if (code == JS::TranscodeResult::Throw) { JS_ClearPendingException(aCx); return NS_ERROR_OUT_OF_MEMORY; } MOZ_ASSERT(IsTranscodeFailureResult(code)); @@ -1684,20 +1683,17 @@ nsresult nsXULPrototypeScript::Serialize rv = aStream->Write32(mLineNo); if (NS_FAILED(rv)) return rv; rv = aStream->Write32(0); // See bug 1418294. if (NS_FAILED(rv)) return rv; JSContext* cx = jsapi.cx(); MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx)); - JS::CompileOptions options(cx); - FillCompileOptions(options); - - return WriteStencil(aStream, cx, options, mStencil); + return WriteStencil(aStream, cx, mStencil); } nsresult nsXULPrototypeScript::SerializeOutOfLine( nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc) { if (!mSrcURI->SchemeIs("chrome")) // Don't cache scripts that don't come from chrome uris. return NS_ERROR_NOT_IMPLEMENTED;
--- a/editor/libeditor/EditorBase.cpp +++ b/editor/libeditor/EditorBase.cpp @@ -5,32 +5,33 @@ #include "EditorBase.h" #include "mozilla/DebugOnly.h" // for DebugOnly #include <stdio.h> // for nullptr, stdout #include <string.h> // for strcmp -#include "ChangeAttributeTransaction.h" // for ChangeAttributeTransaction -#include "CompositionTransaction.h" // for CompositionTransaction -#include "CreateElementTransaction.h" // for CreateElementTransaction -#include "DeleteNodeTransaction.h" // for DeleteNodeTransaction -#include "DeleteRangeTransaction.h" // for DeleteRangeTransaction -#include "DeleteTextTransaction.h" // for DeleteTextTransaction -#include "EditAggregateTransaction.h" // for EditAggregateTransaction -#include "EditTransactionBase.h" // for EditTransactionBase -#include "EditorEventListener.h" // for EditorEventListener -#include "gfxFontUtils.h" // for gfxFontUtils -#include "HTMLEditUtils.h" // for HTMLEditUtils -#include "InsertNodeTransaction.h" // for InsertNodeTransaction -#include "InsertTextTransaction.h" // for InsertTextTransaction -#include "JoinNodeTransaction.h" // for JoinNodeTransaction -#include "PlaceholderTransaction.h" // for PlaceholderTransaction -#include "SplitNodeTransaction.h" // for SplitNodeTransaction +#include "ChangeAttributeTransaction.h" // for ChangeAttributeTransaction +#include "CompositionTransaction.h" // for CompositionTransaction +#include "CreateElementTransaction.h" // for CreateElementTransaction +#include "DeleteNodeTransaction.h" // for DeleteNodeTransaction +#include "DeleteRangeTransaction.h" // for DeleteRangeTransaction +#include "DeleteTextTransaction.h" // for DeleteTextTransaction +#include "EditAggregateTransaction.h" // for EditAggregateTransaction +#include "EditTransactionBase.h" // for EditTransactionBase +#include "EditorEventListener.h" // for EditorEventListener +#include "gfxFontUtils.h" // for gfxFontUtils +#include "HTMLEditUtils.h" // for HTMLEditUtils +#include "InsertNodeTransaction.h" // for InsertNodeTransaction +#include "InsertTextTransaction.h" // for InsertTextTransaction +#include "JoinNodeTransaction.h" // for JoinNodeTransaction +#include "PlaceholderTransaction.h" // for PlaceholderTransaction +#include "SplitNodeTransaction.h" // for SplitNodeTransaction +#include "mozilla/intl/Bidi.h" #include "mozilla/BasePrincipal.h" // for BasePrincipal #include "mozilla/CheckedInt.h" // for CheckedInt #include "mozilla/ComposerCommandsUpdater.h" // for ComposerCommandsUpdater #include "mozilla/ContentEvents.h" // for InternalClipboardEvent #include "mozilla/CSSEditUtils.h" // for CSSEditUtils #include "mozilla/EditAction.h" // for EditSubAction #include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint #include "mozilla/EditorSpellCheck.h" // for EditorSpellCheck @@ -5760,22 +5761,23 @@ EditorBase::AutoCaretBidiLevelManager::A if (NS_WARN_IF(!frameSelection)) { mFailed = true; return; } nsPrevNextBidiLevels levels = frameSelection->GetPrevNextBidiLevels( aPointAtCaret.GetContainerAsContent(), aPointAtCaret.Offset(), true); - nsBidiLevel levelBefore = levels.mLevelBefore; - nsBidiLevel levelAfter = levels.mLevelAfter; - - nsBidiLevel currentCaretLevel = frameSelection->GetCaretBidiLevel(); - - nsBidiLevel levelOfDeletion; + mozilla::intl::Bidi::EmbeddingLevel levelBefore = levels.mLevelBefore; + mozilla::intl::Bidi::EmbeddingLevel levelAfter = levels.mLevelAfter; + + mozilla::intl::Bidi::EmbeddingLevel currentCaretLevel = + frameSelection->GetCaretBidiLevel(); + + mozilla::intl::Bidi::EmbeddingLevel levelOfDeletion; levelOfDeletion = (nsIEditor::eNext == aDirectionAndAmount || nsIEditor::eNextWord == aDirectionAndAmount) ? levelAfter : levelBefore; if (currentCaretLevel == levelOfDeletion) { return; // Perform the deletion }
--- a/editor/libeditor/EditorBase.h +++ b/editor/libeditor/EditorBase.h @@ -1,16 +1,17 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_EditorBase_h #define mozilla_EditorBase_h +#include "mozilla/intl/Bidi.h" #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc. #include "mozilla/EditAction.h" // for EditAction and EditSubAction #include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint #include "mozilla/EventForwards.h" // for InputEventTargetRanges #include "mozilla/Maybe.h" // for Maybe #include "mozilla/OwningNonNull.h" // for OwningNonNull #include "mozilla/TypeInState.h" // for PropItem, StyleCache #include "mozilla/RangeBoundary.h" // for RawRangeBoundary, RangeBoundary @@ -23,17 +24,16 @@ #include "mozilla/dom/Selection.h" #include "mozilla/dom/Text.h" #include "nsAtom.h" // for nsAtom, nsStaticAtom #include "nsCOMPtr.h" // for already_AddRefed, nsCOMPtr #include "nsCycleCollectionParticipant.h" #include "nsGkAtoms.h" #include "nsIContentInlines.h" // for nsINode::IsEditable() #include "nsIEditor.h" // for nsIEditor, etc. -#include "nsIFrame.h" // for nsBidiLevel #include "nsISelectionController.h" // for nsISelectionController constants #include "nsISelectionListener.h" // for nsISelectionListener #include "nsISupportsImpl.h" // for EditorBase::Release, etc. #include "nsIWeakReferenceUtils.h" // for nsWeakPtr #include "nsLiteralString.h" // for NS_LITERAL_STRING #include "nsPIDOMWindow.h" // for nsPIDOMWindowInner, etc. #include "nsString.h" // for nsCString #include "nsTArray.h" // for nsTArray and nsAutoTArray @@ -1980,17 +1980,17 @@ class EditorBase : public nsIEditor, /** * MaybeUpdateCaretBidiLevel() may update caret bidi level and schedule to * paint it if they are necessary. */ void MaybeUpdateCaretBidiLevel(const EditorBase& aEditorBase) const; private: - Maybe<nsBidiLevel> mNewCaretBidiLevel; + Maybe<mozilla::intl::Bidi::EmbeddingLevel> mNewCaretBidiLevel; bool mFailed = false; bool mCanceled = false; }; /** * UndefineCaretBidiLevel() resets bidi level of the caret. */ void UndefineCaretBidiLevel() const;
--- a/gfx/layers/NativeLayerCA.h +++ b/gfx/layers/NativeLayerCA.h @@ -8,16 +8,17 @@ #include <IOSurface/IOSurface.h> #include <deque> #include <unordered_map> #include <ostream> #include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" #include "mozilla/gfx/MacIOSurface.h" #include "mozilla/layers/NativeLayer.h" #include "CFTypeRefPtr.h" #include "nsRegion.h" #include "nsISupportsImpl.h" #ifdef __OBJC__ @@ -114,37 +115,42 @@ class NativeLayerRootCA : public NativeL void SetBackingScale(float aBackingScale); float BackingScale(); already_AddRefed<NativeLayer> CreateLayerForExternalTexture( bool aIsOpaque) override; void SetWindowIsFullscreen(bool aFullscreen); + void NoteMouseMove(); + void NoteMouseMoveAtTime(const TimeStamp& aTime); protected: explicit NativeLayerRootCA(CALayer* aLayer); ~NativeLayerRootCA() override; struct Representation { explicit Representation(CALayer* aRootCALayer); ~Representation(); void Commit(WhichRepresentation aRepresentation, const nsTArray<RefPtr<NativeLayerCA>>& aSublayers, - bool aWindowIsFullscreen); + bool aWindowIsFullscreen, bool aMouseMovedRecently); CALayer* FindVideoLayerToIsolate( WhichRepresentation aRepresentation, const nsTArray<RefPtr<NativeLayerCA>>& aSublayers); CALayer* mRootCALayer = nullptr; // strong - bool mMutated = false; + bool mMutatedLayerStructure = false; + bool mMutatedMouseMovedRecently = false; }; template <typename F> void ForAllRepresentations(F aFn); + void UpdateMouseMovedRecently(); + Mutex mMutex; // protects all other fields Representation mOnscreenRepresentation; Representation mOffscreenRepresentation; NativeLayerRootSnapshotterCA* mWeakSnapshotter = nullptr; nsTArray<RefPtr<NativeLayerCA>> mSublayers; // in z-order float mBackingScale = 1.0f; bool mMutated = false; @@ -158,16 +164,22 @@ class NativeLayerRootCA : public NativeL // Set to false when CommitToScreen() completes successfully. When true, // indicates that CommitToScreen() needs to be called at the next available // opportunity. bool mCommitPending = false; // Updated by the layer's view's window to match the fullscreen state // of that window. bool mWindowIsFullscreen = false; + + // Updated by the layer's view's window call to NoteMouseMoveAtTime(). + TimeStamp mLastMouseMoveTime; + + // Has the mouse recently moved? + bool mMouseMovedRecently = false; }; class RenderSourceNLRS; class NativeLayerRootSnapshotterCA final : public NativeLayerRootSnapshotter { public: static UniquePtr<NativeLayerRootSnapshotterCA> Create( NativeLayerRootCA* aLayerRoot, CALayer* aRootCALayer);
--- a/gfx/layers/NativeLayerCA.mm +++ b/gfx/layers/NativeLayerCA.mm @@ -129,17 +129,19 @@ static CALayer* MakeOffscreenRootCALayer layer.masksToBounds = YES; layer.geometryFlipped = YES; return layer; } NativeLayerRootCA::NativeLayerRootCA(CALayer* aLayer) : mMutex("NativeLayerRootCA"), mOnscreenRepresentation(aLayer), - mOffscreenRepresentation(MakeOffscreenRootCALayer()) {} + mOffscreenRepresentation(MakeOffscreenRootCALayer()) { + NoteMouseMove(); +} NativeLayerRootCA::~NativeLayerRootCA() { MOZ_RELEASE_ASSERT(mSublayers.IsEmpty(), "Please clear all layers before destroying the layer root."); } already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayer( const IntSize& aSize, bool aIsOpaque, SurfacePoolHandle* aSurfacePoolHandle) { @@ -157,27 +159,27 @@ void NativeLayerRootCA::AppendLayer(Nati MutexAutoLock lock(mMutex); RefPtr<NativeLayerCA> layerCA = aLayer->AsNativeLayerCA(); MOZ_RELEASE_ASSERT(layerCA); mSublayers.AppendElement(layerCA); layerCA->SetBackingScale(mBackingScale); layerCA->SetRootWindowIsFullscreen(mWindowIsFullscreen); - ForAllRepresentations([&](Representation& r) { r.mMutated = true; }); + ForAllRepresentations([&](Representation& r) { r.mMutatedLayerStructure = true; }); } void NativeLayerRootCA::RemoveLayer(NativeLayer* aLayer) { MutexAutoLock lock(mMutex); RefPtr<NativeLayerCA> layerCA = aLayer->AsNativeLayerCA(); MOZ_RELEASE_ASSERT(layerCA); mSublayers.RemoveElement(layerCA); - ForAllRepresentations([&](Representation& r) { r.mMutated = true; }); + ForAllRepresentations([&](Representation& r) { r.mMutatedLayerStructure = true; }); } void NativeLayerRootCA::SetLayers(const nsTArray<RefPtr<NativeLayer>>& aLayers) { MutexAutoLock lock(mMutex); // Ideally, we'd just be able to do mSublayers = std::move(aLayers). // However, aLayers has a different type: it carries NativeLayer objects, whereas mSublayers // carries NativeLayerCA objects, so we have to downcast all the elements first. There's one other @@ -190,17 +192,17 @@ void NativeLayerRootCA::SetLayers(const MOZ_RELEASE_ASSERT(layerCA); layerCA->SetBackingScale(mBackingScale); layerCA->SetRootWindowIsFullscreen(mWindowIsFullscreen); layersCA.AppendElement(std::move(layerCA)); } if (layersCA != mSublayers) { mSublayers = std::move(layersCA); - ForAllRepresentations([&](Representation& r) { r.mMutated = true; }); + ForAllRepresentations([&](Representation& r) { r.mMutatedLayerStructure = true; }); } } void NativeLayerRootCA::SetBackingScale(float aBackingScale) { MutexAutoLock lock(mMutex); mBackingScale = aBackingScale; for (auto layer : mSublayers) { @@ -233,17 +235,19 @@ bool NativeLayerRootCA::CommitToScreen() { MutexAutoLock lock(mMutex); if (!NS_IsMainThread() && mOffMainThreadCommitsSuspended) { mCommitPending = true; return false; } - mOnscreenRepresentation.Commit(WhichRepresentation::ONSCREEN, mSublayers, mWindowIsFullscreen); + UpdateMouseMovedRecently(); + mOnscreenRepresentation.Commit(WhichRepresentation::ONSCREEN, mSublayers, mWindowIsFullscreen, + mMouseMovedRecently); mCommitPending = false; } if (StaticPrefs::gfx_webrender_debug_dump_native_layer_tree_to_file()) { static uint32_t sFrameID = 0; uint32_t frameID = sFrameID++; @@ -281,73 +285,77 @@ void NativeLayerRootCA::OnNativeLayerRoo NativeLayerRootSnapshotterCA* aNativeLayerRootSnapshotter) { MutexAutoLock lock(mMutex); MOZ_RELEASE_ASSERT(mWeakSnapshotter == aNativeLayerRootSnapshotter); mWeakSnapshotter = nullptr; } void NativeLayerRootCA::CommitOffscreen() { MutexAutoLock lock(mMutex); - mOffscreenRepresentation.Commit(WhichRepresentation::OFFSCREEN, mSublayers, mWindowIsFullscreen); + mOffscreenRepresentation.Commit(WhichRepresentation::OFFSCREEN, mSublayers, mWindowIsFullscreen, + false); } template <typename F> void NativeLayerRootCA::ForAllRepresentations(F aFn) { aFn(mOnscreenRepresentation); aFn(mOffscreenRepresentation); } NativeLayerRootCA::Representation::Representation(CALayer* aRootCALayer) : mRootCALayer([aRootCALayer retain]) {} NativeLayerRootCA::Representation::~Representation() { - if (mMutated) { + if (mMutatedLayerStructure) { // Clear the root layer's sublayers. At this point the window is usually closed, so this // transaction does not cause any screen updates. AutoCATransaction transaction; mRootCALayer.sublayers = @[]; } [mRootCALayer release]; } void NativeLayerRootCA::Representation::Commit(WhichRepresentation aRepresentation, const nsTArray<RefPtr<NativeLayerCA>>& aSublayers, - bool aWindowIsFullscreen) { - if (!mMutated && + bool aWindowIsFullscreen, bool aMouseMovedRecently) { + bool mightIsolate = (aRepresentation == WhichRepresentation::ONSCREEN && + StaticPrefs::gfx_core_animation_specialize_video()); + bool mustRebuild = (mightIsolate && mMutatedMouseMovedRecently); + + if (!mMutatedLayerStructure && !mustRebuild && std::none_of(aSublayers.begin(), aSublayers.end(), [=](const RefPtr<NativeLayerCA>& layer) { return layer->HasUpdate(aRepresentation); })) { // No updates, skip creating the CATransaction altogether. return; } AutoCATransaction transaction; // Call ApplyChanges on our sublayers first, and then update the root layer's // list of sublayers. The order is important because we need layer->UnderlyingCALayer() // to be non-null, and the underlying CALayer gets lazily initialized in ApplyChanges(). for (auto layer : aSublayers) { layer->ApplyChanges(aRepresentation); } - if (mMutated) { + if (mMutatedLayerStructure || mustRebuild) { NSMutableArray<CALayer*>* sublayers = [NSMutableArray arrayWithCapacity:aSublayers.Length()]; for (auto layer : aSublayers) { [sublayers addObject:layer->UnderlyingCALayer(aRepresentation)]; } mRootCALayer.sublayers = sublayers; // Now that we've set these layer relationships, we check to see if we should break // them and isolate a single video layer. It's important to do this *after* the // sublayers have been set, because we need the relationships there to do the // bounds checking of layer spaces against each other. // Bug 1731821 should eliminate this entire if block. - if (aWindowIsFullscreen && aRepresentation == WhichRepresentation::ONSCREEN && - StaticPrefs::gfx_core_animation_specialize_video()) { + if (mightIsolate && aWindowIsFullscreen && !aMouseMovedRecently) { CALayer* isolatedLayer = FindVideoLayerToIsolate(aRepresentation, aSublayers); if (isolatedLayer) { // Create a full coverage black layer behind the isolated layer. CGFloat rootWidth = mRootCALayer.bounds.size.width; CGFloat rootHeight = mRootCALayer.bounds.size.height; // Reaching the low-power mode requires that there is a single black layer // covering the entire window behind the video layer. Create that layer. @@ -355,19 +363,20 @@ void NativeLayerRootCA::Representation:: blackLayer.position = NSZeroPoint; blackLayer.anchorPoint = NSZeroPoint; blackLayer.bounds = CGRectMake(0, 0, rootWidth, rootHeight); blackLayer.backgroundColor = [[NSColor blackColor] CGColor]; mRootCALayer.sublayers = @[ blackLayer, isolatedLayer ]; } } + } - mMutated = false; - } + mMutatedLayerStructure = false; + mMutatedMouseMovedRecently = false; } CALayer* NativeLayerRootCA::Representation::FindVideoLayerToIsolate( WhichRepresentation aRepresentation, const nsTArray<RefPtr<NativeLayerCA>>& aSublayers) { // Run a heuristic to determine if any one of aSublayers is a video layer that should be // isolated. These layers are ordered back-to-front. This function will return a candidate // CALayer if all of the following are true: // 1) The candidate layer is the topmost layer, and is a video layer. @@ -475,21 +484,42 @@ void NativeLayerRootCA::DumpLayerTreeToF void NativeLayerRootCA::SetWindowIsFullscreen(bool aFullscreen) { if (mWindowIsFullscreen != aFullscreen) { mWindowIsFullscreen = aFullscreen; for (auto layer : mSublayers) { layer->SetRootWindowIsFullscreen(mWindowIsFullscreen); } + + // Treat this as a mouse move, for purposes of resetting our timer. + NoteMouseMove(); + PrepareForCommit(); CommitToScreen(); } } +void NativeLayerRootCA::NoteMouseMove() { mLastMouseMoveTime = TimeStamp::NowLoRes(); } + +void NativeLayerRootCA::NoteMouseMoveAtTime(const TimeStamp& aTime) { mLastMouseMoveTime = aTime; } + +void NativeLayerRootCA::UpdateMouseMovedRecently() { + static const double SECONDS_TO_WAIT = 2.0; + + bool newMouseMovedRecently = + ((TimeStamp::NowLoRes() - mLastMouseMoveTime).ToSeconds() < SECONDS_TO_WAIT); + + if (newMouseMovedRecently != mMouseMovedRecently) { + mMouseMovedRecently = newMouseMovedRecently; + + ForAllRepresentations([&](Representation& r) { r.mMutatedMouseMovedRecently = true; }); + } +} + NativeLayerRootSnapshotterCA::NativeLayerRootSnapshotterCA(NativeLayerRootCA* aLayerRoot, RefPtr<GLContext>&& aGL, CALayer* aRootCALayer) : mLayerRoot(aLayerRoot), mGL(aGL) { AutoCATransaction transaction; mRenderer = [[CARenderer rendererWithCGLContext:gl::GLContextCGL::Cast(mGL)->GetCGLContext() options:nil] retain]; mRenderer.layer = aRootCALayer;
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp +++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp @@ -592,17 +592,16 @@ struct DIGroup { *mKey, ViewAs<ImagePixel>(mVisibleRect, PixelCastJustification::LayerIsImage)); mLastVisibleRect = mVisibleRect; PushImage(aBuilder, itemBounds); } return; } - gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8; std::vector<RefPtr<ScaledFont>> fonts; bool validFonts = true; RefPtr<WebRenderDrawEventRecorder> recorder = MakeAndAddRef<WebRenderDrawEventRecorder>( [&](MemStream& aStream, std::vector<RefPtr<ScaledFont>>& aScaledFonts) { size_t count = aScaledFonts.size(); aStream.write((const char*)&count, sizeof(count)); @@ -615,18 +614,18 @@ struct DIGroup { break; } BlobFont font = {key.value(), scaled}; aStream.write((const char*)&font, sizeof(font)); } fonts = std::move(aScaledFonts); }); - RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget( - gfx::BackendType::SKIA, gfx::IntSize(1, 1), format); + RefPtr<gfx::DrawTarget> dummyDt = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget( recorder, dummyDt, mLayerBounds.ToUnknownRect()); // Setup the gfxContext RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt); context->SetMatrix( Matrix::Scaling(mScale.width, mScale.height) .PostTranslate(mResidualOffset.x, mResidualOffset.y)); @@ -1087,25 +1086,24 @@ static bool IsItemProbablyActive( switch (aItem->GetType()) { case DisplayItemType::TYPE_TRANSFORM: { nsDisplayTransform* transformItem = static_cast<nsDisplayTransform*>(aItem); const Matrix4x4Flagged& t = transformItem->GetTransform(); Matrix t2d; bool is2D = t.Is2D(&t2d); GP("active: %d\n", transformItem->MayBeAnimated(aDisplayListBuilder)); - return transformItem->MayBeAnimated(aDisplayListBuilder, false) || - !is2D || + return transformItem->MayBeAnimated(aDisplayListBuilder) || !is2D || HasActiveChildren(*transformItem->GetChildren(), aBuilder, aResources, aSc, aManager, aDisplayListBuilder); } case DisplayItemType::TYPE_OPACITY: { nsDisplayOpacity* opacityItem = static_cast<nsDisplayOpacity*>(aItem); bool active = opacityItem->NeedsActiveLayer(aDisplayListBuilder, - opacityItem->Frame(), false); + opacityItem->Frame()); GP("active: %d\n", active); return active || HasActiveChildren(*opacityItem->GetChildren(), aBuilder, aResources, aSc, aManager, aDisplayListBuilder); } case DisplayItemType::TYPE_FOREIGN_OBJECT: { return true; }
--- a/image/SourceSurfaceBlobImage.cpp +++ b/image/SourceSurfaceBlobImage.cpp @@ -164,18 +164,18 @@ Maybe<BlobImageKeyData> SourceSurfaceBlo } BlobFont font = {key.value(), scaled}; aStream.write((const char*)&font, sizeof(font)); } fonts = std::move(aScaledFonts); }); - RefPtr<DrawTarget> dummyDt = Factory::CreateDrawTarget( - BackendType::SKIA, IntSize(1, 1), SurfaceFormat::OS_RGBA); + RefPtr<DrawTarget> dummyDt = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); RefPtr<DrawTarget> dt = Factory::CreateRecordingDrawTarget(recorder, dummyDt, imageRectOrigin); if (!dt || !dt->IsValid()) { return Nothing(); } bool contextPaint = mSVGContext && mSVGContext->GetContextPaint();
new file mode 100644 --- /dev/null +++ b/intl/components/gtest/TestBidi.cpp @@ -0,0 +1,278 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "gtest/gtest.h" + +#include "mozilla/intl/Bidi.h" +#include "mozilla/Span.h" +namespace mozilla::intl { + +struct VisualRun { + Span<const char16_t> string; + Bidi::Direction direction; +}; + +/** + * An iterator for visual runs in a paragraph. See Bug 1736597 for integrating + * this into the public API. + */ +class MOZ_STACK_CLASS VisualRunIter { + public: + VisualRunIter(Bidi& aBidi, Span<const char16_t> aParagraph, + Bidi::EmbeddingLevel aLevel) + : mBidi(aBidi), mParagraph(aParagraph) { + // Crash in case of errors by calling unwrap. If this were a real API, this + // would be a TryCreate call. + mBidi.SetParagraph(aParagraph, aLevel).unwrap(); + mRunCount = mBidi.CountRuns().unwrap(); + } + + Maybe<VisualRun> Next() { + if (mRunIndex >= mRunCount) { + return Nothing(); + } + + int32_t stringIndex = -1; + int32_t stringLength = -1; + + Bidi::Direction direction = + mBidi.GetVisualRun(mRunIndex, &stringIndex, &stringLength); + + Span<const char16_t> string(mParagraph.Elements() + stringIndex, + stringLength); + mRunIndex++; + return Some(VisualRun{string, direction}); + } + + private: + Bidi& mBidi; + Span<const char16_t> mParagraph = Span<const char16_t>(); + int32_t mRunIndex = 0; + int32_t mRunCount = 0; +}; + +struct LogicalRun { + Span<const char16_t> string; + Bidi::EmbeddingLevel embeddingLevel; +}; + +/** + * An iterator for logical runs in a paragraph. See Bug 1736597 for integrating + * this into the public API. + */ +class MOZ_STACK_CLASS LogicalRunIter { + public: + LogicalRunIter(Bidi& aBidi, Span<const char16_t> aParagraph, + Bidi::EmbeddingLevel aLevel) + : mBidi(aBidi), mParagraph(aParagraph) { + // Crash in case of errors by calling unwrap. If this were a real API, this + // would be a TryCreate call. + mBidi.SetParagraph(aParagraph, aLevel).unwrap(); + mBidi.CountRuns().unwrap(); + } + + Maybe<LogicalRun> Next() { + if (mRunIndex >= static_cast<int32_t>(mParagraph.Length())) { + return Nothing(); + } + + int32_t logicalLimit; + + Bidi::EmbeddingLevel embeddingLevel; + mBidi.GetLogicalRun(mRunIndex, &logicalLimit, &embeddingLevel); + + Span<const char16_t> string(mParagraph.Elements() + mRunIndex, + logicalLimit - mRunIndex); + + mRunIndex = logicalLimit; + return Some(LogicalRun{string, embeddingLevel}); + } + + private: + Bidi& mBidi; + Span<const char16_t> mParagraph = Span<const char16_t>(); + int32_t mRunIndex = 0; +}; + +TEST(IntlBidi, SimpleLTR) +{ + Bidi bidi{}; + LogicalRunIter logicalRunIter(bidi, MakeStringSpan(u"this is a paragraph"), + Bidi::EmbeddingLevel::DefaultLTR()); + ASSERT_EQ(bidi.GetParagraphEmbeddingLevel(), 0); + ASSERT_EQ(bidi.GetParagraphDirection(), Bidi::ParagraphDirection::LTR); + + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"this is a paragraph")); + ASSERT_EQ(logicalRun->embeddingLevel, 0); + ASSERT_EQ(logicalRun->embeddingLevel.Direction(), Bidi::Direction::LTR); + } + + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isNothing()); + } +} + +TEST(IntlBidi, SimpleRTL) +{ + Bidi bidi{}; + LogicalRunIter logicalRunIter(bidi, MakeStringSpan(u"فايرفوكس رائع"), + Bidi::EmbeddingLevel::DefaultLTR()); + ASSERT_EQ(bidi.GetParagraphEmbeddingLevel(), 1); + ASSERT_EQ(bidi.GetParagraphDirection(), Bidi::ParagraphDirection::RTL); + + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"فايرفوكس رائع")); + ASSERT_EQ(logicalRun->embeddingLevel.Direction(), Bidi::Direction::RTL); + ASSERT_EQ(logicalRun->embeddingLevel, 1); + } + + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isNothing()); + } +} + +TEST(IntlBidi, MultiLevel) +{ + Bidi bidi{}; + LogicalRunIter logicalRunIter( + bidi, MakeStringSpan(u"Firefox is awesome: رائع Firefox"), + Bidi::EmbeddingLevel::DefaultLTR()); + ASSERT_EQ(bidi.GetParagraphEmbeddingLevel(), 0); + ASSERT_EQ(bidi.GetParagraphDirection(), Bidi::ParagraphDirection::Mixed); + + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"Firefox is awesome: ")); + ASSERT_EQ(logicalRun->embeddingLevel, 0); + } + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"رائع")); + ASSERT_EQ(logicalRun->embeddingLevel, 1); + } + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u" Firefox")); + ASSERT_EQ(logicalRun->embeddingLevel, 0); + } + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isNothing()); + } +} + +TEST(IntlBidi, RtlOverride) +{ + Bidi bidi{}; + // Set the paragraph using the RTL embedding mark U+202B, and the LTR + // embedding mark U+202A to increase the embedding level. This mark switches + // the weakly directional character "_". This demonstrates that embedding + // levels can be computed. + LogicalRunIter logicalRunIter( + bidi, MakeStringSpan(u"ltr\u202b___رائع___\u202a___ltr__"), + Bidi::EmbeddingLevel::DefaultLTR()); + ASSERT_EQ(bidi.GetParagraphEmbeddingLevel(), 0); + ASSERT_EQ(bidi.GetParagraphDirection(), Bidi::ParagraphDirection::Mixed); + + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"ltr")); + ASSERT_EQ(logicalRun->embeddingLevel, 0); + ASSERT_EQ(logicalRun->embeddingLevel.Direction(), Bidi::Direction::LTR); + } + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"\u202b___رائع___")); + ASSERT_EQ(logicalRun->embeddingLevel, 1); + ASSERT_EQ(logicalRun->embeddingLevel.Direction(), Bidi::Direction::RTL); + } + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"\u202a___ltr__")); + ASSERT_EQ(logicalRun->embeddingLevel, 2); + ASSERT_EQ(logicalRun->embeddingLevel.Direction(), Bidi::Direction::LTR); + } + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isNothing()); + } +} + +TEST(IntlBidi, VisualRuns) +{ + Bidi bidi{}; + + VisualRunIter visualRunIter( + bidi, + MakeStringSpan( + u"first visual run التشغيل البصري الثاني third visual run"), + Bidi::EmbeddingLevel::DefaultLTR()); + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isSome()); + ASSERT_EQ(run->string, MakeStringSpan(u"first visual run ")); + ASSERT_EQ(run->direction, Bidi::Direction::LTR); + } + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isSome()); + ASSERT_EQ(run->string, MakeStringSpan(u"التشغيل البصري الثاني")); + ASSERT_EQ(run->direction, Bidi::Direction::RTL); + } + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isSome()); + ASSERT_EQ(run->string, MakeStringSpan(u" third visual run")); + ASSERT_EQ(run->direction, Bidi::Direction::LTR); + } + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isNothing()); + } +} + +TEST(IntlBidi, VisualRunsWithEmbeds) +{ + // Compare this test to the logical order test. + Bidi bidi{}; + VisualRunIter visualRunIter( + bidi, MakeStringSpan(u"ltr\u202b___رائع___\u202a___ltr___"), + Bidi::EmbeddingLevel::DefaultLTR()); + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isSome()); + ASSERT_EQ(run->string, MakeStringSpan(u"ltr")); + ASSERT_EQ(run->direction, Bidi::Direction::LTR); + } + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isSome()); + ASSERT_EQ(run->string, MakeStringSpan(u"\u202a___ltr___")); + ASSERT_EQ(run->direction, Bidi::Direction::LTR); + } + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isSome()); + ASSERT_EQ(run->string, MakeStringSpan(u"\u202b___رائع___")); + ASSERT_EQ(run->direction, Bidi::Direction::RTL); + } + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isNothing()); + } +} + +} // namespace mozilla::intl
new file mode 100644 --- /dev/null +++ b/intl/components/gtest/TestDateIntervalFormat.cpp @@ -0,0 +1,193 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "gtest/gtest.h" + +#include "mozilla/intl/DateIntervalFormat.h" +#include "mozilla/intl/DateTimeFormat.h" +#include "mozilla/intl/DateTimePart.h" +#include "mozilla/Span.h" + +#include "unicode/uformattedvalue.h" + +#include "TestBuffer.h" + +namespace mozilla::intl { + +const double DATE201901030000GMT = 1546473600000.0; +const double DATE201901050000GMT = 1546646400000.0; + +TEST(IntlDateIntervalFormat, TryFormatDateTime) +{ + UniquePtr<DateIntervalFormat> dif = + DateIntervalFormat::TryCreate(MakeStringSpan("en-US"), + MakeStringSpan(u"MMddHHmm"), + MakeStringSpan(u"GMT")) + .unwrap(); + + AutoFormattedDateInterval formatted; + + // Pass two same Date time, 'equal' should be true. + bool equal; + auto result = dif->TryFormatDateTime(DATE201901030000GMT, DATE201901030000GMT, + formatted, &equal); + ASSERT_TRUE(result.isOk()); + ASSERT_TRUE(equal); + + auto spanResult = formatted.ToSpan(); + ASSERT_TRUE(spanResult.isOk()); + + ASSERT_EQ(spanResult.unwrap(), MakeStringSpan(u"01/03, 00:00")); + + result = dif->TryFormatDateTime(DATE201901030000GMT, DATE201901050000GMT, + formatted, &equal); + ASSERT_TRUE(result.isOk()); + ASSERT_FALSE(equal); + + spanResult = formatted.ToSpan(); + ASSERT_TRUE(spanResult.isOk()); + ASSERT_EQ(spanResult.unwrap(), + MakeStringSpan(u"01/03, 00:00 – 01/05, 00:00")); +} + +TEST(IntlDateIntervalFormat, TryFormatCalendar) +{ + auto dateTimePatternGenerator = + DateTimePatternGenerator::TryCreate("en").unwrap(); + + UniquePtr<DateTimeFormat> dtFormat = + DateTimeFormat::TryCreateFromSkeleton( + MakeStringSpan("en-US"), MakeStringSpan(u"yMMddHHmm"), + dateTimePatternGenerator.get(), Nothing(), + Some(MakeStringSpan(u"GMT"))) + .unwrap(); + + UniquePtr<DateIntervalFormat> dif = + DateIntervalFormat::TryCreate(MakeStringSpan("en-US"), + MakeStringSpan(u"MMddHHmm"), + MakeStringSpan(u"GMT")) + .unwrap(); + + AutoFormattedDateInterval formatted; + + // Two Calendar objects with the same date time. + auto sameCal = dtFormat->CloneCalendar(DATE201901030000GMT); + ASSERT_TRUE(sameCal.isOk()); + + auto cal = sameCal.unwrap(); + bool equal; + auto result = dif->TryFormatCalendar(*cal, *cal, formatted, &equal); + ASSERT_TRUE(result.isOk()); + ASSERT_TRUE(equal); + + auto spanResult = formatted.ToSpan(); + ASSERT_TRUE(spanResult.isOk()); + ASSERT_EQ(spanResult.unwrap(), MakeStringSpan(u"01/03, 00:00")); + + auto startCal = dtFormat->CloneCalendar(DATE201901030000GMT); + ASSERT_TRUE(startCal.isOk()); + auto endCal = dtFormat->CloneCalendar(DATE201901050000GMT); + ASSERT_TRUE(endCal.isOk()); + + result = dif->TryFormatCalendar(*startCal.unwrap(), *endCal.unwrap(), + formatted, &equal); + ASSERT_TRUE(result.isOk()); + ASSERT_FALSE(equal); + + spanResult = formatted.ToSpan(); + ASSERT_TRUE(spanResult.isOk()); + ASSERT_EQ(spanResult.unwrap(), + MakeStringSpan(u"01/03, 00:00 – 01/05, 00:00")); +} + +TEST(IntlDateIntervalFormat, TryFormattedToParts) +{ + UniquePtr<DateIntervalFormat> dif = + DateIntervalFormat::TryCreate(MakeStringSpan("en-US"), + MakeStringSpan(u"MMddHHmm"), + MakeStringSpan(u"GMT")) + .unwrap(); + + AutoFormattedDateInterval formatted; + bool equal; + auto result = dif->TryFormatDateTime(DATE201901030000GMT, DATE201901050000GMT, + formatted, &equal); + ASSERT_TRUE(result.isOk()); + ASSERT_FALSE(equal); + + Span<const char16_t> formattedSpan = formatted.ToSpan().unwrap(); + ASSERT_EQ(formattedSpan, MakeStringSpan(u"01/03, 00:00 – 01/05, 00:00")); + + mozilla::intl::DateTimePartVector parts; + result = dif->TryFormattedToParts(formatted, parts); + ASSERT_TRUE(result.isOk()); + + auto getSubSpan = [formattedSpan, &parts](size_t index) { + size_t start = index == 0 ? 0 : parts[index - 1].mEndIndex; + size_t end = parts[index].mEndIndex; + return formattedSpan.FromTo(start, end); + }; + + ASSERT_EQ(parts[0].mType, DateTimePartType::Month); + ASSERT_EQ(getSubSpan(0), MakeStringSpan(u"01")); + ASSERT_EQ(parts[0].mSource, DateTimePartSource::StartRange); + + ASSERT_EQ(parts[1].mType, DateTimePartType::Literal); + ASSERT_EQ(getSubSpan(1), MakeStringSpan(u"/")); + ASSERT_EQ(parts[1].mSource, DateTimePartSource::StartRange); + + ASSERT_EQ(parts[2].mType, DateTimePartType::Day); + ASSERT_EQ(getSubSpan(2), MakeStringSpan(u"03")); + ASSERT_EQ(parts[2].mSource, DateTimePartSource::StartRange); + + ASSERT_EQ(parts[3].mType, DateTimePartType::Literal); + ASSERT_EQ(getSubSpan(3), MakeStringSpan(u", ")); + ASSERT_EQ(parts[3].mSource, DateTimePartSource::StartRange); + + ASSERT_EQ(parts[4].mType, DateTimePartType::Hour); + ASSERT_EQ(getSubSpan(4), MakeStringSpan(u"00")); + ASSERT_EQ(parts[4].mSource, DateTimePartSource::StartRange); + + ASSERT_EQ(parts[5].mType, DateTimePartType::Literal); + ASSERT_EQ(getSubSpan(5), MakeStringSpan(u":")); + ASSERT_EQ(parts[5].mSource, DateTimePartSource::StartRange); + + ASSERT_EQ(parts[6].mType, DateTimePartType::Minute); + ASSERT_EQ(getSubSpan(6), MakeStringSpan(u"00")); + ASSERT_EQ(parts[6].mSource, DateTimePartSource::StartRange); + + ASSERT_EQ(parts[7].mType, DateTimePartType::Literal); + ASSERT_EQ(getSubSpan(7), MakeStringSpan(u" – ")); + ASSERT_EQ(parts[7].mSource, DateTimePartSource::Shared); + + ASSERT_EQ(parts[8].mType, DateTimePartType::Month); + ASSERT_EQ(getSubSpan(8), MakeStringSpan(u"01")); + ASSERT_EQ(parts[8].mSource, DateTimePartSource::EndRange); + + ASSERT_EQ(parts[9].mType, DateTimePartType::Literal); + ASSERT_EQ(getSubSpan(9), MakeStringSpan(u"/")); + ASSERT_EQ(parts[9].mSource, DateTimePartSource::EndRange); + + ASSERT_EQ(parts[10].mType, DateTimePartType::Day); + ASSERT_EQ(getSubSpan(10), MakeStringSpan(u"05")); + ASSERT_EQ(parts[10].mSource, DateTimePartSource::EndRange); + + ASSERT_EQ(parts[11].mType, DateTimePartType::Literal); + ASSERT_EQ(getSubSpan(11), MakeStringSpan(u", ")); + ASSERT_EQ(parts[11].mSource, DateTimePartSource::EndRange); + + ASSERT_EQ(parts[12].mType, DateTimePartType::Hour); + ASSERT_EQ(getSubSpan(12), MakeStringSpan(u"00")); + ASSERT_EQ(parts[12].mSource, DateTimePartSource::EndRange); + + ASSERT_EQ(parts[13].mType, DateTimePartType::Literal); + ASSERT_EQ(getSubSpan(13), MakeStringSpan(u":")); + ASSERT_EQ(parts[13].mSource, DateTimePartSource::EndRange); + + ASSERT_EQ(parts[14].mType, DateTimePartType::Minute); + ASSERT_EQ(getSubSpan(14), MakeStringSpan(u"00")); + ASSERT_EQ(parts[14].mSource, DateTimePartSource::EndRange); + + ASSERT_EQ(parts.length(), 15u); +} +} // namespace mozilla::intl
--- a/intl/components/gtest/TestDateTimeFormat.cpp +++ b/intl/components/gtest/TestDateTimeFormat.cpp @@ -1,15 +1,16 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gtest/gtest.h" #include "mozilla/intl/Calendar.h" #include "mozilla/intl/DateTimeFormat.h" +#include "mozilla/intl/DateTimePart.h" #include "mozilla/intl/DateTimePatternGenerator.h" #include "mozilla/Span.h" #include "TestBuffer.h" #include <string_view> namespace mozilla::intl { @@ -532,9 +533,64 @@ TEST(IntlDateTimeFormat, GetAvailableLoc } // Each locale should be found exactly once. ASSERT_EQ(english, 1); ASSERT_EQ(german, 1); ASSERT_EQ(chinese, 1); } +TEST(IntlDateTimeFormat, TryFormatToParts) +{ + auto dateTimePatternGenerator = + DateTimePatternGenerator::TryCreate("en").unwrap(); + + UniquePtr<DateTimeFormat> dtFormat = + DateTimeFormat::TryCreateFromSkeleton( + MakeStringSpan("en-US"), MakeStringSpan(u"yMMddHHmm"), + dateTimePatternGenerator.get(), Nothing(), + Some(MakeStringSpan(u"GMT"))) + .unwrap(); + + TestBuffer<char16_t> buffer; + mozilla::intl::DateTimePartVector parts; + auto result = dtFormat->TryFormatToParts(DATE, buffer, parts); + ASSERT_TRUE(result.isOk()); + + std::u16string_view strView = buffer.get_string_view(); + ASSERT_EQ(strView, u"09/23/2002, 17:07"); + + auto getSubStringView = [strView, &parts](size_t index) { + size_t pos = index == 0 ? 0 : parts[index - 1].mEndIndex; + size_t count = parts[index].mEndIndex - pos; + return strView.substr(pos, count); + }; + + ASSERT_EQ(parts[0].mType, DateTimePartType::Month); + ASSERT_EQ(getSubStringView(0), u"09"); + + ASSERT_EQ(parts[1].mType, DateTimePartType::Literal); + ASSERT_EQ(getSubStringView(1), u"/"); + + ASSERT_EQ(parts[2].mType, DateTimePartType::Day); + ASSERT_EQ(getSubStringView(2), u"23"); + + ASSERT_EQ(parts[3].mType, DateTimePartType::Literal); + ASSERT_EQ(getSubStringView(3), u"/"); + + ASSERT_EQ(parts[4].mType, DateTimePartType::Year); + ASSERT_EQ(getSubStringView(4), u"2002"); + + ASSERT_EQ(parts[5].mType, DateTimePartType::Literal); + ASSERT_EQ(getSubStringView(5), u", "); + + ASSERT_EQ(parts[6].mType, DateTimePartType::Hour); + ASSERT_EQ(getSubStringView(6), u"17"); + + ASSERT_EQ(parts[7].mType, DateTimePartType::Literal); + ASSERT_EQ(getSubStringView(7), u":"); + + ASSERT_EQ(parts[8].mType, DateTimePartType::Minute); + ASSERT_EQ(getSubStringView(8), u"07"); + + ASSERT_EQ(parts.length(), 9u); +} } // namespace mozilla::intl
--- a/intl/components/gtest/moz.build +++ b/intl/components/gtest/moz.build @@ -1,18 +1,20 @@ # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # 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/. UNIFIED_SOURCES += [ + "TestBidi.cpp", "TestCalendar.cpp", "TestCollator.cpp", "TestCurrency.cpp", + "TestDateIntervalFormat.cpp", "TestDateTimeFormat.cpp", "TestListFormat.cpp", "TestLocale.cpp", "TestLocaleCanonicalizer.cpp", "TestMeasureUnit.cpp", "TestNumberFormat.cpp", "TestNumberingSystem.cpp", "TestPluralRules.cpp",
--- a/intl/components/moz.build +++ b/intl/components/moz.build @@ -1,18 +1,21 @@ # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # 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/. EXPORTS.mozilla.intl = [ + "src/Bidi.h", "src/Calendar.h", "src/Collator.h", "src/Currency.h", + "src/DateIntervalFormat.h", "src/DateTimeFormat.h", + "src/DateTimePart.h", "src/DateTimePatternGenerator.h", "src/ICU4CGlue.h", "src/ICU4CLibrary.h", "src/ICUError.h", "src/ListFormat.h", "src/Locale.h", "src/LocaleCanonicalizer.h", "src/MeasureUnit.h", @@ -22,20 +25,23 @@ EXPORTS.mozilla.intl = [ "src/NumberRangeFormat.h", "src/PluralRules.h", "src/RelativeTimeFormat.h", "src/String.h", "src/TimeZone.h", ] UNIFIED_SOURCES += [ + "src/Bidi.cpp", "src/Calendar.cpp", "src/Collator.cpp", "src/Currency.cpp", + "src/DateIntervalFormat.cpp", "src/DateTimeFormat.cpp", + "src/DateTimeFormatUtils.cpp", "src/DateTimePatternGenerator.cpp", "src/ICU4CGlue.cpp", "src/ICU4CLibrary.cpp", "src/ListFormat.cpp", "src/Locale.cpp", "src/LocaleCanonicalizer.cpp", "src/LocaleGenerated.cpp", "src/MeasureUnit.cpp",
new file mode 100644 --- /dev/null +++ b/intl/components/src/Bidi.cpp @@ -0,0 +1,163 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/intl/Bidi.h" +#include "mozilla/Casting.h" +#include "mozilla/intl/ICU4CGlue.h" + +#include "unicode/ubidi.h" + +namespace mozilla::intl { + +Bidi::Bidi() { mBidi = ubidi_open(); } +Bidi::~Bidi() { ubidi_close(mBidi.GetMut()); } + +ICUResult Bidi::SetParagraph(Span<const char16_t> aParagraph, + Bidi::EmbeddingLevel aLevel) { + // Do not allow any reordering of the runs, as this can change the + // performance characteristics of working with runs. In the default mode, + // the levels can be iterated over directly, rather than relying on computing + // logical runs on the fly. This can have negative performance characteristics + // compared to iterating over the levels. + // + // In the UBIDI_REORDER_RUNS_ONLY the levels are encoded with additional + // information which can be safely ignored in this Bidi implementation. + // Note that this check is here since setting the mode must be done before + // calls to setting the paragraph. + MOZ_ASSERT(ubidi_getReorderingMode(mBidi.GetMut()) == UBIDI_REORDER_DEFAULT); + + UErrorCode status = U_ZERO_ERROR; + ubidi_setPara(mBidi.GetMut(), aParagraph.Elements(), + AssertedCast<int32_t>(aParagraph.Length()), aLevel, nullptr, + &status); + + mLevels = nullptr; + + return ToICUResult(status); +} + +Bidi::ParagraphDirection Bidi::GetParagraphDirection() const { + switch (ubidi_getDirection(mBidi.GetConst())) { + case UBIDI_LTR: + return Bidi::ParagraphDirection::LTR; + case UBIDI_RTL: + return Bidi::ParagraphDirection::RTL; + case UBIDI_MIXED: + return Bidi::ParagraphDirection::Mixed; + case UBIDI_NEUTRAL: + // This is only used in `ubidi_getBaseDirection` which is unused in this + // API. + MOZ_ASSERT_UNREACHABLE("Unexpected UBiDiDirection value."); + }; + return Bidi::ParagraphDirection::Mixed; +} + +/* static */ +void Bidi::ReorderVisual(const EmbeddingLevel* aLevels, int32_t aLength, + int32_t* aIndexMap) { + ubidi_reorderVisual(reinterpret_cast<const uint8_t*>(aLevels), aLength, + aIndexMap); +} + +static Bidi::Direction ToBidiDirection(UBiDiDirection aDirection) { + switch (aDirection) { + case UBIDI_LTR: + return Bidi::Direction::LTR; + case UBIDI_RTL: + return Bidi::Direction::RTL; + case UBIDI_MIXED: + case UBIDI_NEUTRAL: + MOZ_ASSERT_UNREACHABLE("Unexpected UBiDiDirection value."); + } + return Bidi::Direction::LTR; +} + +Result<int32_t, ICUError> Bidi::CountRuns() { + UErrorCode status = U_ZERO_ERROR; + int32_t runCount = ubidi_countRuns(mBidi.GetMut(), &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + mLength = ubidi_getProcessedLength(mBidi.GetConst()); + mLevels = mLength > 0 ? reinterpret_cast<const Bidi::EmbeddingLevel*>( + ubidi_getLevels(mBidi.GetMut(), &status)) + : nullptr; + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + return runCount; +} + +void Bidi::GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimitOut, + Bidi::EmbeddingLevel* aLevelOut) { + MOZ_ASSERT(mLevels, "CountRuns hasn't been run?"); + MOZ_RELEASE_ASSERT(aLogicalStart < mLength, "Out of bound"); + EmbeddingLevel level = mLevels[aLogicalStart]; + int32_t limit; + for (limit = aLogicalStart + 1; limit < mLength; limit++) { + if (mLevels[limit] != level) { + break; + } + } + *aLogicalLimitOut = limit; + *aLevelOut = level; +} + +bool Bidi::EmbeddingLevel::IsDefaultLTR() const { + return mValue == UBIDI_DEFAULT_LTR; +}; + +bool Bidi::EmbeddingLevel::IsDefaultRTL() const { + return mValue == UBIDI_DEFAULT_RTL; +}; + +bool Bidi::EmbeddingLevel::IsRTL() const { + // If the least significant bit is 1, then the embedding level + // is right-to-left. + // If the least significant bit is 0, then the embedding level + // is left-to-right. + return (mValue & 0x1) == 1; +}; + +bool Bidi::EmbeddingLevel::IsLTR() const { return !IsRTL(); }; + +bool Bidi::EmbeddingLevel::IsSameDirection(EmbeddingLevel aOther) const { + return (((mValue ^ aOther) & 1) == 0); +} + +Bidi::EmbeddingLevel Bidi::EmbeddingLevel::LTR() { + return Bidi::EmbeddingLevel(0); +}; + +Bidi::EmbeddingLevel Bidi::EmbeddingLevel::RTL() { + return Bidi::EmbeddingLevel(1); +}; + +Bidi::EmbeddingLevel Bidi::EmbeddingLevel::DefaultLTR() { + return Bidi::EmbeddingLevel(UBIDI_DEFAULT_LTR); +}; + +Bidi::EmbeddingLevel Bidi::EmbeddingLevel::DefaultRTL() { + return Bidi::EmbeddingLevel(UBIDI_DEFAULT_RTL); +}; + +Bidi::Direction Bidi::EmbeddingLevel::Direction() { + return IsRTL() ? Direction::RTL : Direction::LTR; +}; + +uint8_t Bidi::EmbeddingLevel::Value() const { return mValue; } + +Bidi::EmbeddingLevel Bidi::GetParagraphEmbeddingLevel() const { + return Bidi::EmbeddingLevel(ubidi_getParaLevel(mBidi.GetConst())); +} + +Bidi::Direction Bidi::GetVisualRun(int32_t aRunIndex, int32_t* aLogicalStart, + int32_t* aLength) { + return ToBidiDirection( + ubidi_getVisualRun(mBidi.GetMut(), aRunIndex, aLogicalStart, aLength)); +} + +} // namespace mozilla::intl
new file mode 100644 --- /dev/null +++ b/intl/components/src/Bidi.h @@ -0,0 +1,241 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef intl_components_Bidi_h_ +#define intl_components_Bidi_h_ + +#include "mozilla/intl/ICU4CGlue.h" + +struct UBiDi; + +namespace mozilla::intl { + +/** + * This component is a Mozilla-focused API for working with bidirectional (bidi) + * text. Text is commonly displayed left to right (LTR), especially for + * Latin-based alphabets. However, languages like Arabic and Hebrew displays + * text right to left (RTL). When displaying text, LTR and RTL text can be + * combined together in the same paragraph. This class gives tools for working + * with unidirectional, and mixed direction paragraphs. + * + * See the Unicode Bidirectional Algorithm document for implementation details: + * https://unicode.org/reports/tr9/ + */ +class Bidi final { + public: + Bidi(); + ~Bidi(); + + // Not copyable or movable + Bidi(const Bidi&) = delete; + Bidi& operator=(const Bidi&) = delete; + + /** + * This enum unambiguously classifies text runs as either being left to right, + * or right to left. + */ + enum class Direction : uint8_t { + // Left to right text. + LTR = 0, + // Right to left text. + RTL = 1, + }; + + /** + * This enum indicates the text direction for the set paragraph. Some + * paragraphs are unidirectional, where they only have one direction, or a + * paragraph could use both LTR and RTL. In this case the paragraph's + * direction would be mixed. + */ + enum ParagraphDirection { LTR, RTL, Mixed }; + + /** + * Embedding levels are numbers that indicate how deeply the bidi text is + * embedded, and the direction of text on that embedding level. When switching + * between strongly LTR code points and strongly RTL code points the embedding + * level normally switches between an embedding level of 0 (LTR) and 1 (RTL). + * The only time the embedding level increases is if the embedding code points + * are used. This is the Left-to-Right Embedding (LRE) code point (U+202A), or + * the Right-to-Left Embedding (RLE) code point (U+202B). The minimum + * embedding level of text is zero, and the maximum explicit depth is 125. + * + * The most significant bit is reserved for additional meaning. It can be used + * to signify in certain APIs that the text should by default be LTR or RTL if + * no strongly directional code points are found. + * + * Bug 1736595: At the time of this writing, some places in Gecko code use a 1 + * in the most significant bit to indicate that an embedding level has not + * been set. This leads to an ambiguous understanding of what the most + * significant bit actually means. + */ + class EmbeddingLevel { + public: + explicit EmbeddingLevel(uint8_t aValue) : mValue(aValue) {} + explicit EmbeddingLevel(int aValue) + : mValue(static_cast<uint8_t>(aValue)) {} + + EmbeddingLevel() = default; + + // Enable the copy operators, but disable move as this is only a uint8_t. + EmbeddingLevel(const EmbeddingLevel& other) = default; + EmbeddingLevel& operator=(const EmbeddingLevel& other) = default; + + /** + * Determine the direction of the embedding level by looking at the least + * significant bit. If it is 0, then it is LTR. If it is 1, then it is RTL. + */ + Bidi::Direction Direction(); + + /** + * Create a left-to-right embedding level. + */ + static EmbeddingLevel LTR(); + + /** + * Create an right-to-left embedding level. + */ + static EmbeddingLevel RTL(); + + /** + * When passed into `SetParagraph`, the direction is determined by first + * strongly directional character, with the default set to left-to-right if + * none is found. + * + * This is encoded with the highest bit set to 1. + */ + static EmbeddingLevel DefaultLTR(); + + /** + * When passed into `SetParagraph`, the direction is determined by first + * strongly directional character, with the default set to right-to-left if + * none is found. + * + * * This is encoded with the highest and lowest bits set to 1. + */ + static EmbeddingLevel DefaultRTL(); + + bool IsDefaultLTR() const; + bool IsDefaultRTL() const; + bool IsLTR() const; + bool IsRTL() const; + bool IsSameDirection(EmbeddingLevel aOther) const; + + /** + * Get the underlying value as a uint8_t. + */ + uint8_t Value() const; + + /** + * Implicitly convert to the underlying value. + */ + operator uint8_t() const { return mValue; } + + private: + uint8_t mValue = 0; + }; + + /** + * Set the current paragraph of text to analyze for its bidi properties. This + * performs the Unicode bidi algorithm as specified by: + * https://unicode.org/reports/tr9/ + * + * After setting the text, the other getter methods can be used to find out + * the directionality of the paragraph text. + */ + ICUResult SetParagraph(Span<const char16_t> aParagraph, + EmbeddingLevel aLevel); + + /** + * Get the embedding level for the paragraph that was set by SetParagraph. + */ + EmbeddingLevel GetParagraphEmbeddingLevel() const; + + /** + * Get the directionality of the paragraph text that was set by SetParagraph. + */ + ParagraphDirection GetParagraphDirection() const; + + /** + * Get the number of runs. This function may invoke the actual reordering on + * the Bidi object, after SetParagraph may have resolved only the levels of + * the text. Therefore, `CountRuns` may have to allocate memory, and may fail + * doing so. + */ + Result<int32_t, ICUError> CountRuns(); + + /** + * Get the next logical run. The logical runs are a run of text that has the + * same directionality and embedding level. These runs are in memory order, + * and not in display order. + * + * Important! `Bidi::CountRuns` must be called before calling this method. + * + * @param aLogicalStart is the offset into the paragraph text that marks the + * logical start of the text. + * @param aLogicalLimitOut is an out param that is the length of the string + * that makes up the logical run. + * @param aLevelOut is an out parameter that returns the embedding level for + * the run + */ + void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimitOut, + EmbeddingLevel* aLevelOut); + + /** + * This is a convenience function that does not use the ICU Bidi object. + * It is intended to be used for when an application has determined the + * embedding levels of objects (character sequences) and just needs to have + * them reordered (L2). + * + * @param aLevels is an array with `aLength` levels that have been + * determined by the application. + * + * @param aLength is the number of levels in the array, or, semantically, + * the number of objects to be reordered. It must be greater than 0. + * + * @param aIndexMap is a pointer to an array of `aLength` + * indexes which will reflect the reordering of the characters. + * The array does not need to be initialized. + * The index map will result in + * `aIndexMap[aVisualIndex]==aLogicalIndex`. + */ + static void ReorderVisual(const EmbeddingLevel* aLevels, int32_t aLength, + int32_t* aIndexMap); + + /** + * Get one run's logical start, length, and directionality. In an RTL run, the + * character at the logical start is visually on the right of the displayed + * run. The length is the number of characters in the run. + * `Bidi::CountRuns` should be called before the runs are retrieved. + * + * @param aRunIndex is the number of the run in visual order, in the + * range `[0..CountRuns-1]`. + * + * @param aLogicalStart is the first logical character index in the text. + * The pointer may be `nullptr` if this index is not needed. + * + * @param aLength is the number of characters (at least one) in the run. + * The pointer may be `nullptr` if this is not needed. + * + * Note that in right-to-left runs, the code places modifier letters before + * base characters and second surrogates before first ones. + */ + Direction GetVisualRun(int32_t aRunIndex, int32_t* aLogicalStart, + int32_t* aLength); + + private: + ICUPointer<UBiDi> mBidi = ICUPointer<UBiDi>(nullptr); + + /** + * An array of levels that is the same length as the paragraph from + * `Bidi::SetParagraph`. + */ + const EmbeddingLevel* mLevels = nullptr; + + /** + * The length of the paragraph from `Bidi::SetParagraph`. + */ + int32_t mLength = 0; +}; + +} // namespace mozilla::intl +#endif
--- a/intl/components/src/Calendar.h +++ b/intl/components/src/Calendar.h @@ -116,22 +116,18 @@ class Calendar final { * Return BCP 47 Unicode locale extension type keywords. */ static Result<Bcp47IdentifierEnumeration, ICUError> GetBcp47KeywordValuesForLocale(const char* aLocale, CommonlyUsed aCommonlyUsed = CommonlyUsed::No); ~Calendar(); - /** - * TODO(Bug 1686965) - Temporarily get the underlying ICU object while - * migrating to the unified API. This should be removed when completing the - * migration. - */ - UCalendar* UnsafeGetUCalendar() const { return mCalendar; } + private: + friend class DateIntervalFormat; + UCalendar* GetUCalendar() const { return mCalendar; } - private: UCalendar* mCalendar = nullptr; }; } // namespace mozilla::intl #endif
new file mode 100644 --- /dev/null +++ b/intl/components/src/DateIntervalFormat.cpp @@ -0,0 +1,287 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "unicode/udateintervalformat.h" + +#include "DateTimeFormatUtils.h" +#include "ScopedICUObject.h" + +#include "mozilla/intl/Calendar.h" +#include "mozilla/intl/DateIntervalFormat.h" + +namespace mozilla::intl { + +/** + * PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), steps 9-11. + * + * Examine the formatted value to see if any interval span field is present. + * + * https://tc39.es/ecma402/#sec-partitiondatetimerangepattern + */ +static ICUResult DateFieldsPracticallyEqual( + const UFormattedValue* aFormattedValue, bool* aEqual) { + if (!aFormattedValue) { + return Err(ICUError::InternalError); + } + + MOZ_ASSERT(aEqual); + *aEqual = false; + UErrorCode status = U_ZERO_ERROR; + UConstrainedFieldPosition* fpos = ucfpos_open(&status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos); + + // We're only interested in UFIELD_CATEGORY_DATE_INTERVAL_SPAN fields. + ucfpos_constrainCategory(fpos, UFIELD_CATEGORY_DATE_INTERVAL_SPAN, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + bool hasSpan = ufmtval_nextPosition(aFormattedValue, fpos, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + // When no date interval span field was found, both dates are "practically + // equal" per PartitionDateTimeRangePattern. + *aEqual = !hasSpan; + return Ok(); +} + +/* static */ +Result<UniquePtr<DateIntervalFormat>, ICUError> DateIntervalFormat::TryCreate( + Span<const char> aLocale, Span<const char16_t> aSkeleton, + Span<const char16_t> aTimeZone) { + UErrorCode status = U_ZERO_ERROR; + UDateIntervalFormat* dif = udtitvfmt_open( + IcuLocale(AssertNullTerminatedString(aLocale)), aSkeleton.data(), + AssertedCast<int32_t>(aSkeleton.size()), aTimeZone.data(), + AssertedCast<int32_t>(aTimeZone.size()), &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + return UniquePtr<DateIntervalFormat>(new DateIntervalFormat(dif)); +} + +DateIntervalFormat::~DateIntervalFormat() { + MOZ_ASSERT(mDateIntervalFormat); + udtitvfmt_close(mDateIntervalFormat.GetMut()); +} + +AutoFormattedDateInterval::AutoFormattedDateInterval() { + mFormatted = udtitvfmt_openResult(&mError); + if (U_FAILURE(mError)) { + mFormatted = nullptr; + } +} + +const UFormattedValue* AutoFormattedDateInterval::Value() const { + if (!IsValid()) { + return nullptr; + } + + UErrorCode status = U_ZERO_ERROR; + const UFormattedValue* value = udtitvfmt_resultAsValue(mFormatted, &status); + if (U_FAILURE(status)) { + return nullptr; + } + + return value; +} + +Result<Span<const char16_t>, ICUError> AutoFormattedDateInterval::ToSpan() + const { + if (!IsValid()) { + return Err(GetError()); + } + + const UFormattedValue* value = Value(); + if (!value) { + return Err(ICUError::InternalError); + } + + UErrorCode status = U_ZERO_ERROR; + int32_t strLength; + const char16_t* str = ufmtval_getString(value, &strLength, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + return Span{str, AssertedCast<size_t>(strLength)}; +} + +AutoFormattedDateInterval::~AutoFormattedDateInterval() { + if (mFormatted) { + udtitvfmt_closeResult(mFormatted); + } +} + +ICUResult DateIntervalFormat::TryFormatCalendar( + const Calendar& aStart, const Calendar& aEnd, + AutoFormattedDateInterval& aFormatted, bool* aPracticallyEqual) const { + MOZ_ASSERT(aFormatted.IsValid()); + + UErrorCode status = U_ZERO_ERROR; + udtitvfmt_formatCalendarToResult( + mDateIntervalFormat.GetConst(), aStart.GetUCalendar(), + aEnd.GetUCalendar(), aFormatted.GetUFormattedDateInterval(), &status); + + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + MOZ_TRY(DateFieldsPracticallyEqual(aFormatted.Value(), aPracticallyEqual)); + return Ok(); +} + +ICUResult DateIntervalFormat::TryFormatDateTime( + double aStart, double aEnd, AutoFormattedDateInterval& aFormatted, + bool* aPracticallyEqual) const { + MOZ_ASSERT(aFormatted.IsValid()); + + UErrorCode status = U_ZERO_ERROR; + udtitvfmt_formatToResult(mDateIntervalFormat.GetConst(), aStart, aEnd, + aFormatted.GetUFormattedDateInterval(), &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + MOZ_TRY(DateFieldsPracticallyEqual(aFormatted.Value(), aPracticallyEqual)); + return Ok(); +} + +ICUResult DateIntervalFormat::TryFormattedToParts( + const AutoFormattedDateInterval& aFormatted, + DateTimePartVector& aParts) const { + MOZ_ASSERT(aFormatted.IsValid()); + const UFormattedValue* value = aFormatted.Value(); + if (!value) { + return Err(ICUError::InternalError); + } + + size_t lastEndIndex = 0; + auto AppendPart = [&](DateTimePartType type, size_t endIndex, + DateTimePartSource source) { + if (!aParts.emplaceBack(type, endIndex, source)) { + return false; + } + + lastEndIndex = endIndex; + return true; + }; + + UErrorCode status = U_ZERO_ERROR; + UConstrainedFieldPosition* fpos = ucfpos_open(&status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos); + + size_t categoryEndIndex = 0; + DateTimePartSource source = DateTimePartSource::Shared; + + while (true) { + bool hasMore = ufmtval_nextPosition(value, fpos, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + if (!hasMore) { + break; + } + + int32_t category = ucfpos_getCategory(fpos, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + int32_t field = ucfpos_getField(fpos, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + int32_t beginIndexInt, endIndexInt; + ucfpos_getIndexes(fpos, &beginIndexInt, &endIndexInt, &status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + MOZ_ASSERT(beginIndexInt <= endIndexInt, + "field iterator returning invalid range"); + + size_t beginIndex = AssertedCast<size_t>(beginIndexInt); + size_t endIndex = AssertedCast<size_t>(endIndexInt); + + // Indices are guaranteed to be returned in order (from left to right). + MOZ_ASSERT(lastEndIndex <= beginIndex, + "field iteration didn't return fields in order start to " + "finish as expected"); + + if (category == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) { + // Append any remaining literal parts before changing the source kind. + if (lastEndIndex < beginIndex) { + if (!AppendPart(DateTimePartType::Literal, beginIndex, source)) { + return Err(ICUError::InternalError); + } + } + + // The special field category UFIELD_CATEGORY_DATE_INTERVAL_SPAN has only + // two allowed values (0 or 1), indicating the begin of the start- resp. + // end-date. + MOZ_ASSERT(field == 0 || field == 1, + "span category has unexpected value"); + + source = field == 0 ? DateTimePartSource::StartRange + : DateTimePartSource::EndRange; + categoryEndIndex = endIndex; + continue; + } + + // Ignore categories other than UFIELD_CATEGORY_DATE. + if (category != UFIELD_CATEGORY_DATE) { + continue; + } + + DateTimePartType type = + ConvertUFormatFieldToPartType(static_cast<UDateFormatField>(field)); + if (lastEndIndex < beginIndex) { + if (!AppendPart(DateTimePartType::Literal, beginIndex, source)) { + return Err(ICUError::InternalError); + } + } + + if (!AppendPart(type, endIndex, source)) { + return Err(ICUError::InternalError); + } + + if (endIndex == categoryEndIndex) { + // Append any remaining literal parts before changing the source kind. + if (lastEndIndex < endIndex) { + if (!AppendPart(DateTimePartType::Literal, endIndex, source)) { + return Err(ICUError::InternalError); + } + } + + source = DateTimePartSource::Shared; + } + } + + // Append any final literal. + auto spanResult = aFormatted.ToSpan(); + if (spanResult.isErr()) { + return spanResult.propagateErr(); + } + size_t formattedSize = spanResult.unwrap().size(); + if (lastEndIndex < formattedSize) { + if (!AppendPart(DateTimePartType::Literal, formattedSize, source)) { + return Err(ICUError::InternalError); + } + } + + return Ok(); +} + +} // namespace mozilla::intl
new file mode 100644 --- /dev/null +++ b/intl/components/src/DateIntervalFormat.h @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef intl_components_DateIntervalFormat_h_ +#define intl_components_DateIntervalFormat_h_ + +#include "mozilla/intl/DateTimePart.h" +#include "mozilla/intl/ICU4CGlue.h" +#include "mozilla/intl/ICUError.h" +#include "mozilla/Result.h" +#include "mozilla/Span.h" +#include "mozilla/UniquePtr.h" + +#include "unicode/utypes.h" + +struct UDateIntervalFormat; +struct UFormattedDateInterval; +struct UFormattedValue; + +namespace mozilla::intl { +class AutoFormattedDateInterval; +class Calendar; + +/** + * This component is a Mozilla-focused API for the date range formatting + * provided by ICU. This DateIntervalFormat class helps to format the range + * between two date-time values. + * + * https://tc39.es/ecma402/#sec-formatdatetimerange + * https://tc39.es/ecma402/#sec-formatdatetimerangetoparts + */ +class DateIntervalFormat final { + public: + /** + * Create a DateIntervalFormat object from locale, skeleton and time zone. + * The format of skeleton can be found in [1]. + * + * Note: Skeleton will be removed in the future. + * + * [1]: https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns + */ + static Result<UniquePtr<DateIntervalFormat>, ICUError> TryCreate( + Span<const char> aLocale, Span<const char16_t> aSkeleton, + Span<const char16_t> aTimeZone); + + ~DateIntervalFormat(); + + /** + * Format a date-time range between two Calendar objects. + * + * DateIntervalFormat cannot be changed to use a proleptic Gregorian + * calendar, so use this method if the start date is before the Gregorian + * calendar is introduced(October 15, 1582), otherwise use TryFormatDateTime + * instead. + * + * The result will be stored in aFormatted, caller can use + * AutoFormattedDateInterval::ToSpan() to get the formatted string, or pass + * the aFormatted to TryFormattedToParts to get the parts vector. + * + * aPracticallyEqual will be set to true if the date times of the two + * calendars are equal. + */ + ICUResult TryFormatCalendar(const Calendar& aStart, const Calendar& aEnd, + AutoFormattedDateInterval& aFormatted, + bool* aPracticallyEqual) const; + + /** + * Format a date-time range between two Unix epoch times in milliseconds. + * + * The result will be stored in aFormatted, caller can use + * AutoFormattedDateInterval::ToSpan() to get the formatted string, or pass + * the aFormatted to TryFormattedToParts to get the parts vector. + * + * aPracticallyEqual will be set to true if the date times of the two + * Unix epoch times are equal. + */ + ICUResult TryFormatDateTime(double aStart, double aEnd, + AutoFormattedDateInterval& aFormatted, + bool* aPracticallyEqual) const; + + /** + * Convert the formatted DateIntervalFormat into several parts. + * + * The caller get the formatted result from either TryFormatCalendar, or + * TryFormatDateTime methods, and instantiate the DateTimePartVector. This + * method will generate the parts and insert them into the vector. + * + * See: + * https://tc39.es/ecma402/#sec-formatdatetimerangetoparts + */ + ICUResult TryFormattedToParts(const AutoFormattedDateInterval& aFormatted, + DateTimePartVector& aParts) const; + + private: + DateIntervalFormat() = delete; + explicit DateIntervalFormat(UDateIntervalFormat* aDif) + : mDateIntervalFormat(aDif) {} + DateIntervalFormat(const DateIntervalFormat&) = delete; + DateIntervalFormat& operator=(const DateIntervalFormat&) = delete; + + ICUPointer<UDateIntervalFormat> mDateIntervalFormat = + ICUPointer<UDateIntervalFormat>(nullptr); +}; + +/** + * A RAII class to hold the formatted value of DateIntervalFormat. + * + * The caller will need to create this AutoFormattedDateInterval on the stack, + * and call IsValid() method to check if the native object + * (UFormattedDateInterval) has been created properly, and then passes this + * object to the methods of DateIntervalFormat. + * The result of the DateIntervalFormat's method will be stored in this object, + * the caller can use ToSpan() method to get the formatted string, or pass it + * to DateIntervalFormat::TryFormattedToParts to get the DateTimePart vector. + * + * The formatted value will be released once this class is destructed. + */ +class MOZ_RAII AutoFormattedDateInterval { + public: + AutoFormattedDateInterval(); + ~AutoFormattedDateInterval(); + + AutoFormattedDateInterval(const AutoFormattedDateInterval& other) = delete; + AutoFormattedDateInterval& operator=(const AutoFormattedDateInterval& other) = + delete; + + AutoFormattedDateInterval(AutoFormattedDateInterval&& other) = delete; + AutoFormattedDateInterval& operator=(AutoFormattedDateInterval&& other) = + delete; + + /** + * Check if the native UFormattedDateInterval was created successfully. + */ + bool IsValid() const { return !!mFormatted; } + + /** + * Get error code if IsValid() returns false. + */ + ICUError GetError() const { return ToICUError(mError); } + + /** + * Get the formatted result. + */ + Result<Span<const char16_t>, ICUError> ToSpan() const; + + private: + friend class DateIntervalFormat; + UFormattedDateInterval* GetUFormattedDateInterval() const { + return mFormatted; + } + + const UFormattedValue* Value() const; + + UFormattedDateInterval* mFormatted = nullptr; + UErrorCode mError = U_ZERO_ERROR; +}; +} // namespace mozilla::intl + +#endif
--- a/intl/components/src/DateTimeFormat.cpp +++ b/intl/components/src/DateTimeFormat.cpp @@ -4,16 +4,17 @@ #include <cstring> #include "unicode/ucal.h" #include "unicode/udat.h" #include "unicode/udatpg.h" #include "unicode/ures.h" +#include "DateTimeFormatUtils.h" #include "ScopedICUObject.h" #include "mozilla/EnumSet.h" #include "mozilla/intl/Calendar.h" #include "mozilla/intl/DateTimeFormat.h" #include "mozilla/intl/DateTimePatternGenerator.h" namespace mozilla::intl { @@ -1104,9 +1105,67 @@ const char* DateTimeFormat::ToString(Dat return "h12"; case HourCycle::H23: return "h23"; case HourCycle::H24: return "h24"; } MOZ_CRASH("Unexpected DateTimeFormat::HourCycle"); } + +ICUResult DateTimeFormat::TryFormatToParts( + UFieldPositionIterator* aFieldPositionIterator, size_t aSpanSize, + DateTimePartVector& aParts) const { + ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose( + aFieldPositionIterator); + + size_t lastEndIndex = 0; + auto AppendPart = [&](DateTimePartType type, size_t endIndex) { + // For the part defined in FormatDateTimeToParts, it doesn't have ||Source|| + // property, we store Shared for simplicity, + if (!aParts.emplaceBack(type, endIndex, DateTimePartSource::Shared)) { + return false; + } + + lastEndIndex = endIndex; + return true; + }; + + int32_t fieldInt, beginIndexInt, endIndexInt; + while ((fieldInt = ufieldpositer_next(aFieldPositionIterator, &beginIndexInt, + &endIndexInt)) >= 0) { + MOZ_ASSERT(beginIndexInt <= endIndexInt, + "field iterator returning invalid range"); + + size_t beginIndex = AssertedCast<size_t>(beginIndexInt); + size_t endIndex = AssertedCast<size_t>(endIndexInt); + + // Technically this isn't guaranteed. But it appears true in pratice, + // and http://bugs.icu-project.org/trac/ticket/12024 is expected to + // correct the documentation lapse. + MOZ_ASSERT(lastEndIndex <= beginIndex, + "field iteration didn't return fields in order start to " + "finish as expected"); + + DateTimePartType type = + ConvertUFormatFieldToPartType(static_cast<UDateFormatField>(fieldInt)); + if (lastEndIndex < beginIndex) { + if (!AppendPart(DateTimePartType::Literal, beginIndex)) { + return Err(ICUError::InternalError); + } + } + + if (!AppendPart(type, endIndex)) { + return Err(ICUError::InternalError); + } + } + + // Append any final literal. + if (lastEndIndex < aSpanSize) { + if (!AppendPart(DateTimePartType::Literal, aSpanSize)) { + return Err(ICUError::InternalError); + } + } + + return Ok(); +} + } // namespace mozilla::intl
--- a/intl/components/src/DateTimeFormat.h +++ b/intl/components/src/DateTimeFormat.h @@ -4,16 +4,18 @@ #ifndef intl_components_DateTimeFormat_h_ #define intl_components_DateTimeFormat_h_ #include <functional> #include "unicode/udat.h" #include "mozilla/Assertions.h" #include "mozilla/intl/ICU4CGlue.h" #include "mozilla/intl/ICUError.h" + +#include "mozilla/intl/DateTimePart.h" #include "mozilla/intl/DateTimePatternGenerator.h" #include "mozilla/Maybe.h" #include "mozilla/Result.h" #include "mozilla/Span.h" #include "mozilla/UniquePtr.h" #include "mozilla/Utf8.h" #include "mozilla/Variant.h" #include "mozilla/Vector.h" @@ -349,16 +351,54 @@ class DateTimeFormat final { aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) { return udat_format(mDateFormat, aUnixEpoch, target, length, nullptr, status); }); } }; /** + * Format the Unix epoch time into a DateTimePartVector. + * + * The caller has to create the buffer and the vector and pass to this method. + * The formatted string will be stored in the buffer and formatted parts in + * the vector. + * + * aUnixEpoch is the number of milliseconds since 1 January 1970, UTC. + * + * See: + * https://tc39.es/ecma402/#sec-formatdatetimetoparts + */ + template <typename B> + ICUResult TryFormatToParts(double aUnixEpoch, B& aBuffer, + DateTimePartVector& aParts) const { + static_assert(std::is_same<typename B::CharType, char16_t>::value, + "Only char16_t is supported (for UTF-16 support) now."); + + UErrorCode status = U_ZERO_ERROR; + UFieldPositionIterator* fpositer = ufieldpositer_open(&status); + if (U_FAILURE(status)) { + return Err(ToICUError(status)); + } + + auto result = FillBufferWithICUCall( + aBuffer, [this, aUnixEpoch, fpositer](UChar* chars, int32_t size, + UErrorCode* status) { + return udat_formatForFields(mDateFormat, aUnixEpoch, chars, size, + fpositer, status); + }); + if (result.isErr()) { + ufieldpositer_close(fpositer); + return result.propagateErr(); + } + + return TryFormatToParts(fpositer, aBuffer.length(), aParts); + } + + /** * Copies the pattern for the current DateTimeFormat to a buffer. * * Warning: This method should not be added to new code. In the near future we * plan to remove it. */ template <typename B> ICUResult GetPattern(B& aBuffer) const { return FillBufferWithICUCall( @@ -419,23 +459,16 @@ class DateTimeFormat final { * For the implementation, with ICU4C, this takes a string pattern and maps it * back to a ComponentsBag. */ Result<ComponentsBag, ICUError> ResolveComponents(); ~DateTimeFormat(); /** - * TODO(Bug 1686965) - Temporarily get the underlying ICU object while - * migrating to the unified API. This should be removed when completing the - * migration. - */ - UDateFormat* UnsafeGetUDateFormat() const { return mDateFormat; } - - /** * Clones the Calendar from a DateTimeFormat, and sets its time with the * relative milliseconds since 1 January 1970, UTC. */ Result<UniquePtr<Calendar>, ICUError> CloneCalendar(double aUnixEpoch) const; /** * Return the hour cycle used in the input pattern or Nothing if none was * found. @@ -469,16 +502,19 @@ class DateTimeFormat final { udat_getAvailable>(); } private: explicit DateTimeFormat(UDateFormat* aDateFormat); ICUResult CacheSkeleton(Span<const char16_t> aSkeleton); + ICUResult TryFormatToParts(UFieldPositionIterator* aFieldPositionIterator, + size_t aSpanSize, + DateTimePartVector& aParts) const; /** * Replaces all hour pattern characters in |patternOrSkeleton| to use the * matching hour representation for |hourCycle|. */ static void ReplaceHourSymbol(Span<char16_t> aPatternOrSkeleton, DateTimeFormat::HourCycle aHourCycle); /**
new file mode 100644 --- /dev/null +++ b/intl/components/src/DateTimeFormatUtils.cpp @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" + +#include "DateTimeFormatUtils.h" + +namespace mozilla::intl { + +DateTimePartType ConvertUFormatFieldToPartType(UDateFormatField fieldName) { + // See intl/icu/source/i18n/unicode/udat.h for a detailed field list. This + // switch is deliberately exhaustive: cases might have to be added/removed + // if this code is compiled with a different ICU with more + // UDateFormatField enum initializers. Please guard such cases with + // appropriate ICU version-testing #ifdefs, should cross-version divergence + // occur. + switch (fieldName) { + case UDAT_ERA_FIELD: + return DateTimePartType::Era; + + case UDAT_YEAR_FIELD: + case UDAT_YEAR_WOY_FIELD: + case UDAT_EXTENDED_YEAR_FIELD: + return DateTimePartType::Year; + + case UDAT_YEAR_NAME_FIELD: + return DateTimePartType::YearName; + + case UDAT_MONTH_FIELD: + case UDAT_STANDALONE_MONTH_FIELD: + return DateTimePartType::Month; + + case UDAT_DATE_FIELD: + case UDAT_JULIAN_DAY_FIELD: + return DateTimePartType::Day; + + case UDAT_HOUR_OF_DAY1_FIELD: + case UDAT_HOUR_OF_DAY0_FIELD: + case UDAT_HOUR1_FIELD: + case UDAT_HOUR0_FIELD: + return DateTimePartType::Hour; + + case UDAT_MINUTE_FIELD: + return DateTimePartType::Minute; + + case UDAT_SECOND_FIELD: + return DateTimePartType::Second; + + case UDAT_DAY_OF_WEEK_FIELD: + case UDAT_STANDALONE_DAY_FIELD: + case UDAT_DOW_LOCAL_FIELD: + case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD: + return DateTimePartType::Weekday; + + case UDAT_AM_PM_FIELD: + case UDAT_FLEXIBLE_DAY_PERIOD_FIELD: + return DateTimePartType::DayPeriod; + + case UDAT_TIMEZONE_FIELD: + case UDAT_TIMEZONE_GENERIC_FIELD: + case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: + return DateTimePartType::TimeZoneName; + + case UDAT_FRACTIONAL_SECOND_FIELD: + return DateTimePartType::FractionalSecondDigits; + +#ifndef U_HIDE_INTERNAL_API + case UDAT_RELATED_YEAR_FIELD: + return DateTimePartType::RelatedYear; +#endif + + case UDAT_DAY_OF_YEAR_FIELD: + case UDAT_WEEK_OF_YEAR_FIELD: + case UDAT_WEEK_OF_MONTH_FIELD: + case UDAT_MILLISECONDS_IN_DAY_FIELD: + case UDAT_TIMEZONE_RFC_FIELD: + case UDAT_QUARTER_FIELD: + case UDAT_STANDALONE_QUARTER_FIELD: + case UDAT_TIMEZONE_SPECIAL_FIELD: + case UDAT_TIMEZONE_ISO_FIELD: + case UDAT_TIMEZONE_ISO_LOCAL_FIELD: + case UDAT_AM_PM_MIDNIGHT_NOON_FIELD: +#ifndef U_HIDE_INTERNAL_API + case UDAT_TIME_SEPARATOR_FIELD: +#endif + // These fields are all unsupported. + return DateTimePartType::Unknown; + +#ifndef U_HIDE_DEPRECATED_API + case UDAT_FIELD_COUNT: + MOZ_ASSERT_UNREACHABLE( + "format field sentinel value returned by " + "iterator!"); +#endif + } + + MOZ_ASSERT_UNREACHABLE( + "unenumerated, undocumented format field returned " + "by iterator"); + return DateTimePartType::Unknown; +} + +} // namespace mozilla::intl
new file mode 100644 --- /dev/null +++ b/intl/components/src/DateTimeFormatUtils.h @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef intl_components_DateTimeFormatUtils_h_ +#define intl_components_DateTimeFormatUtils_h_ +#include "unicode/udat.h" + +#include "mozilla/intl/DateTimePart.h" + +namespace mozilla::intl { +DateTimePartType ConvertUFormatFieldToPartType(UDateFormatField fieldName); +} // namespace mozilla::intl + +#endif
new file mode 100644 --- /dev/null +++ b/intl/components/src/DateTimePart.h @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef intl_components_DateTimePart_h_ +#define intl_components_DateTimePart_h_ + +#include <cstddef> +#include <cstdint> + +#include "mozilla/Vector.h" + +namespace mozilla::intl { + +enum class DateTimePartType : int16_t { + Literal, + Weekday, + Era, + Year, + YearName, + RelatedYear, + Month, + Day, + DayPeriod, + Hour, + Minute, + Second, + FractionalSecondDigits, + TimeZoneName, + Unknown +}; + +enum class DateTimePartSource : int16_t { Shared, StartRange, EndRange }; + +/** + * The 'Part' object defined in FormatDateTimeToParts and + * FormatDateTimeRangeToParts + * + * Each part consists of three properties: ||Type||, ||Value|| and ||Source||, + * with the ||Source|| property is set to DateTimePartSource::Shared by default. + * (Note: From the spec, the part from FormatDateTimeToParts doesn't have the + * ||Source|| property, so if the caller is FormatDateTimeToParts, it should + * ignore the ||Source|| property). + * + * To store DateTimePart more efficiently, it doesn't store the ||Value|| of + * type string in this struct. Instead, it stores the end index of the string + * in the buffer(which is passed to DateTimeFormat::TryFormatToParts() or + * can be got by calling AutoFormattedDateInterval::ToSpan()). The begin index + * of the ||Value|| is the mEndIndex of the previous part. + * + * Buffer + * 0 i j + * +---------------+---------------+---------------+ + * | Part[0].Value | Part[1].Value | Part[2].Value | .... + * +---------------+---------------+---------------+ + * + * Part[0].mEndIndex is i. Part[0].Value is stored in the Buffer[0..i]. + * Part[1].mEndIndex is j. Part[1].Value is stored in the Buffer[i..j]. + * + * See: + * https://tc39.es/ecma402/#sec-formatdatetimetoparts + * https://tc39.es/ecma402/#sec-formatdatetimerangetoparts + */ +struct DateTimePart { + DateTimePart(DateTimePartType type, size_t endIndex, + DateTimePartSource source) + : mEndIndex(endIndex), mType(type), mSource(source) {} + + // See the above comments for details, mEndIndex is placed first for reducing + // padding. + size_t mEndIndex; + DateTimePartType mType; + DateTimePartSource mSource; +}; + +// The common parts are 'month', 'literal', 'day', 'literal', 'year', 'literal', +// 'hour', 'literal', 'minute', 'literal', which are 10 parts, for DateTimeRange +// the number will be doubled, so choosing 32 as the initial length to prevent +// heap allocation. +constexpr size_t INITIAL_DATETIME_PART_VECTOR_SIZE = 32; +using DateTimePartVector = + mozilla::Vector<DateTimePart, INITIAL_DATETIME_PART_VECTOR_SIZE>; + +} // namespace mozilla::intl +#endif
--- a/intl/unicharutil/util/nsBidiUtils.h +++ b/intl/unicharutil/util/nsBidiUtils.h @@ -47,35 +47,16 @@ enum nsCharType { }; /** * This specifies the language directional property of a character set. */ typedef enum nsCharType nsCharType; /** - * Find the direction of an embedding level or paragraph level set by - * the Unicode Bidi Algorithm. (Even levels are left-to-right, odd - * levels right-to-left. - */ -#define IS_LEVEL_RTL(level) (((level)&1) == 1) - -/** - * Check whether two bidi levels have the same parity and thus the same - * directionality - */ -#define IS_SAME_DIRECTION(level1, level2) (((level1 ^ level2) & 1) == 0) - -/** - * Convert from nsBidiLevel to nsBidiDirection - */ -#define DIRECTION_FROM_LEVEL(level) \ - ((IS_LEVEL_RTL(level)) ? NSBIDI_RTL : NSBIDI_LTR) - -/** * definitions of bidirection character types by category */ #define CHARTYPE_IS_RTL(val) \ (((val) == eCharType_RightToLeft) || ((val) == eCharType_RightToLeftArabic)) #define CHARTYPE_IS_WEAK(val) \ (((val) == eCharType_EuropeanNumberSeparator) || \
--- a/js/public/experimental/JSStencil.h +++ b/js/public/experimental/JSStencil.h @@ -91,19 +91,19 @@ extern JS_PUBLIC_API bool StencilIsBorro extern JS_PUBLIC_API bool StencilCanLazilyParse(Stencil* stencil); // Instantiate a module Stencil and return the associated object. Inside the // engine this is a js::ModuleObject. extern JS_PUBLIC_API JSObject* InstantiateModuleStencil( JSContext* cx, const InstantiateOptions& options, Stencil* stencil); // Serialize the Stencil into the transcode buffer. -extern JS_PUBLIC_API TranscodeResult -EncodeStencil(JSContext* cx, const JS::ReadOnlyCompileOptions& options, - Stencil* stencil, TranscodeBuffer& buffer); +extern JS_PUBLIC_API TranscodeResult EncodeStencil(JSContext* cx, + Stencil* stencil, + TranscodeBuffer& buffer); // Deserialize data and create a new Stencil. extern JS_PUBLIC_API TranscodeResult DecodeStencil(JSContext* cx, const ReadOnlyCompileOptions& options, const TranscodeRange& range, Stencil** stencilOut); extern JS_PUBLIC_API size_t SizeOfStencil(Stencil* stencil, mozilla::MallocSizeOf mallocSizeOf);
--- a/js/src/builtin/intl/Collator.cpp +++ b/js/src/builtin/intl/Collator.cpp @@ -346,16 +346,34 @@ static mozilla::intl::Collator* NewIntlC if (optResult.isErr()) { ReportInternalError(cx, optResult.unwrapErr()); return nullptr; } return coll.release(); } +static mozilla::intl::Collator* GetOrCreateCollator( + JSContext* cx, Handle<CollatorObject*> collator) { + // Obtain a cached mozilla::intl::Collator object. + mozilla::intl::Collator* coll = collator->getCollator(); + if (coll) { + return coll; + } + + coll = NewIntlCollator(cx, collator); + if (!coll) { + return nullptr; + } + collator->setCollator(coll); + + intl::AddICUCellMemory(collator, CollatorObject::EstimatedMemoryUse); + return coll; +} + static bool intl_CompareStrings(JSContext* cx, mozilla::intl::Collator* coll, HandleString str1, HandleString str2, MutableHandleValue result) { MOZ_ASSERT(str1); MOZ_ASSERT(str2); if (str1 == str2) { result.setInt32(0); @@ -384,26 +402,19 @@ bool js::intl_CompareStrings(JSContext* MOZ_ASSERT(args.length() == 3); MOZ_ASSERT(args[0].isObject()); MOZ_ASSERT(args[1].isString()); MOZ_ASSERT(args[2].isString()); Rooted<CollatorObject*> collator(cx, &args[0].toObject().as<CollatorObject>()); - // Obtain a cached mozilla::intl::Collator object. - mozilla::intl::Collator* coll = collator->getCollator(); + mozilla::intl::Collator* coll = GetOrCreateCollator(cx, collator); if (!coll) { - coll = NewIntlCollator(cx, collator); - if (!coll) { - return false; - } - collator->setCollator(coll); - - intl::AddICUCellMemory(collator, CollatorObject::EstimatedMemoryUse); + return false; } // Use the UCollator to actually compare the strings. RootedString str1(cx, args[1].toString()); RootedString str2(cx, args[2].toString()); return intl_CompareStrings(cx, coll, str1, str2, args.rval()); }
--- a/js/src/builtin/intl/CommonFunctions.cpp +++ b/js/src/builtin/intl/CommonFunctions.cpp @@ -15,17 +15,16 @@ #include <algorithm> #include "gc/GCEnum.h" #include "gc/Zone.h" #include "gc/ZoneAllocator.h" #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_INTERNAL_INTL_ERROR #include "js/Value.h" -#include "unicode/uformattedvalue.h" #include "vm/JSContext.h" #include "vm/JSObject.h" #include "vm/SelfHosting.h" #include "vm/Stack.h" #include "vm/JSObject-inl.h" bool js::intl::InitializeObject(JSContext* cx, JS::Handle<JSObject*> obj, @@ -143,22 +142,8 @@ void js::intl::AddICUCellMemory(JSObject // the JSObject's zone. AddCellMemory(obj, nbytes, MemoryUse::ICUObject); } void js::intl::RemoveICUCellMemory(JSFreeOp* fop, JSObject* obj, size_t nbytes) { fop->removeCellMemory(obj, nbytes, MemoryUse::ICUObject); } - -JSString* js::intl::FormattedValueToString( - JSContext* cx, const UFormattedValue* formattedValue) { - UErrorCode status = U_ZERO_ERROR; - int32_t strLength; - const char16_t* str = ufmtval_getString(formattedValue, &strLength, &status); - if (U_FAILURE(status)) { - ReportInternalError(cx); - return nullptr; - } - - return NewStringCopyN<CanGC>(cx, str, - mozilla::AssertedCast<uint32_t>(strLength)); -}
--- a/js/src/builtin/intl/CommonFunctions.h +++ b/js/src/builtin/intl/CommonFunctions.h @@ -14,18 +14,16 @@ #include <string.h> #include <type_traits> #include "js/RootingAPI.h" #include "js/Vector.h" #include "unicode/utypes.h" #include "vm/StringType.h" -struct UFormattedValue; - namespace mozilla::intl { enum class ICUError : uint8_t; } namespace js { namespace intl { @@ -160,17 +158,13 @@ static JSString* CallICU(JSContext* cx, } return NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size)); } void AddICUCellMemory(JSObject* obj, size_t nbytes); void RemoveICUCellMemory(JSFreeOp* fop, JSObject* obj, size_t nbytes); - -JSString* FormattedValueToString(JSContext* cx, - const UFormattedValue* formattedValue); - } // namespace intl } // namespace js #endif /* builtin_intl_CommonFunctions_h */
--- a/js/src/builtin/intl/DateTimeFormat.cpp +++ b/js/src/builtin/intl/DateTimeFormat.cpp @@ -6,17 +6,19 @@ /* Intl.DateTimeFormat implementation. */ #include "builtin/intl/DateTimeFormat.h" #include "mozilla/Assertions.h" #include "mozilla/EnumSet.h" #include "mozilla/intl/Calendar.h" +#include "mozilla/intl/DateIntervalFormat.h" #include "mozilla/intl/DateTimeFormat.h" +#include "mozilla/intl/DateTimePart.h" #include "mozilla/intl/DateTimePatternGenerator.h" #include "mozilla/intl/Locale.h" #include "mozilla/intl/TimeZone.h" #include "mozilla/Range.h" #include "mozilla/Span.h" #include "builtin/Array.h" #include "builtin/intl/CommonFunctions.h" @@ -29,22 +31,16 @@ #include "js/CharacterEncoding.h" #include "js/Date.h" #include "js/experimental/Intl.h" // JS::AddMozDateTimeFormatConstructor #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/GCAPI.h" #include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_DefineProperties #include "js/PropertySpec.h" #include "js/StableStringChars.h" -#include "unicode/udat.h" -#include "unicode/udateintervalformat.h" -#include "unicode/uenum.h" -#include "unicode/ufieldpositer.h" -#include "unicode/uloc.h" -#include "unicode/utypes.h" #include "vm/DateTime.h" #include "vm/GlobalObject.h" #include "vm/JSContext.h" #include "vm/PlainObject.h" // js::PlainObject #include "vm/Runtime.h" #include "vm/WellKnownAtom.h" // js_*_str #include "vm/JSObject-inl.h" @@ -190,30 +186,31 @@ bool js::intl_DateTimeFormat(JSContext* return DateTimeFormat(cx, args, true, DateTimeFormatOptions::Standard); } void js::DateTimeFormatObject::finalize(JSFreeOp* fop, JSObject* obj) { MOZ_ASSERT(fop->onMainThread()); auto* dateTimeFormat = &obj->as<DateTimeFormatObject>(); mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat(); - UDateIntervalFormat* dif = dateTimeFormat->getDateIntervalFormat(); + mozilla::intl::DateIntervalFormat* dif = + dateTimeFormat->getDateIntervalFormat(); if (df) { intl::RemoveICUCellMemory( fop, obj, DateTimeFormatObject::UDateFormatEstimatedMemoryUse); delete df; } if (dif) { intl::RemoveICUCellMemory( fop, obj, DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse); - udtitvfmt_close(dif); + delete dif; } } bool JS::AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl) { RootedObject ctor( cx, GlobalObject::createConstructor(cx, MozDateTimeFormat, cx->names().DateTimeFormat, 0)); @@ -983,16 +980,35 @@ static mozilla::intl::DateTimeFormat* Ne // ECMAScript requires the Gregorian calendar to be used from the beginning // of ECMAScript time. df->SetStartTimeIfGregorian(StartOfTime); return df.release(); } +static mozilla::intl::DateTimeFormat* GetOrCreateDateTimeFormat( + JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat) { + // Obtain a cached mozilla::intl::DateTimeFormat object. + mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat(); + if (df) { + return df; + } + + df = NewDateTimeFormat(cx, dateTimeFormat); + if (!df) { + return nullptr; + } + dateTimeFormat->setDateFormat(df); + + intl::AddICUCellMemory(dateTimeFormat, + DateTimeFormatObject::UDateFormatEstimatedMemoryUse); + return df; +} + template <typename T> static bool SetResolvedProperty(JSContext* cx, HandleObject resolved, HandlePropertyName name, mozilla::Maybe<T> intlProp) { if (!intlProp) { return true; } JSString* str = NewStringCopyZ<CanGC>( @@ -1014,27 +1030,20 @@ bool js::intl_resolveDateTimeFormatCompo Rooted<DateTimeFormatObject*> dateTimeFormat(cx); dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>(); RootedObject resolved(cx, &args[1].toObject()); bool includeDateTimeFields = args[2].toBoolean(); - // Obtain a cached mozilla::intl::DateTimeFormat object. - mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat(); + mozilla::intl::DateTimeFormat* df = + GetOrCreateDateTimeFormat(cx, dateTimeFormat); if (!df) { - df = NewDateTimeFormat(cx, dateTimeFormat); - if (!df) { - return false; - } - dateTimeFormat->setDateFormat(df); - - intl::AddICUCellMemory(dateTimeFormat, - DateTimeFormatObject::UDateFormatEstimatedMemoryUse); + return false; } auto result = df->ResolveComponents(); if (result.isErr()) { intl::ReportInternalError(cx, result.unwrapErr()); return false; } @@ -1130,234 +1139,157 @@ static bool intl_FormatDateTime(JSContex } result.setString(str); return true; } using FieldType = js::ImmutablePropertyNamePtr JSAtomState::*; -static FieldType GetFieldTypeForFormatField(UDateFormatField fieldName) { - // See intl/icu/source/i18n/unicode/udat.h for a detailed field list. This - // switch is deliberately exhaustive: cases might have to be added/removed - // if this code is compiled with a different ICU with more - // UDateFormatField enum initializers. Please guard such cases with - // appropriate ICU version-testing #ifdefs, should cross-version divergence - // occur. - switch (fieldName) { - case UDAT_ERA_FIELD: +static FieldType GetFieldTypeForPartType(mozilla::intl::DateTimePartType type) { + switch (type) { + case mozilla::intl::DateTimePartType::Literal: + return &JSAtomState::literal; + case mozilla::intl::DateTimePartType::Era: return &JSAtomState::era; - - case UDAT_YEAR_FIELD: - case UDAT_YEAR_WOY_FIELD: - case UDAT_EXTENDED_YEAR_FIELD: + case mozilla::intl::DateTimePartType::Year: return &JSAtomState::year; - - case UDAT_YEAR_NAME_FIELD: + case mozilla::intl::DateTimePartType::YearName: return &JSAtomState::yearName; - - case UDAT_MONTH_FIELD: - case UDAT_STANDALONE_MONTH_FIELD: + case mozilla::intl::DateTimePartType::RelatedYear: + return &JSAtomState::relatedYear; + case mozilla::intl::DateTimePartType::Month: return &JSAtomState::month; - - case UDAT_DATE_FIELD: - case UDAT_JULIAN_DAY_FIELD: + case mozilla::intl::DateTimePartType::Day: return &JSAtomState::day; - - case UDAT_HOUR_OF_DAY1_FIELD: - case UDAT_HOUR_OF_DAY0_FIELD: - case UDAT_HOUR1_FIELD: - case UDAT_HOUR0_FIELD: + case mozilla::intl::DateTimePartType::Hour: return &JSAtomState::hour; - - case UDAT_MINUTE_FIELD: + case mozilla::intl::DateTimePartType::Minute: return &JSAtomState::minute; - - case UDAT_SECOND_FIELD: + case mozilla::intl::DateTimePartType::Second: return &JSAtomState::second; - - case UDAT_DAY_OF_WEEK_FIELD: - case UDAT_STANDALONE_DAY_FIELD: - case UDAT_DOW_LOCAL_FIELD: - case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD: + case mozilla::intl::DateTimePartType::Weekday: return &JSAtomState::weekday; - - case UDAT_AM_PM_FIELD: - return &JSAtomState::dayPeriod; - - case UDAT_TIMEZONE_FIELD: - case UDAT_TIMEZONE_GENERIC_FIELD: - case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: - return &JSAtomState::timeZoneName; - - case UDAT_FRACTIONAL_SECOND_FIELD: - return &JSAtomState::fractionalSecond; - - case UDAT_FLEXIBLE_DAY_PERIOD_FIELD: + case mozilla::intl::DateTimePartType::DayPeriod: return &JSAtomState::dayPeriod; - -#ifndef U_HIDE_INTERNAL_API - case UDAT_RELATED_YEAR_FIELD: - return &JSAtomState::relatedYear; -#endif - - case UDAT_DAY_OF_YEAR_FIELD: - case UDAT_WEEK_OF_YEAR_FIELD: - case UDAT_WEEK_OF_MONTH_FIELD: - case UDAT_MILLISECONDS_IN_DAY_FIELD: - case UDAT_TIMEZONE_RFC_FIELD: - case UDAT_QUARTER_FIELD: - case UDAT_STANDALONE_QUARTER_FIELD: - case UDAT_TIMEZONE_SPECIAL_FIELD: - case UDAT_TIMEZONE_ISO_FIELD: - case UDAT_TIMEZONE_ISO_LOCAL_FIELD: - case UDAT_AM_PM_MIDNIGHT_NOON_FIELD: -#ifndef U_HIDE_INTERNAL_API - case UDAT_TIME_SEPARATOR_FIELD: -#endif - // These fields are all unsupported. + case mozilla::intl::DateTimePartType::TimeZoneName: + return &JSAtomState::timeZoneName; + case mozilla::intl::DateTimePartType::FractionalSecondDigits: + return &JSAtomState::fractionalSecond; + case mozilla::intl::DateTimePartType::Unknown: return &JSAtomState::unknown; - -#ifndef U_HIDE_DEPRECATED_API - case UDAT_FIELD_COUNT: - MOZ_ASSERT_UNREACHABLE( - "format field sentinel value returned by " - "iterator!"); -#endif } - MOZ_ASSERT_UNREACHABLE( + MOZ_CRASH( "unenumerated, undocumented format field returned " "by iterator"); - return nullptr; } -static bool intl_FormatToPartsDateTime(JSContext* cx, - const mozilla::intl::DateTimeFormat* df, - ClippedTime x, FieldType source, - MutableHandleValue result) { - MOZ_ASSERT(x.isValid()); - - UErrorCode status = U_ZERO_ERROR; - UFieldPositionIterator* fpositer = ufieldpositer_open(&status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; +static FieldType GetFieldTypeForPartSource( + mozilla::intl::DateTimePartSource source) { + switch (source) { + case mozilla::intl::DateTimePartSource::Shared: + return &JSAtomState::shared; + case mozilla::intl::DateTimePartSource::StartRange: + return &JSAtomState::startRange; + case mozilla::intl::DateTimePartSource::EndRange: + return &JSAtomState::endRange; } - ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose( - fpositer); + + MOZ_CRASH( + "unenumerated, undocumented format field returned " + "by iterator"); +} - RootedString overallResult(cx); - overallResult = CallICU(cx, [df, x, fpositer](UChar* chars, int32_t size, - UErrorCode* status) { - return udat_formatForFields( - // TODO(Bug 1686965) - The use of UnsafeGetUDateFormat is a temporary - // migration step until the field position iterator is supported. - df->UnsafeGetUDateFormat(), x.toDouble(), chars, size, fpositer, - status); - }); +// A helper function to create an ArrayObject from DateTimePart objects. +// When hasNoSource is true, we don't need to create the ||Source|| property for +// the DateTimePart object. +static bool CreateDateTimePartArray( + JSContext* cx, mozilla::Span<const char16_t> formattedSpan, + bool hasNoSource, const mozilla::intl::DateTimePartVector& parts, + MutableHandleValue result) { + RootedString overallResult(cx, NewStringCopy<CanGC>(cx, formattedSpan)); if (!overallResult) { return false; } - RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx)); + RootedArrayObject partsArray(cx, + NewDenseFullyAllocatedArray(cx, parts.length())); if (!partsArray) { return false; } + partsArray->ensureDenseInitializedLength(0, parts.length()); if (overallResult->length() == 0) { // An empty string contains no parts, so avoid extra work below. result.setObject(*partsArray); return true; } - size_t lastEndIndex = 0; - RootedObject singlePart(cx); RootedValue val(cx); - auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) { + size_t index = 0; + size_t beginIndex = 0; + for (const mozilla::intl::DateTimePart& part : parts) { singlePart = NewPlainObject(cx); if (!singlePart) { return false; } + FieldType type = GetFieldTypeForPartType(part.mType); val = StringValue(cx->names().*type); if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) { return false; } - JSLinearString* partSubstr = NewDependentString( - cx, overallResult, beginIndex, endIndex - beginIndex); - if (!partSubstr) { + MOZ_ASSERT(part.mEndIndex > beginIndex); + JSLinearString* partStr = NewDependentString(cx, overallResult, beginIndex, + part.mEndIndex - beginIndex); + if (!partStr) { return false; } - - val = StringValue(partSubstr); + val = StringValue(partStr); if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) { return false; } - if (source) { + if (!hasNoSource) { + FieldType source = GetFieldTypeForPartSource(part.mSource); val = StringValue(cx->names().*source); if (!DefineDataProperty(cx, singlePart, cx->names().source, val)) { return false; } } - if (!NewbornArrayPush(cx, partsArray, ObjectValue(*singlePart))) { - return false; - } - - lastEndIndex = endIndex; - return true; - }; - - int32_t fieldInt, beginIndexInt, endIndexInt; - while ((fieldInt = ufieldpositer_next(fpositer, &beginIndexInt, - &endIndexInt)) >= 0) { - MOZ_ASSERT(beginIndexInt >= 0); - MOZ_ASSERT(endIndexInt >= 0); - MOZ_ASSERT(beginIndexInt <= endIndexInt, - "field iterator returning invalid range"); - - size_t beginIndex(beginIndexInt); - size_t endIndex(endIndexInt); - - // Technically this isn't guaranteed. But it appears true in pratice, - // and http://bugs.icu-project.org/trac/ticket/12024 is expected to - // correct the documentation lapse. - MOZ_ASSERT(lastEndIndex <= beginIndex, - "field iteration didn't return fields in order start to " - "finish as expected"); - - if (FieldType type = GetFieldTypeForFormatField( - static_cast<UDateFormatField>(fieldInt))) { - if (lastEndIndex < beginIndex) { - if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex)) { - return false; - } - } - - if (!AppendPart(type, beginIndex, endIndex)) { - return false; - } - } + beginIndex = part.mEndIndex; + partsArray->initDenseElement(index++, ObjectValue(*singlePart)); } - // Append any final literal. - if (lastEndIndex < overallResult->length()) { - if (!AppendPart(&JSAtomState::literal, lastEndIndex, - overallResult->length())) { - return false; - } + MOZ_ASSERT(index == parts.length()); + MOZ_ASSERT(beginIndex == formattedSpan.size()); + result.setObject(*partsArray); + return true; +} + +static bool intl_FormatToPartsDateTime(JSContext* cx, + const mozilla::intl::DateTimeFormat* df, + ClippedTime x, bool hasNoSource, + MutableHandleValue result) { + MOZ_ASSERT(x.isValid()); + + FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx); + mozilla::intl::DateTimePartVector parts; + auto r = df->TryFormatToParts(x.toDouble(), buffer, parts); + if (r.isErr()) { + intl::ReportInternalError(cx, r.unwrapErr()); + return false; } - result.setObject(*partsArray); - return true; + return CreateDateTimePartArray(cx, buffer, hasNoSource, parts, result); } bool js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 3); MOZ_ASSERT(args[0].isObject()); MOZ_ASSERT(args[1].isNumber()); MOZ_ASSERT(args[2].isBoolean()); @@ -1370,41 +1302,33 @@ bool js::intl_FormatDateTime(JSContext* ClippedTime x = TimeClip(args[1].toNumber()); if (!x.isValid()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE, "DateTimeFormat", formatToParts ? "formatToParts" : "format"); return false; } - // Obtain a cached DateTimeFormat object. - mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat(); + mozilla::intl::DateTimeFormat* df = + GetOrCreateDateTimeFormat(cx, dateTimeFormat); if (!df) { - df = NewDateTimeFormat(cx, dateTimeFormat); - if (!df) { - return false; - } - dateTimeFormat->setDateFormat(df); - - intl::AddICUCellMemory(dateTimeFormat, - DateTimeFormatObject::UDateFormatEstimatedMemoryUse); + return false; } - // Use the UDateFormat to actually format the time stamp. - FieldType source = nullptr; - return formatToParts - ? intl_FormatToPartsDateTime(cx, df, x, source, args.rval()) - : intl_FormatDateTime(cx, df, x, args.rval()); + // Use the DateTimeFormat to actually format the time stamp. + return formatToParts ? intl_FormatToPartsDateTime( + cx, df, x, /* hasNoSource */ true, args.rval()) + : intl_FormatDateTime(cx, df, x, args.rval()); } /** - * Returns a new UDateIntervalFormat with the locale and date-time formatting + * Returns a new DateIntervalFormat with the locale and date-time formatting * options of the given DateTimeFormat. */ -static UDateIntervalFormat* NewUDateIntervalFormat( +static mozilla::intl::DateIntervalFormat* NewDateIntervalFormat( JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat, mozilla::intl::DateTimeFormat& mozDtf) { RootedValue value(cx); RootedObject internals(cx, intl::GetInternalsObject(cx, dateTimeFormat)); if (!internals) { return nullptr; } @@ -1438,35 +1362,57 @@ static UDateIntervalFormat* NewUDateInte FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> skeleton(cx); auto skelResult = mozDtf.GetOriginalSkeleton(skeleton, hcPattern); if (skelResult.isErr()) { intl::ReportInternalError(cx, skelResult.unwrapErr()); return nullptr; } - UErrorCode status = U_ZERO_ERROR; - UDateIntervalFormat* dif = udtitvfmt_open( - IcuLocale(locale.get()), skeleton.data(), skeleton.length(), - timeZoneChars.data(), timeZoneChars.size(), &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); + auto dif = mozilla::intl::DateIntervalFormat::TryCreate( + mozilla::MakeStringSpan(locale.get()), skeleton, timeZoneChars); + + if (dif.isErr()) { + js::intl::ReportInternalError(cx, dif.unwrapErr()); return nullptr; } + return dif.unwrap().release(); +} + +static mozilla::intl::DateIntervalFormat* GetOrCreateDateIntervalFormat( + JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat, + mozilla::intl::DateTimeFormat& mozDtf) { + // Obtain a cached DateIntervalFormat object. + mozilla::intl::DateIntervalFormat* dif = + dateTimeFormat->getDateIntervalFormat(); + if (dif) { + return dif; + } + + dif = NewDateIntervalFormat(cx, dateTimeFormat, mozDtf); + if (!dif) { + return nullptr; + } + dateTimeFormat->setDateIntervalFormat(dif); + + intl::AddICUCellMemory( + dateTimeFormat, + DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse); return dif; } /** * PartitionDateTimeRangePattern ( dateTimeFormat, x, y ) */ -static const UFormattedValue* PartitionDateTimeRangePattern( +static bool PartitionDateTimeRangePattern( JSContext* cx, const mozilla::intl::DateTimeFormat* df, - const UDateIntervalFormat* dif, UFormattedDateInterval* formatted, - ClippedTime x, ClippedTime y) { + const mozilla::intl::DateIntervalFormat* dif, + mozilla::intl::AutoFormattedDateInterval& formatted, ClippedTime x, + ClippedTime y, bool* equal) { MOZ_ASSERT(x.isValid()); MOZ_ASSERT(y.isValid()); MOZ_ASSERT(x.toDouble() <= y.toDouble()); // We can't access the calendar used by UDateIntervalFormat to change it to a // proleptic Gregorian calendar. Instead we need to call a different formatter // function which accepts UCalendar instead of UDate. // But creating new UCalendar objects for each call is slow, so when we can @@ -1475,335 +1421,125 @@ static const UFormattedValue* PartitionD // The Gregorian change date "1582-10-15T00:00:00.000Z". constexpr double GregorianChangeDate = -12219292800000.0; // Add a full day to account for time zone offsets. constexpr double GregorianChangeDatePlusOneDay = GregorianChangeDate + msPerDay; - UErrorCode status = U_ZERO_ERROR; + mozilla::intl::ICUResult result = Ok(); if (x.toDouble() < GregorianChangeDatePlusOneDay) { // Create calendar objects for the start and end date by cloning the date // formatter calendar. The date formatter calendar already has the correct // time zone set and was changed to use a proleptic Gregorian calendar. auto startCal = df->CloneCalendar(x.toDouble()); if (startCal.isErr()) { intl::ReportInternalError(cx, startCal.unwrapErr()); - return nullptr; + return false; } auto endCal = df->CloneCalendar(y.toDouble()); if (endCal.isErr()) { intl::ReportInternalError(cx, endCal.unwrapErr()); - return nullptr; + return false; } - udtitvfmt_formatCalendarToResult( - dif, startCal.unwrap()->UnsafeGetUCalendar(), - endCal.unwrap()->UnsafeGetUCalendar(), formatted, &status); + result = dif->TryFormatCalendar(*startCal.unwrap(), *endCal.unwrap(), + formatted, equal); } else { // The common fast path which doesn't require creating calendar objects. - udtitvfmt_formatToResult(dif, x.toDouble(), y.toDouble(), formatted, - &status); - } - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return nullptr; - } - - const UFormattedValue* formattedValue = - udtitvfmt_resultAsValue(formatted, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return nullptr; + result = + dif->TryFormatDateTime(x.toDouble(), y.toDouble(), formatted, equal); } - return formattedValue; -} - -/** - * PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), steps 9-11. - * - * Examine the formatted value to see if any interval span field is present. - */ -static bool DateFieldsPracticallyEqual(JSContext* cx, - const UFormattedValue* formattedValue, - bool* equal) { - UErrorCode status = U_ZERO_ERROR; - UConstrainedFieldPosition* fpos = ucfpos_open(&status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos); - - // We're only interested in UFIELD_CATEGORY_DATE_INTERVAL_SPAN fields. - ucfpos_constrainCategory(fpos, UFIELD_CATEGORY_DATE_INTERVAL_SPAN, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); + if (result.isErr()) { + intl::ReportInternalError(cx, result.unwrapErr()); return false; } - bool hasSpan = ufmtval_nextPosition(formattedValue, fpos, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - - // When no date interval span field was found, both dates are "practically - // equal" per PartitionDateTimeRangePattern. - *equal = !hasSpan; return true; } /** * FormatDateTimeRange( dateTimeFormat, x, y ) */ static bool FormatDateTimeRange(JSContext* cx, const mozilla::intl::DateTimeFormat* df, - const UDateIntervalFormat* dif, ClippedTime x, - ClippedTime y, MutableHandleValue result) { - UErrorCode status = U_ZERO_ERROR; - UFormattedDateInterval* formatted = udtitvfmt_openResult(&status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - ScopedICUObject<UFormattedDateInterval, udtitvfmt_closeResult> toClose( - formatted); - - const UFormattedValue* formattedValue = - PartitionDateTimeRangePattern(cx, df, dif, formatted, x, y); - if (!formattedValue) { + const mozilla::intl::DateIntervalFormat* dif, + ClippedTime x, ClippedTime y, + MutableHandleValue result) { + mozilla::intl::AutoFormattedDateInterval formatted; + if (!formatted.IsValid()) { + intl::ReportInternalError(cx, formatted.GetError()); return false; } - // PartitionDateTimeRangePattern, steps 9-11. bool equal; - if (!DateFieldsPracticallyEqual(cx, formattedValue, &equal)) { + if (!PartitionDateTimeRangePattern(cx, df, dif, formatted, x, y, &equal)) { return false; } // PartitionDateTimeRangePattern, step 12. if (equal) { return intl_FormatDateTime(cx, df, x, result); } - JSString* resultStr = intl::FormattedValueToString(cx, formattedValue); + auto spanResult = formatted.ToSpan(); + if (spanResult.isErr()) { + intl::ReportInternalError(cx, spanResult.unwrapErr()); + return false; + } + JSString* resultStr = NewStringCopy<CanGC>(cx, spanResult.unwrap()); if (!resultStr) { return false; } result.setString(resultStr); return true; } /** * FormatDateTimeRangeToParts ( dateTimeFormat, x, y ) */ -static bool FormatDateTimeRangeToParts(JSContext* cx, - const mozilla::intl::DateTimeFormat* df, - const UDateIntervalFormat* dif, - ClippedTime x, ClippedTime y, - MutableHandleValue result) { - UErrorCode status = U_ZERO_ERROR; - UFormattedDateInterval* formatted = udtitvfmt_openResult(&status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - ScopedICUObject<UFormattedDateInterval, udtitvfmt_closeResult> toClose( - formatted); - - const UFormattedValue* formattedValue = - PartitionDateTimeRangePattern(cx, df, dif, formatted, x, y); - if (!formattedValue) { +static bool FormatDateTimeRangeToParts( + JSContext* cx, const mozilla::intl::DateTimeFormat* df, + const mozilla::intl::DateIntervalFormat* dif, ClippedTime x, ClippedTime y, + MutableHandleValue result) { + mozilla::intl::AutoFormattedDateInterval formatted; + if (!formatted.IsValid()) { + intl::ReportInternalError(cx, formatted.GetError()); return false; } - // PartitionDateTimeRangePattern, steps 9-11. bool equal; - if (!DateFieldsPracticallyEqual(cx, formattedValue, &equal)) { + if (!PartitionDateTimeRangePattern(cx, df, dif, formatted, x, y, &equal)) { return false; } // PartitionDateTimeRangePattern, step 12. if (equal) { - FieldType source = &JSAtomState::shared; - return intl_FormatToPartsDateTime(cx, df, x, source, result); + return intl_FormatToPartsDateTime(cx, df, x, /* hasNoSource */ false, + result); } - RootedString overallResult(cx, - intl::FormattedValueToString(cx, formattedValue)); - if (!overallResult) { - return false; - } - - RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx)); - if (!partsArray) { + mozilla::intl::DateTimePartVector parts; + auto r = dif->TryFormattedToParts(formatted, parts); + if (r.isErr()) { + intl::ReportInternalError(cx, r.unwrapErr()); return false; } - size_t lastEndIndex = 0; - RootedObject singlePart(cx); - RootedValue val(cx); - - auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex, - FieldType source) { - singlePart = NewPlainObject(cx); - if (!singlePart) { - return false; - } - - val = StringValue(cx->names().*type); - if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) { - return false; - } - - JSLinearString* partSubstr = NewDependentString( - cx, overallResult, beginIndex, endIndex - beginIndex); - if (!partSubstr) { - return false; - } - - val = StringValue(partSubstr); - if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) { - return false; - } - - val = StringValue(cx->names().*source); - if (!DefineDataProperty(cx, singlePart, cx->names().source, val)) { - return false; - } - - if (!NewbornArrayPush(cx, partsArray, ObjectValue(*singlePart))) { - return false; - } - - lastEndIndex = endIndex; - return true; - }; - - UConstrainedFieldPosition* fpos = ucfpos_open(&status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); + auto spanResult = formatted.ToSpan(); + if (spanResult.isErr()) { + intl::ReportInternalError(cx, spanResult.unwrapErr()); return false; } - ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos); - - size_t categoryEndIndex = 0; - FieldType source = &JSAtomState::shared; - - while (true) { - bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - if (!hasMore) { - break; - } - - int32_t category = ucfpos_getCategory(fpos, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - - int32_t field = ucfpos_getField(fpos, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - - int32_t beginIndexInt, endIndexInt; - ucfpos_getIndexes(fpos, &beginIndexInt, &endIndexInt, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - - MOZ_ASSERT(beginIndexInt >= 0); - MOZ_ASSERT(endIndexInt >= 0); - MOZ_ASSERT(beginIndexInt <= endIndexInt, - "field iterator returning invalid range"); - - size_t beginIndex = size_t(beginIndexInt); - size_t endIndex = size_t(endIndexInt); - - // Indices are guaranteed to be returned in order (from left to right). - MOZ_ASSERT(lastEndIndex <= beginIndex, - "field iteration didn't return fields in order start to " - "finish as expected"); - - if (category == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) { - // Append any remaining literal parts before changing the source kind. - if (lastEndIndex < beginIndex) { - if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex, - source)) { - return false; - } - } - - // The special field category UFIELD_CATEGORY_DATE_INTERVAL_SPAN has only - // two allowed values (0 or 1), indicating the begin of the start- resp. - // end-date. - MOZ_ASSERT(field == 0 || field == 1, - "span category has unexpected value"); - - source = field == 0 ? &JSAtomState::startRange : &JSAtomState::endRange; - categoryEndIndex = endIndex; - continue; - } - - // Ignore categories other than UFIELD_CATEGORY_DATE. - if (category != UFIELD_CATEGORY_DATE) { - continue; - } - - // Append the field if supported. If not supported, append it as part of the - // next literal part. - if (FieldType type = - GetFieldTypeForFormatField(static_cast<UDateFormatField>(field))) { - if (lastEndIndex < beginIndex) { - if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex, - source)) { - return false; - } - } - - if (!AppendPart(type, beginIndex, endIndex, source)) { - return false; - } - } - - if (endIndex == categoryEndIndex) { - // Append any remaining literal parts before changing the source kind. - if (lastEndIndex < endIndex) { - if (!AppendPart(&JSAtomState::literal, lastEndIndex, endIndex, - source)) { - return false; - } - } - - source = &JSAtomState::shared; - } - } - - // Append any final literal. - if (lastEndIndex < overallResult->length()) { - if (!AppendPart(&JSAtomState::literal, lastEndIndex, - overallResult->length(), source)) { - return false; - } - } - - result.setObject(*partsArray); - return true; + return CreateDateTimePartArray(cx, spanResult.unwrap(), + /* hasNoSource */ false, parts, result); } bool js::intl_FormatDateTimeRange(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 4); MOZ_ASSERT(args[0].isObject()); MOZ_ASSERT(args[1].isNumber()); MOZ_ASSERT(args[2].isNumber()); @@ -1831,40 +1567,25 @@ bool js::intl_FormatDateTimeRange(JSCont formatToParts ? "formatRangeToParts" : "formatRange"); return false; } // Self-hosted code should have checked this condition. MOZ_ASSERT(x.toDouble() <= y.toDouble(), "start date mustn't be after the end date"); - // Obtain a cached mozilla::intl::DateTimeFormat object. - mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat(); + mozilla::intl::DateTimeFormat* df = + GetOrCreateDateTimeFormat(cx, dateTimeFormat); if (!df) { - df = NewDateTimeFormat(cx, dateTimeFormat); - if (!df) { - return false; - } - dateTimeFormat->setDateFormat(df); - - intl::AddICUCellMemory(dateTimeFormat, - DateTimeFormatObject::UDateFormatEstimatedMemoryUse); + return false; } - // Obtain a cached UDateIntervalFormat object. - UDateIntervalFormat* dif = dateTimeFormat->getDateIntervalFormat(); + mozilla::intl::DateIntervalFormat* dif = + GetOrCreateDateIntervalFormat(cx, dateTimeFormat, *df); if (!dif) { - dif = NewUDateIntervalFormat(cx, dateTimeFormat, *df); - if (!dif) { - return false; - } - dateTimeFormat->setDateIntervalFormat(dif); - - intl::AddICUCellMemory( - dateTimeFormat, - DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse); + return false; } - // Use the UDateIntervalFormat to actually format the time range. + // Use the DateIntervalFormat to actually format the time range. return formatToParts ? FormatDateTimeRangeToParts(cx, df, dif, x, y, args.rval()) : FormatDateTimeRange(cx, df, dif, x, y, args.rval()); }
--- a/js/src/builtin/intl/DateTimeFormat.h +++ b/js/src/builtin/intl/DateTimeFormat.h @@ -8,66 +8,66 @@ #define builtin_intl_DateTimeFormat_h #include "builtin/intl/CommonFunctions.h" #include "builtin/SelfHostingDefines.h" #include "js/Class.h" #include "js/RootingAPI.h" #include "vm/NativeObject.h" -struct UDateIntervalFormat; - namespace mozilla::intl { class DateTimeFormat; -} +class DateIntervalFormat; +} // namespace mozilla::intl namespace js { class DateTimeFormatObject : public NativeObject { public: static const JSClass class_; static const JSClass& protoClass_; static constexpr uint32_t INTERNALS_SLOT = 0; - static constexpr uint32_t UDATE_FORMAT_SLOT = 1; - static constexpr uint32_t UDATE_INTERVAL_FORMAT_SLOT = 2; + static constexpr uint32_t DATE_FORMAT_SLOT = 1; + static constexpr uint32_t DATE_INTERVAL_FORMAT_SLOT = 2; static constexpr uint32_t SLOT_COUNT = 3; static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT, "INTERNALS_SLOT must match self-hosting define for internals " "object slot"); // Estimated memory use for UDateFormat (see IcuMemoryUsage). static constexpr size_t UDateFormatEstimatedMemoryUse = 105402; // Estimated memory use for UDateIntervalFormat (see IcuMemoryUsage). static constexpr size_t UDateIntervalFormatEstimatedMemoryUse = 133064; mozilla::intl::DateTimeFormat* getDateFormat() const { - const auto& slot = getFixedSlot(UDATE_FORMAT_SLOT); + const auto& slot = getFixedSlot(DATE_FORMAT_SLOT); if (slot.isUndefined()) { return nullptr; } return static_cast<mozilla::intl::DateTimeFormat*>(slot.toPrivate()); } void setDateFormat(mozilla::intl::DateTimeFormat* dateFormat) { - setFixedSlot(UDATE_FORMAT_SLOT, PrivateValue(dateFormat)); + setFixedSlot(DATE_FORMAT_SLOT, PrivateValue(dateFormat)); } - UDateIntervalFormat* getDateIntervalFormat() const { - const auto& slot = getFixedSlot(UDATE_INTERVAL_FORMAT_SLOT); + mozilla::intl::DateIntervalFormat* getDateIntervalFormat() const { + const auto& slot = getFixedSlot(DATE_INTERVAL_FORMAT_SLOT); if (slot.isUndefined()) { return nullptr; } - return static_cast<UDateIntervalFormat*>(slot.toPrivate()); + return static_cast<mozilla::intl::DateIntervalFormat*>(slot.toPrivate()); } - void setDateIntervalFormat(UDateIntervalFormat* dateIntervalFormat) { - setFixedSlot(UDATE_INTERVAL_FORMAT_SLOT, PrivateValue(dateIntervalFormat)); + void setDateIntervalFormat( + mozilla::intl::DateIntervalFormat* dateIntervalFormat) { + setFixedSlot(DATE_INTERVAL_FORMAT_SLOT, PrivateValue(dateIntervalFormat)); } private: static const JSClassOps classOps_; static const ClassSpec classSpec_; static void finalize(JSFreeOp* fop, JSObject* obj); };
--- a/js/src/builtin/intl/ListFormat.cpp +++ b/js/src/builtin/intl/ListFormat.cpp @@ -205,16 +205,34 @@ static mozilla::intl::ListFormat* NewLis if (result.isOk()) { return result.unwrap().release(); } js::intl::ReportInternalError(cx, result.unwrapErr()); return nullptr; } +static mozilla::intl::ListFormat* GetOrCreateListFormat( + JSContext* cx, Handle<ListFormatObject*> listFormat) { + // Obtain a cached mozilla::intl::ListFormat object. + mozilla::intl::ListFormat* lf = listFormat->getListFormatSlot(); + if (lf) { + return lf; + } + + lf = NewListFormat(cx, listFormat); + if (!lf) { + return nullptr; + } + listFormat->setListFormatSlot(lf); + + intl::AddICUCellMemory(listFormat, ListFormatObject::EstimatedMemoryUse); + return lf; +} + /** * FormatList ( listFormat, list ) */ static bool FormatList(JSContext* cx, mozilla::intl::ListFormat* lf, const mozilla::intl::ListFormat::StringList& list, MutableHandleValue result) { intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> formatBuffer(cx); auto formatResult = lf->Format(list, formatBuffer); @@ -295,26 +313,19 @@ bool js::intl_FormatList(JSContext* cx, CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 3); Rooted<ListFormatObject*> listFormat( cx, &args[0].toObject().as<ListFormatObject>()); bool formatToParts = args[2].toBoolean(); - // Obtain a cached mozilla::intl::ListFormat object. - mozilla::intl::ListFormat* lf = listFormat->getListFormatSlot(); + mozilla::intl::ListFormat* lf = GetOrCreateListFormat(cx, listFormat); if (!lf) { - lf = NewListFormat(cx, listFormat); - if (!lf) { - return false; - } - listFormat->setListFormatSlot(lf); - - intl::AddICUCellMemory(listFormat, ListFormatObject::EstimatedMemoryUse); + return false; } // Collect all strings and their lengths. // // 'strings' takes the ownership of those strings, and 'list' will be passed // to mozilla::intl::ListFormat as a Span. Vector<UniqueTwoByteChars, mozilla::intl::DEFAULT_LIST_LENGTH> strings(cx); mozilla::intl::ListFormat::StringList list;
--- a/js/src/builtin/intl/NumberFormat.cpp +++ b/js/src/builtin/intl/NumberFormat.cpp @@ -773,16 +773,54 @@ static Formatter* NewNumberFormat(JSCont if (result.isOk()) { return result.unwrap().release(); } intl::ReportInternalError(cx, result.unwrapErr()); return nullptr; } +static mozilla::intl::NumberFormat* GetOrCreateNumberFormat( + JSContext* cx, Handle<NumberFormatObject*> numberFormat) { + // Obtain a cached mozilla::intl::NumberFormat object. + mozilla::intl::NumberFormat* nf = numberFormat->getNumberFormatter(); + if (nf) { + return nf; + } + + nf = NewNumberFormat<mozilla::intl::NumberFormat>(cx, numberFormat); + if (!nf) { + return nullptr; + } + numberFormat->setNumberFormatter(nf); + + intl::AddICUCellMemory(numberFormat, NumberFormatObject::EstimatedMemoryUse); + return nf; +} + +static mozilla::intl::NumberRangeFormat* GetOrCreateNumberRangeFormat( + JSContext* cx, Handle<NumberFormatObject*> numberFormat) { + // Obtain a cached mozilla::intl::NumberRangeFormat object. + mozilla::intl::NumberRangeFormat* nrf = + numberFormat->getNumberRangeFormatter(); + if (nrf) { + return nrf; + } + + nrf = NewNumberFormat<mozilla::intl::NumberRangeFormat>(cx, numberFormat); + if (!nrf) { + return nullptr; + } + numberFormat->setNumberRangeFormatter(nrf); + + intl::AddICUCellMemory(numberFormat, + NumberFormatObject::EstimatedRangeFormatterMemoryUse); + return nrf; +} + static FieldType GetFieldTypeForNumberPartType( mozilla::intl::NumberPartType type) { switch (type) { case mozilla::intl::NumberPartType::ApproximatelySign: return &JSAtomState::approximatelySign; case mozilla::intl::NumberPartType::Compact: return &JSAtomState::compact; case mozilla::intl::NumberPartType::Currency: @@ -1121,27 +1159,19 @@ bool js::intl_FormatNumber(JSContext* cx RootedValue value(cx, args[1]); #ifdef NIGHTLY_BUILD if (!ToIntlMathematicalValue(cx, &value)) { return false; } #endif - // Obtain a cached mozilla::intl::NumberFormat object. - mozilla::intl::NumberFormat* nf = numberFormat->getNumberFormatter(); + mozilla::intl::NumberFormat* nf = GetOrCreateNumberFormat(cx, numberFormat); if (!nf) { - nf = NewNumberFormat<mozilla::intl::NumberFormat>(cx, numberFormat); - if (!nf) { - return false; - } - numberFormat->setNumberFormatter(nf); - - intl::AddICUCellMemory(numberFormat, - NumberFormatObject::EstimatedMemoryUse); + return false; } // Actually format the number using ICUError = mozilla::intl::ICUError; bool formatToParts = args[2].toBoolean(); mozilla::Result<std::u16string_view, ICUError> result = mozilla::Err(ICUError::InternalError); @@ -1461,28 +1491,20 @@ bool js::intl_FormatNumberRange(JSContex return false; } if (!ValidateNumberRange(cx, &start, startApprox, &end, endApprox, formatToParts)) { return false; } - // Obtain a cached mozilla::intl::NumberFormat object. using NumberRangeFormat = mozilla::intl::NumberRangeFormat; - NumberRangeFormat* nf = numberFormat->getNumberRangeFormatter(); + NumberRangeFormat* nf = GetOrCreateNumberRangeFormat(cx, numberFormat); if (!nf) { - nf = NewNumberFormat<NumberRangeFormat>(cx, numberFormat); - if (!nf) { - return false; - } - numberFormat->setNumberRangeFormatter(nf); - - intl::AddICUCellMemory( - numberFormat, NumberFormatObject::EstimatedRangeFormatterMemoryUse); + return false; } auto valueRepresentableAsDouble = [](const Value& val, double* num) { if (val.isNumber()) { *num = val.toNumber(); return true; } if (val.isBigInt()) {
--- a/js/src/builtin/intl/PluralRules.cpp +++ b/js/src/builtin/intl/PluralRules.cpp @@ -286,37 +286,48 @@ static mozilla::intl::PluralRules* NewPl if (result.isErr()) { intl::ReportInternalError(cx, result.unwrapErr()); return nullptr; } return result.unwrap().release(); } +static mozilla::intl::PluralRules* GetOrCreatePluralRules( + JSContext* cx, Handle<PluralRulesObject*> pluralRules) { + // Obtain a cached PluralRules object. + mozilla::intl::PluralRules* pr = pluralRules->getPluralRules(); + if (pr) { + return pr; + } + + pr = NewPluralRules(cx, pluralRules); + if (!pr) { + return nullptr; + } + pluralRules->setPluralRules(pr); + + intl::AddICUCellMemory(pluralRules, + PluralRulesObject::UPluralRulesEstimatedMemoryUse); + return pr; +} + bool js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); Rooted<PluralRulesObject*> pluralRules( cx, &args[0].toObject().as<PluralRulesObject>()); double x = args[1].toNumber(); - // Obtain a cached PluralRules object. using PluralRules = mozilla::intl::PluralRules; - PluralRules* pr = pluralRules->getPluralRules(); + PluralRules* pr = GetOrCreatePluralRules(cx, pluralRules); if (!pr) { - pr = NewPluralRules(cx, pluralRules); - if (!pr) { - return false; - } - pluralRules->setPluralRules(pr); - - intl::AddICUCellMemory(pluralRules, - PluralRulesObject::UPluralRulesEstimatedMemoryUse); + return false; } auto keywordResult = pr->Select(x); if (keywordResult.isErr()) { intl::ReportInternalError(cx, keywordResult.unwrapErr()); return false; } @@ -340,28 +351,20 @@ bool js::intl_SelectPluralRuleRange(JSCo if (x > y) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_START_AFTER_END_NUMBER, "PluralRules", "selectRange"); return false; } - // Obtain a cached PluralRules object. using PluralRules = mozilla::intl::PluralRules; - PluralRules* pr = pluralRules->getPluralRules(); + PluralRules* pr = GetOrCreatePluralRules(cx, pluralRules); if (!pr) { - pr = NewPluralRules(cx, pluralRules); - if (!pr) { - return false; - } - pluralRules->setPluralRules(pr); - - intl::AddICUCellMemory(pluralRules, - PluralRulesObject::UPluralRulesEstimatedMemoryUse); + return false; } auto keywordResult = pr->SelectRange(x, y); if (keywordResult.isErr()) { intl::ReportInternalError(cx, keywordResult.unwrapErr()); return false; } @@ -377,28 +380,20 @@ bool js::intl_SelectPluralRuleRange(JSCo bool js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); Rooted<PluralRulesObject*> pluralRules( cx, &args[0].toObject().as<PluralRulesObject>()); - // Obtain a cached PluralRules object. using PluralRules = mozilla::intl::PluralRules; - PluralRules* pr = pluralRules->getPluralRules(); + PluralRules* pr = GetOrCreatePluralRules(cx, pluralRules); if (!pr) { - pr = NewPluralRules(cx, pluralRules); - if (!pr) { - return false; - } - pluralRules->setPluralRules(pr); - - intl::AddICUCellMemory(pluralRules, - PluralRulesObject::UPluralRulesEstimatedMemoryUse); + return false; } auto categoriesResult = pr->Categories(); if (categoriesResult.isErr()) { intl::ReportInternalError(cx, categoriesResult.unwrapErr()); return false; } auto categories = categoriesResult.unwrap();
--- a/js/src/builtin/intl/RelativeTimeFormat.cpp +++ b/js/src/builtin/intl/RelativeTimeFormat.cpp @@ -262,16 +262,36 @@ static mozilla::intl::RelativeTimeFormat if (result.isOk()) { return result.unwrap().release(); } intl::ReportInternalError(cx, result.unwrapErr()); return nullptr; } +static mozilla::intl::RelativeTimeFormat* GetOrCreateRelativeTimeFormat( + JSContext* cx, Handle<RelativeTimeFormatObject*> relativeTimeFormat) { + // Obtain a cached RelativeDateTimeFormatter object. + mozilla::intl::RelativeTimeFormat* rtf = + relativeTimeFormat->getRelativeTimeFormatter(); + if (rtf) { + return rtf; + } + + rtf = NewRelativeTimeFormatter(cx, relativeTimeFormat); + if (!rtf) { + return nullptr; + } + relativeTimeFormat->setRelativeTimeFormatter(rtf); + + intl::AddICUCellMemory(relativeTimeFormat, + RelativeTimeFormatObject::EstimatedMemoryUse); + return rtf; +} + bool js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 4); MOZ_ASSERT(args[0].isObject()); MOZ_ASSERT(args[1].isNumber()); MOZ_ASSERT(args[2].isString()); MOZ_ASSERT(args[3].isBoolean()); @@ -284,28 +304,20 @@ bool js::intl_FormatRelativeTime(JSConte double t = args[1].toNumber(); if (!mozilla::IsFinite(t)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE, "RelativeTimeFormat", formatToParts ? "formatToParts" : "format"); return false; } - // Obtain a cached URelativeDateTimeFormatter object. mozilla::intl::RelativeTimeFormat* rtf = - relativeTimeFormat->getRelativeTimeFormatter(); + GetOrCreateRelativeTimeFormat(cx, relativeTimeFormat); if (!rtf) { - rtf = NewRelativeTimeFormatter(cx, relativeTimeFormat); - if (!rtf) { - return false; - } - relativeTimeFormat->setRelativeTimeFormatter(rtf); - - intl::AddICUCellMemory(relativeTimeFormat, - RelativeTimeFormatObject::EstimatedMemoryUse); + return false; } intl::FieldType jsUnitType; using FormatUnit = mozilla::intl::RelativeTimeFormat::FormatUnit; FormatUnit relTimeUnit; { JSLinearString* unit = args[2].toString()->ensureLinear(cx); if (!unit) {
--- a/js/src/frontend/Stencil.cpp +++ b/js/src/frontend/Stencil.cpp @@ -4086,19 +4086,17 @@ JSObject* JS::InstantiateModuleStencil(J Rooted<CompilationGCOutput> gcOutput(cx); if (!InstantiateStencils(cx, input.get(), *stencil, gcOutput.get())) { return nullptr; } return gcOutput.get().module; } -JS::TranscodeResult JS::EncodeStencil(JSContext* cx, - const JS::ReadOnlyCompileOptions& options, - JS::Stencil* stencil, +JS::TranscodeResult JS::EncodeStencil(JSContext* cx, JS::Stencil* stencil, TranscodeBuffer& buffer) { XDRStencilEncoder encoder(cx, buffer); XDRResult res = encoder.codeStencil(*stencil); if (res.isErr()) { return res.unwrapErr(); } return TranscodeResult::Ok; }
--- a/js/src/jsapi-tests/testStencil.cpp +++ b/js/src/jsapi-tests/testStencil.cpp @@ -208,17 +208,17 @@ BEGIN_TEST(testStencil_Transcode) { CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed)); JS::CompileOptions options(cx); RefPtr<JS::Stencil> stencil = JS::CompileGlobalScriptToStencil(cx, options, srcBuf); CHECK(stencil); // Encode Stencil to XDR - JS::TranscodeResult res = JS::EncodeStencil(cx, options, stencil, buffer); + JS::TranscodeResult res = JS::EncodeStencil(cx, stencil, buffer); CHECK(res == JS::TranscodeResult::Ok); CHECK(!buffer.empty()); // Instantiate and Run JS::InstantiateOptions instantiateOptions(options); JS::RootedScript script( cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil)); JS::RootedValue rval(cx); @@ -286,17 +286,17 @@ BEGIN_TEST(testStencil_TranscodeBorrowin CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed)); JS::CompileOptions options(cx); RefPtr<JS::Stencil> stencil = JS::CompileGlobalScriptToStencil(cx, options, srcBuf); CHECK(stencil); // Encode Stencil to XDR - JS::TranscodeResult res = JS::EncodeStencil(cx, options, stencil, buffer); + JS::TranscodeResult res = JS::EncodeStencil(cx, stencil, buffer); CHECK(res == JS::TranscodeResult::Ok); CHECK(!buffer.empty()); } JS::RootedScript script(cx); { JS::TranscodeRange range(buffer.begin(), buffer.length()); JS::CompileOptions options(cx);
--- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2240,17 +2240,17 @@ static bool StartIncrementalEncoding(JSC } auto* source = stencil->source.get(); if (!initial->steal(cx, std::move(*stencil))) { return false; } - if (!source->startIncrementalEncoding(cx, options, std::move(initial))) { + if (!source->startIncrementalEncoding(cx, std::move(initial))) { return false; } return true; } static bool Evaluate(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp);
--- a/js/src/vm/CompilationAndEvaluation.cpp +++ b/js/src/vm/CompilationAndEvaluation.cpp @@ -111,17 +111,17 @@ static JSScript* CompileSourceBufferAndS } script = gcOutput.get().script; if (!script) { return nullptr; } } - if (!script->scriptSource()->startIncrementalEncoding(cx, options, + if (!script->scriptSource()->startIncrementalEncoding(cx, std::move(stencil))) { return nullptr; } return script; } JSScript* JS::CompileAndStartIncrementalEncoding(
--- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -2215,23 +2215,22 @@ JSScript* GlobalHelperThreadState::finis ReportOutOfMemory(cx); return nullptr; } if (!initial->steal(cx, std::move(*parseTask->stencil_))) { return nullptr; } if (!script->scriptSource()->startIncrementalEncoding( - cx, parseTask->options, std::move(initial))) { + cx, std::move(initial))) { return nullptr; } } else if (parseTask->extensibleStencil_) { if (!script->scriptSource()->startIncrementalEncoding( - cx, parseTask->options, - std::move(parseTask->extensibleStencil_))) { + cx, std::move(parseTask->extensibleStencil_))) { return nullptr; } } } return script; }
--- a/js/src/vm/JSScript.cpp +++ b/js/src/vm/JSScript.cpp @@ -1782,17 +1782,17 @@ bool js::SynchronouslyCompressSource(JSC void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ScriptSourceInfo* info) const { info->misc += mallocSizeOf(this); info->numScripts++; } bool ScriptSource::startIncrementalEncoding( - JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JSContext* cx, UniquePtr<frontend::ExtensibleCompilationStencil>&& initial) { // Encoding failures are reported by the xdrFinalizeEncoder function. if (containsAsmJS()) { return true; } // Remove the reference to the source, to avoid the circular reference. initial->source = nullptr; @@ -1803,17 +1803,17 @@ bool ScriptSource::startIncrementalEncod return false; } AutoIncrementalTimer timer(cx->realm()->timers.xdrEncodingTime); auto failureCase = mozilla::MakeScopeExit([&] { xdrEncoder_.reset(nullptr); }); XDRResult res = xdrEncoder_->setInitial( - cx, options, + cx, std::forward<UniquePtr<frontend::ExtensibleCompilationStencil>>(initial)); if (res.isErr()) { // On encoding failure, let failureCase destroy encoder and return true // to avoid failing any currently executing script. return JS::IsTranscodeFailureResult(res.unwrapErr()); } failureCase.release();
--- a/js/src/vm/JSScript.h +++ b/js/src/vm/JSScript.h @@ -1024,17 +1024,17 @@ class ScriptSource { bool containsAsmJS() const { return containsAsmJS_; } void setContainsAsmJS() { containsAsmJS_ = true; } // Return wether an XDR encoder is present or not. bool hasEncoder() const { return bool(xdrEncoder_); } [[nodiscard]] bool startIncrementalEncoding( - JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JSContext* cx, UniquePtr<frontend::ExtensibleCompilationStencil>&& initial); [[nodiscard]] bool addDelazificationToIncrementalEncoding( JSContext* cx, const frontend::CompilationStencil& stencil); // Linearize the encoded content in the |buffer| provided as argument to // |xdrEncodeTopLevel|, and free the XDR encoder. In case of errors, the // |buffer| is considered undefined.
--- a/js/src/vm/Xdr.cpp +++ b/js/src/vm/Xdr.cpp @@ -286,17 +286,17 @@ XDRResult XDRStencilEncoder::codeStencil XDRIncrementalStencilEncoder::~XDRIncrementalStencilEncoder() { if (merger_) { js_delete(merger_); } } XDRResult XDRIncrementalStencilEncoder::setInitial( - JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JSContext* cx, UniquePtr<frontend::ExtensibleCompilationStencil>&& initial) { MOZ_TRY(frontend::StencilXDR::checkCompilationStencil(*initial)); merger_ = cx->new_<frontend::CompilationStencilMerger>(); if (!merger_) { return mozilla::Err(JS::TranscodeResult::Throw); }
--- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -496,17 +496,17 @@ class XDRIncrementalStencilEncoder { XDRIncrementalStencilEncoder() = default; ~XDRIncrementalStencilEncoder(); XDRResult linearize(JSContext* cx, JS::TranscodeBuffer& buffer, js::ScriptSource* ss); XDRResult setInitial( - JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JSContext* cx, UniquePtr<frontend::ExtensibleCompilationStencil>&& initial); XDRResult addDelazification( JSContext* cx, const frontend::CompilationStencil& delazification); }; } /* namespace js */ #endif /* vm_Xdr_h */
--- a/js/xpconnect/loader/ScriptPreloader.cpp +++ b/js/xpconnect/loader/ScriptPreloader.cpp @@ -1158,21 +1158,17 @@ ScriptPreloader::CachedStencil::CachedSt mProcessTypes = {}; } bool ScriptPreloader::CachedStencil::XDREncode(JSContext* cx) { auto cleanup = MakeScopeExit([&]() { MaybeDropStencil(); }); mXDRData.construct<JS::TranscodeBuffer>(); - JS::CompileOptions compileOptions(cx); - FillCompileOptionsForCachedStencil(compileOptions); - - JS::TranscodeResult code = - JS::EncodeStencil(cx, compileOptions, mStencil, Buffer()); + JS::TranscodeResult code = JS::EncodeStencil(cx, mStencil, Buffer()); if (code == JS::TranscodeResult::Ok) { mXDRRange.emplace(Buffer().begin(), Buffer().length()); mSize = Range().length(); return true; } mXDRData.destroy(); JS_ClearPendingException(cx); return false;
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp +++ b/js/xpconnect/loader/mozJSComponentLoader.cpp @@ -840,17 +840,17 @@ nsresult mozJSComponentLoader::ObjectFor // Write to startup cache only when we didn't have any cache for the script // and compiled it. if (storeIntoStartupCache) { MOZ_ASSERT(options.sourceIsLazy); MOZ_ASSERT(stencil); // We successfully compiled the script, so cache it. - rv = WriteCachedStencil(cache, cachePath, cx, options, stencil); + rv = WriteCachedStencil(cache, cachePath, cx, stencil); // Don't treat failure to write as fatal, since we might be working // with a read-only cache. if (NS_SUCCEEDED(rv)) { LOG(("Successfully wrote to cache\n")); } else { LOG(("Failed to write to cache\n")); }
--- a/js/xpconnect/loader/mozJSLoaderUtils.cpp +++ b/js/xpconnect/loader/mozJSLoaderUtils.cpp @@ -44,20 +44,19 @@ nsresult ReadCachedStencil(StartupCache* } JS::TranscodeRange range(AsBytes(Span(buf, len))); JS::TranscodeResult code = JS::DecodeStencil(cx, options, range, stencilOut); return HandleTranscodeResult(cx, code); } nsresult WriteCachedStencil(StartupCache* cache, nsACString& uri, JSContext* cx, - const JS::ReadOnlyCompileOptions& options, JS::Stencil* stencil) { JS::TranscodeBuffer buffer; - JS::TranscodeResult code = JS::EncodeStencil(cx, options, stencil, buffer); + JS::TranscodeResult code = JS::EncodeStencil(cx, stencil, buffer); if (code != JS::TranscodeResult::Ok) { return HandleTranscodeResult(cx, code); } size_t size = buffer.length(); if (size > UINT32_MAX) { return NS_ERROR_FAILURE; }
--- a/js/xpconnect/loader/mozJSLoaderUtils.h +++ b/js/xpconnect/loader/mozJSLoaderUtils.h @@ -20,12 +20,11 @@ class StartupCache; nsresult ReadCachedStencil(mozilla::scache::StartupCache* cache, nsACString& uri, JSContext* cx, const JS::ReadOnlyCompileOptions& options, JS::Stencil** stencilOut); nsresult WriteCachedStencil(mozilla::scache::StartupCache* cache, nsACString& uri, JSContext* cx, - const JS::ReadOnlyCompileOptions& options, JS::Stencil* stencil); #endif /* mozJSLoaderUtils_h */
--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp +++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp @@ -193,18 +193,17 @@ static bool EvalStencil(JSContext* cx, H nsCString uriStr; if (storeIntoPreloadCache && NS_SUCCEEDED(uri->GetSpec(uriStr))) { ScriptPreloader::GetSingleton().NoteStencil(uriStr, cachePath, stencil); } if (storeIntoStartupCache) { JSAutoRealm ar(cx, script); - WriteCachedStencil(StartupCache::GetSingleton(), cachePath, cx, options, - stencil); + WriteCachedStencil(StartupCache::GetSingleton(), cachePath, cx, stencil); } } return true; } bool mozJSSubScriptLoader::ReadStencil( JS::Stencil** stencilOut, nsIURI* uri, JSContext* cx,
--- a/layout/base/moz.build +++ b/layout/base/moz.build @@ -34,17 +34,16 @@ XPIDL_MODULE = "layout_base" EXPORTS += [ "CaretAssociationHint.h", "FrameProperties.h", "LayoutConstants.h", "LayoutLogging.h", "MobileViewportManager.h", "nsAutoLayoutPhase.h", - "nsBidi.h", "nsBidiPresUtils.h", "nsCaret.h", "nsChangeHint.h", "nsCompatibility.h", "nsCounterManager.h", "nsCSSFrameConstructor.h", "nsFrameManager.h", "nsFrameTraversal.h", @@ -104,17 +103,16 @@ UNIFIED_SOURCES += [ "AccessibleCaretManager.cpp", "DisplayPortUtils.cpp", "GeckoMVMContext.cpp", "GeometryUtils.cpp", "LayoutLogging.cpp", "LayoutTelemetryTools.cpp", "MobileViewportManager.cpp", "MotionPathUtils.cpp", - "nsBidi.cpp", "nsBidiPresUtils.cpp", "nsCaret.cpp", "nsCounterManager.cpp", "nsCSSColorUtils.cpp", "nsCSSFrameConstructor.cpp", "nsDocumentViewer.cpp", "nsFrameManager.cpp", "nsFrameTraversal.cpp",
deleted file mode 100644 --- a/layout/base/nsBidi.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsBidi.h" - -nsresult nsBidi::CountRuns(int32_t* aRunCount) { - UErrorCode errorCode = U_ZERO_ERROR; - *aRunCount = ubidi_countRuns(mBiDi, &errorCode); - if (U_SUCCESS(errorCode)) { - mLength = ubidi_getProcessedLength(mBiDi); - mLevels = mLength > 0 ? ubidi_getLevels(mBiDi, &errorCode) : nullptr; - } - return ICUUtils::UErrorToNsResult(errorCode); -} - -void nsBidi::GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit, - nsBidiLevel* aLevel) { - MOZ_ASSERT(mLevels, "CountRuns hasn't been run?"); - MOZ_RELEASE_ASSERT(aLogicalStart < mLength, "Out of bound"); - // This function implements an alternative approach to get logical - // run that is based on levels of characters, which would avoid O(n^2) - // performance issue when used in a loop over runs. - // Per comment in ubidi_getLogicalRun, that function doesn't use this - // approach because levels have special interpretation when reordering - // mode is UBIDI_REORDER_RUNS_ONLY. Since we don't use this mode in - // Gecko, it should be safe to just use levels for this function. - MOZ_ASSERT(ubidi_getReorderingMode(mBiDi) != UBIDI_REORDER_RUNS_ONLY, - "Don't support UBIDI_REORDER_RUNS_ONLY mode"); - - nsBidiLevel level = mLevels[aLogicalStart]; - int32_t limit; - for (limit = aLogicalStart + 1; limit < mLength; limit++) { - if (mLevels[limit] != level) { - break; - } - } - *aLogicalLimit = limit; - *aLevel = level; -}
deleted file mode 100644 --- a/layout/base/nsBidi.h +++ /dev/null @@ -1,208 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef nsBidi_h__ -#define nsBidi_h__ - -#include "unicode/ubidi.h" -#include "ICUUtils.h" -#include "nsIFrame.h" // for nsBidiLevel/nsBidiDirection declarations - -// nsBidi implemented as a simple wrapper around the bidi reordering engine -// from ICU. -// We could eliminate this and let callers use the ICU functions directly -// once we no longer care about building without ICU available. - -class nsBidi { - public: - /** @brief Default constructor. - * - * The nsBidi object is initially empty. It is assigned - * the Bidi properties of a paragraph by <code>SetPara()</code>. - */ - nsBidi() { mBiDi = ubidi_open(); } - - /** @brief Destructor. */ - ~nsBidi() { ubidi_close(mBiDi); } - - /** - * Perform the Unicode Bidi algorithm. - * - * @param aText is a pointer to the single-paragraph text that the - * Bidi algorithm will be performed on - * (step (P1) of the algorithm is performed externally). - * <strong>The text must be (at least) <code>aLength</code> long. - * </strong> - * - * @param aLength is the length of the text; if <code>aLength==-1</code> then - * the text must be zero-terminated. - * - * @param aParaLevel specifies the default level for the paragraph; - * it is typically 0 (LTR) or 1 (RTL). - * If the function shall determine the paragraph level from the text, - * then <code>aParaLevel</code> can be set to - * either <code>NSBIDI_DEFAULT_LTR</code> - * or <code>NSBIDI_DEFAULT_RTL</code>; - * if there is no strongly typed character, then - * the desired default is used (0 for LTR or 1 for RTL). - * Any other value between 0 and <code>NSBIDI_MAX_EXPLICIT_LEVEL</code> - * is also valid, with odd levels indicating RTL. - */ - nsresult SetPara(const char16_t* aText, int32_t aLength, - nsBidiLevel aParaLevel) { - UErrorCode error = U_ZERO_ERROR; - ubidi_setPara(mBiDi, reinterpret_cast<const UChar*>(aText), aLength, - aParaLevel, nullptr, &error); - return ICUUtils::UErrorToNsResult(error); - } - - /** - * Get the directionality of the text. - * - * @param aDirection receives a <code>NSBIDI_XXX</code> value that indicates - * if the entire text represented by this object is unidirectional, - * and which direction, or if it is mixed-directional. - * - * @see nsBidiDirection - */ - nsBidiDirection GetDirection() { - return nsBidiDirection(ubidi_getDirection(mBiDi)); - } - - /** - * Get the paragraph level of the text. - * - * @param aParaLevel receives a <code>NSBIDI_XXX</code> value indicating - * the paragraph level - * - * @see nsBidiLevel - */ - nsBidiLevel GetParaLevel() { return ubidi_getParaLevel(mBiDi); } - - /** - * Get a logical run. - * This function returns information about a run and is used - * to retrieve runs in logical order.<p> - * This is especially useful for line-breaking on a paragraph. - * <code>CountRuns</code> should be called before this. - * before the runs are retrieved. - * - * @param aLogicalStart is the first character of the run. - * - * @param aLogicalLimit will receive the limit of the run. - * The l-value that you point to here may be the - * same expression (variable) as the one for - * <code>aLogicalStart</code>. - * This pointer cannot be <code>nullptr</code>. - * - * @param aLevel will receive the level of the run. - * This pointer cannot be <code>nullptr</code>. - */ - void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit, - nsBidiLevel* aLevel); - - /** - * Get the number of runs. - * This function may invoke the actual reordering on the - * <code>nsBidi</code> object, after <code>SetPara</code> - * may have resolved only the levels of the text. Therefore, - * <code>CountRuns</code> may have to allocate memory, - * and may fail doing so. - * - * @param aRunCount will receive the number of runs. - */ - nsresult CountRuns(int32_t* aRunCount); - - /** - * Get one run's logical start, length, and directionality, - * which can be 0 for LTR or 1 for RTL. - * In an RTL run, the character at the logical start is - * visually on the right of the displayed run. - * The length is the number of characters in the run.<p> - * <code>CountRuns</code> should be called - * before the runs are retrieved. - * - * @param aRunIndex is the number of the run in visual order, in the - * range <code>[0..CountRuns-1]</code>. - * - * @param aLogicalStart is the first logical character index in the text. - * The pointer may be <code>nullptr</code> if this index is not needed. - * - * @param aLength is the number of characters (at least one) in the run. - * The pointer may be <code>nullptr</code> if this is not needed. - * - * @returns the directionality of the run, - * <code>NSBIDI_LTR==0</code> or <code>NSBIDI_RTL==1</code>, - * never <code>NSBIDI_MIXED</code>. - * - * @see CountRuns<p> - * - * Example: - * @code - * int32_t i, count, logicalStart, visualIndex=0, length; - * nsBidiDirection dir; - * pBidi->CountRuns(&count); - * for(i=0; i<count; ++i) { - * dir = pBidi->GetVisualRun(i, &logicalStart, &length); - * if(NSBIDI_LTR==dir) { - * do { // LTR - * show_char(text[logicalStart++], visualIndex++); - * } while(--length>0); - * } else { - * logicalStart+=length; // logicalLimit - * do { // RTL - * show_char(text[--logicalStart], visualIndex++); - * } while(--length>0); - * } - * } - * @endcode - * - * Note that in right-to-left runs, code like this places - * modifier letters before base characters and second surrogates - * before first ones. - */ - nsBidiDirection GetVisualRun(int32_t aRunIndex, int32_t* aLogicalStart, - int32_t* aLength) { - return nsBidiDirection( - ubidi_getVisualRun(mBiDi, aRunIndex, aLogicalStart, aLength)); - } - - /** - * This is a convenience function that does not use a nsBidi object. - * It is intended to be used for when an application has determined the levels - * of objects (character sequences) and just needs to have them reordered - * (L2). This is equivalent to using <code>GetVisualMap</code> on a - * <code>nsBidi</code> object. - * - * @param aLevels is an array with <code>aLength</code> levels that have been - * determined by the application. - * - * @param aLength is the number of levels in the array, or, semantically, - * the number of objects to be reordered. - * It must be <code>aLength>0</code>. - * - * @param aIndexMap is a pointer to an array of <code>aLength</code> - * indexes which will reflect the reordering of the characters. - * The array does not need to be initialized.<p> - * The index map will result in - * <code>aIndexMap[aVisualIndex]==aLogicalIndex</code>. - */ - static void ReorderVisual(const nsBidiLevel* aLevels, int32_t aLength, - int32_t* aIndexMap) { - ubidi_reorderVisual(aLevels, aLength, aIndexMap); - } - - private: - nsBidi(const nsBidi&) = delete; - void operator=(const nsBidi&) = delete; - - UBiDi* mBiDi; - // The two fields below are updated when CountRuns is called. - const nsBidiLevel* mLevels = nullptr; - int32_t mLength = 0; -}; - -#endif // _nsBidi_h_
--- a/layout/base/nsBidiPresUtils.cpp +++ b/layout/base/nsBidiPresUtils.cpp @@ -1,16 +1,18 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsBidiPresUtils.h" +#include "mozilla/intl/Bidi.h" +#include "mozilla/Casting.h" #include "mozilla/IntegerRange.h" #include "mozilla/Maybe.h" #include "mozilla/PresShell.h" #include "mozilla/dom/Text.h" #include "gfxContext.h" #include "nsFontMetrics.h" #include "nsGkAtoms.h" @@ -34,16 +36,17 @@ #include "nsRubyBaseContainerFrame.h" #include "nsRubyTextContainerFrame.h" #include <algorithm> #undef NOISY_BIDI #undef REALLY_NOISY_BIDI using namespace mozilla; +using EmbeddingLevel = mozilla::intl::Bidi::EmbeddingLevel; static const char16_t kSpace = 0x0020; static const char16_t kZWSP = 0x200B; static const char16_t kLineSeparator = 0x2028; static const char16_t kObjectSubstitute = 0xFFFC; static const char16_t kLRE = 0x202A; static const char16_t kRLE = 0x202B; static const char16_t kLRO = 0x202D; @@ -158,17 +161,17 @@ struct MOZ_STACK_CLASS BidiParagraphData nsAutoString mBuffer; AutoTArray<char16_t, 16> mEmbeddingStack; AutoTArray<FrameInfo, 16> mLogicalFrames; nsTHashMap<nsPtrHashKey<const nsIContent>, int32_t> mContentToFrameIndex; // Cached presentation context for the frames we're processing. nsPresContext* mPresContext; bool mIsVisual; bool mRequiresBidi; - nsBidiLevel mParaLevel; + EmbeddingLevel mParaLevel; nsIContent* mPrevContent; /** * This class is designed to manage the process of mapping a frame to * the line that it's in, when we know that (a) the frames we ask it * about are always in the block's lines and (b) each successive frame * we ask it about is the same as or after (in depth-first search * order) the previous. @@ -330,47 +333,59 @@ struct MOZ_STACK_CLASS BidiParagraphData mIsVisual = false; break; } } } } nsresult SetPara() { - return mPresContext->GetBidiEngine().SetPara(mBuffer.get(), BufferLength(), - mParaLevel); + if (mPresContext->GetBidiEngine() + .SetParagraph(mBuffer, mParaLevel) + .isErr()) { + return NS_ERROR_FAILURE; + }; + return NS_OK; } /** - * mParaLevel can be NSBIDI_DEFAULT_LTR as well as NSBIDI_LTR or NSBIDI_RTL. - * GetParaLevel() returns the actual (resolved) paragraph level which is - * always either NSBIDI_LTR or NSBIDI_RTL + * mParaLevel can be intl::Bidi::Direction::LTR as well as + * intl::Bidi::Direction::LTR or intl::Bidi::Direction::RTL. + * GetParagraphEmbeddingLevel() returns the actual (resolved) paragraph level + * which is always either intl::Bidi::Direction::LTR or + * intl::Bidi::Direction::RTL */ - nsBidiLevel GetParaLevel() { - nsBidiLevel paraLevel = mParaLevel; - if (paraLevel == NSBIDI_DEFAULT_LTR || paraLevel == NSBIDI_DEFAULT_RTL) { - paraLevel = mPresContext->GetBidiEngine().GetParaLevel(); + EmbeddingLevel GetParagraphEmbeddingLevel() { + EmbeddingLevel paraLevel = mParaLevel; + if (paraLevel == EmbeddingLevel::DefaultLTR() || + paraLevel == EmbeddingLevel::DefaultRTL()) { + paraLevel = mPresContext->GetBidiEngine().GetParagraphEmbeddingLevel(); } return paraLevel; } - nsBidiDirection GetDirection() { - return mPresContext->GetBidiEngine().GetDirection(); + intl::Bidi::ParagraphDirection GetParagraphDirection() { + return mPresContext->GetBidiEngine().GetParagraphDirection(); } nsresult CountRuns(int32_t* runCount) { - return mPresContext->GetBidiEngine().CountRuns(runCount); + auto result = mPresContext->GetBidiEngine().CountRuns(); + if (result.isErr()) { + return NS_ERROR_FAILURE; + } + *runCount = result.unwrap(); + return NS_OK; } void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit, - nsBidiLevel* aLevel) { + EmbeddingLevel* aLevel) { mPresContext->GetBidiEngine().GetLogicalRun(aLogicalStart, aLogicalLimit, aLevel); if (mIsVisual) { - *aLevel = GetParaLevel(); + *aLevel = GetParagraphEmbeddingLevel(); } } void ResetData() { mLogicalFrames.Clear(); mContentToFrameIndex.Clear(); mBuffer.SetLength(0); mPrevContent = nullptr; @@ -460,33 +475,33 @@ struct MOZ_STACK_CLASS BidiParagraphData } } }; struct MOZ_STACK_CLASS BidiLineData { AutoTArray<nsIFrame*, 16> mLogicalFrames; AutoTArray<nsIFrame*, 16> mVisualFrames; AutoTArray<int32_t, 16> mIndexMap; - AutoTArray<uint8_t, 16> mLevels; + AutoTArray<EmbeddingLevel, 16> mLevels; bool mIsReordered; BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) { /** * Initialize the logically-ordered array of frames using the top-level * frames of a single line */ bool isReordered = false; bool hasRTLFrames = false; bool hasVirtualControls = false; - auto appendFrame = [&](nsIFrame* frame, nsBidiLevel level) { + auto appendFrame = [&](nsIFrame* frame, EmbeddingLevel level) { mLogicalFrames.AppendElement(frame); mLevels.AppendElement(level); mIndexMap.AppendElement(0); - if (IS_LEVEL_RTL(level)) { + if (level.IsRTL()) { hasRTLFrames = true; } }; bool firstFrame = true; for (nsIFrame* frame = aFirstFrameOnLine; frame && aNumFramesOnLine--; frame = frame->GetNextSibling()) { FrameBidiData bidiData = nsBidiPresUtils::GetFrameBidiData(frame); @@ -497,18 +512,18 @@ struct MOZ_STACK_CLASS BidiLineData { appendFrame(NS_BIDI_CONTROL_FRAME, bidiData.precedingControl); hasVirtualControls = true; } appendFrame(frame, bidiData.embeddingLevel); firstFrame = false; } // Reorder the line - nsBidi::ReorderVisual(mLevels.Elements(), FrameCount(), - mIndexMap.Elements()); + mozilla::intl::Bidi::ReorderVisual(mLevels.Elements(), FrameCount(), + mIndexMap.Elements()); // Strip virtual frames if (hasVirtualControls) { auto originalCount = mLogicalFrames.Length(); AutoTArray<int32_t, 16> realFrameMap; realFrameMap.SetCapacity(originalCount); size_t count = 0; for (auto i : IntegerRange(originalCount)) { @@ -860,17 +875,18 @@ nsresult nsBidiPresUtils::ResolveParagra } aBpd->mBuffer.ReplaceChar(kSeparators, kSpace); int32_t runCount; nsresult rv = aBpd->SetPara(); NS_ENSURE_SUCCESS(rv, rv); - nsBidiLevel embeddingLevel = aBpd->GetParaLevel(); + intl::Bidi::EmbeddingLevel embeddingLevel = + aBpd->GetParagraphEmbeddingLevel(); rv = aBpd->CountRuns(&runCount); NS_ENSURE_SUCCESS(rv, rv); int32_t runLength = 0; // the length of the current run of text int32_t logicalLimit = 0; // the end of the current run + 1 int32_t numRun = -1; int32_t fragmentLength = 0; // the length of the current text frame @@ -892,18 +908,19 @@ nsresult nsBidiPresUtils::ResolveParagra frameCount, runCount); # ifdef REALLY_NOISY_BIDI printf(" block frame tree=:\n"); aBpd->mCurrentBlock->List(stdout); # endif # endif #endif - if (runCount == 1 && frameCount == 1 && aBpd->GetDirection() == NSBIDI_LTR && - aBpd->GetParaLevel() == 0) { + if (runCount == 1 && frameCount == 1 && + aBpd->GetParagraphDirection() == intl::Bidi::ParagraphDirection::LTR && + aBpd->GetParagraphEmbeddingLevel() == 0) { // We have a single left-to-right frame in a left-to-right paragraph, // without bidi isolation from the surrounding text. // Make sure that the embedding level and base level frame properties aren't // set (because if they are this frame used to have some other direction, // so we can't do this optimization), and we're done. nsIFrame* frame = aBpd->FrameAt(0); if (frame != NS_BIDI_CONTROL_FRAME) { FrameBidiData bidiData = frame->GetBidiData(); @@ -915,23 +932,23 @@ nsresult nsBidiPresUtils::ResolveParagra #endif frame->AddStateBits(NS_FRAME_IS_BIDI); return NS_OK; } } } BidiParagraphData::FrameInfo lastRealFrame; - nsBidiLevel lastEmbeddingLevel = kBidiLevelNone; - nsBidiLevel precedingControl = kBidiLevelNone; + EmbeddingLevel lastEmbeddingLevel = kBidiLevelNone; + EmbeddingLevel precedingControl = kBidiLevelNone; auto storeBidiDataToFrame = [&]() { FrameBidiData bidiData; bidiData.embeddingLevel = embeddingLevel; - bidiData.baseLevel = aBpd->GetParaLevel(); + bidiData.baseLevel = aBpd->GetParagraphEmbeddingLevel(); // If a control character doesn't have a lower embedding level than // both the preceding and the following frame, it isn't something // needed for getting the correct result. This optimization should // remove almost all of embeds and overrides, and some of isolates. if (precedingControl >= embeddingLevel || precedingControl >= lastEmbeddingLevel) { bidiData.precedingControl = kBidiLevelNone; } else { @@ -1501,21 +1518,21 @@ nsIFrame* nsBidiPresUtils::GetFirstLeaf( } return firstLeaf; } FrameBidiData nsBidiPresUtils::GetFrameBidiData(nsIFrame* aFrame) { return GetFirstLeaf(aFrame)->GetBidiData(); } -nsBidiLevel nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) { +EmbeddingLevel nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) { return GetFirstLeaf(aFrame)->GetEmbeddingLevel(); } -nsBidiLevel nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame* aFrame) { +EmbeddingLevel nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame* aFrame) { const nsIFrame* firstLeaf = aFrame; while (!IsBidiLeaf(firstLeaf)) { firstLeaf = firstLeaf->PrincipalChildList().FirstChild(); } return firstLeaf->GetBaseLevel(); } void nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame, @@ -1866,17 +1883,17 @@ nscoord nsBidiPresUtils::RepositionInlin } else { index = count - 1; step = -1; limit = -1; } for (; index != limit; index += step) { frame = aBld->VisualFrameAt(index); start += RepositionFrame( - frame, !(IS_LEVEL_RTL(aBld->mLevels[aBld->mIndexMap[index]])), start, + frame, !(aBld->mLevels[aBld->mIndexMap[index]].IsRTL()), start, &continuationStates, aLineWM, false, aContainerSize); } return start; } bool nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine, nsIFrame** aFirstVisual, @@ -2074,17 +2091,17 @@ RemoveDiacritics(char16_t* aText, aText[i - offset] = aText[i]; } aTextLength = i - offset; aText[aTextLength] = 0; } } #endif -void nsBidiPresUtils::CalculateCharType(nsBidi* aBidiEngine, +void nsBidiPresUtils::CalculateCharType(intl::Bidi* aBidiEngine, const char16_t* aText, int32_t& aOffset, int32_t aCharTypeLimit, int32_t& aRunLimit, int32_t& aRunLength, int32_t& aRunCount, uint8_t& aCharType, uint8_t& aPrevCharType) { bool strongTypeFound = false; @@ -2138,79 +2155,83 @@ void nsBidiPresUtils::CalculateCharType( strongTypeFound = true; aCharType = charType; } } aOffset = offset; } -nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, int32_t aLength, - nsBidiLevel aBaseLevel, +nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, size_t aLength, + EmbeddingLevel aBaseLevel, nsPresContext* aPresContext, BidiProcessor& aprocessor, Mode aMode, nsBidiPositionResolve* aPosResolve, int32_t aPosResolveCount, nscoord* aWidth, - nsBidi* aBidiEngine) { + mozilla::intl::Bidi* aBidiEngine) { NS_ASSERTION((aPosResolve == nullptr) != (aPosResolveCount > 0), "Incorrect aPosResolve / aPosResolveCount arguments"); - int32_t runCount; - nsAutoString textBuffer(aText, aLength); textBuffer.ReplaceChar(kSeparators, kSpace); const char16_t* text = textBuffer.get(); - nsresult rv = aBidiEngine->SetPara(text, aLength, aBaseLevel); - if (NS_FAILED(rv)) return rv; + if (aBidiEngine->SetParagraph(Span(text, aLength), aBaseLevel).isErr()) { + return NS_ERROR_FAILURE; + } - rv = aBidiEngine->CountRuns(&runCount); - if (NS_FAILED(rv)) return rv; + auto result = aBidiEngine->CountRuns(); + if (result.isErr()) { + return NS_ERROR_FAILURE; + } + int32_t runCount = result.unwrap(); nscoord xOffset = 0; nscoord width, xEndRun = 0; nscoord totalWidth = 0; int32_t i, start, limit, length; uint32_t visualStart = 0; uint8_t charType; uint8_t prevType = eCharType_LeftToRight; for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) { aPosResolve[nPosResolve].visualIndex = kNotFound; aPosResolve[nPosResolve].visualLeftTwips = kNotFound; aPosResolve[nPosResolve].visualWidth = kNotFound; } for (i = 0; i < runCount; i++) { - nsBidiDirection dir = aBidiEngine->GetVisualRun(i, &start, &length); + mozilla::intl::Bidi::Direction dir = + aBidiEngine->GetVisualRun(i, &start, &length); - nsBidiLevel level; + EmbeddingLevel level; aBidiEngine->GetLogicalRun(start, &limit, &level); - dir = DIRECTION_FROM_LEVEL(level); + dir = level.Direction(); int32_t subRunLength = limit - start; int32_t lineOffset = start; - int32_t typeLimit = std::min(limit, aLength); + int32_t typeLimit = std::min(limit, AssertedCast<int32_t>(aLength)); int32_t subRunCount = 1; int32_t subRunLimit = typeLimit; /* * If |level| is even, i.e. the direction of the run is left-to-right, we * render the subruns from left to right and increment the x-coordinate * |xOffset| by the width of each subrun after rendering. * * If |level| is odd, i.e. the direction of the run is right-to-left, we * render the subruns from right to left. We begin by incrementing |xOffset| * by the width of the whole run, and then decrement it by the width of each * subrun before rendering. After rendering all the subruns, we restore the * x-coordinate of the end of the run for the start of the next run. */ - if (dir == NSBIDI_RTL) { - aprocessor.SetText(text + start, subRunLength, dir); + if (dir == intl::Bidi::Direction::RTL) { + aprocessor.SetText(text + start, subRunLength, + intl::Bidi::Direction::RTL); width = aprocessor.GetWidth(); xOffset += width; xEndRun = xOffset; } while (subRunCount > 0) { // CalculateCharType can increment subRunCount if the run // contains mixed character types @@ -2222,17 +2243,17 @@ nsresult nsBidiPresUtils::ProcessText(co if (int32_t(runVisualText.Length()) < subRunLength) return NS_ERROR_OUT_OF_MEMORY; FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), subRunLength, (nsCharType)charType); aprocessor.SetText(runVisualText.get(), subRunLength, dir); width = aprocessor.GetWidth(); totalWidth += width; - if (dir == NSBIDI_RTL) { + if (dir == mozilla::intl::Bidi::Direction::RTL) { xOffset -= width; } if (aMode == MODE_DRAW) { aprocessor.DrawText(xOffset, width); } /* * The caller may request to calculate the visual position of one @@ -2292,17 +2313,17 @@ nsresult nsBidiPresUtils::ProcessText(co * ^^^^^^ (subWidth) * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide) * ^^ (posResolve->visualWidth) */ nscoord subWidth; // The position in the text where this run's "left part" begins. const char16_t* visualLeftPart; const char16_t* visualRightSide; - if (dir == NSBIDI_RTL) { + if (dir == mozilla::intl::Bidi::Direction::RTL) { // One day, son, this could all be replaced with // mPresContext->GetBidiEngine().GetVisualIndex() ... posResolve->visualIndex = visualStart + (subRunLength - (posResolve->logicalIndex + 1 - start)); // Skipping to the "left part". visualLeftPart = text + posResolve->logicalIndex + 1; // Skipping to the right side of the current character @@ -2321,26 +2342,26 @@ nsresult nsBidiPresUtils::ProcessText(co subWidth = aprocessor.GetWidth(); aprocessor.SetText(visualRightSide, visualLeftLength + 1, dir); posResolve->visualLeftTwips = xOffset + subWidth; posResolve->visualWidth = aprocessor.GetWidth() - subWidth; } } } - if (dir == NSBIDI_LTR) { + if (dir == intl::Bidi::Direction::LTR) { xOffset += width; } --subRunCount; start = lineOffset; subRunLimit = typeLimit; subRunLength = typeLimit - lineOffset; } // while - if (dir == NSBIDI_RTL) { + if (dir == intl::Bidi::Direction::RTL) { xOffset = xEndRun; } visualStart += length; } // for if (aWidth) { *aWidth = totalWidth; @@ -2362,18 +2383,18 @@ class MOZ_STACK_CLASS nsIRenderingContex mFontMetrics(aFontMetrics), mPt(aPt), mText(nullptr), mLength(0) {} ~nsIRenderingContextBidiProcessor() { mFontMetrics->SetTextRunRTL(false); } virtual void SetText(const char16_t* aText, int32_t aLength, - nsBidiDirection aDirection) override { - mFontMetrics->SetTextRunRTL(aDirection == NSBIDI_RTL); + intl::Bidi::Direction aDirection) override { + mFontMetrics->SetTextRunRTL(aDirection == intl::Bidi::Direction::RTL); mText = aText; mLength = aLength; } virtual nscoord GetWidth() override { return nsLayoutUtils::AppUnitWidthOfString(mText, mLength, *mFontMetrics, mTextRunConstructionDrawTarget); } @@ -2394,34 +2415,35 @@ class MOZ_STACK_CLASS nsIRenderingContex DrawTarget* mTextRunConstructionDrawTarget; nsFontMetrics* mFontMetrics; nsPoint mPt; const char16_t* mText; int32_t mLength; }; nsresult nsBidiPresUtils::ProcessTextForRenderingContext( - const char16_t* aText, int32_t aLength, nsBidiLevel aBaseLevel, + const char16_t* aText, int32_t aLength, EmbeddingLevel aBaseLevel, nsPresContext* aPresContext, gfxContext& aRenderingContext, DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics& aFontMetrics, Mode aMode, nscoord aX, nscoord aY, nsBidiPositionResolve* aPosResolve, int32_t aPosResolveCount, nscoord* aWidth) { nsIRenderingContextBidiProcessor processor(&aRenderingContext, aTextRunConstructionDrawTarget, &aFontMetrics, nsPoint(aX, aY)); return ProcessText(aText, aLength, aBaseLevel, aPresContext, processor, aMode, aPosResolve, aPosResolveCount, aWidth, &aPresContext->GetBidiEngine()); } /* static */ -nsBidiLevel nsBidiPresUtils::BidiLevelFromStyle(ComputedStyle* aComputedStyle) { +EmbeddingLevel nsBidiPresUtils::BidiLevelFromStyle( + ComputedStyle* aComputedStyle) { if (aComputedStyle->StyleTextReset()->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) { - return NSBIDI_DEFAULT_LTR; + return EmbeddingLevel::DefaultLTR(); } if (aComputedStyle->StyleVisibility()->mDirection == StyleDirection::Rtl) { - return NSBIDI_RTL; + return EmbeddingLevel::RTL(); } - return NSBIDI_LTR; + return EmbeddingLevel::LTR(); }
--- a/layout/base/nsBidiPresUtils.h +++ b/layout/base/nsBidiPresUtils.h @@ -3,17 +3,17 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef nsBidiPresUtils_h___ #define nsBidiPresUtils_h___ #include "gfxContext.h" -#include "nsBidi.h" +#include "mozilla/intl/Bidi.h" #include "nsBidiUtils.h" #include "nsHashKeys.h" #include "nsCoord.h" #include "nsTArray.h" #include "nsLineBox.h" #ifdef DrawText # undef DrawText @@ -159,17 +159,17 @@ class nsBidiPresUtils { * passing an index would be impossible. * * @param aText The string of text. * @param aLength The length of the string of text. * @param aDirection The direction of the text. The string will never have * mixed direction. */ virtual void SetText(const char16_t* aText, int32_t aLength, - nsBidiDirection aDirection) = 0; + mozilla::intl::Bidi::Direction aDirection) = 0; /** * Returns the measured width of the text given in SetText. If SetText was * not called with valid parameters, the result of this call is undefined. * This call is guaranteed to only be called once between SetText calls. * Will be invoked before DrawText. */ virtual nscoord GetWidth() = 0; @@ -224,52 +224,46 @@ class nsBidiPresUtils { /** * Reorder plain text using the Unicode Bidi algorithm and send it to * a rendering context for rendering. * * @param[in] aText the string to be rendered (in logical order) * @param aLength the number of characters in the string * @param aBaseLevel the base embedding level of the string - * odd values are right-to-left; even values are left-to-right, plus special - * constants as follows (defined in nsBidi.h) - * NSBIDI_LTR - left-to-right string - * NSBIDI_RTL - right-to-left string - * NSBIDI_DEFAULT_LTR - auto direction determined by first strong character, - * default is left-to-right - * NSBIDI_DEFAULT_RTL - auto direction determined by first strong character, - * default is right-to-left - * * @param aPresContext the presentation context * @param aRenderingContext the rendering context to render to * @param aTextRunConstructionContext the rendering context to be used to * construct the textrun (affects font hinting) * @param aX the x-coordinate to render the string * @param aY the y-coordinate to render the string * @param[in,out] aPosResolve array of logical positions to resolve into * visual positions; can be nullptr if this functionality is not required * @param aPosResolveCount number of items in the aPosResolve array */ - static nsresult RenderText( - const char16_t* aText, int32_t aLength, nsBidiLevel aBaseLevel, - nsPresContext* aPresContext, gfxContext& aRenderingContext, - DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics& aFontMetrics, - nscoord aX, nscoord aY, nsBidiPositionResolve* aPosResolve = nullptr, - int32_t aPosResolveCount = 0) { + static nsresult RenderText(const char16_t* aText, int32_t aLength, + mozilla::intl::Bidi::EmbeddingLevel aBaseLevel, + nsPresContext* aPresContext, + gfxContext& aRenderingContext, + DrawTarget* aTextRunConstructionDrawTarget, + nsFontMetrics& aFontMetrics, nscoord aX, + nscoord aY, + nsBidiPositionResolve* aPosResolve = nullptr, + int32_t aPosResolveCount = 0) { return ProcessTextForRenderingContext( aText, aLength, aBaseLevel, aPresContext, aRenderingContext, aTextRunConstructionDrawTarget, aFontMetrics, MODE_DRAW, aX, aY, aPosResolve, aPosResolveCount, nullptr); } - static nscoord MeasureTextWidth(const char16_t* aText, int32_t aLength, - nsBidiLevel aBaseLevel, - nsPresContext* aPresContext, - gfxContext& aRenderingContext, - nsFontMetrics& aFontMetrics) { + static nscoord MeasureTextWidth( + const char16_t* aText, int32_t aLength, + mozilla::intl::Bidi::EmbeddingLevel aBaseLevel, + nsPresContext* aPresContext, gfxContext& aRenderingContext, + nsFontMetrics& aFontMetrics) { nscoord length; nsresult rv = ProcessTextForRenderingContext( aText, aLength, aBaseLevel, aPresContext, aRenderingContext, aRenderingContext.GetDrawTarget(), aFontMetrics, MODE_MEASURE, 0, 0, nullptr, 0, &length); return NS_SUCCEEDED(rv) ? length : 0; } @@ -312,109 +306,103 @@ class nsBidiPresUtils { /** * Get the bidi data of the given (inline) frame. */ static mozilla::FrameBidiData GetFrameBidiData(nsIFrame* aFrame); /** * Get the bidi embedding level of the given (inline) frame. */ - static nsBidiLevel GetFrameEmbeddingLevel(nsIFrame* aFrame); + static mozilla::intl::Bidi::EmbeddingLevel GetFrameEmbeddingLevel( + nsIFrame* aFrame); /** * Get the bidi base level of the given (inline) frame. */ - static nsBidiLevel GetFrameBaseLevel(const nsIFrame* aFrame); + static mozilla::intl::Bidi::EmbeddingLevel GetFrameBaseLevel( + const nsIFrame* aFrame); /** - * Get an nsBidiDirection representing the direction implied by the - * bidi base level of the frame. - * @return NSBIDI_LTR (left-to-right) or NSBIDI_RTL (right-to-left) - * NSBIDI_MIXED will never be returned. + * Get a mozilla::intl::Bidi::Direction representing the direction implied by + * the bidi base level of the frame. + * @return mozilla::intl::Bidi::Direction */ - static nsBidiDirection ParagraphDirection(const nsIFrame* aFrame) { - return DIRECTION_FROM_LEVEL(GetFrameBaseLevel(aFrame)); + static mozilla::intl::Bidi::Direction ParagraphDirection( + const nsIFrame* aFrame) { + return GetFrameBaseLevel(aFrame).Direction(); } /** - * Get an nsBidiDirection representing the direction implied by the - * bidi embedding level of the frame. - * @return NSBIDI_LTR (left-to-right) or NSBIDI_RTL (right-to-left) - * NSBIDI_MIXED will never be returned. + * Get a mozilla::intl::Bidi::Direction representing the direction implied by + * the bidi embedding level of the frame. + * @return mozilla::intl::Bidi::Direction */ - static nsBidiDirection FrameDirection(nsIFrame* aFrame) { - return DIRECTION_FROM_LEVEL(GetFrameEmbeddingLevel(aFrame)); + static mozilla::intl::Bidi::Direction FrameDirection(nsIFrame* aFrame) { + return GetFrameEmbeddingLevel(aFrame).Direction(); } static bool IsFrameInParagraphDirection(nsIFrame* aFrame) { return ParagraphDirection(aFrame) == FrameDirection(aFrame); } // This is faster than nsBidiPresUtils::IsFrameInParagraphDirection, // because it uses the frame pointer passed in without drilling down to // the leaf frame. static bool IsReversedDirectionFrame(const nsIFrame* aFrame) { mozilla::FrameBidiData bidiData = aFrame->GetBidiData(); - return !IS_SAME_DIRECTION(bidiData.embeddingLevel, bidiData.baseLevel); + return !bidiData.embeddingLevel.IsSameDirection(bidiData.baseLevel); } enum Mode { MODE_DRAW, MODE_MEASURE }; /** * Reorder plain text using the Unicode Bidi algorithm and send it to * a processor for rendering or measuring * * @param[in] aText the string to be processed (in logical order) * @param aLength the number of characters in the string * @param aBaseLevel the base embedding level of the string - * odd values are right-to-left; even values are left-to-right, plus special - * constants as follows (defined in nsBidi.h) - * NSBIDI_LTR - left-to-right string - * NSBIDI_RTL - right-to-left string - * NSBIDI_DEFAULT_LTR - auto direction determined by first strong character, - * default is left-to-right - * NSBIDI_DEFAULT_RTL - auto direction determined by first strong character, - * default is right-to-left - * * @param aPresContext the presentation context * @param aprocessor the bidi processor * @param aMode the operation to process * MODE_DRAW - invokes DrawText on the processor for each substring * MODE_MEASURE - does not invoke DrawText on the processor * Note that the string is always measured, regardless of mode * @param[in,out] aPosResolve array of logical positions to resolve into * visual positions; can be nullptr if this functionality is not required * @param aPosResolveCount number of items in the aPosResolve array * @param[out] aWidth Pointer to where the width will be stored (may be null) */ - static nsresult ProcessText(const char16_t* aText, int32_t aLength, - nsBidiLevel aBaseLevel, + static nsresult ProcessText(const char16_t* aText, size_t aLength, + mozilla::intl::Bidi::EmbeddingLevel aBaseLevel, nsPresContext* aPresContext, BidiProcessor& aprocessor, Mode aMode, nsBidiPositionResolve* aPosResolve, int32_t aPosResolveCount, nscoord* aWidth, - nsBidi* aBidiEngine); + mozilla::intl::Bidi* aBidiEngine); /** * Use style attributes to determine the base paragraph level to pass to the * bidi algorithm. * - * If |unicode-bidi| is set to "[-moz-]plaintext", returns NSBIDI_DEFAULT_LTR, - * in other words the direction is determined from the first strong character - * in the text according to rules P2 and P3 of the bidi algorithm, or LTR if - * there is no strong character. + * If |unicode-bidi| is set to "[-moz-]plaintext", returns + * EmbeddingLevel::DefaultLTR, in other words the direction is determined from + * the first strong character in the text according to rules P2 and P3 of the + * bidi algorithm, or LTR if there is no strong character. * - * Otherwise returns NSBIDI_LTR or NSBIDI_RTL depending on the value of - * |direction| + * Otherwise returns EmbeddingLevel::LTR or EmbeddingLevel::RTL depending on + * the value of |direction| */ - static nsBidiLevel BidiLevelFromStyle(mozilla::ComputedStyle* aComputedStyle); + static mozilla::intl::Bidi::EmbeddingLevel BidiLevelFromStyle( + mozilla::ComputedStyle* aComputedStyle); private: static nsresult ProcessTextForRenderingContext( - const char16_t* aText, int32_t aLength, nsBidiLevel aBaseLevel, + const char16_t* aText, int32_t aLength, + mozilla::intl::Bidi::EmbeddingLevel aBaseLevel, nsPresContext* aPresContext, gfxContext& aRenderingContext, DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics& aFontMetrics, Mode aMode, nscoord aX, // DRAW only nscoord aY, // DRAW only nsBidiPositionResolve* aPosResolve, /* may be null */ int32_t aPosResolveCount, nscoord* aWidth /* may be null */); @@ -567,18 +555,18 @@ class nsBidiPresUtils { * @param aFirstIndex index of aFrame in mLogicalFrames * @param aLastIndex index of the last frame to be removed * * @see Resolve() * @see EnsureBidiContinuation() */ static void RemoveBidiContinuation(BidiParagraphData* aBpd, nsIFrame* aFrame, int32_t aFirstIndex, int32_t aLastIndex); - static void CalculateCharType(nsBidi* aBidiEngine, const char16_t* aText, - int32_t& aOffset, int32_t aCharTypeLimit, - int32_t& aRunLimit, int32_t& aRunLength, - int32_t& aRunCount, uint8_t& aCharType, - uint8_t& aPrevCharType); + static void CalculateCharType(mozilla::intl::Bidi* aBidiEngine, + const char16_t* aText, int32_t& aOffset, + int32_t aCharTypeLimit, int32_t& aRunLimit, + int32_t& aRunLength, int32_t& aRunCount, + uint8_t& aCharType, uint8_t& aPrevCharType); static void StripBidiControlCharacters(char16_t* aText, int32_t& aTextLength); }; #endif /* nsBidiPresUtils_h___ */
--- a/layout/base/nsCaret.cpp +++ b/layout/base/nsCaret.cpp @@ -7,16 +7,17 @@ /* the caret is the text cursor used, e.g., when editing */ #include "nsCaret.h" #include <algorithm> #include "gfxUtils.h" #include "mozilla/gfx/2D.h" +#include "mozilla/intl/Bidi.h" #include "nsCOMPtr.h" #include "nsFontMetrics.h" #include "nsITimer.h" #include "nsFrameSelection.h" #include "nsIFrame.h" #include "nsIScrollableFrame.h" #include "nsIContent.h" #include "nsIFrameInlines.h" @@ -34,16 +35,18 @@ #include "mozilla/dom/Selection.h" #include "nsIBidiKeyboard.h" #include "nsContentUtils.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::gfx; +using EmbeddingLevel = mozilla::intl::Bidi::EmbeddingLevel; + // The bidi indicator hangs off the caret to one side, to show which // direction the typing is in. It needs to be at least 2x2 to avoid looking like // an insignificant dot static const int32_t kMinBidiIndicatorPixels = 2; // The default caret blinking rate (in ms of blinking interval) static const uint32_t kDefaultCaretBlinkRate = 500; @@ -384,17 +387,18 @@ nsIFrame* nsCaret::GetFrameAndOffset(con } if (!focusNode || !focusNode->IsContent() || !aSelection) { return nullptr; } nsIContent* contentNode = focusNode->AsContent(); nsFrameSelection* frameSelection = aSelection->GetFrameSelection(); - nsBidiLevel bidiLevel = frameSelection->GetCaretBidiLevel(); + mozilla::intl::Bidi::EmbeddingLevel bidiLevel = + frameSelection->GetCaretBidiLevel(); return nsCaret::GetCaretFrameForNodeOffset( frameSelection, contentNode, focusOffset, frameSelection->GetHint(), bidiLevel, aUnadjustedFrame, aFrameOffset); } /* static */ nsIFrame* nsCaret::GetGeometry(const Selection* aSelection, nsRect* aRect) { @@ -639,17 +643,18 @@ void nsCaret::StopBlinking() { if (mBlinkTimer) { mBlinkTimer->Cancel(); mBlinkRate = 0; } } nsIFrame* nsCaret::GetCaretFrameForNodeOffset( nsFrameSelection* aFrameSelection, nsIContent* aContentNode, - int32_t aOffset, CaretAssociationHint aFrameHint, nsBidiLevel aBidiLevel, + int32_t aOffset, CaretAssociationHint aFrameHint, + mozilla::intl::Bidi::EmbeddingLevel aBidiLevel, nsIFrame** aReturnUnadjustedFrame, int32_t* aReturnOffset) { if (!aFrameSelection) { return nullptr; } PresShell* presShell = aFrameSelection->GetPresShell(); if (!presShell) { return nullptr; @@ -691,18 +696,20 @@ nsIFrame* nsCaret::GetCaretFrameForNodeO // If there has been a reflow, take the caret Bidi level to be the level of // the current frame if (aBidiLevel & BIDI_LEVEL_UNDEFINED) { aBidiLevel = theFrame->GetEmbeddingLevel(); } nsIFrame* frameBefore; nsIFrame* frameAfter; - nsBidiLevel levelBefore; // Bidi level of the character before the caret - nsBidiLevel levelAfter; // Bidi level of the character after the caret + mozilla::intl::Bidi::EmbeddingLevel + levelBefore; // Bidi level of the character before the caret + mozilla::intl::Bidi::EmbeddingLevel + levelAfter; // Bidi level of the character after the caret auto [start, end] = theFrame->GetOffsets(); if (start == 0 || end == 0 || start == theFrameOffset || end == theFrameOffset) { nsPrevNextBidiLevels levels = aFrameSelection->GetPrevNextBidiLevels(aContentNode, aOffset, false); /* Boundary condition, we need to know the Bidi levels of the characters @@ -715,105 +722,111 @@ nsIFrame* nsCaret::GetCaretFrameForNodeO if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore)) { aBidiLevel = std::max(aBidiLevel, std::min(levelBefore, levelAfter)); // rule c3 aBidiLevel = std::min(aBidiLevel, std::max(levelBefore, levelAfter)); // rule c4 if (aBidiLevel == levelBefore || // rule c1 (aBidiLevel > levelBefore && aBidiLevel < levelAfter && - IS_SAME_DIRECTION(aBidiLevel, levelBefore)) || // rule c5 + aBidiLevel.IsSameDirection(levelBefore)) || // rule c5 (aBidiLevel < levelBefore && aBidiLevel > levelAfter && - IS_SAME_DIRECTION(aBidiLevel, levelBefore))) // rule c9 + aBidiLevel.IsSameDirection(levelBefore))) // rule c9 { if (theFrame != frameBefore) { if (frameBefore) { // if there is a frameBefore, move into it theFrame = frameBefore; std::tie(start, end) = theFrame->GetOffsets(); theFrameOffset = end; } else { // if there is no frameBefore, we must be at the beginning of // the line so we stay with the current frame. Exception: when // the first frame on the line has a different Bidi level from // the paragraph level, there is no real frame for the caret to // be in. We have to find the visually first frame on the line. - nsBidiLevel baseLevel = frameAfter->GetBaseLevel(); + mozilla::intl::Bidi::EmbeddingLevel baseLevel = + frameAfter->GetBaseLevel(); if (baseLevel != levelAfter) { nsPeekOffsetStruct pos(eSelectBeginLine, eDirPrevious, 0, nsPoint(0, 0), false, true, false, true, false); if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) { theFrame = pos.mResultFrame; theFrameOffset = pos.mContentOffset; } } } } } else if (aBidiLevel == levelAfter || // rule c2 (aBidiLevel > levelBefore && aBidiLevel < levelAfter && - IS_SAME_DIRECTION(aBidiLevel, levelAfter)) || // rule c6 + aBidiLevel.IsSameDirection(levelAfter)) || // rule c6 (aBidiLevel < levelBefore && aBidiLevel > levelAfter && - IS_SAME_DIRECTION(aBidiLevel, levelAfter))) // rule c10 + aBidiLevel.IsSameDirection(levelAfter))) // rule c10 { if (theFrame != frameAfter) { if (frameAfter) { // if there is a frameAfter, move into it theFrame = frameAfter; std::tie(start, end) = theFrame->GetOffsets(); theFrameOffset = start; } else { // if there is no frameAfter, we must be at the end of the line // so we stay with the current frame. // Exception: when the last frame on the line has a different // Bidi level from the paragraph level, there is no real frame // for the caret to be in. We have to find the visually last // frame on the line. - nsBidiLevel baseLevel = frameBefore->GetBaseLevel(); + mozilla::intl::Bidi::EmbeddingLevel baseLevel = + frameBefore->GetBaseLevel(); if (baseLevel != levelBefore) { nsPeekOffsetStruct pos(eSelectEndLine, eDirNext, 0, nsPoint(0, 0), false, true, false, true, false); if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) { theFrame = pos.mResultFrame; theFrameOffset = pos.mContentOffset; } } } } } else if (aBidiLevel > levelBefore && aBidiLevel < levelAfter && // rule c7/8 // before and after have the same parity - IS_SAME_DIRECTION(levelBefore, levelAfter) && + levelBefore.IsSameDirection(levelAfter) && // caret has different parity - !IS_SAME_DIRECTION(aBidiLevel, levelAfter)) { + !aBidiLevel.IsSameDirection(levelAfter)) { if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel( frameAfter, eDirNext, aBidiLevel, &theFrame))) { std::tie(start, end) = theFrame->GetOffsets(); levelAfter = theFrame->GetEmbeddingLevel(); - if (IS_LEVEL_RTL(aBidiLevel)) // c8: caret to the right of the - // rightmost character - theFrameOffset = IS_LEVEL_RTL(levelAfter) ? start : end; - else // c7: caret to the left of the leftmost character - theFrameOffset = IS_LEVEL_RTL(levelAfter) ? end : start; + if (aBidiLevel.IsRTL()) { + // c8: caret to the right of the rightmost character + theFrameOffset = levelAfter.IsRTL() ? start : end; + } else { + // c7: caret to the left of the leftmost character + theFrameOffset = levelAfter.IsRTL() ? end : start; + } } } else if (aBidiLevel < levelBefore && aBidiLevel > levelAfter && // rule c11/12 // before and after have the same parity - IS_SAME_DIRECTION(levelBefore, levelAfter) && + levelBefore.IsSameDirection(levelAfter) && // caret has different parity - !IS_SAME_DIRECTION(aBidiLevel, levelAfter)) { + !aBidiLevel.IsSameDirection(levelAfter)) { if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel( frameBefore, eDirPrevious, aBidiLevel, &theFrame))) { std::tie(start, end) = theFrame->GetOffsets(); levelBefore = theFrame->GetEmbeddingLevel(); - if (IS_LEVEL_RTL(aBidiLevel)) // c12: caret to the left of the - // leftmost character - theFrameOffset = IS_LEVEL_RTL(levelBefore) ? end : start; - else // c11: caret to the right of the rightmost character - theFrameOffset = IS_LEVEL_RTL(levelBefore) ? start : end; + if (aBidiLevel.IsRTL()) { + // c12: caret to the left of the leftmost character + theFrameOffset = levelBefore.IsRTL() ? end : start; + } else { + // c11: caret to the right of the rightmost character + theFrameOffset = levelBefore.IsRTL() ? start : end; + } } } } } } } *aReturnOffset = theFrameOffset;
--- a/layout/base/nsCaret.h +++ b/layout/base/nsCaret.h @@ -4,16 +4,17 @@ * 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/. */ /* the caret is the text cursor used, e.g., when editing */ #ifndef nsCaret_h__ #define nsCaret_h__ +#include "mozilla/intl/Bidi.h" #include "mozilla/MemoryReporting.h" #include "mozilla/dom/Selection.h" #include "nsCoord.h" #include "nsISelectionListener.h" #include "nsIWeakReferenceUtils.h" #include "CaretAssociationHint.h" #include "nsPoint.h" #include "nsRect.h" @@ -174,17 +175,18 @@ class nsCaret final : public nsISelectio * aSelection is not collapsed. * This rect does not include any extra decorations for bidi. * @param aRect must be non-null */ static nsIFrame* GetGeometry(const mozilla::dom::Selection* aSelection, nsRect* aRect); static nsIFrame* GetCaretFrameForNodeOffset( nsFrameSelection* aFrameSelection, nsIContent* aContentNode, - int32_t aOffset, CaretAssociationHint aFrameHint, uint8_t aBidiLevel, + int32_t aOffset, CaretAssociationHint aFrameHint, + mozilla::intl::Bidi::EmbeddingLevel aBidiLevel, nsIFrame** aReturnUnadjustedFrame, int32_t* aReturnOffset); static nsRect GetGeometryForFrame(nsIFrame* aFrame, int32_t aFrameOffset, nscoord* aBidiIndicatorSize); // Get the frame and frame offset based on the focus node and focus offset // of aSelection. If aOverrideNode and aOverride are provided, use them // instead. // @param aFrameOffset return the frame offset if non-null.
--- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -47,16 +47,17 @@ #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/dom/HTMLImageElement.h" #include "mozilla/dom/HTMLMediaElementBinding.h" #include "mozilla/dom/HTMLVideoElement.h" #include "mozilla/dom/InspectorFontFace.h" #include "mozilla/dom/KeyframeEffect.h" #include "mozilla/dom/SVGViewportElement.h" #include "mozilla/dom/UIEvent.h" +#include "mozilla/intl/Bidi.h" #include "mozilla/EffectCompositor.h" #include "mozilla/EffectSet.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventStateManager.h" #include "mozilla/FloatingPoint.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/gfxVars.h" #include "mozilla/gfx/PathHelpers.h" @@ -1537,17 +1538,20 @@ nsIFrame* nsLayoutUtils::GetNearestOverf // static nsRect nsLayoutUtils::GetScrolledRect(nsIFrame* aScrolledFrame, const nsRect& aScrolledFrameOverflowArea, const nsSize& aScrollPortSize, StyleDirection aDirection) { WritingMode wm = aScrolledFrame->GetWritingMode(); // Potentially override the frame's direction to use the direction found // by ScrollFrameHelper::GetScrolledFrameDir() - wm.SetDirectionFromBidiLevel(aDirection == StyleDirection::Rtl ? 1 : 0); + wm.SetDirectionFromBidiLevel( + aDirection == StyleDirection::Rtl + ? mozilla::intl::Bidi::EmbeddingLevel::RTL() + : mozilla::intl::Bidi::EmbeddingLevel::LTR()); nscoord x1 = aScrolledFrameOverflowArea.x, x2 = aScrolledFrameOverflowArea.XMost(), y1 = aScrolledFrameOverflowArea.y, y2 = aScrolledFrameOverflowArea.YMost(); const bool isHorizontalWM = !wm.IsVertical(); const bool isVerticalWM = wm.IsVertical(); @@ -5547,17 +5551,18 @@ nscoord nsLayoutUtils::AppUnitWidthOfStr nscoord nsLayoutUtils::AppUnitWidthOfStringBidi(const char16_t* aString, uint32_t aLength, const nsIFrame* aFrame, nsFontMetrics& aFontMetrics, gfxContext& aContext) { nsPresContext* presContext = aFrame->PresContext(); if (presContext->BidiEnabled()) { - nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(aFrame->Style()); + mozilla::intl::Bidi::EmbeddingLevel level = + nsBidiPresUtils::BidiLevelFromStyle(aFrame->Style()); return nsBidiPresUtils::MeasureTextWidth( aString, aLength, level, presContext, aContext, aFontMetrics); } aFontMetrics.SetTextRunRTL(false); aFontMetrics.SetVertical(aFrame->GetWritingMode().IsVertical()); aFontMetrics.SetTextOrientation(aFrame->StyleVisibility()->mTextOrientation); return nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics, aContext.GetDrawTarget()); @@ -5626,17 +5631,18 @@ void nsLayoutUtils::DrawString(const nsI aFontMetrics.SetVertical(WritingMode(aComputedStyle).IsVertical()); } aFontMetrics.SetTextOrientation( aComputedStyle->StyleVisibility()->mTextOrientation); nsPresContext* presContext = aFrame->PresContext(); if (presContext->BidiEnabled()) { - nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(aComputedStyle); + mozilla::intl::Bidi::EmbeddingLevel level = + nsBidiPresUtils::BidiLevelFromStyle(aComputedStyle); rv = nsBidiPresUtils::RenderText(aString, aLength, level, presContext, *aContext, aContext->GetDrawTarget(), aFontMetrics, aPoint.x, aPoint.y); } if (NS_FAILED(rv)) { aFontMetrics.SetTextRunRTL(false); DrawUniDirString(aString, aLength, aPoint, aFontMetrics, *aContext); }
--- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -95,17 +95,16 @@ #include "mozilla/dom/ImageTracker.h" // Needed for Start/Stop of Image Animation #include "imgIContainer.h" #include "nsIImageLoadingContent.h" #include "nsBidiUtils.h" #include "nsServiceManagerUtils.h" -#include "nsBidi.h" #include "mozilla/dom/URL.h" #include "mozilla/ServoCSSParser.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::gfx; using namespace mozilla::layers; @@ -284,18 +283,17 @@ nsPresContext::nsPresContext(dom::Docume mQuirkSheetAdded(false), mHadNonBlankPaint(false), mHadContentfulPaint(false), mHadNonTickContentfulPaint(false), mHadContentfulPaintComposite(false), #ifdef DEBUG mInitialized(false), #endif - mColorSchemeOverride(dom::PrefersColorSchemeOverride::None) -{ + mColorSchemeOverride(dom::PrefersColorSchemeOverride::None) { #ifdef DEBUG PodZero(&mLayoutPhaseCount); #endif if (!IsDynamic()) { mImageAnimationMode = imgIContainer::kDontAnimMode; mNeverAnimate = true; } else { @@ -2678,21 +2676,21 @@ uint64_t nsPresContext::GetRestyleGenera uint64_t nsPresContext::GetUndisplayedRestyleGeneration() const { if (!mRestyleManager) { return 0; } return mRestyleManager->GetUndisplayedRestyleGeneration(); } -nsBidi& nsPresContext::GetBidiEngine() { +mozilla::intl::Bidi& nsPresContext::GetBidiEngine() { MOZ_ASSERT(NS_IsMainThread()); if (!mBidiEngine) { - mBidiEngine.reset(new nsBidi()); + mBidiEngine.reset(new mozilla::intl::Bidi()); } return *mBidiEngine; } void nsPresContext::FlushFontFeatureValues() { if (!mPresShell) { return; // we've been torn down }
--- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -4,16 +4,17 @@ * 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/. */ /* a presentation of a document, part 1 */ #ifndef nsPresContext_h___ #define nsPresContext_h___ +#include "mozilla/intl/Bidi.h" #include "mozilla/AppUnits.h" #include "mozilla/Attributes.h" #include "mozilla/EnumeratedArray.h" #include "mozilla/MediaEmulationData.h" #include "mozilla/MemoryReporting.h" #include "mozilla/NotNull.h" #include "mozilla/PreferenceSheet.h" #include "mozilla/PresShellForwards.h" @@ -38,17 +39,16 @@ #include "nsCycleCollectionParticipant.h" #include "nsChangeHint.h" #include "gfxTypes.h" #include "gfxRect.h" #include "nsTArray.h" #include "nsThreadUtils.h" #include "Units.h" -class nsBidi; class nsIPrintSettings; class nsDocShell; class nsIDocShell; class nsITheme; class nsITimer; class nsIContent; class nsIFrame; class nsFrameManager; @@ -1074,17 +1074,17 @@ class nsPresContext : public nsISupports bool HasWarnedAboutTooLargeDashedOrDottedRadius() const { return mHasWarnedAboutTooLargeDashedOrDottedRadius; } void SetHasWarnedAboutTooLargeDashedOrDottedRadius() { mHasWarnedAboutTooLargeDashedOrDottedRadius = true; } - nsBidi& GetBidiEngine(); + mozilla::intl::Bidi& GetBidiEngine(); gfxFontFeatureValueSet* GetFontFeatureValuesLookup() const { return mFontFeatureValuesLookup; } protected: friend class nsRunnableMethod<nsPresContext>; void ThemeChangedInternal(); @@ -1202,17 +1202,17 @@ class nsPresContext : public nsISupports gfxSize mLastFontInflationScreenSize; int32_t mCurAppUnitsPerDevPixel; int32_t mAutoQualityMinFontSizePixelsPref; nsCOMPtr<nsITheme> mTheme; nsCOMPtr<nsIPrintSettings> mPrintSettings; - mozilla::UniquePtr<nsBidi> mBidiEngine; + mozilla::UniquePtr<mozilla::intl::Bidi> mBidiEngine; AutoTArray<TransactionInvalidations, 4> mTransactions; // text performance metrics mozilla::UniquePtr<gfxTextPerfMetrics> mTextPerf; mozilla::UniquePtr<gfxMissingFontRecorder> mMissingFonts;
--- a/layout/base/tests/chrome/printpreview_helper.xhtml +++ b/layout/base/tests/chrome/printpreview_helper.xhtml @@ -1269,58 +1269,33 @@ async function runTest45() { let test = "print_page_size4.html"; let ref = "print_page_size4_ref.html"; await compareFiles(test, ref, fuzz); requestAnimationFrame(() => setTimeout(runTest46)); } // Test that small elements don't get clipped from the bottom of the page when // using a < 1.0 scaling factor. -// TODO: enable this when bug 1725486 is fixed. -async function runTest46_DISABLED() { +async function runTest46() { var fuzz = { maxDifferent: 0, maxDifference: 0 }; let test = "bug1722890.html"; let ref = "bug1722890_ref.html"; await compareFiles(test, ref, { maxDifferent: fuzz.maxDifferent, maxDifference: fuzz.maxDifference, test: { settings: { - scaling: 0.5 + scaling: 0.5, + shrinkToFit: false } }, ref: { settings: { - scaling: 1.0 - } - } - }); - finish(); -} - -// Tests whether or not scaling from print preview tests actually works. -// Scaling currently doesn't work, and this test will begin to fail if scaling -// gets fixed. In that case, we can enable runTest46_DISABLED instead of this -// test. -// See bug 1725486 for this issue. -async function runTest46() { - // We can't load the exact same thing twice, as that won't fire the second - // load event and the test will just timeout. - let src = '<div style="background: blue; width: 50px; height: 50px;"></div>'; - let test = "data:text/html,<!-- test -->" + src; - let ref = "data:text/html,<!-- ref -->" + src; - await compareFiles(test, ref, { - test: { - settings: { - scaling: 0.5 - } - }, - ref: { - settings: { - scaling: 1.0 + scaling: 1.0, + shrinkToFit: false } } }); finish(); } ]]></script> <table style="border: 1px solid black;" xmlns="http://www.w3.org/1999/xhtml">
--- a/layout/generic/WritingModes.h +++ b/layout/generic/WritingModes.h @@ -4,16 +4,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WritingModes_h_ #define WritingModes_h_ #include <ostream> +#include "mozilla/intl/Bidi.h" #include "mozilla/ComputedStyle.h" #include "mozilla/EnumeratedRange.h" #include "nsRect.h" #include "nsBidiUtils.h" // It is the caller's responsibility to operate on logical-coordinate objects // with matched writing modes. Failure to do so will be a runtime bug; the @@ -519,18 +520,18 @@ class WritingMode { * If it turns out that our bidi direction already matches what plaintext * resolution determined, there's nothing to do here. If it didn't (i.e. if * the rtl-ness doesn't match), then we correct the direction by flipping the * same bits that get flipped in the constructor's CSS 'direction'-based * chunk. * * XXX change uint8_t to UBiDiLevel after bug 924851 */ - void SetDirectionFromBidiLevel(uint8_t level) { - if (IS_LEVEL_RTL(level) == IsBidiLTR()) { + void SetDirectionFromBidiLevel(mozilla::intl::Bidi::EmbeddingLevel level) { + if (level.IsRTL() == IsBidiLTR()) { mWritingMode ^= StyleWritingMode::RTL | StyleWritingMode::INLINE_REVERSED; } } /** * Compare two WritingModes for equality. */ bool operator==(const WritingMode& aOther) const {
--- a/layout/generic/nsFrameList.cpp +++ b/layout/generic/nsFrameList.cpp @@ -1,16 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsFrameList.h" +#include "mozilla/intl/Bidi.h" #include "mozilla/ArenaObjectID.h" #include "mozilla/PresShell.h" #include "nsBidiPresUtils.h" #include "nsContainerFrame.h" #include "nsGkAtoms.h" #include "nsILineIterator.h" #include "nsLayoutUtils.h" #include "nsPresContext.h" @@ -300,25 +301,26 @@ void nsFrameList::List(FILE* out) const #endif nsIFrame* nsFrameList::GetPrevVisualFor(nsIFrame* aFrame) const { if (!mFirstChild) return nullptr; nsIFrame* parent = mFirstChild->GetParent(); if (!parent) return aFrame ? aFrame->GetPrevSibling() : LastChild(); - nsBidiDirection paraDir = nsBidiPresUtils::ParagraphDirection(mFirstChild); + mozilla::intl::Bidi::Direction paraDir = + nsBidiPresUtils::ParagraphDirection(mFirstChild); nsAutoLineIterator iter = parent->GetLineIterator(); if (!iter) { // Parent is not a block Frame if (parent->IsLineFrame()) { // Line frames are not bidi-splittable, so need to consider bidi // reordering - if (paraDir == NSBIDI_LTR) { + if (paraDir == mozilla::intl::Bidi::Direction::LTR) { return nsBidiPresUtils::GetFrameToLeftOf(aFrame, mFirstChild, -1); } else { // RTL return nsBidiPresUtils::GetFrameToRightOf(aFrame, mFirstChild, -1); } } else { // Just get the next or prev sibling, depending on block and frame // direction. if (nsBidiPresUtils::IsFrameInParagraphDirection(mFirstChild)) { @@ -340,55 +342,56 @@ nsIFrame* nsFrameList::GetPrevVisualFor( thisLine = iter->GetNumLines(); } nsIFrame* frame = nullptr; if (aFrame) { auto line = iter->GetLine(thisLine).unwrap(); - if (paraDir == NSBIDI_LTR) { + if (paraDir == mozilla::intl::Bidi::Direction::LTR) { frame = nsBidiPresUtils::GetFrameToLeftOf(aFrame, line.mFirstFrameOnLine, line.mNumFramesOnLine); } else { // RTL frame = nsBidiPresUtils::GetFrameToRightOf(aFrame, line.mFirstFrameOnLine, line.mNumFramesOnLine); } } if (!frame && thisLine > 0) { // Get the last frame of the previous line auto line = iter->GetLine(thisLine - 1).unwrap(); - if (paraDir == NSBIDI_LTR) { + if (paraDir == mozilla::intl::Bidi::Direction::LTR) { frame = nsBidiPresUtils::GetFrameToLeftOf(nullptr, line.mFirstFrameOnLine, line.mNumFramesOnLine); } else { // RTL frame = nsBidiPresUtils::GetFrameToRightOf( nullptr, line.mFirstFrameOnLine, line.mNumFramesOnLine); } } return frame; } nsIFrame* nsFrameList::GetNextVisualFor(nsIFrame* aFrame) const { if (!mFirstChild) return nullptr; nsIFrame* parent = mFirstChild->GetParent(); if (!parent) return aFrame ? aFrame->GetPrevSibling() : mFirstChild; - nsBidiDirection paraDir = nsBidiPresUtils::ParagraphDirection(mFirstChild); + mozilla::intl::Bidi::Direction paraDir = + nsBidiPresUtils::ParagraphDirection(mFirstChild); nsAutoLineIterator iter = parent->GetLineIterator(); if (!iter) { // Parent is not a block Frame if (parent->IsLineFrame()) { // Line frames are not bidi-splittable, so need to consider bidi // reordering - if (paraDir == NSBIDI_LTR) { + if (paraDir == mozilla::intl::Bidi::Direction::LTR) { return nsBidiPresUtils::GetFrameToRightOf(aFrame, mFirstChild, -1); } else { // RTL return nsBidiPresUtils::GetFrameToLeftOf(aFrame, mFirstChild, -1); } } else { // Just get the next or prev sibling, depending on block and frame // direction. if (nsBidiPresUtils::IsFrameInParagraphDirection(mFirstChild)) { @@ -410,31 +413,31 @@ nsIFrame* nsFrameList::GetNextVisualFor( thisLine = -1; } nsIFrame* frame = nullptr; if (aFrame) { auto line = iter->GetLine(thisLine).unwrap(); - if (paraDir == NSBIDI_LTR) { + if (paraDir == mozilla::intl::Bidi::Direction::LTR) { frame = nsBidiPresUtils::GetFrameToRightOf(aFrame, line.mFirstFrameOnLine, line.mNumFramesOnLine); } else { // RTL frame = nsBidiPresUtils::GetFrameToLeftOf(aFrame, line.mFirstFrameOnLine, line.mNumFramesOnLine); } } int32_t numLines = iter->GetNumLines(); if (!frame && thisLine < numLines - 1) { // Get the first frame of the next line auto line = iter->GetLine(thisLine + 1).unwrap(); - if (paraDir == NSBIDI_LTR) { + if (paraDir == mozilla::intl::Bidi::Direction::LTR) { frame = nsBidiPresUtils::GetFrameToRightOf( nullptr, line.mFirstFrameOnLine, line.mNumFramesOnLine); } else { // RTL frame = nsBidiPresUtils::GetFrameToLeftOf(nullptr, line.mFirstFrameOnLine, line.mNumFramesOnLine); } } return frame;
--- a/layout/generic/nsFrameSelection.cpp +++ b/layout/generic/nsFrameSelection.cpp @@ -5,16 +5,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * Implementation of nsFrameSelection */ #include "nsFrameSelection.h" +#include "mozilla/intl/Bidi.h" #include "mozilla/Attributes.h" #include "mozilla/AutoRestore.h" #include "mozilla/BasePrincipal.h" #include "mozilla/EventStates.h" #include "mozilla/HTMLEditor.h" #include "mozilla/Logging.h" #include "mozilla/PresShell.h" #include "mozilla/ScrollTypes.h" @@ -594,33 +595,35 @@ nsresult nsFrameSelection::ConstrainFram // aRetPoint = aPoint + aFrame->GetOffsetTo(*aRetFrame); return NS_OK; } void nsFrameSelection::SetCaretBidiLevelAndMaybeSchedulePaint( - nsBidiLevel aLevel) { + mozilla::intl::Bidi::EmbeddingLevel aLevel) { // If the current level is undefined, we have just inserted new text. // In this case, we don't want to reset the keyboard language mCaret.mBidiLevel = aLevel; RefPtr<nsCaret> caret; if (mPresShell && (caret = mPresShell->GetCaret())) { caret->SchedulePaint(); } } -nsBidiLevel nsFrameSelection::GetCaretBidiLevel() const { +mozilla::intl::Bidi::EmbeddingLevel nsFrameSelection::GetCaretBidiLevel() + const { return mCaret.mBidiLevel; } void nsFrameSelection::UndefineCaretBidiLevel() { - mCaret.mBidiLevel |= BIDI_LEVEL_UNDEFINED; + mCaret.mBidiLevel = mozilla::intl::Bidi::EmbeddingLevel(mCaret.mBidiLevel | + BIDI_LEVEL_UNDEFINED); } #ifdef PRINT_RANGE void printRange(nsRange* aDomRange) { if (!aDomRange) { printf("NULL Range\n"); } nsINode* startNode = aDomRange->GetStartContainer(); @@ -657,19 +660,20 @@ static nsINode* GetClosestInclusiveTable current = current->GetParent(); } return nullptr; } static nsDirection GetCaretDirection(const nsIFrame& aFrame, nsDirection aDirection, bool aVisualMovement) { - const nsBidiDirection paragraphDirection = + const mozilla::intl::Bidi::Direction paragraphDirection = nsBidiPresUtils::ParagraphDirection(&aFrame); - return (aVisualMovement && paragraphDirection == NSBIDI_RTL) + return (aVisualMovement && + paragraphDirection == mozilla::intl::Bidi::Direction::RTL) ? nsDirection(1 - aDirection) : aDirection; } nsresult nsFrameSelection::MoveCaret(nsDirection aDirection, bool aContinueSelection, const nsSelectionAmount aAmount, CaretMovementStyle aMovementStyle) { @@ -923,17 +927,18 @@ nsPrevNextBidiLevels nsFrameSelection::G nsIContent* aNode, uint32_t aContentOffset, CaretAssociateHint aHint, bool aJumpLines) { // Get the level of the frames on each side nsIFrame* currentFrame; int32_t currentOffset; nsDirection direction; nsPrevNextBidiLevels levels{}; - levels.SetData(nullptr, nullptr, 0, 0); + levels.SetData(nullptr, nullptr, mozilla::intl::Bidi::EmbeddingLevel::LTR(), + mozilla::intl::Bidi::EmbeddingLevel::LTR()); currentFrame = GetFrameForNodeOffset( aNode, static_cast<int32_t>(aContentOffset), aHint, ¤tOffset); if (!currentFrame) { return levels; } auto [frameStart, frameEnd] = currentFrame->GetOffsets(); @@ -942,29 +947,30 @@ nsPrevNextBidiLevels nsFrameSelection::G direction = eDirPrevious; } else if (frameStart == currentOffset) { direction = eDirPrevious; } else if (frameEnd == currentOffset) { direction = eDirNext; } else { // we are neither at the beginning nor at the end of the frame, so we have // no worries - nsBidiLevel currentLevel = currentFrame->GetEmbeddingLevel(); + mozilla::intl::Bidi::EmbeddingLevel currentLevel = + currentFrame->GetEmbeddingLevel(); levels.SetData(currentFrame, currentFrame, currentLevel, currentLevel); return levels; } nsIFrame* newFrame = currentFrame ->GetFrameFromDirection(direction, false, aJumpLines, true, false) .mFrame; FrameBidiData currentBidi = currentFrame->GetBidiData(); - nsBidiLevel currentLevel = currentBidi.embeddingLevel; - nsBidiLevel newLevel = + mozilla::intl::Bidi::EmbeddingLevel currentLevel = currentBidi.embeddingLevel; + mozilla::intl::Bidi::EmbeddingLevel newLevel = newFrame ? newFrame->GetEmbeddingLevel() : currentBidi.baseLevel; // If not jumping lines, disregard br frames, since they might be positioned // incorrectly. // XXX This could be removed once bug 339786 is fixed. if (!aJumpLines) { if (currentFrame->IsBrFrame()) { currentFrame = nullptr; @@ -979,22 +985,23 @@ nsPrevNextBidiLevels nsFrameSelection::G if (direction == eDirNext) levels.SetData(currentFrame, newFrame, currentLevel, newLevel); else levels.SetData(newFrame, currentFrame, newLevel, currentLevel); return levels; } -nsresult nsFrameSelection::GetFrameFromLevel(nsIFrame* aFrameIn, - nsDirection aDirection, - nsBidiLevel aBidiLevel, - nsIFrame** aFrameOut) const { +nsresult nsFrameSelection::GetFrameFromLevel( + nsIFrame* aFrameIn, nsDirection aDirection, + mozilla::intl::Bidi::EmbeddingLevel aBidiLevel, + nsIFrame** aFrameOut) const { NS_ENSURE_STATE(mPresShell); - nsBidiLevel foundLevel = 0; + mozilla::intl::Bidi::EmbeddingLevel foundLevel = + mozilla::intl::Bidi::EmbeddingLevel::LTR(); nsIFrame* foundFrame = aFrameIn; nsCOMPtr<nsIFrameEnumerator> frameTraversal; nsresult result; nsCOMPtr<nsIFrameTraversal> trav( do_CreateInstance(kFrameTraversalCID, &result)); if (NS_FAILED(result)) return result;
--- a/layout/generic/nsFrameSelection.h +++ b/layout/generic/nsFrameSelection.h @@ -2,16 +2,17 @@ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef nsFrameSelection_h___ #define nsFrameSelection_h___ +#include "mozilla/intl/Bidi.h" #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/EventForwards.h" #include "mozilla/dom/Selection.h" #include "mozilla/Result.h" #include "mozilla/TextRange.h" #include "mozilla/UniquePtr.h" #include "nsIFrame.h" @@ -20,17 +21,17 @@ #include "nsISelectionListener.h" #include "nsITableCellLayout.h" #include "WordMovementType.h" #include "CaretAssociationHint.h" #include "nsBidiPresUtils.h" class nsRange; -#define BIDI_LEVEL_UNDEFINED 0x80 +#define BIDI_LEVEL_UNDEFINED mozilla::intl::Bidi::EmbeddingLevel(0x80) //---------------------------------------------------------------------- // Selection interface struct SelectionDetails { SelectionDetails() : mStart(), mEnd(), mSelectionType(mozilla::SelectionType::eInvalid) { @@ -167,26 +168,27 @@ struct MOZ_STACK_CLASS nsPeekOffsetStruc // logically after the caret". // // Used with: eSelectLine, eSelectBeginLine, eSelectEndLine. mozilla::CaretAssociationHint mAttach; }; struct nsPrevNextBidiLevels { void SetData(nsIFrame* aFrameBefore, nsIFrame* aFrameAfter, - nsBidiLevel aLevelBefore, nsBidiLevel aLevelAfter) { + mozilla::intl::Bidi::EmbeddingLevel aLevelBefore, + mozilla::intl::Bidi::EmbeddingLevel aLevelAfter) { mFrameBefore = aFrameBefore; mFrameAfter = aFrameAfter; mLevelBefore = aLevelBefore; mLevelAfter = aLevelAfter; } nsIFrame* mFrameBefore; nsIFrame* mFrameAfter; - nsBidiLevel mLevelBefore; - nsBidiLevel mLevelAfter; + mozilla::intl::Bidi::EmbeddingLevel mLevelBefore; + mozilla::intl::Bidi::EmbeddingLevel mLevelAfter; }; namespace mozilla { class SelectionChangeEventDispatcher; namespace dom { class Selection; } // namespace dom @@ -470,22 +472,23 @@ class nsFrameSelection final { enum class SelectionIntoView { IfChanged, Yes }; MOZ_CAN_RUN_SCRIPT nsresult PageMove(bool aForward, bool aExtend, nsIFrame* aFrame, SelectionIntoView aSelectionIntoView); void SetHint(CaretAssociateHint aHintRight) { mCaret.mHint = aHintRight; } CaretAssociateHint GetHint() const { return mCaret.mHint; } - void SetCaretBidiLevelAndMaybeSchedulePaint(nsBidiLevel aLevel); + void SetCaretBidiLevelAndMaybeSchedulePaint( + mozilla::intl::Bidi::EmbeddingLevel aLevel); /** * GetCaretBidiLevel gets the caret bidi level. */ - nsBidiLevel GetCaretBidiLevel() const; + mozilla::intl::Bidi::EmbeddingLevel GetCaretBidiLevel() const; /** * UndefineCaretBidiLevel sets the caret bidi level to "undefined". */ void UndefineCaretBidiLevel(); /** * PhysicalMove will generally be called from the nsiselectioncontroller @@ -690,17 +693,17 @@ class nsFrameSelection final { * * @param aPresContext is the context to use * @param aFrameIn is the frame to start from * @param aDirection is the direction to scan * @param aBidiLevel is the level to search for * @param aFrameOut will hold the frame returned */ nsresult GetFrameFromLevel(nsIFrame* aFrameIn, nsDirection aDirection, - nsBidiLevel aBidiLevel, + mozilla::intl::Bidi::EmbeddingLevel aBidiLevel, nsIFrame** aFrameOut) const; /** * MaintainSelection will track the normal selection as being "sticky". * Dragging or extending selection will never allow for a subset * (or the whole) of the maintained selection to become unselected. * Primary use: double click selecting then dragging on second click * @@ -1035,25 +1038,26 @@ class nsFrameSelection final { int16_t mSelectionChangeReasons = nsISelectionListener::NO_REASON; // For visual display purposes. int16_t mDisplaySelection = nsISelectionController::SELECTION_OFF; struct Caret { // Hint to tell if the selection is at the end of this line or beginning of // next. CaretAssociateHint mHint = mozilla::CARET_ASSOCIATE_BEFORE; - nsBidiLevel mBidiLevel = BIDI_LEVEL_UNDEFINED; + mozilla::intl::Bidi::EmbeddingLevel mBidiLevel = BIDI_LEVEL_UNDEFINED; bool IsVisualMovement(bool aContinueSelection, CaretMovementStyle aMovementStyle) const; }; Caret mCaret; - nsBidiLevel mKbdBidiLevel = NSBIDI_LTR; + mozilla::intl::Bidi::EmbeddingLevel mKbdBidiLevel = + mozilla::intl::Bidi::EmbeddingLevel::LTR(); class DesiredCaretPos { public: // the position requested by the Key Handling for up down nsresult FetchPos(nsPoint& aDesiredCaretPos, const mozilla::PresShell& aPresShell, mozilla::dom::Selection& aNormalSelection) const;
--- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -16,16 +16,17 @@ #include "nsCOMPtr.h" #include "nsIContentViewer.h" #include "nsPresContext.h" #include "nsView.h" #include "nsViewportInfo.h" #include "nsContainerFrame.h" #include "nsGkAtoms.h" #include "nsNameSpaceManager.h" +#include "mozilla/intl/Bidi.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/gfx/gfxVars.h" #include "nsFontMetrics.h" #include "nsBoxLayoutState.h" #include "mozilla/dom/NodeInfo.h" #include "nsScrollbarFrame.h" #include "nsINode.h" #include "nsIScrollbarMediator.h" @@ -7112,17 +7113,18 @@ nsRect ScrollFrameHelper::GetScrolledRec } StyleDirection ScrollFrameHelper::GetScrolledFrameDir() const { // If the scrolled frame has unicode-bidi: plaintext, the paragraph // direction set by the text content overrides the direction of the frame if (mScrolledFrame->StyleTextReset()->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) { if (nsIFrame* child = mScrolledFrame->PrincipalChildList().FirstChild()) { - return nsBidiPresUtils::ParagraphDirection(child) == NSBIDI_LTR + return nsBidiPresUtils::ParagraphDirection(child) == + mozilla::intl::Bidi::Direction::LTR ? StyleDirection::Ltr : StyleDirection::Rtl; } } return IsBidiLTR() ? StyleDirection::Ltr : StyleDirection::Rtl; } nsRect ScrollFrameHelper::GetUnsnappedScrolledRectInternal(
--- a/layout/generic/nsIFrame.cpp +++ b/layout/generic/nsIFrame.cpp @@ -20,16 +20,17 @@ #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/AncestorIterator.h" #include "mozilla/dom/ElementInlines.h" #include "mozilla/dom/ImageTracker.h" #include "mozilla/dom/Selection.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/gfxVars.h" #include "mozilla/gfx/PathHelpers.h" +#include "mozilla/intl/Bidi.h" #include "mozilla/PresShell.h" #include "mozilla/PresShellInlines.h" #include "mozilla/ResultExtensions.h" #include "mozilla/Sprintf.h" #include "mozilla/StaticAnalysisFunctions.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/StaticPrefs_print.h" #include "mozilla/SVGMaskFrame.h" @@ -1692,17 +1693,18 @@ nsRect nsIFrame::GetPaddingRect() const } WritingMode nsIFrame::WritingModeForLine(WritingMode aSelfWM, nsIFrame* aSubFrame) const { MOZ_ASSERT(aSelfWM == GetWritingMode()); WritingMode writingMode = aSelfWM; if (StyleTextReset()->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) { - nsBidiLevel frameLevel = nsBidiPresUtils::GetFrameBaseLevel(aSubFrame); + mozilla::intl::Bidi::EmbeddingLevel frameLevel = + nsBidiPresUtils::GetFrameBaseLevel(aSubFrame); writingMode.SetDirectionFromBidiLevel(frameLevel); } return writingMode; } nsRect nsIFrame::GetMarginRect() const { return GetMarginRectRelativeToSelf() + GetPosition(); @@ -7918,18 +7920,19 @@ void nsIFrame::ListGeneric(nsACString& a bool hasNormalPosition; nsPoint normalPosition = GetNormalPosition(&hasNormalPosition); if (hasNormalPosition) { aTo += nsPrintfCString(" normal-position=%s", ConvertToString(normalPosition, aFlags).c_str()); } if (HasProperty(BidiDataProperty())) { FrameBidiData bidi = GetBidiData(); - aTo += nsPrintfCString(" bidi(%d,%d,%d)", bidi.baseLevel, - bidi.embeddingLevel, bidi.precedingControl); + aTo += nsPrintfCString(" bidi(%d,%d,%d)", bidi.baseLevel.Value(), + bidi.embeddingLevel.Value(), + bidi.precedingControl.Value()); } if (IsTransformed()) { aTo += nsPrintfCString(" transformed"); } if (ChildrenHavePerspective()) { aTo += nsPrintfCString(" perspective"); } if (Extend3DContext()) { @@ -8100,17 +8103,17 @@ nsresult nsIFrame::GetPointFromOffset(in // Find the direction of the frame from the EmbeddingLevelProperty, // which is the resolved bidi level set in // nsBidiPresUtils::ResolveParagraph (odd levels = right-to-left). // If the embedding level isn't set, just use the CSS direction // property. bool hasBidiData; FrameBidiData bidiData = GetProperty(BidiDataProperty(), &hasBidiData); bool isRTL = hasBidiData - ? IS_LEVEL_RTL(bidiData.embeddingLevel) + ? bidiData.embeddingLevel.IsRTL() : StyleVisibility()->mDirection == StyleDirection::Rtl; if ((!isRTL && inOffset > newOffset) || (isRTL && inOffset <= newOffset)) { pt = contentRect.TopRight(); } } } *outPoint = pt; @@ -9097,17 +9100,18 @@ Result<bool, nsresult> nsIFrame::IsVisua MOZ_TRY(aLineIterator->CheckLineOrder(aLine, &isReordered, &firstFrame, &lastFrame)); nsIFrame** framePtr = aDirection == eDirPrevious ? &firstFrame : &lastFrame; if (!*framePtr) { return true; } - bool frameIsRTL = (nsBidiPresUtils::FrameDirection(*framePtr) == NSBIDI_RTL); + bool frameIsRTL = (nsBidiPresUtils::FrameDirection(*framePtr) == + mozilla::intl::Bidi::Direction::RTL); if ((frameIsRTL == lineIsRTL) == (aDirection == eDirPrevious)) { nsIFrame::GetFirstLeaf(framePtr); } else { nsIFrame::GetLastLeaf(framePtr); } return *framePtr == this; }
--- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -73,16 +73,17 @@ #include "nsStyleStruct.h" #include "Visibility.h" #include "nsChangeHint.h" #include "mozilla/ComputedStyleInlines.h" #include "mozilla/EnumSet.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/CompositorHitTestInfo.h" #include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/intl/Bidi.h" #include "nsDisplayItemTypes.h" #include "nsPresContext.h" #include "nsTHashSet.h" #ifdef ACCESSIBILITY # include "mozilla/a11y/AccTypes.h" #endif @@ -354,86 +355,16 @@ class nsReflowStatus final { }; #define NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics) \ aStatus.UpdateTruncated(aReflowInput, aMetrics); // Convert nsReflowStatus to a human-readable string. std::ostream& operator<<(std::ostream& aStream, const nsReflowStatus& aStatus); -/** - * nsBidiLevel is the type of the level values in our Unicode Bidi - * implementation. - * It holds an embedding level and indicates the visual direction - * by its bit 0 (even/odd value).<p> - * - * <li><code>aParaLevel</code> can be set to the - * pseudo-level values <code>NSBIDI_DEFAULT_LTR</code> - * and <code>NSBIDI_DEFAULT_RTL</code>.</li></ul> - * - * @see nsBidi::SetPara - * - * <p>The related constants are not real, valid level values. - * <code>NSBIDI_DEFAULT_XXX</code> can be used to specify - * a default for the paragraph level for - * when the <code>SetPara</code> function - * shall determine it but there is no - * strongly typed character in the input.<p> - * - * Note that the value for <code>NSBIDI_DEFAULT_LTR</code> is even - * and the one for <code>NSBIDI_DEFAULT_RTL</code> is odd, - * just like with normal LTR and RTL level values - - * these special values are designed that way. Also, the implementation - * assumes that NSBIDI_MAX_EXPLICIT_LEVEL is odd. - * - * @see NSBIDI_DEFAULT_LTR - * @see NSBIDI_DEFAULT_RTL - * @see NSBIDI_LEVEL_OVERRIDE - * @see NSBIDI_MAX_EXPLICIT_LEVEL - */ -typedef uint8_t nsBidiLevel; - -/** - * Paragraph level setting. - * If there is no strong character, then set the paragraph level to 0 - * (left-to-right). - */ -#define NSBIDI_DEFAULT_LTR 0xfe - -/** - * Paragraph level setting. - * If there is no strong character, then set the paragraph level to 1 - * (right-to-left). - */ -#define NSBIDI_DEFAULT_RTL 0xff - -/** - * Maximum explicit embedding level. - * (The maximum resolved level can be up to - * <code>NSBIDI_MAX_EXPLICIT_LEVEL+1</code>). - */ -#define NSBIDI_MAX_EXPLICIT_LEVEL 125 - -/** Bit flag for level input. - * Overrides directional properties. - */ -#define NSBIDI_LEVEL_OVERRIDE 0x80 - -/** - * <code>nsBidiDirection</code> values indicate the text direction. - */ -enum nsBidiDirection { - /** All left-to-right text This is a 0 value. */ - NSBIDI_LTR, - /** All right-to-left text This is a 1 value. */ - NSBIDI_RTL, - /** Mixed-directional text. */ - NSBIDI_MIXED -}; - namespace mozilla { // https://drafts.csswg.org/css-align-3/#baseline-sharing-group enum class BaselineSharingGroup { // NOTE Used as an array index so must be 0 and 1. First = 0, Last = 1, }; @@ -468,25 +399,26 @@ struct IntrinsicSize { bool operator==(const IntrinsicSize& rhs) const { return width == rhs.width && height == rhs.height; } bool operator!=(const IntrinsicSize& rhs) const { return !(*this == rhs); } }; // Pseudo bidi embedding level indicating nonexistence. -static const nsBidiLevel kBidiLevelNone = 0xff; +static const mozilla::intl::Bidi::EmbeddingLevel kBidiLevelNone = + mozilla::intl::Bidi::EmbeddingLevel(0xff); struct FrameBidiData { - nsBidiLevel baseLevel; - nsBidiLevel embeddingLevel; + mozilla::intl::Bidi::EmbeddingLevel baseLevel; + mozilla::intl::Bidi::EmbeddingLevel embeddingLevel; // The embedding level of virtual bidi formatting character before // this frame if any. kBidiLevelNone is used to indicate nonexistence // or unnecessity of such virtual character. - nsBidiLevel precedingControl; + mozilla::intl::Bidi::EmbeddingLevel precedingControl; }; } // namespace mozilla /// Generic destructor for frame properties. Calls delete. template <typename T> static void DeleteValue(T* aPropertyValue) { delete aPropertyValue; @@ -1393,19 +1325,23 @@ class nsIFrame : public nsQueryFrame { bool exists; mozilla::FrameBidiData bidiData = GetProperty(BidiDataProperty(), &exists); if (!exists) { bidiData.precedingControl = mozilla::kBidiLevelNone; } return bidiData; } - nsBidiLevel GetBaseLevel() const { return GetBidiData().baseLevel; } - - nsBidiLevel GetEmbeddingLevel() const { return GetBidiData().embeddingLevel; } + mozilla::intl::Bidi::EmbeddingLevel GetBaseLevel() const { + return GetBidiData().baseLevel; + } + + mozilla::intl::Bidi::EmbeddingLevel GetEmbeddingLevel() const { + return GetBidiData().embeddingLevel; + } /** * Return the distance between the border edge of the frame and the * margin edge of the frame. Like GetRect(), returns the dimensions * as of the most recent reflow. * * This doesn't include any margin collapsing that may have occurred. * It also doesn't consider GetSkipSides()/GetLogicalSkipSides(), so
--- a/layout/generic/nsImageFrame.cpp +++ b/layout/generic/nsImageFrame.cpp @@ -7,16 +7,17 @@ /* rendering object for replaced elements with image data */ #include "nsImageFrame.h" #include "TextDrawTarget.h" #include "gfx2DGlue.h" #include "gfxContext.h" #include "gfxUtils.h" +#include "mozilla/intl/Bidi.h" #include "mozilla/ComputedStyle.h" #include "mozilla/DebugOnly.h" #include "mozilla/Encoding.h" #include "mozilla/EventStates.h" #include "mozilla/HTMLEditor.h" #include "mozilla/dom/ImageTracker.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Helpers.h" @@ -1429,41 +1430,41 @@ void nsImageFrame::DisplayAltText(nsPres uint32_t maxFit; // number of characters that fit nscoord strWidth = MeasureString(str, strLen, iSize, maxFit, aRenderingContext, *fm); // Display the text nsresult rv = NS_ERROR_FAILURE; if (aPresContext->BidiEnabled()) { - nsBidiDirection dir; + mozilla::intl::Bidi::EmbeddingLevel level; nscoord x, y; if (isVertical) { x = pt.x + maxDescent; if (wm.IsBidiLTR()) { y = aRect.y; - dir = NSBIDI_LTR; + level = mozilla::intl::Bidi::EmbeddingLevel::LTR(); } else { y = aRect.YMost() - strWidth; - dir = NSBIDI_RTL; + level = mozilla::intl::Bidi::EmbeddingLevel::RTL(); } } else { y = pt.y + maxAscent; if (wm.IsBidiLTR()) { x = aRect.x; - dir = NSBIDI_LTR; + level = mozilla::intl::Bidi::EmbeddingLevel::LTR(); } else { x = aRect.XMost() - strWidth; - dir = NSBIDI_RTL; + level = mozilla::intl::Bidi::EmbeddingLevel::RTL(); } } rv = nsBidiPresUtils::RenderText( - str, maxFit, dir, aPresContext, aRenderingContext, + str, maxFit, level, aPresContext, aRenderingContext, aRenderingContext.GetDrawTarget(), *fm, x, y); } if (NS_FAILED(rv)) { nsLayoutUtils::DrawUniDirString(str, maxFit, isVertical ? nsPoint(pt.x + maxDescent, pt.y) : nsPoint(pt.x, pt.y + maxAscent), *fm, aRenderingContext);
--- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -64,16 +64,17 @@ #include "nsTextFragment.h" #include "nsGkAtoms.h" #include "nsFrameSelection.h" #include "nsRange.h" #include "nsCSSRendering.h" #include "nsContentUtils.h" #include "nsLineBreaker.h" #include "nsIFrameInlines.h" +#include "mozilla/intl/Bidi.h" #include "mozilla/intl/WordBreaker.h" #include "mozilla/ServoStyleSet.h" #include <algorithm> #include <limits> #include <type_traits> #ifdef ACCESSIBILITY # include "nsAccessibilityService.h" @@ -1930,17 +1931,17 @@ bool BuildTextRunsScanner::ContinueTextR } // Map inline-end and inline-start to physical sides for checking presence // of non-zero margin/border/padding. Side side1 = wm.PhysicalSide(eLogicalSideIEnd); Side side2 = wm.PhysicalSide(eLogicalSideIStart); // If the frames have an embedding level that is opposite to the writing // mode, we need to swap which sides we're checking. - if (IS_LEVEL_RTL(aFrame1->GetEmbeddingLevel()) == wm.IsBidiLTR()) { + if (aFrame1->GetEmbeddingLevel().IsRTL() == wm.IsBidiLTR()) { std::swap(side1, side2); } if (PreventCrossBoundaryShaping(aFrame1, ancestor, side1) || PreventCrossBoundaryShaping(aFrame2, ancestor, side2)) { return false; } } @@ -2391,17 +2392,17 @@ already_AddRefed<gfxTextRun> BuildTextRu } if (flags2 & nsTextFrameUtils::Flags::HasTab) { flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING; } if (flags2 & nsTextFrameUtils::Flags::HasShy) { flags |= gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS; } - if (mBidiEnabled && (IS_LEVEL_RTL(firstFrame->GetEmbeddingLevel()))) { + if (mBidiEnabled && (firstFrame->GetEmbeddingLevel().IsRTL())) { flags |= gfx::ShapedTextFlags::TEXT_IS_RTL; } if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { flags2 |= nsTextFrameUtils::Flags::TrailingWhitespace; } if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { flags |= gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR; }
--- a/layout/painting/moz.build +++ b/layout/painting/moz.build @@ -10,16 +10,17 @@ with Files("**"): EXPORTS += [ "ActiveLayerTracker.h", "DisplayItemClip.h", "DisplayItemClipChain.h", "DisplayListClipState.h", "HitTestInfo.h", "LayerState.h", "MatrixStack.h", + "nsCSSRendering.h", "nsCSSRenderingBorders.h", "nsCSSRenderingGradients.h", "nsDisplayItemTypes.h", "nsDisplayItemTypesList.h", "nsDisplayList.h", "nsDisplayListArenaTypes.h", "nsDisplayListInvalidation.h", "nsImageRenderer.h",
--- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -4742,39 +4742,23 @@ void nsDisplayOpacity::Paint(nsDisplayLi // TODO: Compute a bounds rect to pass to PushLayer for a smaller // allocation. aCtx->GetDrawTarget()->PushLayer(false, GetOpacity(), nullptr, gfx::Matrix()); GetChildren()->Paint(aBuilder, aCtx, mFrame->PresContext()->AppUnitsPerDevPixel()); aCtx->GetDrawTarget()->PopLayer(); } -/** - * This doesn't take into account layer scaling --- the layer may be - * rendered at a higher (or lower) resolution, affecting the retained layer - * size --- but this should be good enough. - */ -static bool IsItemTooSmallForActiveLayer(nsIFrame* aFrame) { - nsIntRect visibleDevPixels = - aFrame->InkOverflowRectRelativeToSelf().ToOutsidePixels( - aFrame->PresContext()->AppUnitsPerDevPixel()); - return visibleDevPixels.Size() < - nsIntSize(StaticPrefs::layout_min_active_layer_size(), - StaticPrefs::layout_min_active_layer_size()); -} - /* static */ bool nsDisplayOpacity::NeedsActiveLayer(nsDisplayListBuilder* aBuilder, - nsIFrame* aFrame, - bool aEnforceMinimumSize) { + nsIFrame* aFrame) { return EffectCompositor::HasAnimationsForCompositor( aFrame, DisplayItemType::TYPE_OPACITY) || (ActiveLayerTracker::IsStyleAnimated( - aBuilder, aFrame, nsCSSPropertyIDSet::OpacityProperties()) && - !(aEnforceMinimumSize && IsItemTooSmallForActiveLayer(aFrame))); + aBuilder, aFrame, nsCSSPropertyIDSet::OpacityProperties())); } bool nsDisplayOpacity::CanApplyOpacity(WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const { return !EffectCompositor::HasAnimationsForCompositor( mFrame, DisplayItemType::TYPE_OPACITY); } @@ -6874,29 +6858,27 @@ void nsDisplayTransform::Paint(nsDisplay } RefPtr<SourceSurface> untransformedSurf = untransformedDT->Snapshot(); trans.PreTranslate(pixelBounds.X(), pixelBounds.Y(), 0); aCtx->GetDrawTarget()->Draw3DTransformedSurface(untransformedSurf, trans); } -bool nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder, - bool aEnforceMinimumSize) const { +bool nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder) const { // If EffectCompositor::HasAnimationsForCompositor() is true then we can // completely bypass the main thread for this animation, so it is always // worthwhile. // For ActiveLayerTracker::IsTransformAnimated() cases the main thread is // already involved so there is less to be gained. // Therefore we check that the *post-transform* bounds of this item are // big enough to justify an active layer. return EffectCompositor::HasAnimationsForCompositor( mFrame, DisplayItemType::TYPE_TRANSFORM) || - (ActiveLayerTracker::IsTransformAnimated(aBuilder, mFrame) && - !(aEnforceMinimumSize && IsItemTooSmallForActiveLayer(mFrame))); + (ActiveLayerTracker::IsTransformAnimated(aBuilder, mFrame)); } nsRect nsDisplayTransform::TransformUntransformedBounds( nsDisplayListBuilder* aBuilder, const Matrix4x4Flagged& aMatrix) const { bool snap; const nsRect untransformedBounds = GetUntransformedBounds(aBuilder, &snap); // GetTransform always operates in dev pixels. const float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
--- a/layout/painting/nsDisplayList.h +++ b/layout/painting/nsDisplayList.h @@ -5006,18 +5006,18 @@ class nsDisplayOpacity : public nsDispla /** * Returns true if ShouldFlattenAway() applied opacity to children. */ bool OpacityAppliedToChildren() const { return mChildOpacityState == ChildOpacityState::Applied; } - static bool NeedsActiveLayer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, - bool aEnforceMinimumSize = true); + static bool NeedsActiveLayer(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame); void WriteDebugInfo(std::stringstream& aStream) override; bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override; bool CreateWebRenderCommands( wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, layers::RenderRootStateManager* aManager, nsDisplayListBuilder* aDisplayListBuilder) override; @@ -6251,18 +6251,17 @@ class nsDisplayTransform : public nsPain * affect the decision on other frames in the preserve 3d tree. * |aDirtyRect| is updated to the area that should be prerendered. */ static PrerenderInfo ShouldPrerenderTransformedContent( nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect); bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override; - bool MayBeAnimated(nsDisplayListBuilder* aBuilder, - bool aEnforceMinimumSize = true) const; + bool MayBeAnimated(nsDisplayListBuilder* aBuilder) const; void WriteDebugInfo(std::stringstream& aStream) override; bool CanMoveAsync() override { return EffectCompositor::HasAnimationsForCompositor( mFrame, DisplayItemType::TYPE_TRANSFORM); }
--- a/layout/style/ServoBindings.toml +++ b/layout/style/ServoBindings.toml @@ -28,17 +28,16 @@ headers = [ "mozilla/LookAndFeel.h", "mozilla/GeckoBindings.h", "mozilla/ServoBindings.h", "mozilla/ComputedStyle.h", "mozilla/PresShell.h", "mozilla/ServoTraversalStatistics.h", "mozilla/SizeOfState.h", "nsCSSProps.h", - "nsContentUtils.h", "nsMappedAttributes.h", "nsNameSpaceManager.h", ] raw-lines = [ # FIXME(emilio): Incrementally remove these "pub use"s. Probably # mozilla::css and mozilla::dom are easier. "pub use self::root::*;", "pub use self::root::mozilla::*;", @@ -175,17 +174,17 @@ whitelist-vars = [ "NS_RADIUS_.*", "BORDER_COLOR_.*", "BORDER_STYLE_.*", "CSS_PSEUDO_ELEMENT_.*", "SERVO_CSS_PSEUDO_ELEMENT_FLAGS_.*", "kNameSpaceID_.*", "kGenericFont_.*", "kPresContext_.*", - "nsContentUtils_.*", + "nsNameSpaceManager_.*", "GECKO_IS_NIGHTLY", "NS_SAME_AS_FOREGROUND_COLOR", "mozilla::detail::gGkAtoms", "mozilla::detail::kGkAtomsArrayOffset", "mozilla::dom::SVGPathSeg_Binding::PATHSEG_.*", ] # TODO(emilio): A bunch of types here can go away once we generate bindings and # structs together.
--- a/layout/xul/nsTextBoxFrame.cpp +++ b/layout/xul/nsTextBoxFrame.cpp @@ -3,16 +3,17 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsTextBoxFrame.h" #include "gfx2DGlue.h" #include "gfxUtils.h" +#include "mozilla/intl/Bidi.h" #include "mozilla/Attributes.h" #include "mozilla/ComputedStyle.h" #include "mozilla/Preferences.h" #include "mozilla/PresShell.h" #include "mozilla/layers/RenderRootStateManager.h" #include "mozilla/gfx/2D.h" #include "nsFontMetrics.h" #include "nsReadableUtils.h" @@ -479,17 +480,18 @@ void nsTextBoxFrame::DrawText(gfxContext aOverrideColor ? *aOverrideColor : StyleText()->mColor.ToColor()); ColorPattern colorPattern(color); aRenderingContext.SetDeviceColor(color); nsresult rv = NS_ERROR_FAILURE; if (mState & NS_FRAME_IS_BIDI) { presContext->SetBidiEnabled(); - nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(Style()); + mozilla::intl::Bidi::EmbeddingLevel level = + nsBidiPresUtils::BidiLevelFromStyle(Style()); if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { // We let the RenderText function calculate the mnemonic's // underline position for us. nsBidiPositionResolve posResolve; posResolve.logicalIndex = mAccessKeyInfo->mAccesskeyIndex; rv = nsBidiPresUtils::RenderText( mCroppedTitle.get(), mCroppedTitle.Length(), level, presContext, aRenderingContext, refDrawTarget, *fontMet, baselinePt.x,
--- a/memory/build/Utils.h +++ b/memory/build/Utils.h @@ -15,30 +15,16 @@ // Helper for log2 of powers of 2 at compile time. template <size_t N> struct Log2 : mozilla::tl::CeilingLog2<N> { using mozilla::tl::CeilingLog2<N>::value; static_assert(1ULL << value == N, "Number is not a power of 2"); }; #define LOG2(N) Log2<N>::value -// Like Log2, but ignores 0. -template <size_t N> -struct Log2Or0 : mozilla::tl::CeilingLog2<N> { - using mozilla::tl::CeilingLog2<N>::value; - static_assert(1ULL << value == N, "Number is not a power of 2"); -}; -template <> -struct Log2Or0<0> { - // This makes no sense but neither does any other value. It's just enough - // that this can be used on the unused side of a conditional expression. - static const size_t value = 0; -}; -#define LOG2_OR_0(N) Log2Or0<N>::value - enum class Order { eLess = -1, eEqual = 0, eGreater = 1, }; // Compare two integers. Returns whether the first integer is Less, // Equal or Greater than the second integer.
--- a/memory/build/malloc_decls.h +++ b/memory/build/malloc_decls.h @@ -58,21 +58,24 @@ NOTHROW_MALLOC_DECL(memalign, void*, siz NOTHROW_MALLOC_DECL(posix_memalign, int, void**, size_t, size_t) NOTHROW_MALLOC_DECL(aligned_alloc, void*, size_t, size_t) NOTHROW_MALLOC_DECL(valloc, void*, size_t) NOTHROW_MALLOC_DECL(malloc_usable_size, size_t, usable_ptr_t) MALLOC_DECL(malloc_good_size, size_t, size_t) # endif # if MALLOC_FUNCS & MALLOC_FUNCS_JEMALLOC -// The 2nd argument points to an optional array exactly JEMALLOC_MAX_STATS_BINS -// long to be filled in (if non-null). Any unused bin has it's size set to zero. +// The 2nd argument points to an optional array exactly +// jemalloc_stats_num_bins() long to be filled in (if non-null). MALLOC_DECL(jemalloc_stats_internal, void, jemalloc_stats_t*, jemalloc_bin_stats_t*) +// Return the size of the jemalloc_bin_stats_t array. +MALLOC_DECL(jemalloc_stats_num_bins, size_t) + // On some operating systems (Mac), we use madvise(MADV_FREE) to hand pages // back to the operating system. On Mac, the operating system doesn't take // this memory back immediately; instead, the OS takes it back only when the // machine is running out of physical memory. // // This is great from the standpoint of efficiency, but it makes measuring our // actual RSS difficult, because pages which we've MADV_FREE'd shouldn't count // against our RSS.
--- a/memory/build/mozjemalloc.cpp +++ b/memory/build/mozjemalloc.cpp @@ -478,31 +478,31 @@ static size_t gPageSize; #endif #ifdef MALLOC_STATIC_PAGESIZE # define DECLARE_GLOBAL(type, name) # define DEFINE_GLOBALS # define END_GLOBALS # define DEFINE_GLOBAL(type) static const type # define GLOBAL_LOG2 LOG2 -# define GLOBAL_LOG2_OR_0 LOG2_OR_0 # define GLOBAL_ASSERT_HELPER1(x) static_assert(x, # x) # define GLOBAL_ASSERT_HELPER2(x, y) static_assert(x, y) # define GLOBAL_ASSERT(...) \ MACRO_CALL( \ MOZ_PASTE_PREFIX_AND_ARG_COUNT(GLOBAL_ASSERT_HELPER, __VA_ARGS__), \ (__VA_ARGS__)) +# define GLOBAL_CONSTEXPR constexpr #else # define DECLARE_GLOBAL(type, name) static type name; # define DEFINE_GLOBALS static void DefineGlobals() { # define END_GLOBALS } # define DEFINE_GLOBAL(type) # define GLOBAL_LOG2 FloorLog2 -# define GLOBAL_LOG2_OR_0 FloorLog2 # define GLOBAL_ASSERT MOZ_RELEASE_ASSERT +# define GLOBAL_CONSTEXPR #endif DECLARE_GLOBAL(size_t, gMaxSubPageClass) DECLARE_GLOBAL(uint8_t, gNumSubPageClasses) DECLARE_GLOBAL(uint8_t, gPageSize2Pow) DECLARE_GLOBAL(size_t, gPageSizeMask) DECLARE_GLOBAL(size_t, gChunkNumPages) DECLARE_GLOBAL(size_t, gChunkHeaderNumPages) @@ -515,20 +515,22 @@ DEFINE_GLOBAL(size_t) gMaxSubPageClass = gPageSize / 2 >= kMinSubPageClass ? gPageSize / 2 : 0; // Max size class for bins. #define gMaxBinClass \ (gMaxSubPageClass ? gMaxSubPageClass : kMaxQuantumWideClass) // Number of sub-page bins. DEFINE_GLOBAL(uint8_t) -gNumSubPageClasses = - static_cast<uint8_t>(gMaxSubPageClass ? GLOBAL_LOG2_OR_0(gMaxSubPageClass) - - LOG2(kMinSubPageClass) + 1 - : 0); +gNumSubPageClasses = []() GLOBAL_CONSTEXPR -> uint8_t { + if GLOBAL_CONSTEXPR (gMaxSubPageClass != 0) { + return FloorLog2(gMaxSubPageClass) - LOG2(kMinSubPageClass) + 1; + } + return 0; +}(); DEFINE_GLOBAL(uint8_t) gPageSize2Pow = GLOBAL_LOG2(gPageSize); DEFINE_GLOBAL(size_t) gPageSizeMask = gPageSize - 1; // Number of pages in a chunk. DEFINE_GLOBAL(size_t) gChunkNumPages = kChunkSize >> gPageSize2Pow; // Number of pages necessary for a chunk header. @@ -3959,18 +3961,16 @@ static bool malloc_init_hard() { _getprogname(), "Compile-time page size does not divide the runtime one.\n"); MOZ_CRASH(); } #else gRealPageSize = gPageSize = (size_t)result; #endif - MOZ_RELEASE_ASSERT(JEMALLOC_MAX_STATS_BINS >= NUM_SMALL_CLASSES); - // Get runtime configuration. if ((opts = getenv("MALLOC_OPTIONS"))) { for (i = 0; opts[i] != '\0'; i++) { unsigned j, nreps; bool nseen; // Parse repetition count, if any. for (nreps = 0, nseen = false;; i++, nseen = true) { @@ -4319,20 +4319,17 @@ inline void MozJemalloc::jemalloc_stats_ if (!aStats) { return; } if (!malloc_init()) { memset(aStats, 0, sizeof(*aStats)); return; } if (aBinStats) { - // An assertion in malloc_init_hard will guarantee that - // JEMALLOC_MAX_STATS_BINS >= NUM_SMALL_CLASSES. - memset(aBinStats, 0, - sizeof(jemalloc_bin_stats_t) * JEMALLOC_MAX_STATS_BINS); + memset(aBinStats, 0, sizeof(jemalloc_bin_stats_t) * NUM_SMALL_CLASSES); } // Gather runtime settings. aStats->opt_junk = opt_junk; aStats->opt_zero = opt_zero; aStats->quantum = kQuantum; aStats->quantum_max = kMaxQuantumClass; aStats->quantum_wide = kQuantumWide; @@ -4448,16 +4445,21 @@ inline void MozJemalloc::jemalloc_stats_ aStats->mapped += non_arena_mapped; aStats->bookkeeping += chunk_header_size; aStats->waste -= chunk_header_size; MOZ_ASSERT(aStats->mapped >= aStats->allocated + aStats->waste + aStats->page_cache + aStats->bookkeeping); } +template <> +inline size_t MozJemalloc::jemalloc_stats_num_bins() { + return NUM_SMALL_CLASSES; +} + #ifdef MALLOC_DOUBLE_PURGE // Explicitly remove all of this chunk's MADV_FREE'd pages from memory. static void hard_purge_chunk(arena_chunk_t* aChunk) { // See similar logic in arena_t::Purge(). for (size_t i = gChunkHeaderNumPages; i < gChunkNumPages; i++) { // Find all adjacent pages with CHUNK_MAP_MADVISED set. size_t npages;
--- a/memory/build/mozjemalloc_types.h +++ b/memory/build/mozjemalloc_types.h @@ -108,19 +108,16 @@ typedef struct { // bin stats array entry is unused (no more bins). size_t num_non_full_runs; // The number of non-full runs size_t num_runs; // The number of runs in this bin size_t bytes_unused; // The unallocated bytes across all these bins size_t bytes_total; // The total storage area for runs in this bin, size_t bytes_per_run; // The number of bytes per run, including headers. } jemalloc_bin_stats_t; -// This is the total number of bins. -#define JEMALLOC_MAX_STATS_BINS 51 - enum PtrInfoTag { // The pointer is not currently known to the allocator. // 'addr', 'size', and 'arenaId' are always 0. TagUnknown, // The pointer is within a live allocation. // 'addr', 'size', and 'arenaId' describe the allocation. TagLiveAlloc,
--- a/memory/build/mozmemory.h +++ b/memory/build/mozmemory.h @@ -6,16 +6,17 @@ #ifndef mozmemory_h #define mozmemory_h // This header is meant to be used when the following functions are // necessary: // - malloc_good_size (used to be called je_malloc_usable_in_advance) // - jemalloc_stats +// - jemalloc_stats_num_bins // - jemalloc_purge_freed_pages // - jemalloc_free_dirty_pages