зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to mozilla-central. a=merge
This commit is contained in:
Коммит
2e3b0483e3
|
@ -646,12 +646,40 @@ nsRect LocalAccessible::ParentRelativeBounds() {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,3 +14,4 @@ skip-if = webrender # Bug 1734271
|
|||
https_first_disabled = true
|
||||
skip-if = e10s && os == 'win' # bug 1372296
|
||||
[browser_zero_area.js]
|
||||
[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 }
|
||||
);
|
|
@ -681,6 +681,40 @@ 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.
|
||||
|
|
|
@ -375,17 +375,12 @@ var FullScreen = {
|
|||
// 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");
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -236,7 +236,7 @@ panelview[mainview] > .panel-header {
|
|||
%endif
|
||||
|
||||
#tabbrowser-tabs[positionpinnedtabs] > #tabbrowser-arrowscrollbox > .tabbrowser-tab[pinned] {
|
||||
position: fixed !important;
|
||||
position: absolute !important;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
|
|
@ -4103,6 +4103,7 @@ BrowserGlue.prototype = {
|
|||
const buttons = [
|
||||
{
|
||||
"l10n-id": "restore-session-startup-suggestion-button",
|
||||
primary: true,
|
||||
callback: () => {
|
||||
win.PanelUI.selectAndMarkItem([
|
||||
"appMenu-history-button",
|
||||
|
|
|
@ -297,6 +297,10 @@ $letter-fallback-color: $white;
|
|||
}
|
||||
|
||||
.edit-topsites-wrapper {
|
||||
.top-site-inner > .top-site-button > .tile {
|
||||
border: 1px solid var(--newtab-border-color);
|
||||
}
|
||||
|
||||
.modal {
|
||||
box-shadow: $shadow-secondary;
|
||||
left: 0;
|
||||
|
|
|
@ -760,6 +760,9 @@ main.has-snippet {
|
|||
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;
|
||||
|
|
|
@ -764,6 +764,9 @@ main.has-snippet {
|
|||
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;
|
||||
|
|
|
@ -760,6 +760,9 @@ main.has-snippet {
|
|||
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;
|
||||
|
|
|
@ -34,6 +34,7 @@ const KNOWN_SEARCH_SOURCES = new Map([
|
|||
["searchbar", "searchbar"],
|
||||
["system", "system"],
|
||||
["urlbar", "urlbar"],
|
||||
["urlbar-handoff", "urlbar_handoff"],
|
||||
["urlbar-searchmode", "urlbar_searchmode"],
|
||||
["webextension", "webextension"],
|
||||
]);
|
||||
|
@ -204,6 +205,7 @@ class BrowserSearchTelemetryHandler {
|
|||
case "urlbar":
|
||||
case "searchbar":
|
||||
case "urlbar-searchmode":
|
||||
case "urlbar-handoff":
|
||||
this._handleSearchAndUrlbar(browser, engine, source, details);
|
||||
break;
|
||||
case "abouthome":
|
||||
|
|
|
@ -51,6 +51,7 @@ SEARCH_COUNTS - SAP usage
|
|||
- ``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``
|
||||
|
||||
|
@ -62,6 +63,7 @@ browser.engagement.navigation.*
|
|||
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``
|
||||
|
@ -115,6 +117,7 @@ browser.search.content.*
|
|||
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``
|
||||
|
|
|
@ -72,7 +72,15 @@ 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;
|
||||
|
@ -190,6 +198,50 @@ add_task(async function test_source_urlbar() {
|
|||
);
|
||||
});
|
||||
|
||||
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(
|
||||
|
|
|
@ -4208,6 +4208,13 @@ var SessionStoreInternal = {
|
|||
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);
|
||||
|
@ -4402,13 +4409,6 @@ var SessionStoreInternal = {
|
|||
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;
|
||||
|
|
|
@ -101,6 +101,7 @@ skip-if =
|
|||
[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
|
||||
|
|
|
@ -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);
|
||||
});
|
|
@ -8,7 +8,7 @@ const { TabUnloader } = ChromeUtils.import(
|
|||
);
|
||||
|
||||
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 {
|
||||
|
@ -114,7 +114,7 @@ async function onLoad() {
|
|||
document
|
||||
.getElementById("button-unload")
|
||||
.addEventListener("click", async () => {
|
||||
await TabUnloader.unloadLeastRecentlyUsedTab();
|
||||
await TabUnloader.unloadLeastRecentlyUsedTab(null);
|
||||
await refreshData();
|
||||
});
|
||||
await refreshData();
|
||||
|
|
|
@ -684,6 +684,7 @@ class UrlbarInput {
|
|||
*
|
||||
*/
|
||||
handoff(searchString, searchEngine) {
|
||||
this._isHandoffSession = true;
|
||||
if (UrlbarPrefs.get("shouldHandOffToSearchMode") && searchEngine) {
|
||||
this.search(searchString, {
|
||||
searchEngine,
|
||||
|
@ -2232,15 +2233,22 @@ class UrlbarInput {
|
|||
_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 }
|
||||
);
|
||||
}
|
||||
|
@ -2733,6 +2741,8 @@ class UrlbarInput {
|
|||
|
||||
_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
|
||||
|
|
|
@ -328,6 +328,8 @@ 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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -513,6 +513,7 @@ ${RemoveDefaultBrowserAgentShortcut}
|
|||
${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
|
||||
|
@ -604,6 +605,7 @@ ${RemoveDefaultBrowserAgentShortcut}
|
|||
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"
|
||||
|
||||
|
@ -673,6 +675,7 @@ ${RemoveDefaultBrowserAgentShortcut}
|
|||
${WriteApplicationsSupportedType} ${RegKey} ".svg"
|
||||
${WriteApplicationsSupportedType} ${RegKey} ".webm"
|
||||
${WriteApplicationsSupportedType} ${RegKey} ".webp"
|
||||
${WriteApplicationsSupportedType} ${RegKey} ".avif"
|
||||
${WriteApplicationsSupportedType} ${RegKey} ".xht"
|
||||
${WriteApplicationsSupportedType} ${RegKey} ".xhtml"
|
||||
${WriteApplicationsSupportedType} ${RegKey} ".xml"
|
||||
|
@ -1622,6 +1625,8 @@ Function SetAsDefaultAppUserHKCU
|
|||
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"
|
||||
|
|
|
@ -496,6 +496,7 @@ Section "Uninstall"
|
|||
${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
|
||||
|
|
|
@ -29,6 +29,10 @@ 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],
|
||||
|
@ -82,6 +86,10 @@ let DefaultTabUnloaderMethods = {
|
|||
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) {
|
||||
|
@ -155,7 +163,7 @@ var TabUnloader = {
|
|||
},
|
||||
|
||||
// This method is exposed on nsITabUnloader
|
||||
async unloadTabAsync() {
|
||||
async unloadTabAsync(minInactiveDuration = kMinInactiveDurationInMs) {
|
||||
const watcher = Cc["@mozilla.org/xpcom/memory-watcher;1"].getService(
|
||||
Ci.nsIAvailableMemoryWatcherBase
|
||||
);
|
||||
|
@ -174,7 +182,9 @@ var TabUnloader = {
|
|||
}
|
||||
|
||||
this._isUnloading = true;
|
||||
const isTabUnloaded = await this.unloadLeastRecentlyUsedTab();
|
||||
const isTabUnloaded = await this.unloadLeastRecentlyUsedTab(
|
||||
minInactiveDuration
|
||||
);
|
||||
this._isUnloading = false;
|
||||
|
||||
watcher.onUnloadAttemptCompleted(
|
||||
|
@ -186,6 +196,10 @@ var TabUnloader = {
|
|||
* 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:
|
||||
|
@ -210,11 +224,24 @@ var TabUnloader = {
|
|||
* 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.
|
||||
|
@ -279,8 +306,10 @@ var TabUnloader = {
|
|||
* 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)) {
|
||||
|
|
|
@ -86,7 +86,7 @@ async function pressure() {
|
|||
"TabBrowserDiscarded",
|
||||
true
|
||||
);
|
||||
TabUnloader.unloadTabAsync();
|
||||
TabUnloader.unloadTabAsync(null);
|
||||
return tabDiscarded;
|
||||
}
|
||||
|
||||
|
@ -106,12 +106,12 @@ function pressureAndObserve(aExpectedTopic) {
|
|||
};
|
||||
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,
|
||||
|
@ -214,7 +214,7 @@ add_task(async function test() {
|
|||
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(
|
||||
|
|
|
@ -41,6 +41,10 @@ let TestTabUnloaderMethods = {
|
|||
return 3;
|
||||
},
|
||||
|
||||
getNow() {
|
||||
return 100;
|
||||
},
|
||||
|
||||
*iterateProcesses(tab) {
|
||||
for (let process of tab.process.split(",")) {
|
||||
yield Number(process);
|
||||
|
@ -133,6 +137,13 @@ let unloadTests = [
|
|||
],
|
||||
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"],
|
||||
|
@ -407,7 +418,10 @@ add_task(async function doTests() {
|
|||
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 += ",";
|
||||
|
|
|
@ -31,8 +31,15 @@
|
|||
--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) {
|
||||
|
|
|
@ -30,7 +30,8 @@ panel[type="arrow"][side="right"] {
|
|||
#customizationui-widget-panel,
|
||||
#identity-popup,
|
||||
#permission-popup,
|
||||
#protections-popup {
|
||||
#protections-popup,
|
||||
#appMenu-notification-popup {
|
||||
margin-top: -1px; /* Overrides value from panelUI.inc.css */
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
--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 */
|
||||
|
@ -74,6 +75,10 @@
|
|||
--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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -462,15 +462,17 @@ description#identity-popup-content-verifier,
|
|||
.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
|
||||
|
@ -589,11 +591,14 @@ description#identity-popup-content-verifier,
|
|||
}
|
||||
|
||||
#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);
|
||||
|
@ -1005,18 +1010,6 @@ description#identity-popup-content-verifier,
|
|||
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);
|
||||
|
|
|
@ -164,7 +164,8 @@ panelmultiview[transitioning] > .panel-viewcontainer > .panel-viewstack > panelv
|
|||
#customizationui-widget-panel,
|
||||
#identity-popup,
|
||||
#permission-popup,
|
||||
#protections-popup {
|
||||
#protections-popup,
|
||||
#appMenu-notification-popup {
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
|
|
|
@ -74,9 +74,9 @@
|
|||
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);
|
||||
}
|
||||
|
@ -85,6 +85,7 @@
|
|||
#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,
|
||||
|
@ -94,6 +95,7 @@
|
|||
#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,
|
||||
|
|
|
@ -314,20 +314,15 @@
|
|||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
@ -406,14 +401,16 @@
|
|||
.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 {
|
||||
|
@ -594,16 +591,18 @@
|
|||
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 {
|
||||
|
|
|
@ -4,26 +4,15 @@
|
|||
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::
|
||||
You can find the solution file at ``$OBJDIR/msvs/mozilla.sln``.
|
||||
|
||||
mach configure
|
||||
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 you have built recently, your tree is already configured.)
|
||||
|
||||
Then, simply generate the Visual Studio build backend::
|
||||
|
||||
mach build-backend -b VisualStudio
|
||||
|
||||
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
|
||||
=====================
|
||||
|
@ -59,20 +48,8 @@ Libraries
|
|||
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
|
||||
===========================
|
||||
|
|
|
@ -95,7 +95,7 @@ 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.
|
||||
|
@ -103,7 +103,14 @@ ifndef FUZZING_JS_FUZZILLI
|
|||
# -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
|
||||
|
|
|
@ -43,14 +43,17 @@ add_task(async function() {
|
|||
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");
|
||||
|
@ -67,7 +70,9 @@ add_task(async function() {
|
|||
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;
|
||||
|
|
|
@ -145,17 +145,17 @@ 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",
|
||||
|
|
|
@ -256,6 +256,7 @@
|
|||
#include "nsCSSPropertyID.h"
|
||||
#include "nsCSSProps.h"
|
||||
#include "nsCSSPseudoElements.h"
|
||||
#include "nsCSSRendering.h"
|
||||
#include "nsCanvasFrame.h"
|
||||
#include "nsCaseTreatment.h"
|
||||
#include "nsCharsetSource.h"
|
||||
|
@ -396,6 +397,7 @@
|
|||
#include "nsSerializationHelper.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsStringFlags.h"
|
||||
#include "nsStyleUtil.h"
|
||||
#include "nsStringIterator.h"
|
||||
#include "nsStyleSheetService.h"
|
||||
#include "nsStyleStruct.h"
|
||||
|
@ -14452,6 +14454,88 @@ Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicateFunc) {
|
|||
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;
|
||||
|
|
|
@ -3402,6 +3402,9 @@ class Document : public nsINode,
|
|||
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;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
#include "mozilla/dom/Selection.h"
|
||||
#include "mozilla/intl/Bidi.h"
|
||||
|
||||
#include "mozilla/AccessibleCaretEventHub.h"
|
||||
#include "mozilla/AsyncEventDispatcher.h"
|
||||
|
@ -385,7 +386,9 @@ Nullable<int16_t> Selection::GetCaretBidiLevel(
|
|||
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);
|
||||
|
@ -403,7 +406,7 @@ void Selection::SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel,
|
|||
mFrameSelection->UndefineCaretBidiLevel();
|
||||
} else {
|
||||
mFrameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(
|
||||
aCaretBidiLevel.Value());
|
||||
mozilla::intl::Bidi::EmbeddingLevel(aCaretBidiLevel.Value()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1357,7 +1360,8 @@ nsIFrame* Selection::GetPrimaryOrCaretFrameForNodeOffset(nsIContent* aContent,
|
|||
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,
|
||||
|
@ -3298,9 +3302,10 @@ void Selection::Modify(const nsAString& aAlter, const nsAString& aDirection,
|
|||
// 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);
|
||||
mozilla::intl::Bidi::Direction paraDir =
|
||||
nsBidiPresUtils::ParagraphDirection(frame);
|
||||
|
||||
if (paraDir == NSBIDI_RTL && visual) {
|
||||
if (paraDir == mozilla::intl::Bidi::Direction::RTL && visual) {
|
||||
if (amount == eSelectBeginLine) {
|
||||
amount = eSelectEndLine;
|
||||
forward = !forward;
|
||||
|
@ -3474,7 +3479,9 @@ nsresult Selection::SelectionLanguageChange(bool aLangRTL) {
|
|||
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;
|
||||
}
|
||||
|
@ -3488,12 +3495,12 @@ nsresult Selection::SelectionLanguageChange(bool aLangRTL) {
|
|||
|
||||
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
|
||||
|
@ -3511,26 +3518,30 @@ nsresult Selection::SelectionLanguageChange(bool aLangRTL) {
|
|||
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
|
||||
|
|
|
@ -38,18 +38,18 @@ var tests = {
|
|||
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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "nsContentUtils.h"
|
||||
|
||||
#include "mozilla/intl/Bidi.h"
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "mozilla/PresShellInlines.h"
|
||||
#include "mozilla/SVGImageContext.h"
|
||||
|
@ -3503,11 +3504,11 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor
|
|||
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;
|
||||
|
@ -3873,7 +3874,9 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
|||
// 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()) {
|
||||
|
@ -4014,7 +4017,9 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
|||
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);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#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"
|
||||
|
@ -19,8 +20,8 @@
|
|||
#include "FilterDescription.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "nsICanvasRenderingContextInternal.h"
|
||||
#include "nsBidi.h"
|
||||
#include "nsColor.h"
|
||||
#include "nsIFrame.h"
|
||||
|
||||
class gfxFontGroup;
|
||||
class nsGlobalWindowInner;
|
||||
|
@ -797,7 +798,7 @@ class CanvasRenderingContext2D final : public nsICanvasRenderingContextInternal,
|
|||
|
||||
nsTArray<RegionInfo> mHitRegionsOptions;
|
||||
|
||||
nsBidi mBidiEngine;
|
||||
mozilla::intl::Bidi mBidiEngine;
|
||||
|
||||
/**
|
||||
* Returns true if a shadow should be drawn along with a
|
||||
|
|
|
@ -67,6 +67,7 @@ dictionary DnsCacheEntry {
|
|||
double expiration = 0;
|
||||
boolean trr = false;
|
||||
DOMString originAttributesSuffix = "";
|
||||
DOMString flags = "";
|
||||
};
|
||||
|
||||
[GenerateConversionToJS]
|
||||
|
|
|
@ -10,17 +10,17 @@ 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).
|
||||
|
|
|
@ -463,7 +463,7 @@ class VideoData : public MediaData {
|
|||
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,
|
||||
|
|
|
@ -970,7 +970,7 @@ already_AddRefed<VideoData> ChromiumCDMParent::CreateVideoFrame(
|
|||
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();
|
||||
}
|
||||
|
@ -1037,7 +1037,8 @@ void ChromiumCDMParent::ActorDestroy(ActorDestroyReason aWhy) {
|
|||
|
||||
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(
|
||||
|
@ -1084,6 +1085,7 @@ RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMParent::InitializeVideoDecoder(
|
|||
|
||||
mVideoDecoderInitialized = true;
|
||||
mImageContainer = aImageContainer;
|
||||
mKnowsCompositor = aKnowsCompositor;
|
||||
mVideoInfo = aInfo;
|
||||
mVideoFrameBufferSize = bufferSize;
|
||||
|
||||
|
|
|
@ -88,7 +88,8 @@ class ChromiumCDMParent final : public PChromiumCDMParent,
|
|||
// 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);
|
||||
|
@ -181,6 +182,7 @@ class ChromiumCDMParent final : public PChromiumCDMParent,
|
|||
MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
|
||||
|
||||
RefPtr<layers::ImageContainer> mImageContainer;
|
||||
RefPtr<layers::KnowsCompositor> mKnowsCompositor;
|
||||
VideoInfo mVideoInfo;
|
||||
uint64_t mLastStreamOffset = 0;
|
||||
|
||||
|
|
|
@ -211,10 +211,10 @@ RefPtr<MediaDataDecoder::DecodePromise> AOMDecoder::ProcessDecode(
|
|||
: 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",
|
||||
|
|
|
@ -69,9 +69,10 @@ already_AddRefed<MediaData> BlankVideoDataCreator::Create(
|
|||
|
||||
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,
|
||||
|
|
|
@ -20,7 +20,8 @@ ChromiumCDMVideoDecoder::ChromiumCDMVideoDecoder(
|
|||
mConfig(aParams.mConfig),
|
||||
mCrashHelper(aParams.mCrashHelper),
|
||||
mGMPThread(GetGMPThread()),
|
||||
mImageContainer(aParams.mImageContainer) {}
|
||||
mImageContainer(aParams.mImageContainer),
|
||||
mKnowsCompositor(aParams.mKnowsCompositor) {}
|
||||
|
||||
ChromiumCDMVideoDecoder::~ChromiumCDMVideoDecoder() = default;
|
||||
|
||||
|
@ -89,10 +90,12 @@ RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMVideoDecoder::Init() {
|
|||
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 {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "ChromiumCDMParent.h"
|
||||
#include "PlatformDecoderModule.h"
|
||||
#include "mozilla/layers/KnowsCompositor.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -40,6 +41,7 @@ class ChromiumCDMVideoDecoder
|
|||
RefPtr<GMPCrashHelper> mCrashHelper;
|
||||
nsCOMPtr<nsISerialEventTarget> mGMPThread;
|
||||
RefPtr<layers::ImageContainer> mImageContainer;
|
||||
RefPtr<layers::KnowsCompositor> mKnowsCompositor;
|
||||
MozPromiseHolder<InitPromise> mInitPromise;
|
||||
bool mConvertToAnnexB = false;
|
||||
};
|
||||
|
|
|
@ -34,7 +34,8 @@ static bool IsOnGMPThread() {
|
|||
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);
|
||||
|
@ -64,7 +65,7 @@ void GMPVideoDecoder::Decoded(GMPVideoi420Frame* aDecodedFrame) {
|
|||
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));
|
||||
|
@ -123,7 +124,8 @@ GMPVideoDecoder::GMPVideoDecoder(const GMPVideoDecoderParams& aParams)
|
|||
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)) {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/layers/KnowsCompositor.h"
|
||||
#if !defined(GMPVideoDecoder_h_)
|
||||
# define GMPVideoDecoder_h_
|
||||
|
||||
|
@ -22,6 +23,7 @@ struct MOZ_STACK_CLASS GMPVideoDecoderParams {
|
|||
const VideoInfo& mConfig;
|
||||
layers::ImageContainer* mImageContainer;
|
||||
GMPCrashHelper* mCrashHelper;
|
||||
layers::KnowsCompositor* mKnowsCompositor;
|
||||
};
|
||||
|
||||
DDLoggedTypeDeclNameAndBase(GMPVideoDecoder, MediaDataDecoder);
|
||||
|
@ -87,6 +89,7 @@ class GMPVideoDecoder : public MediaDataDecoder,
|
|||
|
||||
int64_t mLastStreamOffset = 0;
|
||||
RefPtr<layers::ImageContainer> mImageContainer;
|
||||
RefPtr<layers::KnowsCompositor> mKnowsCompositor;
|
||||
|
||||
MozPromiseHolder<DecodePromise> mDecodePromise;
|
||||
MozPromiseHolder<DecodePromise> mDrainPromise;
|
||||
|
|
|
@ -952,7 +952,7 @@ already_AddRefed<VideoData> MediaDataHelper::CreateYUV420VideoData(
|
|||
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 "
|
||||
|
|
|
@ -48,8 +48,12 @@ already_AddRefed<XRViewerPose> XRFrame::GetViewerPose(
|
|||
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
|
||||
|
@ -145,11 +149,10 @@ already_AddRefed<XRPose> XRFrame::GetPose(const XRSpace& aSpace,
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -175,11 +175,19 @@ bool XRSession::IsImmersive() const {
|
|||
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) {
|
||||
|
|
|
@ -64,7 +64,7 @@ class XRSession final : public DOMEventTargetHelper, public nsARefreshObserver {
|
|||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
// WebIDL Attributes
|
||||
XRVisibilityState VisibilityState();
|
||||
XRVisibilityState VisibilityState() const;
|
||||
XRRenderState* RenderState();
|
||||
XRInputSourceArray* InputSources();
|
||||
|
||||
|
@ -98,6 +98,7 @@ class XRSession final : public DOMEventTargetHelper, public nsARefreshObserver {
|
|||
void ExitPresent();
|
||||
RefPtr<XRViewerPose> PooledViewerPose(const gfx::Matrix4x4Double& aTransform,
|
||||
bool aEmulatedPosition);
|
||||
bool CanReportPoses() const;
|
||||
|
||||
// nsARefreshObserver
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
|
|
|
@ -716,3 +716,25 @@ 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);
|
||||
};
|
||||
|
|
|
@ -1578,11 +1578,10 @@ nsXULPrototypeScript::nsXULPrototypeScript(uint32_t aLineNo)
|
|||
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) {
|
||||
|
@ -1689,10 +1688,7 @@ nsresult nsXULPrototypeScript::Serialize(
|
|||
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(
|
||||
|
|
|
@ -10,22 +10,23 @@
|
|||
#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
|
||||
|
@ -5765,12 +5766,13 @@ EditorBase::AutoCaretBidiLevelManager::AutoCaretBidiLevelManager(
|
|||
nsPrevNextBidiLevels levels = frameSelection->GetPrevNextBidiLevels(
|
||||
aPointAtCaret.GetContainerAsContent(), aPointAtCaret.Offset(), true);
|
||||
|
||||
nsBidiLevel levelBefore = levels.mLevelBefore;
|
||||
nsBidiLevel levelAfter = levels.mLevelAfter;
|
||||
mozilla::intl::Bidi::EmbeddingLevel levelBefore = levels.mLevelBefore;
|
||||
mozilla::intl::Bidi::EmbeddingLevel levelAfter = levels.mLevelAfter;
|
||||
|
||||
nsBidiLevel currentCaretLevel = frameSelection->GetCaretBidiLevel();
|
||||
mozilla::intl::Bidi::EmbeddingLevel currentCaretLevel =
|
||||
frameSelection->GetCaretBidiLevel();
|
||||
|
||||
nsBidiLevel levelOfDeletion;
|
||||
mozilla::intl::Bidi::EmbeddingLevel levelOfDeletion;
|
||||
levelOfDeletion = (nsIEditor::eNext == aDirectionAndAmount ||
|
||||
nsIEditor::eNextWord == aDirectionAndAmount)
|
||||
? levelAfter
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#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
|
||||
|
@ -28,7 +29,6 @@
|
|||
#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.
|
||||
|
@ -1985,7 +1985,7 @@ class EditorBase : public nsIEditor,
|
|||
void MaybeUpdateCaretBidiLevel(const EditorBase& aEditorBase) const;
|
||||
|
||||
private:
|
||||
Maybe<nsBidiLevel> mNewCaretBidiLevel;
|
||||
Maybe<mozilla::intl::Bidi::EmbeddingLevel> mNewCaretBidiLevel;
|
||||
bool mFailed = false;
|
||||
bool mCanceled = false;
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <ostream>
|
||||
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
#include "mozilla/gfx/MacIOSurface.h"
|
||||
#include "mozilla/layers/NativeLayer.h"
|
||||
|
@ -119,6 +120,8 @@ class NativeLayerRootCA : public NativeLayerRoot {
|
|||
bool aIsOpaque) override;
|
||||
|
||||
void SetWindowIsFullscreen(bool aFullscreen);
|
||||
void NoteMouseMove();
|
||||
void NoteMouseMoveAtTime(const TimeStamp& aTime);
|
||||
|
||||
protected:
|
||||
explicit NativeLayerRootCA(CALayer* aLayer);
|
||||
|
@ -129,17 +132,20 @@ class NativeLayerRootCA : public NativeLayerRoot {
|
|||
~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;
|
||||
|
@ -163,6 +169,12 @@ class NativeLayerRootCA : public NativeLayerRoot {
|
|||
// 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;
|
||||
|
|
|
@ -134,7 +134,9 @@ static CALayer* MakeOffscreenRootCALayer() {
|
|||
NativeLayerRootCA::NativeLayerRootCA(CALayer* aLayer)
|
||||
: mMutex("NativeLayerRootCA"),
|
||||
mOnscreenRepresentation(aLayer),
|
||||
mOffscreenRepresentation(MakeOffscreenRootCALayer()) {}
|
||||
mOffscreenRepresentation(MakeOffscreenRootCALayer()) {
|
||||
NoteMouseMove();
|
||||
}
|
||||
|
||||
NativeLayerRootCA::~NativeLayerRootCA() {
|
||||
MOZ_RELEASE_ASSERT(mSublayers.IsEmpty(),
|
||||
|
@ -162,7 +164,7 @@ void NativeLayerRootCA::AppendLayer(NativeLayer* aLayer) {
|
|||
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) {
|
||||
|
@ -172,7 +174,7 @@ void NativeLayerRootCA::RemoveLayer(NativeLayer* aLayer) {
|
|||
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) {
|
||||
|
@ -195,7 +197,7 @@ void NativeLayerRootCA::SetLayers(const nsTArray<RefPtr<NativeLayer>>& aLayers)
|
|||
|
||||
if (layersCA != mSublayers) {
|
||||
mSublayers = std::move(layersCA);
|
||||
ForAllRepresentations([&](Representation& r) { r.mMutated = true; });
|
||||
ForAllRepresentations([&](Representation& r) { r.mMutatedLayerStructure = true; });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,7 +240,9 @@ bool NativeLayerRootCA::CommitToScreen() {
|
|||
return false;
|
||||
}
|
||||
|
||||
mOnscreenRepresentation.Commit(WhichRepresentation::ONSCREEN, mSublayers, mWindowIsFullscreen);
|
||||
UpdateMouseMovedRecently();
|
||||
mOnscreenRepresentation.Commit(WhichRepresentation::ONSCREEN, mSublayers, mWindowIsFullscreen,
|
||||
mMouseMovedRecently);
|
||||
|
||||
mCommitPending = false;
|
||||
}
|
||||
|
@ -286,7 +290,8 @@ void NativeLayerRootCA::OnNativeLayerRootSnapshotterDestroyed(
|
|||
|
||||
void NativeLayerRootCA::CommitOffscreen() {
|
||||
MutexAutoLock lock(mMutex);
|
||||
mOffscreenRepresentation.Commit(WhichRepresentation::OFFSCREEN, mSublayers, mWindowIsFullscreen);
|
||||
mOffscreenRepresentation.Commit(WhichRepresentation::OFFSCREEN, mSublayers, mWindowIsFullscreen,
|
||||
false);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
|
@ -299,7 +304,7 @@ 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;
|
||||
|
@ -311,8 +316,12 @@ NativeLayerRootCA::Representation::~Representation() {
|
|||
|
||||
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);
|
||||
})) {
|
||||
|
@ -329,7 +338,7 @@ void NativeLayerRootCA::Representation::Commit(WhichRepresentation aRepresentati
|
|||
layer->ApplyChanges(aRepresentation);
|
||||
}
|
||||
|
||||
if (mMutated) {
|
||||
if (mMutatedLayerStructure || mustRebuild) {
|
||||
NSMutableArray<CALayer*>* sublayers = [NSMutableArray arrayWithCapacity:aSublayers.Length()];
|
||||
for (auto layer : aSublayers) {
|
||||
[sublayers addObject:layer->UnderlyingCALayer(aRepresentation)];
|
||||
|
@ -341,8 +350,7 @@ void NativeLayerRootCA::Representation::Commit(WhichRepresentation aRepresentati
|
|||
// 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.
|
||||
|
@ -360,9 +368,10 @@ void NativeLayerRootCA::Representation::Commit(WhichRepresentation aRepresentati
|
|||
mRootCALayer.sublayers = @[ blackLayer, isolatedLayer ];
|
||||
}
|
||||
}
|
||||
|
||||
mMutated = false;
|
||||
}
|
||||
|
||||
mMutatedLayerStructure = false;
|
||||
mMutatedMouseMovedRecently = false;
|
||||
}
|
||||
|
||||
CALayer* NativeLayerRootCA::Representation::FindVideoLayerToIsolate(
|
||||
|
@ -480,11 +489,32 @@ void NativeLayerRootCA::SetWindowIsFullscreen(bool 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)
|
||||
|
|
|
@ -597,7 +597,6 @@ struct DIGroup {
|
|||
return;
|
||||
}
|
||||
|
||||
gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8;
|
||||
std::vector<RefPtr<ScaledFont>> fonts;
|
||||
bool validFonts = true;
|
||||
RefPtr<WebRenderDrawEventRecorder> recorder =
|
||||
|
@ -620,8 +619,8 @@ struct DIGroup {
|
|||
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());
|
||||
|
@ -1092,15 +1091,14 @@ static bool IsItemProbablyActive(
|
|||
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,
|
||||
|
|
|
@ -169,8 +169,8 @@ Maybe<BlobImageKeyData> SourceSurfaceBlobImage::RecordDrawing(
|
|||
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);
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#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"
|
||||
|
@ -537,4 +538,59 @@ TEST(IntlDateTimeFormat, GetAvailableLocales)
|
|||
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
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
# 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",
|
||||
|
|
|
@ -4,10 +4,13 @@
|
|||
# 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",
|
||||
|
@ -27,10 +30,13 @@ EXPORTS.mozilla.intl = [
|
|||
]
|
||||
|
||||
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",
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -121,14 +121,10 @@ class Calendar final {
|
|||
|
||||
~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; }
|
||||
|
||||
UCalendar* mCalendar = nullptr;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -9,6 +9,7 @@
|
|||
#include "unicode/udatpg.h"
|
||||
#include "unicode/ures.h"
|
||||
|
||||
#include "DateTimeFormatUtils.h"
|
||||
#include "ScopedICUObject.h"
|
||||
|
||||
#include "mozilla/EnumSet.h"
|
||||
|
@ -1109,4 +1110,62 @@ const char* DateTimeFormat::ToString(DateTimeFormat::HourCycle aHourCycle) {
|
|||
}
|
||||
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
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#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"
|
||||
|
@ -353,6 +355,44 @@ class DateTimeFormat final {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -423,13 +463,6 @@ class DateTimeFormat final {
|
|||
|
||||
~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.
|
||||
|
@ -474,6 +507,9 @@ class DateTimeFormat final {
|
|||
|
||||
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|.
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -51,25 +51,6 @@ enum nsCharType {
|
|||
*/
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -96,9 +96,9 @@ 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
|
||||
|
|
|
@ -351,6 +351,24 @@ static mozilla::intl::Collator* NewIntlCollator(
|
|||
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) {
|
||||
|
@ -389,16 +407,9 @@ bool js::intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp) {
|
|||
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.
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#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"
|
||||
|
@ -148,17 +147,3 @@ 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));
|
||||
}
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
#include "unicode/utypes.h"
|
||||
#include "vm/StringType.h"
|
||||
|
||||
struct UFormattedValue;
|
||||
|
||||
namespace mozilla::intl {
|
||||
enum class ICUError : uint8_t;
|
||||
}
|
||||
|
@ -165,10 +163,6 @@ static JSString* CallICU(JSContext* cx, const ICUStringFunction& strFn) {
|
|||
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
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
#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"
|
||||
|
@ -34,12 +36,6 @@
|
|||
#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"
|
||||
|
@ -195,7 +191,8 @@ void js::DateTimeFormatObject::finalize(JSFreeOp* fop, JSObject* obj) {
|
|||
|
||||
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(
|
||||
|
@ -208,7 +205,7 @@ void js::DateTimeFormatObject::finalize(JSFreeOp* fop, JSObject* obj) {
|
|||
intl::RemoveICUCellMemory(
|
||||
fop, obj, DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse);
|
||||
|
||||
udtitvfmt_close(dif);
|
||||
delete dif;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -988,6 +985,25 @@ static mozilla::intl::DateTimeFormat* NewDateTimeFormat(
|
|||
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,
|
||||
|
@ -1019,17 +1035,10 @@ bool js::intl_resolveDateTimeFormatComponents(JSContext* cx, unsigned argc,
|
|||
|
||||
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();
|
||||
|
@ -1135,133 +1144,79 @@ static bool intl_FormatDateTime(JSContext* cx,
|
|||
|
||||
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:
|
||||
return &JSAtomState::month;
|
||||
|
||||
case UDAT_DATE_FIELD:
|
||||
case UDAT_JULIAN_DAY_FIELD:
|
||||
return &JSAtomState::day;
|
||||
|
||||
case UDAT_HOUR_OF_DAY1_FIELD:
|
||||
case UDAT_HOUR_OF_DAY0_FIELD:
|
||||
case UDAT_HOUR1_FIELD:
|
||||
case UDAT_HOUR0_FIELD:
|
||||
return &JSAtomState::hour;
|
||||
|
||||
case UDAT_MINUTE_FIELD:
|
||||
return &JSAtomState::minute;
|
||||
|
||||
case UDAT_SECOND_FIELD:
|
||||
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:
|
||||
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:
|
||||
return &JSAtomState::dayPeriod;
|
||||
|
||||
#ifndef U_HIDE_INTERNAL_API
|
||||
case UDAT_RELATED_YEAR_FIELD:
|
||||
case mozilla::intl::DateTimePartType::RelatedYear:
|
||||
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::Month:
|
||||
return &JSAtomState::month;
|
||||
case mozilla::intl::DateTimePartType::Day:
|
||||
return &JSAtomState::day;
|
||||
case mozilla::intl::DateTimePartType::Hour:
|
||||
return &JSAtomState::hour;
|
||||
case mozilla::intl::DateTimePartType::Minute:
|
||||
return &JSAtomState::minute;
|
||||
case mozilla::intl::DateTimePartType::Second:
|
||||
return &JSAtomState::second;
|
||||
case mozilla::intl::DateTimePartType::Weekday:
|
||||
return &JSAtomState::weekday;
|
||||
case mozilla::intl::DateTimePartType::DayPeriod:
|
||||
return &JSAtomState::dayPeriod;
|
||||
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);
|
||||
|
||||
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);
|
||||
});
|
||||
MOZ_CRASH(
|
||||
"unenumerated, undocumented format field returned "
|
||||
"by iterator");
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -1269,92 +1224,69 @@ static bool intl_FormatToPartsDateTime(JSContext* cx,
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append any final literal.
|
||||
if (lastEndIndex < overallResult->length()) {
|
||||
if (!AppendPart(&JSAtomState::literal, lastEndIndex,
|
||||
overallResult->length())) {
|
||||
return false;
|
||||
}
|
||||
beginIndex = part.mEndIndex;
|
||||
partsArray->initDenseElement(index++, ObjectValue(*singlePart));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -1375,31 +1307,23 @@ bool js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp) {
|
|||
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);
|
||||
|
@ -1443,25 +1367,47 @@ static UDateIntervalFormat* NewUDateIntervalFormat(
|
|||
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());
|
||||
|
@ -1480,7 +1426,7 @@ static const UFormattedValue* PartitionDateTimeRangePattern(
|
|||
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
|
||||
|
@ -1488,70 +1434,28 @@ static const UFormattedValue* PartitionDateTimeRangePattern(
|
|||
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;
|
||||
result =
|
||||
dif->TryFormatDateTime(x.toDouble(), y.toDouble(), formatted, equal);
|
||||
}
|
||||
|
||||
const UFormattedValue* formattedValue =
|
||||
udtitvfmt_resultAsValue(formatted, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1560,26 +1464,17 @@ static bool DateFieldsPracticallyEqual(JSContext* cx,
|
|||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1588,7 +1483,12 @@ static bool FormatDateTimeRange(JSContext* cx,
|
|||
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;
|
||||
}
|
||||
|
@ -1600,205 +1500,41 @@ static bool FormatDateTimeRange(JSContext* cx,
|
|||
/**
|
||||
* 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) {
|
||||
mozilla::intl::DateTimePartVector parts;
|
||||
auto r = dif->TryFormattedToParts(formatted, parts);
|
||||
if (r.isErr()) {
|
||||
intl::ReportInternalError(cx, r.unwrapErr());
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
|
||||
if (!partsArray) {
|
||||
auto spanResult = formatted.ToSpan();
|
||||
if (spanResult.isErr()) {
|
||||
intl::ReportInternalError(cx, spanResult.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);
|
||||
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) {
|
||||
|
@ -1836,34 +1572,19 @@ bool js::intl_FormatDateTimeRange(JSContext* cx, unsigned argc, Value* vp) {
|
|||
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());
|
||||
|
|
|
@ -13,11 +13,10 @@
|
|||
#include "js/RootingAPI.h"
|
||||
#include "vm/NativeObject.h"
|
||||
|
||||
struct UDateIntervalFormat;
|
||||
|
||||
namespace mozilla::intl {
|
||||
class DateTimeFormat;
|
||||
}
|
||||
class DateIntervalFormat;
|
||||
} // namespace mozilla::intl
|
||||
|
||||
namespace js {
|
||||
|
||||
|
@ -27,8 +26,8 @@ class DateTimeFormatObject : public NativeObject {
|
|||
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,
|
||||
|
@ -42,7 +41,7 @@ class DateTimeFormatObject : public NativeObject {
|
|||
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;
|
||||
}
|
||||
|
@ -50,19 +49,20 @@ class DateTimeFormatObject : public NativeObject {
|
|||
}
|
||||
|
||||
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:
|
||||
|
|
|
@ -210,6 +210,24 @@ static mozilla::intl::ListFormat* NewListFormat(
|
|||
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 )
|
||||
*/
|
||||
|
@ -300,16 +318,9 @@ bool js::intl_FormatList(JSContext* cx, unsigned argc, Value* vp) {
|
|||
|
||||
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.
|
||||
|
|
|
@ -778,6 +778,44 @@ static Formatter* NewNumberFormat(JSContext* cx,
|
|||
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) {
|
||||
|
@ -1126,17 +1164,9 @@ bool js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp) {
|
|||
}
|
||||
#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
|
||||
|
@ -1466,18 +1496,10 @@ bool js::intl_FormatNumberRange(JSContext* cx, unsigned argc, Value* vp) {
|
|||
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) {
|
||||
|
|
|
@ -291,6 +291,25 @@ static mozilla::intl::PluralRules* NewPluralRules(
|
|||
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);
|
||||
|
@ -300,18 +319,10 @@ bool js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) {
|
|||
|
||||
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);
|
||||
|
@ -345,18 +356,10 @@ bool js::intl_SelectPluralRuleRange(JSContext* cx, unsigned argc, Value* vp) {
|
|||
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);
|
||||
|
@ -382,18 +385,10 @@ bool js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp) {
|
|||
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();
|
||||
|
|
|
@ -267,6 +267,26 @@ static mozilla::intl::RelativeTimeFormat* NewRelativeTimeFormatter(
|
|||
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);
|
||||
|
@ -289,18 +309,10 @@ bool js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp) {
|
|||
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;
|
||||
|
|
|
@ -4091,9 +4091,7 @@ JSObject* JS::InstantiateModuleStencil(JSContext* cx,
|
|||
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);
|
||||
|
|
|
@ -213,7 +213,7 @@ BEGIN_TEST(testStencil_Transcode) {
|
|||
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());
|
||||
|
||||
|
@ -291,7 +291,7 @@ BEGIN_TEST(testStencil_TranscodeBorrowing) {
|
|||
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());
|
||||
}
|
||||
|
|
|
@ -2245,7 +2245,7 @@ static bool StartIncrementalEncoding(JSContext* cx,
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!source->startIncrementalEncoding(cx, options, std::move(initial))) {
|
||||
if (!source->startIncrementalEncoding(cx, std::move(initial))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ static JSScript* CompileSourceBufferAndStartIncrementalEncoding(
|
|||
}
|
||||
}
|
||||
|
||||
if (!script->scriptSource()->startIncrementalEncoding(cx, options,
|
||||
if (!script->scriptSource()->startIncrementalEncoding(cx,
|
||||
std::move(stencil))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -2220,13 +2220,12 @@ JSScript* GlobalHelperThreadState::finishSingleParseTask(
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1787,7 +1787,7 @@ void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
|
|||
}
|
||||
|
||||
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()) {
|
||||
|
@ -1808,7 +1808,7 @@ bool ScriptSource::startIncrementalEncoding(
|
|||
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
|
||||
|
|
|
@ -1029,7 +1029,7 @@ class ScriptSource {
|
|||
bool hasEncoder() const { return bool(xdrEncoder_); }
|
||||
|
||||
[[nodiscard]] bool startIncrementalEncoding(
|
||||
JSContext* cx, const JS::ReadOnlyCompileOptions& options,
|
||||
JSContext* cx,
|
||||
UniquePtr<frontend::ExtensibleCompilationStencil>&& initial);
|
||||
|
||||
[[nodiscard]] bool addDelazificationToIncrementalEncoding(
|
||||
|
|
|
@ -291,7 +291,7 @@ XDRIncrementalStencilEncoder::~XDRIncrementalStencilEncoder() {
|
|||
}
|
||||
|
||||
XDRResult XDRIncrementalStencilEncoder::setInitial(
|
||||
JSContext* cx, const JS::ReadOnlyCompileOptions& options,
|
||||
JSContext* cx,
|
||||
UniquePtr<frontend::ExtensibleCompilationStencil>&& initial) {
|
||||
MOZ_TRY(frontend::StencilXDR::checkCompilationStencil(*initial));
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче