Update On Tue Jan 21 19:51:51 CET 2025

This commit is contained in:
github-action[bot] 2025-01-21 19:51:52 +01:00
parent e049474a96
commit 490a5a2db1
2029 changed files with 22345 additions and 158165 deletions

113
Cargo.lock generated
View file

@ -51,7 +51,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce34de545ad29bcc00cb1b87a94c132256dcf83aa7eeb9674482568405a6ff0a"
dependencies = [
"alsa-sys",
"bitflags 2.6.0",
"bitflags 2.7.0",
"libc",
"nix 0.26.99",
]
@ -451,7 +451,7 @@ dependencies = [
name = "bindgen"
version = "0.69.4"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"cexpr",
"clang-sys",
"itertools",
@ -484,14 +484,14 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
name = "bitflags"
version = "1.999.999"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
]
[[package]]
name = "bitflags"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
dependencies = [
"serde",
]
@ -1278,7 +1278,7 @@ source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=2407441a2f67341a
dependencies = [
"atomic",
"audio-mixer",
"bitflags 2.6.0",
"bitflags 2.7.0",
"coreaudio-sys-utils",
"cubeb-backend",
"float-cmp",
@ -1608,7 +1608,7 @@ dependencies = [
name = "dom"
version = "0.1.0"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"malloc_size_of",
]
@ -2662,7 +2662,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"gpu-alloc-types",
]
@ -2672,7 +2672,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
]
[[package]]
@ -2693,7 +2693,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"gpu-descriptor-types",
"hashbrown 0.14.5",
]
@ -2704,7 +2704,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
]
[[package]]
@ -3858,10 +3858,9 @@ dependencies = [
[[package]]
name = "metal"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3572083504c43e14aec05447f8a3d57cce0f66d7a3c1b9058572eca4d70ab9"
source = "git+https://github.com/gfx-rs/metal-rs.git?rev=ef768ff9d7#ef768ff9d742ae6a0f4e83ddc8031264e7d460c4"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"block",
"core-graphics-types",
"foreign-types",
@ -3971,7 +3970,7 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cd8a9fb054833d2f402e82e256aeef544e595e45fe8fca2de6d03ed605f6647"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"debugid",
"num-derive",
"num-traits",
@ -4004,7 +4003,7 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c75ff36a030d76801ed7ec3ea4ae45f12c0f1297f3447790288194274e9aa98"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"byteorder",
"cfg-if",
"crash-context",
@ -4299,7 +4298,7 @@ dependencies = [
"allocator-api2",
"arrayvec",
"bindgen 0.69.4",
"bitflags 2.6.0",
"bitflags 2.7.0",
"byteorder",
"bytes",
"cc",
@ -4471,11 +4470,11 @@ checksum = "a2983372caf4480544083767bf2d27defafe32af49ab4df3a0b7fc90793a3664"
[[package]]
name = "naga"
version = "23.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6#15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6"
source = "git+https://github.com/gfx-rs/wgpu?rev=aa7bec65b90028e4db6ec8def8589b52097d92f9#aa7bec65b90028e4db6ec8def8589b52097d92f9"
dependencies = [
"arrayvec",
"bit-set",
"bitflags 2.6.0",
"bitflags 2.7.0",
"cfg_aliases",
"codespan-reporting",
"hexf-parse",
@ -4484,6 +4483,7 @@ dependencies = [
"rustc-hash",
"serde",
"spirv",
"strum",
"termcolor",
"thiserror 2.0.9",
"unicode-xid",
@ -4642,7 +4642,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"cfg-if",
"cfg_aliases",
"libc",
@ -4698,7 +4698,7 @@ dependencies = [
name = "nsstring"
version = "0.1.0"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"encoding_rs",
]
@ -4918,7 +4918,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ec3b43050c38ffb9de87e17d874e9956e3a9131b343c9b7b7002597727c3891"
dependencies = [
"arrayvec",
"bitflags 2.6.0",
"bitflags 2.7.0",
"thiserror 1.999.999",
"zerocopy",
"zerocopy-derive",
@ -5160,7 +5160,7 @@ version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"hex",
]
@ -5217,7 +5217,7 @@ name = "pulse"
version = "0.3.0"
source = "git+https://github.com/mozilla/cubeb-pulse-rs?rev=8678dcab1c287de79c4c184ccc2e065bc62b70e2#8678dcab1c287de79c4c184ccc2e065bc62b70e2"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"pulse-ffi",
]
@ -5477,7 +5477,7 @@ checksum = "2c6d906922d99c677624d2042a93f89b2b7df0f6411032237d5d99a602c2487c"
dependencies = [
"arrayref",
"bincode",
"bitflags 2.6.0",
"bitflags 2.7.0",
"byteorder",
"id-arena",
"lazy_static",
@ -5521,7 +5521,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
dependencies = [
"base64 0.21.3",
"bitflags 2.6.0",
"bitflags 2.7.0",
"serde",
"serde_derive",
]
@ -5564,7 +5564,7 @@ version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
@ -5638,13 +5638,19 @@ version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rustversion"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
[[package]]
name = "ryu"
version = "1.0.12"
@ -5702,7 +5708,7 @@ dependencies = [
name = "selectors"
version = "0.26.0"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"cssparser",
"derive_more 0.99.999",
"fxhash",
@ -5992,7 +5998,7 @@ version = "0.3.0+sdk-1.3.268.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
]
[[package]]
@ -6072,6 +6078,28 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "style"
version = "0.0.1"
@ -6080,7 +6108,7 @@ dependencies = [
"arrayvec",
"atomic_refcell",
"bindgen 0.69.4",
"bitflags 2.6.0",
"bitflags 2.7.0",
"byteorder",
"cssparser",
"derive_more 0.99.999",
@ -6144,7 +6172,7 @@ name = "style_traits"
version = "0.0.1"
dependencies = [
"app_units",
"bitflags 2.6.0",
"bitflags 2.7.0",
"cssparser",
"euclid",
"lazy_static",
@ -7110,7 +7138,7 @@ version = "0.219.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c771866898879073c53b565a6c7b49953795159836714ac56a5befb581227c5"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"indexmap 2.2.6",
]
@ -7182,7 +7210,7 @@ version = "0.62.0"
dependencies = [
"allocator-api2",
"bincode",
"bitflags 2.6.0",
"bitflags 2.7.0",
"build-parallel",
"byteorder",
"derive_more 0.99.999",
@ -7219,7 +7247,7 @@ name = "webrender_api"
version = "0.62.0"
dependencies = [
"app_units",
"bitflags 2.6.0",
"bitflags 2.7.0",
"byteorder",
"crossbeam-channel",
"euclid",
@ -7267,7 +7295,7 @@ dependencies = [
name = "webrender_build"
version = "0.0.2"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"lazy_static",
"serde",
]
@ -7294,11 +7322,11 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "23.0.1"
source = "git+https://github.com/gfx-rs/wgpu?rev=15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6#15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6"
source = "git+https://github.com/gfx-rs/wgpu?rev=aa7bec65b90028e4db6ec8def8589b52097d92f9#aa7bec65b90028e4db6ec8def8589b52097d92f9"
dependencies = [
"arrayvec",
"bit-vec",
"bitflags 2.6.0",
"bitflags 2.7.0",
"cfg_aliases",
"document-features",
"indexmap 2.2.6",
@ -7319,13 +7347,13 @@ dependencies = [
[[package]]
name = "wgpu-hal"
version = "23.0.1"
source = "git+https://github.com/gfx-rs/wgpu?rev=15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6#15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6"
source = "git+https://github.com/gfx-rs/wgpu?rev=aa7bec65b90028e4db6ec8def8589b52097d92f9#aa7bec65b90028e4db6ec8def8589b52097d92f9"
dependencies = [
"android_system_properties",
"arrayvec",
"ash",
"bit-set",
"bitflags 2.6.0",
"bitflags 2.7.0",
"block",
"cfg_aliases",
"core-graphics-types",
@ -7341,6 +7369,7 @@ dependencies = [
"naga",
"objc",
"once_cell",
"ordered-float",
"parking_lot",
"profiling",
"range-alloc",
@ -7358,9 +7387,9 @@ dependencies = [
[[package]]
name = "wgpu-types"
version = "23.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6#15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6"
source = "git+https://github.com/gfx-rs/wgpu?rev=aa7bec65b90028e4db6ec8def8589b52097d92f9#aa7bec65b90028e4db6ec8def8589b52097d92f9"
dependencies = [
"bitflags 2.6.0",
"bitflags 2.7.0",
"js-sys",
"serde",
"web-sys",

View file

@ -21,6 +21,7 @@ Please note that some targeting attributes require stricter controls on the tele
* [canCreateSelectableProfiles](#cancreateselectableprofiles)
* [creditCardsSaved](#creditcardssaved)
* [currentDate](#currentdate)
* [currentTabGroups](#currentTabGroups)
* [defaultPDFHandler](#defaultpdfhandler)
* [devToolsOpenedCount](#devtoolsopenedcount)
* [distributionId](#distributionid)
@ -67,6 +68,7 @@ Please note that some targeting attributes require stricter controls on the tele
* [providerCohorts](#providercohorts)
* [recentBookmarks](#recentbookmarks)
* [region](#region)
* [savedTabGroups](#savedtabgroups)
* [screenImpressions](#screenimpressions)
* [searchEngines](#searchengines)
* [sync](#sync)
@ -620,6 +622,13 @@ Pref used by system administrators to disallow add-ons from installed altogether
```ts
declare const xpinstallEnabled: boolean;
```
### `currentTabGroups`
Returns the number of currently open tab groups.
### `savedTabGroups`
Returns the number of tab groups the user has saved.
### `hasPinnedTabs`

View file

@ -53,6 +53,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
// eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
SelectableProfileService:
"resource:///modules/profiles/SelectableProfileService.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
TargetingContext: "resource://messaging-system/targeting/Targeting.sys.mjs",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
TelemetrySession: "resource://gre/modules/TelemetrySession.sys.mjs",
@ -748,6 +749,18 @@ const TargetingGetters = {
get needsUpdate() {
return QueryCache.queries.CheckBrowserNeedsUpdate.get();
},
get savedTabGroups() {
return lazy.SessionStore.getSavedTabGroups().length;
},
get currentTabGroups() {
let win = lazy.BrowserWindowTracker.getTopWindow();
// If there's no window, there can't be any current tab groups.
if (!win) {
return 0;
}
let totalTabGroups = win.gBrowser.getAllTabGroups().length;
return totalTabGroups;
},
get hasPinnedTabs() {
for (let win of Services.wm.getEnumerator("navigator:browser")) {
if (win.closed || !win.ownerGlobal.gBrowser) {

View file

@ -865,6 +865,104 @@ export const ASRouterTriggerListeners = new Map([
},
},
],
[
"tabGroupCreated",
{
id: "tabGroupCreated",
_initialized: false,
_triggerHandler: null,
// Number of tab groups the user created this session
_tabGroupsCreated: 0,
init(triggerHandler) {
this._triggerHandler = triggerHandler;
if (!this._initialized) {
lazy.EveryWindow.registerCallback(
this.id,
win => {
win.addEventListener("TabGroupCreateDone", this);
},
win => {
win.removeEventListener("TabGroupCreateDone", this);
}
);
this._initialized = true;
}
},
handleEvent(event) {
if (this._initialized) {
if (!event.target.ownerGlobal.gBrowser) {
return;
}
const { gBrowser } = event.target.ownerGlobal;
this._tabGroupsCreated++;
this._triggerHandler(gBrowser.selectedBrowser, {
id: this.id,
context: {
tabGroupsCreatedCount: this._tabGroupsCreated,
},
});
}
},
uninit() {
if (this._initialized) {
lazy.EveryWindow.unregisterCallback(this.id);
this._initialized = false;
this._triggerHandler = null;
this._tabGroupsCreated = 0;
}
},
},
],
[
"tabGroupClosed",
{
id: "tabGroupClosed",
_initialized: false,
_triggerHandler: null,
// Number of tab groups the user closed this session
_tabGroupsClosed: 0,
init(triggerHandler) {
this._triggerHandler = triggerHandler;
if (!this._initialized) {
lazy.EveryWindow.registerCallback(
this.id,
win => {
win.addEventListener("TabGroupRemoved", this);
},
win => {
win.removeEventListener("TabGroupRemoved", this);
}
);
this._initialized = true;
}
},
handleEvent(event) {
if (this._initialized) {
if (!event.target.ownerGlobal.gBrowser) {
return;
}
const { gBrowser } = event.target.ownerGlobal;
this._tabGroupsClosed++;
this._triggerHandler(gBrowser.selectedBrowser, {
id: this.id,
context: {
tabGroupsClosedCount: this._tabGroupsClosed,
},
});
}
},
uninit() {
if (this._initialized) {
lazy.EveryWindow.unregisterCallback(this.id);
this._initialized = false;
this._triggerHandler = null;
this._tabGroupsClosed = 0;
}
},
},
],
[
"activityAfterIdle",
{

View file

@ -19,6 +19,61 @@ const isMSIX =
Services.sysinfo.getProperty("hasWinPackageId", false);
const MESSAGES = () => [
{
id: "CLOSE_TAB_GROUP_TEST_CALLOUT",
template: "feature_callout",
groups: ["cfr"],
content: {
id: "CLOSE_TAB_GROUP_TEST_CALLOUT",
template: "multistage",
backdrop: "transparent",
transitions: false,
screens: [
{
id: "CLOSE_TAB_GROUP_TEST_CALLOUT",
anchors: [
{
selector: "#alltabs-button",
panel_position: {
anchor_attachment: "bottomcenter",
callout_attachment: "topright",
},
},
],
content: {
position: "callout",
padding: 16,
width: "412px",
title_logo: {
imageURL:
"chrome://browser/content/asrouter/assets/smiling-fox-icon.svg",
width: "24px",
height: "24px",
marginInline: "0 16px",
},
title: {
raw: "If you close a tab group, you can reopen it here anytime.",
},
primary_button: {
label: {
raw: "Got it",
},
action: {
dismiss: true,
},
},
},
},
],
},
targeting: "tabGroupsClosedCount == 1",
trigger: {
id: "tabGroupClosed",
},
frequency: {
lifetime: 1,
},
},
{
id: "CONTENT_TILES_TEST",
targeting: 'providerCohorts.panel_local_testing == "SHOW_TEST"',

View file

@ -23,7 +23,7 @@ add_task(async function test_PanelTestProvider() {
milestone_message: 0,
update_action: 1,
spotlight: 6,
feature_callout: 4,
feature_callout: 5,
pb_newtab: 2,
toast_notification: 3,
bookmarks_bar_button: 1,

View file

@ -28,14 +28,31 @@ XPCOMUtils.defineLazyScriptGetter(
/* End Shared Places Import */
var gCumulativeSearches = 0;
function init() {
window.addEventListener("load", () => {
let uidensity = window.top.document.documentElement.getAttribute("uidensity");
if (uidensity) {
document.documentElement.setAttribute("uidensity", uidensity);
}
document.getElementById("bookmarks-view").place =
let view = document.getElementById("bookmarks-view");
view.place =
"place:type=" + Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY;
view.addEventListener("keypress", event =>
PlacesUIUtils.onSidebarTreeKeyPress(event)
);
view.addEventListener("click", event =>
PlacesUIUtils.onSidebarTreeClick(event)
);
view.addEventListener("mousemove", event =>
PlacesUIUtils.onSidebarTreeMouseMove(event)
);
view.addEventListener("mouseout", () =>
PlacesUIUtils.setMouseoverURL("", window)
);
document
.getElementById("search-box")
.addEventListener("command", searchBookmarks);
let bhTooltip = document.getElementById("bhTooltip");
bhTooltip.addEventListener("popupshowing", event => {
@ -44,17 +61,19 @@ function init() {
bhTooltip.addEventListener("popuphiding", () =>
bhTooltip.removeAttribute("position")
);
}
});
function searchBookmarks(event) {
let { value } = event.currentTarget;
function searchBookmarks(aSearchString) {
var tree = document.getElementById("bookmarks-view");
if (!aSearchString) {
if (!value) {
// eslint-disable-next-line no-self-assign
tree.place = tree.place;
} else {
Glean.sidebar.search.bookmarks.add(1);
gCumulativeSearches++;
tree.applyFilter(aSearchString, PlacesUtils.bookmarks.userContentRoots);
tree.applyFilter(value, PlacesUtils.bookmarks.userContentRoots);
}
}
@ -72,10 +91,10 @@ function clearCumulativeCounter() {
gCumulativeSearches = 0;
}
function unloadBookmarksSidebar() {
window.addEventListener("unload", () => {
clearCumulativeCounter();
PlacesUIUtils.setMouseoverURL("", window);
}
});
window.addEventListener("SidebarFocused", () =>
document.getElementById("search-box").focus()

View file

@ -9,9 +9,8 @@
class="sidebar-panel"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="init();"
onunload="unloadBookmarksSidebar();"
data-l10n-id="bookmarks-sidebar-content">
data-l10n-id="bookmarks-sidebar-content"
csp="default-src chrome:; style-src-elem chrome: 'unsafe-inline';">
<script src="chrome://browser/content/places/bookmarksSidebar.js"/>
<script src="chrome://global/content/globalOverlay.js"/>
@ -52,8 +51,7 @@
<search-textbox id="search-box" flex="1"
data-l10n-id="places-bookmarks-search"
data-l10n-attrs="placeholder"
aria-controls="bookmarks-view"
oncommand="searchBookmarks(this.value);"/>
aria-controls="bookmarks-view"/>
</hbox>
<tree id="bookmarks-view"
@ -62,11 +60,7 @@
flex="1"
hidecolumnpicker="true"
context="placesContext"
singleclickopens="true"
onkeypress="PlacesUIUtils.onSidebarTreeKeyPress(event);"
onclick="PlacesUIUtils.onSidebarTreeClick(event);"
onmousemove="PlacesUIUtils.onSidebarTreeMouseMove(event);"
onmouseout="PlacesUIUtils.setMouseoverURL('', window);">
singleclickopens="true">
<treecols>
<treecol id="title" flex="1" primary="true" hideheader="true"/>
</treecols>

View file

@ -72,6 +72,7 @@
this.addEventListener("mouseleave", this);
this.addEventListener("focusin", this);
this.addEventListener("focusout", this);
this.addEventListener("contextmenu", this);
}
init() {
@ -1486,6 +1487,23 @@
?.removeAttribute("rolluponmousewheel");
}
on_contextmenu(event) {
// When pressing the context menu key (as opposed to right-clicking)
// while a tab group label has aria focus (as opposed to DOM focus),
// open the tab group context menu as if the label had DOM focus.
// The button property is used to differentiate between key and mouse.
if (
event.button == 0 &&
this.ariaFocusedItem &&
isTabGroupLabel(this.ariaFocusedItem)
) {
gBrowser.tabGroupMenu.openEditModal(
this.ariaFocusedItem.closest("tab-group")
);
event.preventDefault();
}
}
get emptyTabTitle() {
// Normal tab title is used also in the permanent private browsing mode.
const l10nId =

View file

@ -133,6 +133,32 @@ add_task(async function test_TabGroupKeyboardFocus() {
await EventUtils.synthesizeKey("KEY_Enter");
Assert.ok(tabGroup.collapsed, "Tab group should be collapsed once again");
let editor = document.getElementById("tab-group-editor");
let panelShown = BrowserTestUtils.waitForPopupEvent(editor.panel, "shown");
//XXX Should simulate a context menu event, but this doesn't seem to work:
//await EventUtils.synthesizeKey("VK_CONTEXT_MENU");
gBrowser.tabContainer.on_contextmenu({ button: 0, preventDefault: () => {} });
info("Waiting for the context menu key to open the group context menu");
await panelShown;
is(
editor.panel.state,
"open",
"Tab group context menu should be open after hitting context menu key"
);
is(
gBrowser.tabContainer.ariaFocusedItem,
tabGroup.labelElement,
"Keyboard focus should remain on tab group label while group context menu is open"
);
let panelHidden = BrowserTestUtils.waitForPopupEvent(editor.panel, "hidden");
EventUtils.synthesizeKey("VK_ESCAPE");
await panelHidden;
is(
gBrowser.tabContainer.ariaFocusedItem,
tabGroup.labelElement,
"Keyboard focus should remain on tab group label after closing the group menu context menu"
);
info("Validate that keyboard focus skips over tabs in collapsed tab groups");
await synthesizeKeyToChangeKeyboardFocus(tab4, "KEY_ArrowRight");
is(

View file

@ -322,6 +322,17 @@ async function testSuggestion(
add_task(async function test_MLSuggest() {
const { cleanup, remoteClients } = await setup();
const originalIntentOptions = MLSuggest.INTENT_OPTIONS;
const originalNerOptions = MLSuggest.NER_OPTIONS;
// Stubs to indicate that wasm is being mocked
sinon.stub(MLSuggest, "INTENT_OPTIONS").get(() => {
return { ...originalIntentOptions, modelId: "test-echo" };
});
sinon.stub(MLSuggest, "NER_OPTIONS").get(() => {
return { ...originalNerOptions, modelId: "test-echo" };
});
await MLSuggest.initialize();
await testSuggestion(
"restaurants in seattle, wa",

View file

@ -116,10 +116,6 @@ add_task(async function () {
let IPs = [
"192.168.1.1",
"[::]",
"[::1]",
"[1::]",
"[::]",
"[::1]",
"[1::]",
"[1:2:3:4:5:6:7::]",

View file

@ -43,6 +43,12 @@ async function setup() {
add_setup(async function () {
const { cleanup, remoteClients } = await setup();
const originalConfig = MLAutofill.readConfig();
sinon.stub(MLAutofill, "readConfig").callsFake(() => {
return { ...originalConfig, modelId: "test-echo" };
});
sinon.stub(MLAutofill, "run").callsFake(request => {
const context = request.args[0];
/* The input has the following format:

View file

@ -16,7 +16,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"af": {
"pin": false,
@ -35,7 +35,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"an": {
"pin": false,
@ -54,7 +54,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ar": {
"pin": false,
@ -73,7 +73,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ast": {
"pin": false,
@ -92,7 +92,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"az": {
"pin": false,
@ -111,7 +111,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"be": {
"pin": false,
@ -130,7 +130,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"bg": {
"pin": false,
@ -149,7 +149,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"bn": {
"pin": false,
@ -168,7 +168,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"bo": {
"pin": false,
@ -187,7 +187,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"br": {
"pin": false,
@ -206,7 +206,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"brx": {
"pin": false,
@ -225,7 +225,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"bs": {
"pin": false,
@ -244,7 +244,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ca": {
"pin": false,
@ -263,7 +263,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ca-valencia": {
"pin": false,
@ -282,7 +282,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"cak": {
"pin": false,
@ -301,7 +301,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ckb": {
"pin": false,
@ -320,7 +320,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"cs": {
"pin": false,
@ -339,7 +339,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"cy": {
"pin": false,
@ -358,7 +358,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"da": {
"pin": false,
@ -377,7 +377,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"de": {
"pin": false,
@ -396,7 +396,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"dsb": {
"pin": false,
@ -415,7 +415,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"el": {
"pin": false,
@ -434,7 +434,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"en-CA": {
"pin": false,
@ -453,7 +453,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"en-GB": {
"pin": false,
@ -472,7 +472,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"eo": {
"pin": false,
@ -491,7 +491,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"es-AR": {
"pin": false,
@ -510,7 +510,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"es-CL": {
"pin": false,
@ -529,7 +529,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"es-ES": {
"pin": false,
@ -548,7 +548,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"es-MX": {
"pin": false,
@ -567,7 +567,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"et": {
"pin": false,
@ -586,7 +586,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"eu": {
"pin": false,
@ -605,7 +605,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"fa": {
"pin": false,
@ -624,7 +624,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ff": {
"pin": false,
@ -643,7 +643,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"fi": {
"pin": false,
@ -662,7 +662,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"fr": {
"pin": false,
@ -681,7 +681,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"fur": {
"pin": false,
@ -700,7 +700,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"fy-NL": {
"pin": false,
@ -719,7 +719,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ga-IE": {
"pin": false,
@ -738,7 +738,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"gd": {
"pin": false,
@ -757,7 +757,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"gl": {
"pin": false,
@ -776,7 +776,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"gn": {
"pin": false,
@ -795,7 +795,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"gu-IN": {
"pin": false,
@ -814,7 +814,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"he": {
"pin": false,
@ -833,7 +833,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"hi-IN": {
"pin": false,
@ -852,7 +852,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"hr": {
"pin": false,
@ -871,7 +871,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"hsb": {
"pin": false,
@ -890,7 +890,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"hu": {
"pin": false,
@ -909,7 +909,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"hy-AM": {
"pin": false,
@ -928,7 +928,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"hye": {
"pin": false,
@ -947,7 +947,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ia": {
"pin": false,
@ -966,7 +966,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"id": {
"pin": false,
@ -985,7 +985,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"is": {
"pin": false,
@ -1004,7 +1004,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"it": {
"pin": false,
@ -1023,7 +1023,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ja": {
"pin": false,
@ -1040,7 +1040,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ja-JP-mac": {
"pin": false,
@ -1048,7 +1048,7 @@
"macosx64",
"macosx64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ka": {
"pin": false,
@ -1067,7 +1067,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"kab": {
"pin": false,
@ -1086,7 +1086,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"kk": {
"pin": false,
@ -1105,7 +1105,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"km": {
"pin": false,
@ -1124,7 +1124,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"kn": {
"pin": false,
@ -1143,7 +1143,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ko": {
"pin": false,
@ -1162,7 +1162,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"lij": {
"pin": false,
@ -1181,7 +1181,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"lo": {
"pin": false,
@ -1200,7 +1200,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"lt": {
"pin": false,
@ -1219,7 +1219,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ltg": {
"pin": false,
@ -1238,7 +1238,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"lv": {
"pin": false,
@ -1257,7 +1257,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"meh": {
"pin": false,
@ -1276,7 +1276,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"mk": {
"pin": false,
@ -1295,7 +1295,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"mr": {
"pin": false,
@ -1314,7 +1314,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ms": {
"pin": false,
@ -1333,7 +1333,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"my": {
"pin": false,
@ -1352,7 +1352,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"nb-NO": {
"pin": false,
@ -1371,7 +1371,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ne-NP": {
"pin": false,
@ -1390,7 +1390,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"nl": {
"pin": false,
@ -1409,7 +1409,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"nn-NO": {
"pin": false,
@ -1428,7 +1428,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"oc": {
"pin": false,
@ -1447,7 +1447,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"pa-IN": {
"pin": false,
@ -1466,7 +1466,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"pl": {
"pin": false,
@ -1485,7 +1485,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"pt-BR": {
"pin": false,
@ -1504,7 +1504,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"pt-PT": {
"pin": false,
@ -1523,7 +1523,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"rm": {
"pin": false,
@ -1542,7 +1542,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ro": {
"pin": false,
@ -1561,7 +1561,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ru": {
"pin": false,
@ -1580,7 +1580,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"sat": {
"pin": false,
@ -1599,7 +1599,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"sc": {
"pin": false,
@ -1618,7 +1618,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"scn": {
"pin": false,
@ -1637,7 +1637,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"sco": {
"pin": false,
@ -1656,7 +1656,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"si": {
"pin": false,
@ -1675,7 +1675,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"sk": {
"pin": false,
@ -1694,7 +1694,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"skr": {
"pin": false,
@ -1713,7 +1713,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"sl": {
"pin": false,
@ -1732,7 +1732,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"son": {
"pin": false,
@ -1751,7 +1751,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"sq": {
"pin": false,
@ -1770,7 +1770,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"sr": {
"pin": false,
@ -1789,7 +1789,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"sv-SE": {
"pin": false,
@ -1808,7 +1808,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"szl": {
"pin": false,
@ -1827,7 +1827,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ta": {
"pin": false,
@ -1846,7 +1846,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"te": {
"pin": false,
@ -1865,7 +1865,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"tg": {
"pin": false,
@ -1884,7 +1884,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"th": {
"pin": false,
@ -1903,7 +1903,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"tl": {
"pin": false,
@ -1922,7 +1922,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"tr": {
"pin": false,
@ -1941,7 +1941,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"trs": {
"pin": false,
@ -1960,7 +1960,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"uk": {
"pin": false,
@ -1979,7 +1979,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"ur": {
"pin": false,
@ -1998,7 +1998,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"uz": {
"pin": false,
@ -2017,7 +2017,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"vi": {
"pin": false,
@ -2036,7 +2036,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"wo": {
"pin": false,
@ -2055,7 +2055,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"xh": {
"pin": false,
@ -2074,7 +2074,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"zh-CN": {
"pin": false,
@ -2093,7 +2093,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
},
"zh-TW": {
"pin": false,
@ -2112,6 +2112,6 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "65bf3f948c794d8c7b5544020f2d0a5876224706"
"revision": "3132c609fc331e42213a31b798df8213b5bd7146"
}
}

View file

@ -10,7 +10,7 @@ SOURCES += ["pure_virtual.c"]
FORCE_STATIC_LIB = True
USE_STATIC_LIBS = True
USE_STATIC_MSVCRT = True
# Build a real library so that the linker can remove it if the symbol
# is never used.

View file

@ -6,3 +6,7 @@ license = "MIT OR Apache-2.0"
[lib]
path = "lib.rs"
[features]
default = ["std"]
std = []

View file

@ -9,6 +9,8 @@ path = "lib.rs"
# This list is taken from web-sys 0.3.70's Cargo.toml
[features]
default = ["std"]
std = []
AbortController = []
AbortSignal = ["EventTarget"]
AddEventListenerOptions = []

View file

@ -12,5 +12,5 @@ SharedLibrary("crashinjectdll")
DEFFILE = "crashinjectdll.def"
USE_STATIC_LIBS = True
USE_STATIC_MSVCRT = True
NO_PGO = True

View file

@ -11,7 +11,7 @@ if CONFIG["ENABLE_TESTS"]:
SOURCES += [
"crashinject.cpp",
]
USE_STATIC_LIBS = True
USE_STATIC_MSVCRT = True
NO_PGO = True

View file

@ -488,12 +488,11 @@ export function sortThreads(a, b) {
}
}
// Order the frame targets and the worker targets by their target name
if (a.targetType == "frame" && b.targetType == "frame") {
return a.name.localeCompare(b.name);
} else if (
a.targetType.endsWith("worker") &&
b.targetType.endsWith("worker")
// Order the frame, worker and content script targets by their target name
if (
(a.targetType == "frame" && b.targetType == "frame") ||
(a.targetType.endsWith("worker") && b.targetType.endsWith("worker")) ||
(a.targetType == "content_script" && b.targetType == "content_script")
) {
return a.name.localeCompare(b.name);
}

View file

@ -2,8 +2,10 @@
* 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/>. */
import { truncateString } from "devtools/shared/string";
import { WorkerDispatcher } from "devtools/client/shared/worker-utils";
const MAX_SCRIPT_LOG_LENGTH = 500;
const WORKER_URL =
"resource://devtools/client/debugger/dist/pretty-print-worker.js";
@ -16,12 +18,28 @@ export class PrettyPrintDispatcher extends WorkerDispatcher {
#prettyPrintInlineScriptTask = this.task("prettyPrintInlineScript");
#getSourceMapForTask = this.task("getSourceMapForTask");
prettyPrint(options) {
return this.#prettyPrintTask(options);
async prettyPrint(options) {
try {
return await this.#prettyPrintTask(options);
} catch (e) {
console.error(
`[pretty-print] Failed to pretty print script (${options.url}):\n`,
truncateString(options.sourceText, MAX_SCRIPT_LOG_LENGTH)
);
throw e;
}
}
prettyPrintInlineScript(options) {
return this.#prettyPrintInlineScriptTask(options);
async prettyPrintInlineScript(options) {
try {
return await this.#prettyPrintInlineScriptTask(options);
} catch (e) {
console.error(
`[pretty-print] Failed to pretty print inline script (${options.url}):\n`,
truncateString(options.sourceText, MAX_SCRIPT_LOG_LENGTH)
);
throw e;
}
}
getSourceMap(taskId) {

View file

@ -9,6 +9,23 @@
add_task(async function () {
const extension = await installAndStartContentScriptExtension();
const otherExtension = ExtensionTestUtils.loadExtension({
manifest: {
name: "Other extension",
content_scripts: [
{
js: ["other_content_script.js"],
matches: ["https://example.com/*"],
run_at: "document_idle",
},
],
},
files: {
"other_content_script.js": "// This one does nothing",
},
});
await otherExtension.startup();
let dbg = await initDebugger("doc-content-script-sources.html");
ok(
!sourceExists(dbg, "content_script.js"),
@ -32,7 +49,9 @@ add_task(async function () {
"doc-content-script-sources.html",
"doc-strict.html",
"content_script.js",
"other_content_script.js",
]);
is(dbg.selectors.getSourceCount(), 4, "There are only three sources");
await waitForSources(dbg, "content_script.js");
await selectSource(dbg, "content_script.js");
@ -46,7 +65,12 @@ add_task(async function () {
// Verify that the content script is below the target of the frame it was executed against
Assert.deepEqual(
sourceTreeThreadLabels,
["Main Thread", "Test content script extension", "Debugger test page"],
[
"Main Thread",
"Other extension",
"Test content script extension",
"Debugger test page",
],
"The threads are correctly ordered"
);
const threadPanelLabels = [...findAllElements(dbg, "threadsPaneItems")].map(
@ -77,7 +101,6 @@ add_task(async function () {
gBrowser.reloadTab(gBrowser.selectedTab);
await waitForPaused(dbg);
await waitForSelectedSource(dbg, "content_script.js");
await waitFor(
() => findElementWithSelector(dbg, ".sources-list .focused"),
"Source is focused"
@ -87,6 +110,20 @@ add_task(async function () {
findSource(dbg, "content_script.js").id,
2
);
const pausedThread = dbg.selectors.getCurrentThread();
await stepIn(dbg);
is(
dbg.selectors.getCurrentThread(),
pausedThread,
"We step in the same thread"
);
await assertPausedAtSourceAndLine(
dbg,
findSource(dbg, "content_script.js").id,
7
);
await resume(dbg);
}
@ -95,15 +132,19 @@ add_task(async function () {
await closeTab(dbg, "content_script.js");
is(
dbg.selectors.getAllThreads().length,
2,
"Ensure that we only get the main thread and the content script thread"
await waitFor(
() => dbg.selectors.getAllThreads().length == 3,
"Ensure that we only get the main thread and the two content scripts thread"
);
await waitFor(
() => dbg.selectors.getSourceCount() == 4,
"There are only three sources"
);
await extension.unload();
await otherExtension.unload();
await waitFor(
() => dbg.selectors.getAllThreads().length == 1,
"After unloading the add-on, the content script thread is removed"
() => dbg.selectors.getAllThreads().length == 2,
"After unloading the add-on, the content script thread is removed, but there is still two html documents"
);
});

View file

@ -5,9 +5,7 @@
"use strict";
const TextEditor = require("resource://devtools/client/inspector/markup/views/text-editor.js");
const {
truncateString,
} = require("resource://devtools/shared/inspector/utils.js");
const { truncateString } = require("resource://devtools/shared/string.js");
const {
editableField,
InplaceEditor,

View file

@ -823,7 +823,7 @@ skip-if = ["true"] # Bug 1765369
["browser_webconsole_wasm_errors.js"]
["browser_webconsole_webextension_promise_rejection.js"]
["browser_webconsole_webextension_content_script.js"]
["browser_webconsole_websocket.js"]

View file

@ -23,19 +23,52 @@ add_task(async function () {
files: {
"content-script.js": function () {
/* global browser */
console.log("def");
// Create an iframe with a privileged document of the extension
const iframe = document.createElement("iframe");
iframe.src = browser.runtime.getURL(`iframe.html`);
document.body.appendChild(iframe);
Promise.reject("abc");
},
"iframe.html": `<div>Extension iframe</div> <script src="iframe.js"></script>`,
"iframe.js": `console.log("iframe log"); throw new Error("iframe exception")`,
},
});
await extension.startup();
const hud = await openNewTabAndConsole(TEST_URI);
await waitFor(() => findErrorMessage(hud, "uncaught exception: abc"));
// Open the debugger with the content script setting turned on in order
// to be able to show the content script target in the console evaluation context
// For now, console messages and errors are shown without having to enable the content script targets
await checkUniqueMessageExists(hud, "uncaught exception: abc", ".error");
await checkUniqueMessageExists(hud, "def", ".console-api");
await checkUniqueMessageExists(hud, "iframe log", ".console-api");
await checkUniqueMessageExists(
hud,
"Uncaught Error: iframe exception",
".error"
);
// Enable the content script preference in order to see content scripts messages,
// sources and target.
const onTargetProcessed = waitForTargetProcessed(
hud.commands,
target => target.targetType == "content_script"
);
await pushPref("devtools.debugger.show-content-scripts", true);
await onTargetProcessed;
// Wait for more to let a chance to process unexpected duplicated messages
await wait(500);
await checkUniqueMessageExists(hud, "uncaught exception: abc", ".error");
await checkUniqueMessageExists(hud, "def", ".console-api");
await hud.toolbox.selectTool("jsdebugger");
const evaluationContextSelectorButton = hud.ui.outputNode.querySelector(

View file

@ -11,9 +11,7 @@ const {
const {
moveInfobar,
} = require("resource://devtools/server/actors/highlighters/utils/markup.js");
const {
truncateString,
} = require("resource://devtools/shared/inspector/utils.js");
const { truncateString } = require("resource://devtools/shared/string.js");
const STRINGS_URI = "devtools/shared/locales/accessibility.properties";
loader.lazyRequireGetter(

View file

@ -71,6 +71,10 @@ class ConsoleMessageWatcher {
const listener = new ConsoleAPIListener(window, onConsoleAPICall, {
excludeMessagesBoundToWindow: isTargetActorContentProcess,
matchExactWindow: targetActor.ignoreSubFrames,
addonId:
targetActor.targetType === Targets.TYPES.CONTENT_SCRIPT
? targetActor.addonId
: null,
});
this.listener = listener;
listener.init();

View file

@ -51,7 +51,9 @@ class WebExtensionContentScriptTargetActor extends BaseTargetActor {
// Use a debugger against a unique global
this.makeDebugger = makeDebugger.bind(null, {
findDebuggees: _dbg => [this.contentScriptSandbox],
shouldAddNewGlobalAsDebuggee: () => true,
// Only accept the content script sandbox and nothing else.
shouldAddNewGlobalAsDebuggee: () => false,
});
}

View file

@ -131,7 +131,10 @@ function createTargetsForWatcher(watcherDataObject, _isProcessActorStartup) {
const sandboxes = lazy.ExtensionContent.getAllContentScriptGlobals();
for (const contentScriptSandbox of sandboxes) {
const metadata = Cu.getSandboxMetadata(contentScriptSandbox);
if (metadata["browser-id"] != browserId) {
// Ignore sandboxes without metadata which are related to the hack
// of bug 1214658, which spawns content script sandboxes in order
// to expose chrome/browser API to iframes loading extension documents.
if (!metadata || metadata["browser-id"] != browserId) {
continue;
}
createContentScriptTargetActor(watcherDataObject, {

View file

@ -6,9 +6,7 @@
// Test the accessible highlighter's infobar content.
const {
truncateString,
} = require("resource://devtools/shared/inspector/utils.js");
const { truncateString } = require("resource://devtools/shared/string.js");
const {
MAX_STRING_LENGTH,
} = require("resource://devtools/server/actors/highlighters/utils/accessibility.js");

File diff suppressed because one or more lines are too long

View file

@ -4,4 +4,4 @@
# 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/.
DevToolsModules("css-logic.js", "utils.js")
DevToolsModules("css-logic.js")

View file

@ -69,6 +69,7 @@ DevToolsModules(
"picker-constants.js",
"plural-form.js",
"protocol.js",
"string.js",
"system.js",
"ThreadSafeDevToolsUtils.js",
"throttle.js",

View file

@ -92,6 +92,8 @@ const {
const COMMON_PROTOCOLS = ["http", "https", "file"];
const HTTPISH = new Set(["http", "https"]);
// Regex used to identify user:password tokens in url strings.
// This is not a strict valid characters check, because we try to fixup this
// part of the url too.
@ -331,9 +333,9 @@ URIFixup.prototype = {
(!lazy.possiblyHostPortRegex.test(uriString) &&
!lazy.userPasswordRegex.test(uriString))
) {
// Just try to create an URL out of it.
// Just try to create a URL out of it.
try {
info.fixedURI = Services.io.newURI(uriString);
info.fixedURI = makeURIWithFixedLocalHosts(uriString, fixupFlags);
} catch (ex) {
if (ex.result != Cr.NS_ERROR_MALFORMED_URI) {
throw ex;
@ -395,7 +397,7 @@ URIFixup.prototype = {
lazy.maxOneTabRegex.test(uriString) &&
!detectSpaceInCredentials(untrimmedURIString)
) {
let uriWithProtocol = fixupURIProtocol(uriString);
let uriWithProtocol = fixupURIProtocol(uriString, fixupFlags);
if (uriWithProtocol) {
info.fixedURI = uriWithProtocol;
info.fixupChangedProtocol = true;
@ -529,7 +531,7 @@ URIFixup.prototype = {
!submission ||
// For security reasons (avoid redirecting to file, data, or other unsafe
// protocols) we only allow fixup to http/https search engines.
!submission.uri.scheme.startsWith("http")
!HTTPISH.has(submission.uri.scheme)
) {
throw new Components.Exception(
"Invalid search submission uri",
@ -561,7 +563,7 @@ URIFixup.prototype = {
FIXUP_FLAG_FIX_SCHEME_TYPOS
);
if (scheme != "http" && scheme != "https") {
if (!HTTPISH.has(scheme)) {
throw new Components.Exception(
"Scheme should be either http or https",
Cr.NS_ERROR_FAILURE
@ -572,7 +574,7 @@ URIFixup.prototype = {
info.fixedURI = Services.io.newURI(fixedSchemeUriString);
let host = info.fixedURI.host;
if (host != "http" && host != "https" && host != "localhost") {
if (!HTTPISH.has(host) && host != "localhost") {
let modifiedHostname = maybeAddPrefixAndSuffix(host);
updateHostAndScheme(info, modifiedHostname);
info.preferredURI = info.fixedURI;
@ -908,7 +910,7 @@ function maybeSetAlternateFixedURI(info, fixupFlags) {
// Don't create an alternate uri for localhost, because it would be confusing.
// Ditto for 'http' and 'https' as these are frequently the result of typos, e.g.
// 'https//foo' (note missing : ).
if (oldHost == "localhost" || oldHost == "http" || oldHost == "https") {
if (oldHost == "localhost" || HTTPISH.has(oldHost)) {
return false;
}
@ -972,10 +974,11 @@ function fileURIFixup(uriString) {
* user:pass@no-scheme.com
*
* @param {string} uriString The string to fixup.
* @param {Number} fixupFlags The fixup flags to use.
* @returns {nsIURI} an nsIURI built adding the default protocol to the string,
* or null if fixing was not possible.
*/
function fixupURIProtocol(uriString) {
function fixupURIProtocol(uriString, fixupFlags) {
// The longest URI scheme on the IANA list is 36 chars + 3 for ://
let schemeChars = uriString.slice(0, 39);
@ -984,13 +987,41 @@ function fixupURIProtocol(uriString) {
uriString = "http://" + uriString;
}
try {
return Services.io.newURI(uriString);
return makeURIWithFixedLocalHosts(uriString, fixupFlags);
} catch (ex) {
// We generated an invalid uri.
}
return null;
}
/**
* A thin wrapper around `newURI` that fixes up the host if it's
* 0.0.0.0 or ::, which are no longer valid. Aims to facilitate
* user typos and/or "broken" links output by commandline tools.
*
* @param {string} uriString The string to make into a URI.
* @param {Number} fixupFlags The fixup flags to use.
* @throws NS_ERROR_MALFORMED_URI if the uri is invalid.
*/
function makeURIWithFixedLocalHosts(uriString, fixupFlags) {
let uri = Services.io.newURI(uriString);
// We only want to fix up 0.0.0.0 if the URL came from the user, either
// from the address bar or as a commandline argument (ie clicking links
// in other applications, terminal, etc.). We can't use
// FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP for this as that isn't normally allowed
// for external links, and the other flags are sometimes used for
// web-provided content. So we cheat and use the scheme typo flag.
if (fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS && HTTPISH.has(uri.scheme)) {
if (uri.host == "0.0.0.0") {
uri = uri.mutate().setHost("127.0.0.1").finalize();
} else if (uri.host == "::") {
uri = uri.mutate().setHost("[::1]").finalize();
}
}
return uri;
}
/**
* Tries to fixup a string to a search url.
* @param {string} uriString the string to fixup.

View file

@ -17,6 +17,10 @@ var flagInputs = [
Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT,
];
function schemeTypoOnly(flags) {
return flags & Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
}
/*
The following properties are supported for these test cases:
{
@ -788,6 +792,136 @@ var testcases = [
keywordLookup: true,
affectedByDNSForSingleWordHosts: true,
},
{
input: "0.0.0.0",
fixedURI: "http://127.0.0.1/",
protocolChange: true,
shouldRunTest: schemeTypoOnly,
},
{
input: "0.0.0.0/",
fixedURI: "http://127.0.0.1/",
protocolChange: true,
shouldRunTest: schemeTypoOnly,
},
{
input: "0.0.0.0:8080",
fixedURI: "http://127.0.0.1:8080/",
protocolChange: true,
shouldRunTest: schemeTypoOnly,
},
{
input: "0.0.0.0:8080/blah?blah",
fixedURI: "http://127.0.0.1:8080/blah?blah",
protocolChange: true,
shouldRunTest: schemeTypoOnly,
},
{
input: "http://0.0.0.0",
fixedURI: "http://127.0.0.1/",
shouldRunTest: schemeTypoOnly,
},
{
input: "http://0.0.0.0/",
fixedURI: "http://127.0.0.1/",
shouldRunTest: schemeTypoOnly,
},
{
input: "http://0.0.0.0:8080",
fixedURI: "http://127.0.0.1:8080/",
shouldRunTest: schemeTypoOnly,
},
{
input: "http://0.0.0.0:8080/blah?blah",
fixedURI: "http://127.0.0.1:8080/blah?blah",
shouldRunTest: schemeTypoOnly,
},
{
input: "https://0.0.0.0",
fixedURI: "https://127.0.0.1/",
shouldRunTest: schemeTypoOnly,
},
{
input: "https://0.0.0.0/",
fixedURI: "https://127.0.0.1/",
shouldRunTest: schemeTypoOnly,
},
{
input: "https://0.0.0.0:8080",
fixedURI: "https://127.0.0.1:8080/",
shouldRunTest: schemeTypoOnly,
},
{
input: "https://0.0.0.0:8080/blah?blah",
fixedURI: "https://127.0.0.1:8080/blah?blah",
shouldRunTest: schemeTypoOnly,
},
{
input: "[::]",
fixedURI: "http://[::1]/",
protocolChange: true,
shouldRunTest: schemeTypoOnly,
},
{
input: "[::]/",
fixedURI: "http://[::1]/",
protocolChange: true,
shouldRunTest: schemeTypoOnly,
},
{
input: "[::]:8080",
fixedURI: "http://[::1]:8080/",
protocolChange: true,
shouldRunTest: schemeTypoOnly,
},
{
input: "[::]:8080/blah?blah",
fixedURI: "http://[::1]:8080/blah?blah",
protocolChange: true,
shouldRunTest: schemeTypoOnly,
},
{
input: "http://[::]",
fixedURI: "http://[::1]/",
shouldRunTest: schemeTypoOnly,
},
{
input: "http://[::]/",
fixedURI: "http://[::1]/",
shouldRunTest: schemeTypoOnly,
},
{
input: "http://[::]:8080",
fixedURI: "http://[::1]:8080/",
shouldRunTest: schemeTypoOnly,
},
{
input: "http://[::]:8080/blah?blah",
fixedURI: "http://[::1]:8080/blah?blah",
shouldRunTest: schemeTypoOnly,
},
{
input: "https://[::]",
fixedURI: "https://[::1]/",
shouldRunTest: schemeTypoOnly,
},
{
input: "https://[::]/",
fixedURI: "https://[::1]/",
shouldRunTest: schemeTypoOnly,
},
{
input: "https://[::]:8080",
fixedURI: "https://[::1]:8080/",
shouldRunTest: schemeTypoOnly,
},
{
input: "https://[::]:8080/blah?blah",
fixedURI: "https://[::1]:8080/blah?blah",
shouldRunTest: schemeTypoOnly,
},
];
if (AppConstants.platform == "win") {

View file

@ -303,7 +303,7 @@ void HTMLSlotElement::InsertAssignedNode(uint32_t aIndex, nsIContent& aNode) {
MOZ_ASSERT(!aNode.GetAssignedSlot(), "Losing track of a slot");
mAssignedNodes.InsertElementAt(aIndex, &aNode);
aNode.SetAssignedSlot(this);
SetStates(ElementState::HAS_SLOTTED, true);
RecalculateHasSlottedState();
SlotAssignedNodeAdded(this, aNode);
}
@ -311,10 +311,33 @@ void HTMLSlotElement::AppendAssignedNode(nsIContent& aNode) {
MOZ_ASSERT(!aNode.GetAssignedSlot(), "Losing track of a slot");
mAssignedNodes.AppendElement(&aNode);
aNode.SetAssignedSlot(this);
SetStates(ElementState::HAS_SLOTTED, true);
RecalculateHasSlottedState();
SlotAssignedNodeAdded(this, aNode);
}
void HTMLSlotElement::RecalculateHasSlottedState() {
bool hasSlotted = false;
// Find the first node that makes this a slotted element.
for (const RefPtr<nsINode>& assignedNode : mAssignedNodes) {
if (auto* slot = HTMLSlotElement::FromNode(assignedNode)) {
if (slot->IsInShadowTree() &&
!slot->State().HasState(ElementState::HAS_SLOTTED)) {
continue;
}
}
hasSlotted = true;
break;
}
if (State().HasState(ElementState::HAS_SLOTTED) != hasSlotted) {
SetStates(ElementState::HAS_SLOTTED, hasSlotted);
// If slot is a slotted node itself, the assigned slot needs to
// RecalculateHasSlottedState:
if (auto* slot = GetAssignedSlot()) {
slot->RecalculateHasSlottedState();
}
}
}
void HTMLSlotElement::RemoveAssignedNode(nsIContent& aNode) {
// This one runs from unlinking, so we can't guarantee that the slot pointer
// hasn't been cleared.
@ -322,7 +345,8 @@ void HTMLSlotElement::RemoveAssignedNode(nsIContent& aNode) {
"How exactly?");
mAssignedNodes.RemoveElement(&aNode);
aNode.SetAssignedSlot(nullptr);
SetStates(ElementState::HAS_SLOTTED, !mAssignedNodes.IsEmpty());
RecalculateHasSlottedState();
SlotAssignedNodeRemoved(this, aNode);
}
@ -335,7 +359,7 @@ void HTMLSlotElement::ClearAssignedNodes() {
}
mAssignedNodes.Clear();
SetStates(ElementState::HAS_SLOTTED, false);
RecalculateHasSlottedState();
}
void HTMLSlotElement::EnqueueSlotChangeEvent() {

View file

@ -69,6 +69,8 @@ class HTMLSlotElement final : public nsGenericHTMLElement {
void RemoveManuallyAssignedNode(nsIContent&);
void RecalculateHasSlottedState();
protected:
virtual ~HTMLSlotElement();
JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;

View file

@ -26,7 +26,7 @@ if CONFIG["OS_ARCH"] == "WINNT":
"user32",
]
USE_STATIC_LIBS = True
USE_STATIC_MSVCRT = True
NoVisibilityFlags()
# Don't use STL wrappers; this isn't Gecko code
DisableStlWrapping()

View file

@ -18,7 +18,7 @@ SOURCES += [
SharedLibrary("fakeopenh264")
USE_STATIC_LIBS = True
USE_STATIC_MSVCRT = True
NoVisibilityFlags()
# Don't use STL wrappers; this isn't Gecko code
DisableStlWrapping()

View file

@ -11,6 +11,7 @@
#include "js/TypeDecls.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/Maybe.h"
#include "mozilla/Preferences.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
@ -36,6 +37,12 @@ extern Atomic<bool, mozilla::Relaxed> sCSSHacksChecked;
extern Atomic<bool, mozilla::Relaxed> sCSSHacksPresent;
TEST_F(TelemetryTestFixture, UnexpectedPrivilegedLoadsTelemetryTest) {
// Enable telemetry pref.
bool prefDefault = Preferences::GetBool(
"dom.security.unexpected_system_load_telemetry_enabled");
Preferences::SetBool("dom.security.unexpected_system_load_telemetry_enabled",
true);
// Disable JS/CSS Hacks Detection, which would consider this current profile
// as uninteresting for our measurements:
bool origJSHacksPresent = sJSHacksPresent;
@ -302,4 +309,8 @@ TEST_F(TelemetryTestFixture, UnexpectedPrivilegedLoadsTelemetryTest) {
sJSHacksChecked = origJSHacksChecked;
sCSSHacksPresent = origCSSHacksPresent;
sCSSHacksChecked = origCSSHacksChecked;
// Restore telemetry pref
Preferences::SetBool("dom.security.unexpected_system_load_telemetry_enabled",
prefDefault);
}

View file

@ -38,11 +38,11 @@
is(
message,
`
Shader '' parsing error: expected global item ('struct', 'const', 'var', 'alias', ';', 'fn') or the end of the file, found '?'
Shader '' parsing error: expected global item (\`struct\`, \`const\`, \`var\`, \`alias\`, \`fn\`, \`diagnostic\`, \`enable\`, \`requires\`, \`;\`) or the end of the file, found "?"
┌─ wgsl:1:12
1 │ /*🐈🐈🐈🐈🐈🐈🐈*/?
│ ^ expected global item ('struct', 'const', 'var', 'alias', ';', 'fn') or the end of the file
│ ^ expected global item (\`struct\`, \`const\`, \`var\`, \`alias\`, \`fn\`, \`diagnostic\`, \`enable\`, \`requires\`, \`;\`) or the end of the file
`
);

View file

@ -11,7 +11,7 @@
#include "HTMLEditUtils.h" // for HTMLEditUtils
#include "HTMLEditHelpers.h" // for SplitNodeResult
#include "TextEditor.h" // for TextEditor
#include "WSRunObject.h" // for WSRunScanner
#include "WSRunScanner.h" // for WSRunScanner
#include "mozilla/CaretAssociationHint.h" // for CaretAssociationHint
#include "mozilla/IntegerRange.h" // for IntegerRange
@ -224,8 +224,7 @@ void AutoClonedRangeArray::EnsureRangesInTextNode(const Text& aTextNode) {
Result<bool, nsresult>
AutoClonedRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent(
const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent,
const Element* aEditingHost) {
IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent) {
if (IsCollapsed()) {
return false;
}
@ -246,7 +245,7 @@ AutoClonedRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent(
"Changing range in selection may cause running script");
Result<bool, nsresult> result =
WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
aHTMLEditor, range, aEditingHost);
aHTMLEditor, range);
if (result.isErr()) {
NS_WARNING(
"WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() "

View file

@ -197,8 +197,7 @@ class MOZ_STACK_CLASS AutoClonedRangeArray {
};
Result<bool, nsresult> ShrinkRangesIfStartFromOrEndAfterAtomicContent(
const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent,
const dom::Element* aEditingHost);
IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent);
/**
* The following methods are same as `Selection`'s methods.

View file

@ -879,6 +879,10 @@ class EditorDOMPointBase final {
return true;
}
[[nodiscard]] bool IsInContentNodeAndValid() const {
return IsInContentNode() && IsSetAndValid();
}
[[nodiscard]] bool IsInComposedDoc() const {
return IsSet() && mParent->IsInComposedDoc();
}
@ -887,6 +891,10 @@ class EditorDOMPointBase final {
return IsInComposedDoc() && IsSetAndValid();
}
[[nodiscard]] bool IsInContentNodeAndValidInComposedDoc() const {
return IsInContentNode() && IsSetAndValidInComposedDoc();
}
bool IsStartOfContainer() const {
// If we're referring the first point in the container:
// If mParent is not a container like a text node, mOffset is 0.

View file

@ -126,9 +126,9 @@ class SplitNodeResult; // HTMLEditHelpers.h
class SplitNodeTransaction; // SplitNodeTransaction.h
class SplitRangeOffFromNodeResult; // HTMLEditHelpers.h
class SplitRangeOffResult; // HTMLEditHelpers.h
class WhiteSpaceVisibilityKeeper; // WSRunObject.h
class WSRunScanner; // WSRunObject.h
class WSScanResult; // WSRunObject.h
class WhiteSpaceVisibilityKeeper; // WhiteSpaceVisibilityKeeper.h
class WSRunScanner; // WSRunScanner.h
class WSScanResult; // WSRunScanner.h
/******************************************************************************
* structs

View file

@ -6,15 +6,12 @@
#include "HTMLEditHelpers.h"
#include "CSSEditUtils.h"
#include "EditorDOMPoint.h"
#include "HTMLEditor.h"
#include "HTMLEditUtils.h"
#include "PendingStyles.h"
#include "WSRunObject.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/Text.h"
#include "nsIContent.h"
#include "nsINode.h"
#include "nsRange.h"

View file

@ -8,7 +8,6 @@
#include "HTMLEditorInlines.h"
#include "HTMLEditorNestedClasses.h"
#include <algorithm>
#include <utility>
#include "AutoClonedRangeArray.h"
@ -21,13 +20,13 @@
#include "HTMLEditHelpers.h"
#include "HTMLEditUtils.h"
#include "PendingStyles.h" // for SpecifiedStyle
#include "WSRunObject.h"
#include "WhiteSpaceVisibilityKeeper.h"
#include "WSRunScanner.h"
#include "ErrorList.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/EditorForwards.h"
#include "mozilla/IntegerRange.h"
@ -35,10 +34,7 @@
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Maybe.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/RangeUtils.h"
#include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
#include "mozilla/TextComposition.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
@ -49,8 +45,6 @@
#include "mozilla/dom/RangeBinding.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/StaticRange.h"
#include "mozilla/mozalloc.h"
#include "nsAString.h"
#include "nsAtom.h"
#include "nsCRT.h"
#include "nsCRTGlue.h"
@ -60,9 +54,7 @@
#include "nsError.h"
#include "nsFrameSelection.h"
#include "nsGkAtoms.h"
#include "nsHTMLDocument.h"
#include "nsIContent.h"
#include "nsID.h"
#include "nsIFrame.h"
#include "nsINode.h"
#include "nsLiteralString.h"
@ -75,7 +67,6 @@
#include "nsTArray.h"
#include "nsTextNode.h"
#include "nsThreadUtils.h"
#include "nsUnicharUtils.h"
class nsISupports;
@ -1259,7 +1250,7 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
WhiteSpaceVisibilityKeeper::ReplaceText(
*this, aInsertionString,
EditorDOMRange(pointToInsert, compositionEndPoint),
InsertTextTo::ExistingTextNodeIfAvailable, *editingHost);
InsertTextTo::ExistingTextNodeIfAvailable);
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
NS_WARNING("WhiteSpaceVisibilityKeeper::ReplaceText() failed");
return replaceTextResult.propagateErr();
@ -1461,15 +1452,14 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
if (!lineText.Contains(u'\t')) {
return WhiteSpaceVisibilityKeeper::InsertText(
*this, lineText, currentPoint,
GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset),
*editingHost);
GetInsertTextTo(inclusiveNextLinefeedOffset,
lineStartOffset));
}
nsAutoString formattedLineText(lineText);
formattedLineText.ReplaceSubstring(u"\t"_ns, u" "_ns);
return WhiteSpaceVisibilityKeeper::InsertText(
*this, formattedLineText, currentPoint,
GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset),
*editingHost);
GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset));
}();
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
@ -1490,8 +1480,8 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
}
Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError =
WhiteSpaceVisibilityKeeper::InsertLineBreak(
*lineBreakType, *this, currentPoint, *editingHost);
WhiteSpaceVisibilityKeeper::InsertLineBreak(*lineBreakType, *this,
currentPoint);
if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) {
NS_WARNING(
nsPrintfCString(
@ -1710,8 +1700,7 @@ nsresult HTMLEditor::InsertLineBreakAsSubAction() {
Result<CreateLineBreakResult, nsresult>
insertPaddingBRElementResultOrError =
WhiteSpaceVisibilityKeeper::InsertLineBreak(
LineBreakType::BRElement, *this, pointToPutCaret,
*editingHost);
LineBreakType::BRElement, *this, pointToPutCaret);
if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
NS_WARNING(
"WhiteSpaceVisibilityKeeper::InsertLineBreak(LineBreakType::"
@ -2413,8 +2402,8 @@ Result<CreateElementResult, nsresult> HTMLEditor::HandleInsertBRElement(
splitLinkNodeResult.inspect().AtSplitPoint<EditorDOMPoint>();
}
Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
WhiteSpaceVisibilityKeeper::InsertLineBreak(
LineBreakType::BRElement, *this, pointToBreak, aEditingHost);
WhiteSpaceVisibilityKeeper::InsertLineBreak(LineBreakType::BRElement,
*this, pointToBreak);
if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) {
NS_WARNING(
"WhiteSpaceVisibilityKeeper::InsertLineBreak(LineBreakType::"
@ -2443,7 +2432,7 @@ Result<CreateElementResult, nsresult> HTMLEditor::HandleInsertBRElement(
Result<CreateLineBreakResult, nsresult>
insertPaddingBRElementResultOrError =
WhiteSpaceVisibilityKeeper::InsertLineBreak(
LineBreakType::BRElement, *this, afterBRElement, aEditingHost);
LineBreakType::BRElement, *this, afterBRElement);
NS_WARNING_ASSERTION(insertPaddingBRElementResultOrError.isOk(),
"WhiteSpaceVisibilityKeeper::InsertLineBreak("
"LineBreakType::BRElement) failed");
@ -3371,13 +3360,12 @@ HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
*newCaretPosition.ContainerAs<nsIContent>(),
HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost,
BlockInlineCheck::UseComputedDisplayStyle)) {
Element* editingHost = ComputeEditingHost();
// Try to put caret next to immediately after previous editable leaf.
nsIContent* previousContent =
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
newCaretPosition, *editableBlockElementOrInlineEditingHost,
{LeafNodeType::LeafNodeOrNonEditableNode},
BlockInlineCheck::UseComputedDisplayStyle, editingHost);
newCaretPosition, {LeafNodeType::LeafNodeOrNonEditableNode},
BlockInlineCheck::UseComputedDisplayStyle,
editableBlockElementOrInlineEditingHost);
if (previousContent &&
!HTMLEditUtils::IsBlockElement(
*previousContent, BlockInlineCheck::UseComputedDisplayStyle)) {
@ -3392,10 +3380,9 @@ HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
else if (nsIContent* nextContent =
HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
newCaretPosition,
*editableBlockElementOrInlineEditingHost,
{LeafNodeType::LeafNodeOrNonEditableNode},
BlockInlineCheck::UseComputedDisplayStyle,
editingHost)) {
editableBlockElementOrInlineEditingHost)) {
newCaretPosition = nextContent->IsText() ||
HTMLEditUtils::IsContainerNode(*nextContent)
? EditorDOMPoint(nextContent, 0)

View file

@ -13,7 +13,7 @@
#include "EditorForwards.h" // for CollectChildrenOptions
#include "EditorUtils.h" // for EditorUtils
#include "HTMLEditHelpers.h" // for EditorInlineStyle
#include "WSRunObject.h" // for WSRunScanner
#include "WSRunScanner.h" // for WSRunScanner
#include "mozilla/ArrayUtils.h" // for ArrayLength
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
@ -1043,8 +1043,9 @@ Maybe<EditorLineBreakType> HTMLEditUtils::GetUnnecessaryLineBreak(
content =
aScanLineBreak == ScanLineBreak::AtEndOfBlock
? HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
*content, aBlockElement, leafNodeOrNonEditableNode,
BlockInlineCheck::UseComputedDisplayStyle)
*content, leafNodeOrNonEditableNode,
BlockInlineCheck::UseComputedDisplayStyle,
&aBlockElement)
: HTMLEditUtils::GetPreviousContent(
*content, onlyPrecedingLine,
BlockInlineCheck::UseComputedDisplayStyle,
@ -1118,13 +1119,12 @@ Maybe<EditorLineBreakType> HTMLEditUtils::GetUnnecessaryLineBreak(
BlockInlineCheck::UseComputedDisplayStyle);
for (nsIContent* content =
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
*lastLineBreakContent, *blockElement,
leafNodeOrNonEditableNodeOrChildBlock,
BlockInlineCheck::UseComputedDisplayStyle);
*lastLineBreakContent, leafNodeOrNonEditableNodeOrChildBlock,
BlockInlineCheck::UseComputedDisplayStyle, blockElement);
content;
content = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
*content, *blockElement, leafNodeOrNonEditableNodeOrChildBlock,
BlockInlineCheck::UseComputedDisplayStyle)) {
*content, leafNodeOrNonEditableNodeOrChildBlock,
BlockInlineCheck::UseComputedDisplayStyle, blockElement)) {
if (HTMLEditUtils::IsBlockElement(
*content, BlockInlineCheck::UseComputedDisplayStyle) ||
(content->IsElement() && !content->IsHTMLElement())) {
@ -2093,7 +2093,7 @@ EditorDOMPointType HTMLEditUtils::GetPreviousEditablePoint(
// There may be invisible trailing white-spaces which should be
// ignored. Let's scan its start.
return WSRunScanner::GetAfterLastVisiblePoint<EditorDOMPointType>(
*textNode, aAncestorLimiter);
*textNode);
}
// If it's a container element, return end of it. Otherwise, return
@ -2204,8 +2204,7 @@ EditorDOMPointType HTMLEditUtils::GetNextEditablePoint(
}
// There may be invisible leading white-spaces which should be
// ignored. Let's scan its start.
return WSRunScanner::GetFirstVisiblePoint<EditorDOMPointType>(
*textNode, aAncestorLimiter);
return WSRunScanner::GetFirstVisiblePoint<EditorDOMPointType>(*textNode);
}
// If it's a container element, return start of it. Otherwise, return
@ -2223,7 +2222,8 @@ Element* HTMLEditUtils::GetAncestorElement(
MOZ_ASSERT(
aAncestorTypes.contains(AncestorType::ClosestBlockElement) ||
aAncestorTypes.contains(AncestorType::MostDistantInlineElementInBlock) ||
aAncestorTypes.contains(AncestorType::ButtonElement));
aAncestorTypes.contains(AncestorType::ButtonElement) ||
aAncestorTypes.contains(AncestorType::AllowRootOrAncestorLimiterElement));
if (&aContent == aAncestorLimiter) {
return nullptr;
@ -2242,7 +2242,12 @@ Element* HTMLEditUtils::GetAncestorElement(
aAncestorTypes.contains(AncestorType::IgnoreHRElement);
const bool lookingForButtonElement =
aAncestorTypes.contains(AncestorType::ButtonElement);
const bool lookingForAnyElement =
aAncestorTypes.contains(AncestorType::AllowRootOrAncestorLimiterElement);
auto IsSearchingElementType = [&](const nsIContent& aContent) -> bool {
if (lookingForAnyElement) {
return aContent.IsElement();
}
if (!aContent.IsElement() ||
(ignoreHRElement && aContent.IsHTMLElement(nsGkAtoms::hr))) {
return false;
@ -2267,6 +2272,9 @@ Element* HTMLEditUtils::GetAncestorElement(
}
if (ignoreHRElement && element->IsHTMLElement(nsGkAtoms::hr)) {
if (element == aAncestorLimiter) {
if (lookingForAnyElement) {
lastAncestorElement = element;
}
break;
}
continue;
@ -2285,6 +2293,9 @@ Element* HTMLEditUtils::GetAncestorElement(
}
if (element == aAncestorLimiter || element == theBodyElement ||
element == theDocumentElement) {
if (lookingForAnyElement) {
lastAncestorElement = element;
}
break;
}
lastAncestorElement = element;
@ -2302,7 +2313,8 @@ Element* HTMLEditUtils::GetInclusiveAncestorElement(
MOZ_ASSERT(
aAncestorTypes.contains(AncestorType::ClosestBlockElement) ||
aAncestorTypes.contains(AncestorType::MostDistantInlineElementInBlock) ||
aAncestorTypes.contains(AncestorType::ButtonElement));
aAncestorTypes.contains(AncestorType::ButtonElement) ||
aAncestorTypes.contains(AncestorType::AllowRootOrAncestorLimiterElement));
const Element* theBodyElement = aContent.OwnerDoc()->GetBody();
const Element* theDocumentElement = aContent.OwnerDoc()->GetDocumentElement();
@ -2316,7 +2328,12 @@ Element* HTMLEditUtils::GetInclusiveAncestorElement(
aAncestorTypes.contains(AncestorType::ButtonElement);
const bool ignoreHRElement =
aAncestorTypes.contains(AncestorType::IgnoreHRElement);
const bool lookingForAnyElement =
aAncestorTypes.contains(AncestorType::AllowRootOrAncestorLimiterElement);
auto IsSearchingElementType = [&](const nsIContent& aContent) -> bool {
if (lookingForAnyElement) {
return aContent.IsElement();
}
if (!aContent.IsElement() ||
(ignoreHRElement && aContent.IsHTMLElement(nsGkAtoms::hr))) {
return false;
@ -2374,7 +2391,10 @@ Element* HTMLEditUtils::GetInclusiveAncestorElement(
}
if (&aContent == aAncestorLimiter) {
return nullptr;
return aAncestorTypes.contains(
AncestorType::AllowRootOrAncestorLimiterElement)
? Element::FromNode(const_cast<nsIContent&>(aContent))
: nullptr;
}
return HTMLEditUtils::GetAncestorElement(aContent, aAncestorTypes,

View file

@ -1235,7 +1235,7 @@ class HTMLEditUtils final {
return content;
}
if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
aNode.IsEditable() && !content->IsEditable()) {
!HTMLEditUtils::IsSimplyEditableNode(*content)) {
return content;
}
content = content->GetLastChild();
@ -1283,7 +1283,7 @@ class HTMLEditUtils final {
return content;
}
if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
aNode.IsEditable() && !content->IsEditable()) {
!HTMLEditUtils::IsSimplyEditableNode(*content)) {
return content;
}
content = content->GetFirstChild();
@ -1294,21 +1294,15 @@ class HTMLEditUtils final {
/**
* GetNextLeafContentOrNextBlockElement() returns next leaf content or
* next block element of aStartContent inside aAncestorLimiter.
* Note that the result may be a contet outside aCurrentBlock if
* aStartContent equals aCurrentBlock.
*
* @param aStartContent The start content to scan next content.
* @param aCurrentBlock Must be ancestor of aStartContent. Dispite
* the name, inline content is allowed if
* aStartContent is in an inline editing host.
* @param aLeafNodeTypes See LeafNodeType.
* @param aAncestorLimiter Optional, setting this guarantees the
* result is in aAncestorLimiter unless
* aStartContent is not a descendant of this.
* @param aAncestorLimiter Optional, if you set this, it must be an
* inclusive ancestor of aStartContent.
*/
static nsIContent* GetNextLeafContentOrNextBlockElement(
const nsIContent& aStartContent, const nsIContent& aCurrentBlock,
const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck,
const nsIContent& aStartContent, const LeafNodeTypes& aLeafNodeTypes,
BlockInlineCheck aBlockInlineCheck,
const Element* aAncestorLimiter = nullptr) {
MOZ_ASSERT_IF(
aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
@ -1325,11 +1319,12 @@ class HTMLEditUtils final {
return nullptr;
}
for (Element* parentElement : aStartContent.AncestorsOfType<Element>()) {
if (parentElement == &aCurrentBlock) {
if (parentElement == aAncestorLimiter ||
HTMLEditUtils::IsBlockElement(*parentElement, aBlockInlineCheck)) {
return nullptr;
}
if (parentElement == aAncestorLimiter) {
NS_WARNING("Reached editing host while climbing up the DOM tree");
if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
!parentElement->IsEditable()) {
return nullptr;
}
nextContent = parentElement->GetNextSibling();
@ -1350,7 +1345,7 @@ class HTMLEditUtils final {
return nextContent;
}
if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
aStartContent.IsEditable() && !nextContent->IsEditable()) {
!nextContent->IsEditable()) {
return nextContent;
}
if (HTMLEditUtils::IsContainerNode(*nextContent)) {
@ -1371,8 +1366,7 @@ class HTMLEditUtils final {
template <typename PT, typename CT>
static nsIContent* GetNextLeafContentOrNextBlockElement(
const EditorDOMPointBase<PT, CT>& aStartPoint,
const nsIContent& aCurrentBlock, const LeafNodeTypes& aLeafNodeTypes,
BlockInlineCheck aBlockInlineCheck,
const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck,
const Element* aAncestorLimiter = nullptr) {
MOZ_ASSERT(aStartPoint.IsSet());
MOZ_ASSERT_IF(
@ -1386,28 +1380,30 @@ class HTMLEditUtils final {
}
if (aStartPoint.IsInTextNode()) {
return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
*aStartPoint.template ContainerAs<Text>(), aCurrentBlock,
aLeafNodeTypes, aBlockInlineCheck, aAncestorLimiter);
*aStartPoint.template ContainerAs<Text>(), aLeafNodeTypes,
aBlockInlineCheck, aAncestorLimiter);
}
if (!HTMLEditUtils::IsContainerNode(
*aStartPoint.template ContainerAs<nsIContent>())) {
return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
*aStartPoint.template ContainerAs<nsIContent>(), aCurrentBlock,
aLeafNodeTypes, aBlockInlineCheck, aAncestorLimiter);
*aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes,
aBlockInlineCheck, aAncestorLimiter);
}
nsCOMPtr<nsIContent> nextContent = aStartPoint.GetChild();
if (!nextContent) {
if (aStartPoint.GetContainer() == &aCurrentBlock) {
if (aStartPoint.GetContainer() == aAncestorLimiter ||
HTMLEditUtils::IsBlockElement(
*aStartPoint.template ContainerAs<nsIContent>(),
aBlockInlineCheck)) {
// We are at end of the block.
return nullptr;
}
// We are at end of non-block container
return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
*aStartPoint.template ContainerAs<nsIContent>(), aCurrentBlock,
aLeafNodeTypes, IgnoreInsideBlockBoundary(aBlockInlineCheck),
aAncestorLimiter);
*aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes,
IgnoreInsideBlockBoundary(aBlockInlineCheck), aAncestorLimiter);
}
// We have a next node. If it's a block, return it.
@ -1415,8 +1411,7 @@ class HTMLEditUtils final {
return nextContent;
}
if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
aStartPoint.GetContainer()->IsEditable() &&
!nextContent->IsEditable()) {
!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) {
return nextContent;
}
if (HTMLEditUtils::IsContainerNode(*nextContent)) {
@ -1435,21 +1430,15 @@ class HTMLEditUtils final {
* GetPreviousLeafContentOrPreviousBlockElement() returns previous leaf
* content or previous block element of aStartContent inside
* aAncestorLimiter.
* Note that the result may be a content outside aCurrentBlock if
* aStartContent equals aCurrentBlock.
*
* @param aStartContent The start content to scan previous content.
* @param aCurrentBlock Must be ancestor of aStartContent. Despite
* the name, inline content is allowed if
* aStartContent is in an inline editing host.
* @param aLeafNodeTypes See LeafNodeType.
* @param aAncestorLimiter Optional, setting this guarantees the
* result is in aAncestorLimiter unless
* aStartContent is not a descendant of this.
* @param aAncestorLimiter Optional, if you set this, it must be an
* inclusive ancestor of aStartContent.
*/
static nsIContent* GetPreviousLeafContentOrPreviousBlockElement(
const nsIContent& aStartContent, const nsIContent& aCurrentBlock,
const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck,
const nsIContent& aStartContent, const LeafNodeTypes& aLeafNodeTypes,
BlockInlineCheck aBlockInlineCheck,
const Element* aAncestorLimiter = nullptr) {
MOZ_ASSERT_IF(
aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
@ -1468,11 +1457,12 @@ class HTMLEditUtils final {
return nullptr;
}
for (Element* parentElement : aStartContent.AncestorsOfType<Element>()) {
if (parentElement == &aCurrentBlock) {
if (parentElement == aAncestorLimiter ||
HTMLEditUtils::IsBlockElement(*parentElement, aBlockInlineCheck)) {
return nullptr;
}
if (parentElement == aAncestorLimiter) {
NS_WARNING("Reached editing host while climbing up the DOM tree");
if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
!parentElement->IsEditable()) {
return nullptr;
}
previousContent = parentElement->GetPreviousSibling();
@ -1493,7 +1483,7 @@ class HTMLEditUtils final {
return previousContent;
}
if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
aStartContent.IsEditable() && !previousContent->IsEditable()) {
!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
return previousContent;
}
if (HTMLEditUtils::IsContainerNode(*previousContent)) {
@ -1514,8 +1504,7 @@ class HTMLEditUtils final {
template <typename PT, typename CT>
static nsIContent* GetPreviousLeafContentOrPreviousBlockElement(
const EditorDOMPointBase<PT, CT>& aStartPoint,
const nsIContent& aCurrentBlock, const LeafNodeTypes& aLeafNodeTypes,
BlockInlineCheck aBlockInlineCheck,
const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck,
const Element* aAncestorLimiter = nullptr) {
MOZ_ASSERT(aStartPoint.IsSet());
MOZ_ASSERT_IF(
@ -1529,27 +1518,29 @@ class HTMLEditUtils final {
}
if (aStartPoint.IsInTextNode()) {
return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
*aStartPoint.template ContainerAs<Text>(), aCurrentBlock,
aLeafNodeTypes, aBlockInlineCheck, aAncestorLimiter);
*aStartPoint.template ContainerAs<Text>(), aLeafNodeTypes,
aBlockInlineCheck, aAncestorLimiter);
}
if (!HTMLEditUtils::IsContainerNode(
*aStartPoint.template ContainerAs<nsIContent>())) {
return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
*aStartPoint.template ContainerAs<nsIContent>(), aCurrentBlock,
aLeafNodeTypes, aBlockInlineCheck, aAncestorLimiter);
*aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes,
aBlockInlineCheck, aAncestorLimiter);
}
if (aStartPoint.IsStartOfContainer()) {
if (aStartPoint.GetContainer() == &aCurrentBlock) {
if (aStartPoint.GetContainer() == aAncestorLimiter ||
HTMLEditUtils::IsBlockElement(
*aStartPoint.template ContainerAs<nsIContent>(),
aBlockInlineCheck)) {
// We are at start of the block.
return nullptr;
}
// We are at start of non-block container
return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
*aStartPoint.template ContainerAs<nsIContent>(), aCurrentBlock,
aLeafNodeTypes, IgnoreInsideBlockBoundary(aBlockInlineCheck),
aAncestorLimiter);
*aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes,
IgnoreInsideBlockBoundary(aBlockInlineCheck), aAncestorLimiter);
}
nsCOMPtr<nsIContent> previousContent =
@ -1563,8 +1554,7 @@ class HTMLEditUtils final {
return previousContent;
}
if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
aStartPoint.GetContainer()->IsEditable() &&
!previousContent->IsEditable()) {
!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
return previousContent;
}
if (HTMLEditUtils::IsContainerNode(*previousContent)) {
@ -1620,11 +1610,23 @@ class HTMLEditUtils final {
* aAncestorTypes.
*/
enum class AncestorType {
// If there is an ancestor block, it's a limiter of the scan.
ClosestBlockElement,
// If there is no ancestor block in the range, the topmost inline element is
// a limiter of the scan.
MostDistantInlineElementInBlock,
EditableElement,
IgnoreHRElement, // Ignore ancestor <hr> element since invalid structure
// Ignore ancestor <hr> elements to check whether a block.
IgnoreHRElement,
// If there is an ancestor <button> element, it's also a limiter of the
// scan.
ButtonElement,
// The root element of the scan start node or the ancestor limiter may be
// return if there is no proper element.
AllowRootOrAncestorLimiterElement,
// Limit to editable elements. If it reaches an non-editable element,
// return its child element.
EditableElement,
};
using AncestorTypes = EnumSet<AncestorType>;
constexpr static AncestorTypes

View file

@ -24,7 +24,8 @@
#include "PendingStyles.h"
#include "ReplaceTextTransaction.h"
#include "SplitNodeTransaction.h"
#include "WSRunObject.h"
#include "WhiteSpaceVisibilityKeeper.h"
#include "WSRunScanner.h"
#include "mozilla/ComposerCommandsUpdater.h"
#include "mozilla/ContentIterator.h"
@ -1180,7 +1181,7 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
// Chromium collapses selection to start of the editing host when this
// is the last leaf content. So, we don't need special handling here.
leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
*leafElement, *editingHost,
*leafElement,
{LeafNodeType::LeafNodeOrNonEditableNode,
LeafNodeType::LeafNodeOrChildBlock},
BlockInlineCheck::UseComputedDisplayStyle, editingHost);
@ -1206,7 +1207,7 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
}
// If it's an invisible text node, keep scanning next leaf.
leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
*leafContent, *editingHost,
*leafContent,
{LeafNodeType::LeafNodeOrNonEditableNode,
LeafNodeType::LeafNodeOrChildBlock},
BlockInlineCheck::UseComputedDisplayStyle, editingHost);
@ -1253,7 +1254,7 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
// Otherwise, we must meet an empty block element or a data node like
// comment node. Let's ignore it.
leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
*leafContent, *editingHost,
*leafContent,
{LeafNodeType::LeafNodeOrNonEditableNode,
LeafNodeType::LeafNodeOrChildBlock},
BlockInlineCheck::UseComputedDisplayStyle, editingHost);

View file

@ -17,7 +17,8 @@
#include "InternetCiter.h"
#include "PendingStyles.h"
#include "SelectionState.h"
#include "WSRunObject.h"
#include "WhiteSpaceVisibilityKeeper.h"
#include "WSRunScanner.h"
#include "ErrorList.h"
#include "mozilla/dom/Comment.h"

View file

@ -7,7 +7,6 @@
#include "HTMLEditor.h"
#include "HTMLEditorNestedClasses.h"
#include <algorithm>
#include <utility>
#include "AutoClonedRangeArray.h"
@ -19,16 +18,12 @@
#include "HTMLEditHelpers.h"
#include "HTMLEditorInlines.h"
#include "HTMLEditUtils.h"
#include "WSRunObject.h"
#include "WhiteSpaceVisibilityKeeper.h"
#include "WSRunScanner.h"
#include "ErrorList.h"
#include "js/ErrorReport.h"
#include "mozilla/Assertions.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/ComputedStyle.h" // for ComputedStyle
#include "mozilla/ContentIterator.h"
#include "mozilla/EditorDOMPoint.h"
#include "mozilla/EditorForwards.h"
#include "mozilla/InternalMutationEvent.h"
#include "mozilla/Logging.h"
#include "mozilla/Maybe.h"
@ -41,8 +36,6 @@
#include "mozilla/dom/ElementInlines.h" // for Element::IsContentEditablePlainTextOnly
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/mozalloc.h"
#include "nsAString.h"
#include "nsAtom.h"
#include "nsComputedDOMStyle.h" // for nsComputedDOMStyle
#include "nsContentUtils.h"
@ -212,8 +205,7 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
const Element& aEditingHost);
nsresult ComputeRangesToDeleteTextAroundCollapsedRanges(
nsIEditor::EDirection aDirectionAndAmount,
AutoClonedSelectionRangeArray& aRangesToDelete,
const Element& aEditingHost) const;
AutoClonedSelectionRangeArray& aRangesToDelete) const;
/**
* Handles deletion of collapsed selection at white-spaces in a text node.
@ -261,7 +253,7 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
const WSRunScanner& aWSRunScannerAtCaret,
const Element& aEditingHost);
nsresult ComputeRangesToDeleteAtomicContent(
Element* aEditingHost, const nsIContent& aAtomicContent,
const nsIContent& aAtomicContent,
AutoClonedSelectionRangeArray& aRangesToDelete) const;
/**
@ -1446,16 +1438,12 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
if (NS_WARN_IF(!startPoint.IsSet())) {
return NS_ERROR_FAILURE;
}
RefPtr<Element> editingHost = aHTMLEditor.ComputeEditingHost();
if (NS_WARN_IF(!editingHost)) {
return NS_ERROR_FAILURE;
}
if (startPoint.IsInContentNode()) {
AutoEmptyBlockAncestorDeleter deleter;
if (deleter.ScanEmptyBlockInclusiveAncestor(
aHTMLEditor, *startPoint.ContainerAs<nsIContent>())) {
nsresult rv = deleter.ComputeTargetRanges(
aHTMLEditor, aDirectionAndAmount, *editingHost, aRangesToDelete);
aHTMLEditor, aDirectionAndAmount, aEditingHost, aRangesToDelete);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"AutoEmptyBlockAncestorDeleter::ComputeTargetRanges() failed");
@ -1491,8 +1479,7 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
Result<bool, nsresult> shrunkenResult =
aRangesToDelete.ShrinkRangesIfStartFromOrEndAfterAtomicContent(
aHTMLEditor, aDirectionAndAmount,
AutoClonedRangeArray::IfSelectingOnlyOneAtomicContent::Collapse,
editingHost);
AutoClonedRangeArray::IfSelectingOnlyOneAtomicContent::Collapse);
if (shrunkenResult.isErr()) {
NS_WARNING(
"AutoClonedRangeArray::"
@ -1512,7 +1499,7 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
return NS_SUCCESS_DOM_NO_OPERATION;
}
nsresult rv = FallbackToComputeRangesToDeleteRangesWithTransaction(
aHTMLEditor, aRangesToDelete, *editingHost);
aHTMLEditor, aRangesToDelete, aEditingHost);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"AutoDeleteRangesHandler::"
@ -1531,7 +1518,7 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
return NS_SUCCESS_DOM_NO_OPERATION;
}
WSRunScanner wsRunScannerAtCaret(
editingHost, caretPoint,
&aEditingHost, caretPoint,
BlockInlineCheck::UseComputedDisplayOutsideStyle);
const WSScanResult scanFromCaretPointResult =
aDirectionAndAmount == nsIEditor::eNext
@ -1776,8 +1763,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
Result<bool, nsresult> shrunkenResult =
aRangesToDelete.ShrinkRangesIfStartFromOrEndAfterAtomicContent(
aHTMLEditor, aDirectionAndAmount,
AutoClonedRangeArray::IfSelectingOnlyOneAtomicContent::Collapse,
&aEditingHost);
AutoClonedRangeArray::IfSelectingOnlyOneAtomicContent::Collapse);
if (MOZ_UNLIKELY(shrunkenResult.isErr())) {
NS_WARNING(
"AutoClonedRangeArray::"
@ -1957,8 +1943,8 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
NS_WARNING("AutoClonedRangeArray::Collapse() failed");
return NS_ERROR_FAILURE;
}
rv = ComputeRangesToDeleteTextAroundCollapsedRanges(
aDirectionAndAmount, aRangesToDelete, aEditingHost);
rv = ComputeRangesToDeleteTextAroundCollapsedRanges(aDirectionAndAmount,
aRangesToDelete);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"AutoDeleteRangesHandler::"
@ -1982,8 +1968,8 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
"removable atomic content");
return NS_ERROR_FAILURE;
}
nsresult rv = ComputeRangesToDeleteAtomicContent(
aWSRunScannerAtCaret.GetEditingHost(), *atomicContent, aRangesToDelete);
nsresult rv =
ComputeRangesToDeleteAtomicContent(*atomicContent, aRangesToDelete);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent() failed");
@ -2257,8 +2243,7 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
nsresult HTMLEditor::AutoDeleteRangesHandler::
ComputeRangesToDeleteTextAroundCollapsedRanges(
nsIEditor::EDirection aDirectionAndAmount,
AutoClonedSelectionRangeArray& aRangesToDelete,
const Element& aEditingHost) const {
AutoClonedSelectionRangeArray& aRangesToDelete) const {
MOZ_ASSERT(aDirectionAndAmount == nsIEditor::eNext ||
aDirectionAndAmount == nsIEditor::ePrevious);
@ -2272,8 +2257,7 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::
EditorDOMRangeInTexts rangeToDelete;
if (aDirectionAndAmount == nsIEditor::eNext) {
Result<EditorDOMRangeInTexts, nsresult> result =
WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(caretPosition,
aEditingHost);
WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(caretPosition);
if (result.isErr()) {
NS_WARNING(
"WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom() failed");
@ -2285,8 +2269,7 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::
}
} else {
Result<EditorDOMRangeInTexts, nsresult> result =
WSRunScanner::GetRangeInTextNodesToBackspaceFrom(caretPosition,
aEditingHost);
WSRunScanner::GetRangeInTextNodesToBackspaceFrom(caretPosition);
if (result.isErr()) {
NS_WARNING("WSRunScanner::GetRangeInTextNodesToBackspaceFrom() failed");
return result.unwrapErr();
@ -2317,7 +2300,7 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteTextAroundCollapsedRanges(
aDirectionAndAmount == nsIEditor::ePrevious);
nsresult rv = ComputeRangesToDeleteTextAroundCollapsedRanges(
aDirectionAndAmount, aRangesToDelete, aEditingHost);
aDirectionAndAmount, aRangesToDelete);
if (NS_FAILED(rv)) {
return Err(NS_ERROR_FAILURE);
}
@ -2718,11 +2701,10 @@ nsIContent* HTMLEditor::AutoDeleteRangesHandler::GetAtomicContentToDelete(
nsresult
HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent(
Element* aEditingHost, const nsIContent& aAtomicContent,
const nsIContent& aAtomicContent,
AutoClonedSelectionRangeArray& aRangesToDelete) const {
EditorDOMRange rangeToDelete =
WSRunScanner::GetRangesForDeletingAtomicContent(aEditingHost,
aAtomicContent);
WSRunScanner::GetRangesForDeletingAtomicContent(aAtomicContent);
if (!rangeToDelete.IsPositioned()) {
NS_WARNING("WSRunScanner::GetRangeForDeleteAContentNode() failed");
return NS_ERROR_FAILURE;
@ -3914,7 +3896,6 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges(
EditorDOMRange firstRange(aRangesToDelete.FirstRangeRef());
EditorDOMRange extendedRange =
WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
aHTMLEditor.ComputeEditingHost(),
EditorDOMRange(aRangesToDelete.FirstRangeRef()));
if (firstRange != extendedRange) {
nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd(

View file

@ -15,7 +15,6 @@
#include "EditorUtils.h"
#include "HTMLEditHelpers.h"
#include "HTMLEditUtils.h"
#include "WSRunObject.h"
#include "mozilla/Assertions.h"
#include "mozilla/OwningNonNull.h"
@ -39,6 +38,8 @@
namespace mozilla {
using namespace dom;
using EditorType = EditorUtils::EditorType;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;

View file

@ -17,7 +17,7 @@
#include "HTMLEditUtils.h"
#include "PendingStyles.h"
#include "SelectionState.h"
#include "WSRunObject.h"
#include "WSRunScanner.h"
#include "mozilla/Assertions.h"
#include "mozilla/ContentIterator.h"

View file

@ -0,0 +1,955 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WSRunScanner.h"
#include "EditorDOMPoint.h"
#include "ErrorList.h"
#include "HTMLEditor.h"
#include "HTMLEditUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h" // for AssertedCast
#include "nsDebug.h"
#include "nsError.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsRange.h"
#include "nsTextFragment.h"
namespace mozilla {
using namespace dom;
template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPoint& aPoint) const;
template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorRawDOMPoint& aPoint) const;
template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPointInText& aPoint) const;
template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorRawDOMPointInText& aPoint) const;
template WSScanResult
WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPoint& aPoint) const;
template WSScanResult
WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
const EditorRawDOMPoint& aPoint) const;
template WSScanResult
WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPointInText& aPoint) const;
template WSScanResult
WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
const EditorRawDOMPointInText& aPoint) const;
template EditorDOMPoint WSRunScanner::GetAfterLastVisiblePoint(Text& aTextNode);
template EditorRawDOMPoint WSRunScanner::GetAfterLastVisiblePoint(
Text& aTextNode);
template EditorDOMPoint WSRunScanner::GetFirstVisiblePoint(Text& aTextNode);
template EditorRawDOMPoint WSRunScanner::GetFirstVisiblePoint(Text& aTextNode);
template <typename PT, typename CT>
WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPointBase<PT, CT>& aPoint) const {
MOZ_ASSERT(aPoint.IsSet());
MOZ_ASSERT(aPoint.IsInComposedDoc());
if (MOZ_UNLIKELY(!aPoint.IsSet())) {
return WSScanResult::Error();
}
// We may not be able to check editable state in uncomposed tree as expected.
// For example, only some descendants in an editing host is temporarily
// removed from the tree, they are not editable unless nested contenteditable
// attribute is set to "true".
if (MOZ_UNLIKELY(!aPoint.IsInComposedDoc())) {
return WSScanResult(WSScanResult::ScanDirection::Backward,
*aPoint.template ContainerAs<nsIContent>(),
WSType::InUncomposedDoc, mBlockInlineCheck);
}
if (!TextFragmentDataAtStartRef().IsInitialized()) {
return WSScanResult::Error();
}
// If the range has visible text and start of the visible text is before
// aPoint, return previous character in the text.
const VisibleWhiteSpacesData& visibleWhiteSpaces =
TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
if (visibleWhiteSpaces.IsInitialized() &&
visibleWhiteSpaces.StartRef().IsBefore(aPoint)) {
// If the visible things are not editable, we shouldn't scan "editable"
// things now. Whether keep scanning editable things or not should be
// considered by the caller.
if (aPoint.GetChild() && !aPoint.GetChild()->IsEditable()) {
return WSScanResult(WSScanResult::ScanDirection::Backward,
*aPoint.GetChild(), WSType::SpecialContent,
mBlockInlineCheck);
}
const auto atPreviousChar =
GetPreviousEditableCharPoint<EditorRawDOMPointInText>(aPoint);
// When it's a non-empty text node, return it.
if (atPreviousChar.IsSet() && !atPreviousChar.IsContainerEmpty()) {
MOZ_ASSERT(!atPreviousChar.IsEndOfContainer());
return WSScanResult(WSScanResult::ScanDirection::Backward,
atPreviousChar.template NextPoint<EditorDOMPoint>(),
atPreviousChar.IsCharCollapsibleASCIISpaceOrNBSP()
? WSType::CollapsibleWhiteSpaces
: atPreviousChar.IsCharPreformattedNewLine()
? WSType::PreformattedLineBreak
: WSType::NonCollapsibleCharacters,
mBlockInlineCheck);
}
}
if (NS_WARN_IF(TextFragmentDataAtStartRef().StartRawReason() ==
WSType::UnexpectedError)) {
return WSScanResult::Error();
}
switch (TextFragmentDataAtStartRef().StartRawReason()) {
case WSType::CollapsibleWhiteSpaces:
case WSType::NonCollapsibleCharacters:
case WSType::PreformattedLineBreak:
MOZ_ASSERT(TextFragmentDataAtStartRef().StartRef().IsSet());
// XXX: If we find the character at last of a text node and we started
// scanning from following text node of it, some callers may work with the
// point in the following text node instead of end of the found text node.
return WSScanResult(WSScanResult::ScanDirection::Backward,
TextFragmentDataAtStartRef().StartRef(),
TextFragmentDataAtStartRef().StartRawReason(),
mBlockInlineCheck);
default:
break;
}
// Otherwise, return the start of the range.
if (TextFragmentDataAtStartRef().GetStartReasonContent() !=
TextFragmentDataAtStartRef().StartRef().GetContainer()) {
if (NS_WARN_IF(!TextFragmentDataAtStartRef().GetStartReasonContent())) {
return WSScanResult::Error();
}
// In this case, TextFragmentDataAtStartRef().StartRef().Offset() is not
// meaningful.
return WSScanResult(WSScanResult::ScanDirection::Backward,
*TextFragmentDataAtStartRef().GetStartReasonContent(),
TextFragmentDataAtStartRef().StartRawReason(),
mBlockInlineCheck);
}
if (NS_WARN_IF(!TextFragmentDataAtStartRef().StartRef().IsSet())) {
return WSScanResult::Error();
}
return WSScanResult(WSScanResult::ScanDirection::Backward,
TextFragmentDataAtStartRef().StartRef(),
TextFragmentDataAtStartRef().StartRawReason(),
mBlockInlineCheck);
}
template <typename PT, typename CT>
WSScanResult WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPointBase<PT, CT>& aPoint) const {
MOZ_ASSERT(aPoint.IsSet());
MOZ_ASSERT(aPoint.IsInComposedDoc());
if (MOZ_UNLIKELY(!aPoint.IsSet())) {
return WSScanResult::Error();
}
// We may not be able to check editable state in uncomposed tree as expected.
// For example, only some descendants in an editing host is temporarily
// removed from the tree, they are not editable unless nested contenteditable
// attribute is set to "true".
if (MOZ_UNLIKELY(!aPoint.IsInComposedDoc())) {
return WSScanResult(WSScanResult::ScanDirection::Forward,
*aPoint.template ContainerAs<nsIContent>(),
WSType::InUncomposedDoc, mBlockInlineCheck);
}
if (!TextFragmentDataAtStartRef().IsInitialized()) {
return WSScanResult::Error();
}
// If the range has visible text and aPoint equals or is before the end of the
// visible text, return inclusive next character in the text.
const VisibleWhiteSpacesData& visibleWhiteSpaces =
TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
if (visibleWhiteSpaces.IsInitialized() &&
aPoint.EqualsOrIsBefore(visibleWhiteSpaces.EndRef())) {
// If the visible things are not editable, we shouldn't scan "editable"
// things now. Whether keep scanning editable things or not should be
// considered by the caller.
if (aPoint.GetChild() && !aPoint.GetChild()->IsEditable()) {
return WSScanResult(WSScanResult::ScanDirection::Forward,
*aPoint.GetChild(), WSType::SpecialContent,
mBlockInlineCheck);
}
const auto atNextChar =
GetInclusiveNextEditableCharPoint<EditorDOMPoint>(aPoint);
// When it's a non-empty text node, return it.
if (atNextChar.IsSet() && !atNextChar.IsContainerEmpty()) {
return WSScanResult(WSScanResult::ScanDirection::Forward, atNextChar,
!atNextChar.IsEndOfContainer() &&
atNextChar.IsCharCollapsibleASCIISpaceOrNBSP()
? WSType::CollapsibleWhiteSpaces
: !atNextChar.IsEndOfContainer() &&
atNextChar.IsCharPreformattedNewLine()
? WSType::PreformattedLineBreak
: WSType::NonCollapsibleCharacters,
mBlockInlineCheck);
}
}
if (NS_WARN_IF(TextFragmentDataAtStartRef().EndRawReason() ==
WSType::UnexpectedError)) {
return WSScanResult::Error();
}
switch (TextFragmentDataAtStartRef().EndRawReason()) {
case WSType::CollapsibleWhiteSpaces:
case WSType::NonCollapsibleCharacters:
case WSType::PreformattedLineBreak:
MOZ_ASSERT(TextFragmentDataAtStartRef().StartRef().IsSet());
// XXX: If we find the character at start of a text node and we
// started scanning from preceding text node of it, some callers may want
// to work with the point at end of the preceding text node instead of
// start of the found text node.
return WSScanResult(WSScanResult::ScanDirection::Forward,
TextFragmentDataAtStartRef().EndRef(),
TextFragmentDataAtStartRef().EndRawReason(),
mBlockInlineCheck);
default:
break;
}
// Otherwise, return the end of the range.
if (TextFragmentDataAtStartRef().GetEndReasonContent() !=
TextFragmentDataAtStartRef().EndRef().GetContainer()) {
if (NS_WARN_IF(!TextFragmentDataAtStartRef().GetEndReasonContent())) {
return WSScanResult::Error();
}
// In this case, TextFragmentDataAtStartRef().EndRef().Offset() is not
// meaningful.
return WSScanResult(WSScanResult::ScanDirection::Forward,
*TextFragmentDataAtStartRef().GetEndReasonContent(),
TextFragmentDataAtStartRef().EndRawReason(),
mBlockInlineCheck);
}
if (NS_WARN_IF(!TextFragmentDataAtStartRef().EndRef().IsSet())) {
return WSScanResult::Error();
}
return WSScanResult(WSScanResult::ScanDirection::Forward,
TextFragmentDataAtStartRef().EndRef(),
TextFragmentDataAtStartRef().EndRawReason(),
mBlockInlineCheck);
}
// static
template <typename EditorDOMPointType>
EditorDOMPointType WSRunScanner::GetAfterLastVisiblePoint(Text& aTextNode) {
EditorDOMPoint atLastCharOfTextNode(
&aTextNode, AssertedCast<uint32_t>(std::max<int64_t>(
static_cast<int64_t>(aTextNode.Length()) - 1, 0)));
if (!atLastCharOfTextNode.IsContainerEmpty() &&
!atLastCharOfTextNode.IsCharCollapsibleASCIISpace()) {
return EditorDOMPointType::AtEndOf(aTextNode);
}
const TextFragmentData textFragmentData(
Scan::EditableNodes, atLastCharOfTextNode,
BlockInlineCheck::UseComputedDisplayStyle);
if (NS_WARN_IF(!textFragmentData.IsInitialized())) {
return EditorDOMPointType(); // TODO: Make here return error with Err.
}
const EditorDOMRange& invisibleWhiteSpaceRange =
textFragmentData.InvisibleTrailingWhiteSpaceRangeRef();
if (!invisibleWhiteSpaceRange.IsPositioned() ||
invisibleWhiteSpaceRange.Collapsed()) {
return EditorDOMPointType::AtEndOf(aTextNode);
}
return invisibleWhiteSpaceRange.StartRef().To<EditorDOMPointType>();
}
// static
template <typename EditorDOMPointType>
EditorDOMPointType WSRunScanner::GetFirstVisiblePoint(Text& aTextNode) {
EditorDOMPoint atStartOfTextNode(&aTextNode, 0);
if (!atStartOfTextNode.IsContainerEmpty() &&
atStartOfTextNode.IsCharCollapsibleASCIISpace()) {
return atStartOfTextNode.To<EditorDOMPointType>();
}
const TextFragmentData textFragmentData(
Scan::EditableNodes, atStartOfTextNode,
BlockInlineCheck::UseComputedDisplayStyle);
if (NS_WARN_IF(!textFragmentData.IsInitialized())) {
return EditorDOMPointType(); // TODO: Make here return error with Err.
}
const EditorDOMRange& invisibleWhiteSpaceRange =
textFragmentData.InvisibleLeadingWhiteSpaceRangeRef();
if (!invisibleWhiteSpaceRange.IsPositioned() ||
invisibleWhiteSpaceRange.Collapsed()) {
return atStartOfTextNode.To<EditorDOMPointType>();
}
return invisibleWhiteSpaceRange.EndRef().To<EditorDOMPointType>();
}
char16_t WSRunScanner::GetCharAt(Text* aTextNode, uint32_t aOffset) const {
// return 0 if we can't get a char, for whatever reason
if (NS_WARN_IF(!aTextNode) ||
NS_WARN_IF(aOffset >= aTextNode->TextDataLength())) {
return 0;
}
return aTextNode->TextFragment().CharAt(aOffset);
}
/*****************************************************************************
* Implementation for new white-space normalizer
*****************************************************************************/
// static
EditorDOMRangeInTexts
WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
const TextFragmentData& aStart, const TextFragmentData& aEnd) {
// Corresponding to handling invisible white-spaces part of
// `TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange()` and
// `TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange()`
MOZ_ASSERT(aStart.ScanStartRef().IsSetAndValid());
MOZ_ASSERT(aEnd.ScanStartRef().IsSetAndValid());
MOZ_ASSERT(aStart.ScanStartRef().EqualsOrIsBefore(aEnd.ScanStartRef()));
MOZ_ASSERT(aStart.ScanStartRef().IsInTextNode());
MOZ_ASSERT(aEnd.ScanStartRef().IsInTextNode());
// XXX `GetReplaceRangeDataAtEndOfDeletionRange()` and
// `GetReplaceRangeDataAtStartOfDeletionRange()` use
// `GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt()` and
// `GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt()`.
// However, they are really odd as mentioned with "XXX" comments
// in them. For the new white-space normalizer, we need to treat
// invisible white-spaces stricter because the legacy path handles
// white-spaces multiple times (e.g., calling `HTMLEditor::
// DeleteNodeIfInvisibleAndEditableTextNode()` later) and that hides
// the bug, but in the new path, we should stop doing same things
// multiple times for both performance and footprint. Therefore,
// even though the result might be different in some edge cases,
// we should use clean path for now. Perhaps, we should fix the odd
// cases before shipping `beforeinput` event in release channel.
const EditorDOMRange& invisibleLeadingWhiteSpaceRange =
aStart.InvisibleLeadingWhiteSpaceRangeRef();
const EditorDOMRange& invisibleTrailingWhiteSpaceRange =
aEnd.InvisibleTrailingWhiteSpaceRangeRef();
const bool hasInvisibleLeadingWhiteSpaces =
invisibleLeadingWhiteSpaceRange.IsPositioned() &&
!invisibleLeadingWhiteSpaceRange.Collapsed();
const bool hasInvisibleTrailingWhiteSpaces =
invisibleLeadingWhiteSpaceRange != invisibleTrailingWhiteSpaceRange &&
invisibleTrailingWhiteSpaceRange.IsPositioned() &&
!invisibleTrailingWhiteSpaceRange.Collapsed();
EditorDOMRangeInTexts result(aStart.ScanStartRef().AsInText(),
aEnd.ScanStartRef().AsInText());
MOZ_ASSERT(result.IsPositionedAndValid());
if (!hasInvisibleLeadingWhiteSpaces && !hasInvisibleTrailingWhiteSpaces) {
return result;
}
MOZ_ASSERT_IF(
hasInvisibleLeadingWhiteSpaces && hasInvisibleTrailingWhiteSpaces,
invisibleLeadingWhiteSpaceRange.StartRef().IsBefore(
invisibleTrailingWhiteSpaceRange.StartRef()));
const EditorDOMPoint& aroundFirstInvisibleWhiteSpace =
hasInvisibleLeadingWhiteSpaces
? invisibleLeadingWhiteSpaceRange.StartRef()
: invisibleTrailingWhiteSpaceRange.StartRef();
if (aroundFirstInvisibleWhiteSpace.IsBefore(result.StartRef())) {
if (aroundFirstInvisibleWhiteSpace.IsInTextNode()) {
result.SetStart(aroundFirstInvisibleWhiteSpace.AsInText());
MOZ_ASSERT(result.IsPositionedAndValid());
} else {
const auto atFirstInvisibleWhiteSpace =
hasInvisibleLeadingWhiteSpaces
? aStart.GetInclusiveNextCharPoint<EditorDOMPointInText>(
aroundFirstInvisibleWhiteSpace, IgnoreNonEditableNodes::Yes)
: aEnd.GetInclusiveNextCharPoint<EditorDOMPointInText>(
aroundFirstInvisibleWhiteSpace,
IgnoreNonEditableNodes::Yes);
MOZ_ASSERT(atFirstInvisibleWhiteSpace.IsSet());
MOZ_ASSERT(
atFirstInvisibleWhiteSpace.EqualsOrIsBefore(result.StartRef()));
result.SetStart(atFirstInvisibleWhiteSpace);
MOZ_ASSERT(result.IsPositionedAndValid());
}
}
MOZ_ASSERT_IF(
hasInvisibleLeadingWhiteSpaces && hasInvisibleTrailingWhiteSpaces,
invisibleLeadingWhiteSpaceRange.EndRef().IsBefore(
invisibleTrailingWhiteSpaceRange.EndRef()));
const EditorDOMPoint& afterLastInvisibleWhiteSpace =
hasInvisibleTrailingWhiteSpaces
? invisibleTrailingWhiteSpaceRange.EndRef()
: invisibleLeadingWhiteSpaceRange.EndRef();
if (afterLastInvisibleWhiteSpace.EqualsOrIsBefore(result.EndRef())) {
MOZ_ASSERT(result.IsPositionedAndValid());
return result;
}
if (afterLastInvisibleWhiteSpace.IsInTextNode()) {
result.SetEnd(afterLastInvisibleWhiteSpace.AsInText());
MOZ_ASSERT(result.IsPositionedAndValid());
return result;
}
const auto atLastInvisibleWhiteSpace =
hasInvisibleTrailingWhiteSpaces
? aEnd.GetPreviousCharPoint<EditorDOMPointInText>(
afterLastInvisibleWhiteSpace, IgnoreNonEditableNodes::Yes)
: aStart.GetPreviousCharPoint<EditorDOMPointInText>(
afterLastInvisibleWhiteSpace, IgnoreNonEditableNodes::Yes);
MOZ_ASSERT(atLastInvisibleWhiteSpace.IsSet());
MOZ_ASSERT(atLastInvisibleWhiteSpace.IsContainerEmpty() ||
atLastInvisibleWhiteSpace.IsAtLastContent());
MOZ_ASSERT(result.EndRef().EqualsOrIsBefore(atLastInvisibleWhiteSpace));
result.SetEnd(atLastInvisibleWhiteSpace.IsEndOfContainer()
? atLastInvisibleWhiteSpace
: atLastInvisibleWhiteSpace.NextPoint());
MOZ_ASSERT(result.IsPositionedAndValid());
return result;
}
// static
Result<EditorDOMRangeInTexts, nsresult>
WSRunScanner::GetRangeInTextNodesToBackspaceFrom(const EditorDOMPoint& aPoint) {
// Corresponding to computing delete range part of
// `WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace()`
MOZ_ASSERT(aPoint.IsSetAndValid());
const TextFragmentData textFragmentDataAtCaret(
Scan::EditableNodes, aPoint, BlockInlineCheck::UseComputedDisplayStyle);
if (NS_WARN_IF(!textFragmentDataAtCaret.IsInitialized())) {
return Err(NS_ERROR_FAILURE);
}
auto atPreviousChar =
textFragmentDataAtCaret.GetPreviousCharPoint<EditorDOMPointInText>(
aPoint, IgnoreNonEditableNodes::Yes);
if (!atPreviousChar.IsSet()) {
return EditorDOMRangeInTexts(); // There is no content in the block.
}
// XXX When previous char point is in an empty text node, we do nothing,
// but this must look odd from point of user view. We should delete
// something before aPoint.
if (atPreviousChar.IsEndOfContainer()) {
return EditorDOMRangeInTexts();
}
// Extend delete range if previous char is a low surrogate following
// a high surrogate.
EditorDOMPointInText atNextChar = atPreviousChar.NextPoint();
if (!atPreviousChar.IsStartOfContainer()) {
if (atPreviousChar.IsCharLowSurrogateFollowingHighSurrogate()) {
atPreviousChar = atPreviousChar.PreviousPoint();
}
// If caret is in middle of a surrogate pair, delete the surrogate pair
// (blink-compat).
else if (atPreviousChar.IsCharHighSurrogateFollowedByLowSurrogate()) {
atNextChar = atNextChar.NextPoint();
}
}
// If previous char is an collapsible white-spaces, delete all adjacent
// white-spaces which are collapsed together.
EditorDOMRangeInTexts rangeToDelete;
if (atPreviousChar.IsCharCollapsibleASCIISpace() ||
atPreviousChar.IsCharPreformattedNewLineCollapsedWithWhiteSpaces()) {
const auto startToDelete =
textFragmentDataAtCaret
.GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>(
atPreviousChar, nsIEditor::ePrevious,
IgnoreNonEditableNodes::Yes);
if (!startToDelete.IsSet()) {
NS_WARNING(
"WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
return Err(NS_ERROR_FAILURE);
}
const auto endToDelete =
textFragmentDataAtCaret
.GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>(
atPreviousChar, nsIEditor::ePrevious,
IgnoreNonEditableNodes::Yes);
if (!endToDelete.IsSet()) {
NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
return Err(NS_ERROR_FAILURE);
}
rangeToDelete = EditorDOMRangeInTexts(startToDelete, endToDelete);
}
// if previous char is not a collapsible white-space, remove it.
else {
rangeToDelete = EditorDOMRangeInTexts(atPreviousChar, atNextChar);
}
// If there is no removable and visible content, we should do nothing.
if (rangeToDelete.Collapsed()) {
return EditorDOMRangeInTexts();
}
// And also delete invisible white-spaces if they become visible.
const TextFragmentData textFragmentDataAtStart =
rangeToDelete.StartRef() != aPoint
? TextFragmentData(Scan::EditableNodes, rangeToDelete.StartRef(),
BlockInlineCheck::UseComputedDisplayStyle)
: textFragmentDataAtCaret;
const TextFragmentData textFragmentDataAtEnd =
rangeToDelete.EndRef() != aPoint
? TextFragmentData(Scan::EditableNodes, rangeToDelete.EndRef(),
BlockInlineCheck::UseComputedDisplayStyle)
: textFragmentDataAtCaret;
if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized()) ||
NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
return Err(NS_ERROR_FAILURE);
}
EditorDOMRangeInTexts extendedRangeToDelete =
WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
textFragmentDataAtStart, textFragmentDataAtEnd);
MOZ_ASSERT(extendedRangeToDelete.IsPositionedAndValid());
return extendedRangeToDelete.IsPositioned() ? extendedRangeToDelete
: rangeToDelete;
}
// static
Result<EditorDOMRangeInTexts, nsresult>
WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(
const EditorDOMPoint& aPoint) {
// Corresponding to computing delete range part of
// `WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace()`
MOZ_ASSERT(aPoint.IsSetAndValid());
TextFragmentData textFragmentDataAtCaret(
Scan::EditableNodes, aPoint, BlockInlineCheck::UseComputedDisplayStyle);
if (NS_WARN_IF(!textFragmentDataAtCaret.IsInitialized())) {
return Err(NS_ERROR_FAILURE);
}
auto atCaret =
textFragmentDataAtCaret.GetInclusiveNextCharPoint<EditorDOMPointInText>(
aPoint, IgnoreNonEditableNodes::Yes);
if (!atCaret.IsSet()) {
return EditorDOMRangeInTexts(); // There is no content in the block.
}
// If caret is in middle of a surrogate pair, we should remove next
// character (blink-compat).
if (!atCaret.IsEndOfContainer() &&
atCaret.IsCharLowSurrogateFollowingHighSurrogate()) {
atCaret = atCaret.NextPoint();
}
// XXX When next char point is in an empty text node, we do nothing,
// but this must look odd from point of user view. We should delete
// something after aPoint.
if (atCaret.IsEndOfContainer()) {
return EditorDOMRangeInTexts();
}
// Extend delete range if previous char is a low surrogate following
// a high surrogate.
EditorDOMPointInText atNextChar = atCaret.NextPoint();
if (atCaret.IsCharHighSurrogateFollowedByLowSurrogate()) {
atNextChar = atNextChar.NextPoint();
}
// If next char is a collapsible white-space, delete all adjacent white-spaces
// which are collapsed together.
EditorDOMRangeInTexts rangeToDelete;
if (atCaret.IsCharCollapsibleASCIISpace() ||
atCaret.IsCharPreformattedNewLineCollapsedWithWhiteSpaces()) {
const auto startToDelete =
textFragmentDataAtCaret
.GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>(
atCaret, nsIEditor::eNext, IgnoreNonEditableNodes::Yes);
if (!startToDelete.IsSet()) {
NS_WARNING(
"WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
return Err(NS_ERROR_FAILURE);
}
const EditorDOMPointInText endToDelete =
textFragmentDataAtCaret
.GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>(
atCaret, nsIEditor::eNext, IgnoreNonEditableNodes::Yes);
if (!endToDelete.IsSet()) {
NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
return Err(NS_ERROR_FAILURE);
}
rangeToDelete = EditorDOMRangeInTexts(startToDelete, endToDelete);
}
// if next char is not a collapsible white-space, remove it.
else {
rangeToDelete = EditorDOMRangeInTexts(atCaret, atNextChar);
}
// If there is no removable and visible content, we should do nothing.
if (rangeToDelete.Collapsed()) {
return EditorDOMRangeInTexts();
}
// And also delete invisible white-spaces if they become visible.
const TextFragmentData textFragmentDataAtStart =
rangeToDelete.StartRef() != aPoint
? TextFragmentData(Scan::EditableNodes, rangeToDelete.StartRef(),
BlockInlineCheck::UseComputedDisplayStyle)
: textFragmentDataAtCaret;
const TextFragmentData textFragmentDataAtEnd =
rangeToDelete.EndRef() != aPoint
? TextFragmentData(Scan::EditableNodes, rangeToDelete.EndRef(),
BlockInlineCheck::UseComputedDisplayStyle)
: textFragmentDataAtCaret;
if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized()) ||
NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
return Err(NS_ERROR_FAILURE);
}
EditorDOMRangeInTexts extendedRangeToDelete =
WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
textFragmentDataAtStart, textFragmentDataAtEnd);
MOZ_ASSERT(extendedRangeToDelete.IsPositionedAndValid());
return extendedRangeToDelete.IsPositioned() ? extendedRangeToDelete
: rangeToDelete;
}
// static
EditorDOMRange WSRunScanner::GetRangesForDeletingAtomicContent(
const nsIContent& aAtomicContent) {
if (aAtomicContent.IsHTMLElement(nsGkAtoms::br)) {
// Preceding white-spaces should be preserved, but the following
// white-spaces should be invisible around `<br>` element.
const TextFragmentData textFragmentDataAfterBRElement(
Scan::EditableNodes, EditorDOMPoint::After(aAtomicContent),
BlockInlineCheck::UseComputedDisplayStyle);
if (NS_WARN_IF(!textFragmentDataAfterBRElement.IsInitialized())) {
return EditorDOMRange(); // TODO: Make here return error with Err.
}
const EditorDOMRangeInTexts followingInvisibleWhiteSpaces =
textFragmentDataAfterBRElement.GetNonCollapsedRangeInTexts(
textFragmentDataAfterBRElement
.InvisibleLeadingWhiteSpaceRangeRef());
return followingInvisibleWhiteSpaces.IsPositioned() &&
!followingInvisibleWhiteSpaces.Collapsed()
? EditorDOMRange(
EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
followingInvisibleWhiteSpaces.EndRef())
: EditorDOMRange(
EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
EditorDOMPoint::After(aAtomicContent));
}
if (!HTMLEditUtils::IsBlockElement(
aAtomicContent, BlockInlineCheck::UseComputedDisplayStyle)) {
// Both preceding and following white-spaces around it should be preserved
// around inline elements like `<img>`.
return EditorDOMRange(
EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
EditorDOMPoint::After(aAtomicContent));
}
// Both preceding and following white-spaces can be invisible around a
// block element.
const TextFragmentData textFragmentDataBeforeAtomicContent(
Scan::EditableNodes,
EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
BlockInlineCheck::UseComputedDisplayStyle);
if (NS_WARN_IF(!textFragmentDataBeforeAtomicContent.IsInitialized())) {
return EditorDOMRange(); // TODO: Make here return error with Err.
}
const EditorDOMRangeInTexts precedingInvisibleWhiteSpaces =
textFragmentDataBeforeAtomicContent.GetNonCollapsedRangeInTexts(
textFragmentDataBeforeAtomicContent
.InvisibleTrailingWhiteSpaceRangeRef());
const TextFragmentData textFragmentDataAfterAtomicContent(
Scan::EditableNodes, EditorDOMPoint::After(aAtomicContent),
BlockInlineCheck::UseComputedDisplayStyle);
if (NS_WARN_IF(!textFragmentDataAfterAtomicContent.IsInitialized())) {
return EditorDOMRange(); // TODO: Make here return error with Err.
}
const EditorDOMRangeInTexts followingInvisibleWhiteSpaces =
textFragmentDataAfterAtomicContent.GetNonCollapsedRangeInTexts(
textFragmentDataAfterAtomicContent
.InvisibleLeadingWhiteSpaceRangeRef());
if (precedingInvisibleWhiteSpaces.StartRef().IsSet() &&
followingInvisibleWhiteSpaces.EndRef().IsSet()) {
return EditorDOMRange(precedingInvisibleWhiteSpaces.StartRef(),
followingInvisibleWhiteSpaces.EndRef());
}
if (precedingInvisibleWhiteSpaces.StartRef().IsSet()) {
return EditorDOMRange(precedingInvisibleWhiteSpaces.StartRef(),
EditorDOMPoint::After(aAtomicContent));
}
if (followingInvisibleWhiteSpaces.EndRef().IsSet()) {
return EditorDOMRange(
EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
followingInvisibleWhiteSpaces.EndRef());
}
return EditorDOMRange(
EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
EditorDOMPoint::After(aAtomicContent));
}
// static
EditorDOMRange WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
const HTMLEditor& aHTMLEditor, const Element& aLeftBlockElement,
const Element& aRightBlockElement,
const EditorDOMPoint& aPointContainingTheOtherBlock) {
MOZ_ASSERT(&aLeftBlockElement != &aRightBlockElement);
MOZ_ASSERT_IF(
aPointContainingTheOtherBlock.IsSet(),
aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement ||
aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement);
MOZ_ASSERT_IF(
aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement,
aRightBlockElement.IsInclusiveDescendantOf(
aPointContainingTheOtherBlock.GetChild()));
MOZ_ASSERT_IF(
aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement,
aLeftBlockElement.IsInclusiveDescendantOf(
aPointContainingTheOtherBlock.GetChild()));
MOZ_ASSERT_IF(
!aPointContainingTheOtherBlock.IsSet(),
!aRightBlockElement.IsInclusiveDescendantOf(&aLeftBlockElement));
MOZ_ASSERT_IF(
!aPointContainingTheOtherBlock.IsSet(),
!aLeftBlockElement.IsInclusiveDescendantOf(&aRightBlockElement));
MOZ_ASSERT_IF(!aPointContainingTheOtherBlock.IsSet(),
EditorRawDOMPoint(const_cast<Element*>(&aLeftBlockElement))
.IsBefore(EditorRawDOMPoint(
const_cast<Element*>(&aRightBlockElement))));
EditorDOMRange range;
// Include trailing invisible white-spaces in aLeftBlockElement.
const TextFragmentData textFragmentDataAtEndOfLeftBlockElement(
Scan::EditableNodes,
aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement
? aPointContainingTheOtherBlock
: EditorDOMPoint::AtEndOf(const_cast<Element&>(aLeftBlockElement)),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (NS_WARN_IF(!textFragmentDataAtEndOfLeftBlockElement.IsInitialized())) {
return EditorDOMRange(); // TODO: Make here return error with Err.
}
if (textFragmentDataAtEndOfLeftBlockElement.StartsFromInvisibleBRElement()) {
// If the left block element ends with an invisible `<br>` element,
// it'll be deleted (and it means there is no invisible trailing
// white-spaces). Therefore, the range should start from the invisible
// `<br>` element.
range.SetStart(EditorDOMPoint(
textFragmentDataAtEndOfLeftBlockElement.StartReasonBRElementPtr()));
} else {
const EditorDOMRange& trailingWhiteSpaceRange =
textFragmentDataAtEndOfLeftBlockElement
.InvisibleTrailingWhiteSpaceRangeRef();
if (trailingWhiteSpaceRange.StartRef().IsSet()) {
range.SetStart(trailingWhiteSpaceRange.StartRef());
} else {
range.SetStart(textFragmentDataAtEndOfLeftBlockElement.ScanStartRef());
}
}
// Include leading invisible white-spaces in aRightBlockElement.
const TextFragmentData textFragmentDataAtStartOfRightBlockElement(
Scan::EditableNodes,
aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement &&
!aPointContainingTheOtherBlock.IsEndOfContainer()
? aPointContainingTheOtherBlock.NextPoint()
: EditorDOMPoint(const_cast<Element*>(&aRightBlockElement), 0u),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (NS_WARN_IF(!textFragmentDataAtStartOfRightBlockElement.IsInitialized())) {
return EditorDOMRange(); // TODO: Make here return error with Err.
}
const EditorDOMRange& leadingWhiteSpaceRange =
textFragmentDataAtStartOfRightBlockElement
.InvisibleLeadingWhiteSpaceRangeRef();
if (leadingWhiteSpaceRange.EndRef().IsSet()) {
range.SetEnd(leadingWhiteSpaceRange.EndRef());
} else {
range.SetEnd(textFragmentDataAtStartOfRightBlockElement.ScanStartRef());
}
return range;
}
// static
EditorDOMRange
WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
const EditorDOMRange& aRange) {
MOZ_ASSERT(aRange.IsPositionedAndValid());
MOZ_ASSERT(aRange.EndRef().IsSetAndValid());
MOZ_ASSERT(aRange.StartRef().IsSetAndValid());
EditorDOMRange result;
const TextFragmentData textFragmentDataAtStart(
Scan::EditableNodes, aRange.StartRef(),
BlockInlineCheck::UseComputedDisplayStyle);
if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized())) {
return EditorDOMRange(); // TODO: Make here return error with Err.
}
const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtStart =
textFragmentDataAtStart.GetNonCollapsedRangeInTexts(
textFragmentDataAtStart.InvisibleLeadingWhiteSpaceRangeRef());
if (invisibleLeadingWhiteSpacesAtStart.IsPositioned() &&
!invisibleLeadingWhiteSpacesAtStart.Collapsed()) {
result.SetStart(invisibleLeadingWhiteSpacesAtStart.StartRef());
} else {
const EditorDOMRangeInTexts invisibleTrailingWhiteSpacesAtStart =
textFragmentDataAtStart.GetNonCollapsedRangeInTexts(
textFragmentDataAtStart.InvisibleTrailingWhiteSpaceRangeRef());
if (invisibleTrailingWhiteSpacesAtStart.IsPositioned() &&
!invisibleTrailingWhiteSpacesAtStart.Collapsed()) {
MOZ_ASSERT(
invisibleTrailingWhiteSpacesAtStart.StartRef().EqualsOrIsBefore(
aRange.StartRef()));
result.SetStart(invisibleTrailingWhiteSpacesAtStart.StartRef());
}
// If there is no invisible white-space and the line starts with a
// text node, shrink the range to start of the text node.
else if (!aRange.StartRef().IsInTextNode() &&
(textFragmentDataAtStart.StartsFromBlockBoundary() ||
textFragmentDataAtStart.StartsFromInlineEditingHostBoundary()) &&
textFragmentDataAtStart.EndRef().IsInTextNode()) {
result.SetStart(textFragmentDataAtStart.EndRef());
}
}
if (!result.StartRef().IsSet()) {
result.SetStart(aRange.StartRef());
}
const TextFragmentData textFragmentDataAtEnd(
Scan::EditableNodes, aRange.EndRef(),
BlockInlineCheck::UseComputedDisplayStyle);
if (NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
return EditorDOMRange(); // TODO: Make here return error with Err.
}
const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd =
textFragmentDataAtEnd.GetNonCollapsedRangeInTexts(
textFragmentDataAtEnd.InvisibleTrailingWhiteSpaceRangeRef());
if (invisibleLeadingWhiteSpacesAtEnd.IsPositioned() &&
!invisibleLeadingWhiteSpacesAtEnd.Collapsed()) {
result.SetEnd(invisibleLeadingWhiteSpacesAtEnd.EndRef());
} else {
const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd =
textFragmentDataAtEnd.GetNonCollapsedRangeInTexts(
textFragmentDataAtEnd.InvisibleLeadingWhiteSpaceRangeRef());
if (invisibleLeadingWhiteSpacesAtEnd.IsPositioned() &&
!invisibleLeadingWhiteSpacesAtEnd.Collapsed()) {
MOZ_ASSERT(aRange.EndRef().EqualsOrIsBefore(
invisibleLeadingWhiteSpacesAtEnd.EndRef()));
result.SetEnd(invisibleLeadingWhiteSpacesAtEnd.EndRef());
}
// If there is no invisible white-space and the line ends with a text
// node, shrink the range to end of the text node.
else if (!aRange.EndRef().IsInTextNode() &&
(textFragmentDataAtEnd.EndsByBlockBoundary() ||
textFragmentDataAtEnd.EndsByInlineEditingHostBoundary()) &&
textFragmentDataAtEnd.StartRef().IsInTextNode()) {
result.SetEnd(EditorDOMPoint::AtEndOf(
*textFragmentDataAtEnd.StartRef().ContainerAs<Text>()));
}
}
if (!result.EndRef().IsSet()) {
result.SetEnd(aRange.EndRef());
}
MOZ_ASSERT(result.IsPositionedAndValid());
return result;
}
/******************************************************************************
* Utilities for other things.
******************************************************************************/
// static
Result<bool, nsresult>
WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
const HTMLEditor& aHTMLEditor, nsRange& aRange) {
MOZ_ASSERT(aRange.IsPositioned());
MOZ_ASSERT(!aRange.IsInAnySelection(),
"Changing range in selection may cause running script");
if (NS_WARN_IF(!aRange.GetStartContainer()) ||
NS_WARN_IF(!aRange.GetEndContainer())) {
return Err(NS_ERROR_FAILURE);
}
if (!aRange.GetStartContainer()->IsContent() ||
!aRange.GetEndContainer()->IsContent()) {
return false;
}
// If the range crosses a block boundary, we should do nothing for now
// because it hits a bug of inserting a padding `<br>` element after
// joining the blocks.
if (HTMLEditUtils::GetInclusiveAncestorElement(
*aRange.GetStartContainer()->AsContent(),
HTMLEditUtils::ClosestEditableBlockElementExceptHRElement,
BlockInlineCheck::UseComputedDisplayStyle) !=
HTMLEditUtils::GetInclusiveAncestorElement(
*aRange.GetEndContainer()->AsContent(),
HTMLEditUtils::ClosestEditableBlockElementExceptHRElement,
BlockInlineCheck::UseComputedDisplayStyle)) {
return false;
}
nsIContent* startContent = nullptr;
if (aRange.GetStartContainer() && aRange.GetStartContainer()->IsText() &&
aRange.GetStartContainer()->AsText()->Length() == aRange.StartOffset()) {
// If next content is a visible `<br>` element, special inline content
// (e.g., `<img>`, non-editable text node, etc) or a block level void
// element like `<hr>`, the range should start with it.
const TextFragmentData textFragmentDataAtStart(
Scan::EditableNodes, EditorRawDOMPoint(aRange.StartRef()),
BlockInlineCheck::UseComputedDisplayStyle);
if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized())) {
return Err(NS_ERROR_FAILURE);
}
if (textFragmentDataAtStart.EndsByVisibleBRElement()) {
startContent = textFragmentDataAtStart.EndReasonBRElementPtr();
} else if (textFragmentDataAtStart.EndsBySpecialContent() ||
(textFragmentDataAtStart.EndsByOtherBlockElement() &&
!HTMLEditUtils::IsContainerNode(
*textFragmentDataAtStart
.EndReasonOtherBlockElementPtr()))) {
startContent = textFragmentDataAtStart.GetEndReasonContent();
}
}
nsIContent* endContent = nullptr;
if (aRange.GetEndContainer() && aRange.GetEndContainer()->IsText() &&
!aRange.EndOffset()) {
// If previous content is a visible `<br>` element, special inline content
// (e.g., `<img>`, non-editable text node, etc) or a block level void
// element like `<hr>`, the range should end after it.
const TextFragmentData textFragmentDataAtEnd(
Scan::EditableNodes, EditorRawDOMPoint(aRange.EndRef()),
BlockInlineCheck::UseComputedDisplayStyle);
if (NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
return Err(NS_ERROR_FAILURE);
}
if (textFragmentDataAtEnd.StartsFromVisibleBRElement()) {
endContent = textFragmentDataAtEnd.StartReasonBRElementPtr();
} else if (textFragmentDataAtEnd.StartsFromSpecialContent() ||
(textFragmentDataAtEnd.StartsFromOtherBlockElement() &&
!HTMLEditUtils::IsContainerNode(
*textFragmentDataAtEnd
.StartReasonOtherBlockElementPtr()))) {
endContent = textFragmentDataAtEnd.GetStartReasonContent();
}
}
if (!startContent && !endContent) {
return false;
}
nsresult rv = aRange.SetStartAndEnd(
startContent ? RangeBoundary(
startContent->GetParentNode(),
startContent->GetPreviousSibling()) // at startContent
: aRange.StartRef(),
endContent ? RangeBoundary(endContent->GetParentNode(),
endContent) // after endContent
: aRange.EndRef());
if (NS_FAILED(rv)) {
NS_WARNING("nsRange::SetStartAndEnd() failed");
return Err(rv);
}
return true;
}
} // namespace mozilla

View file

@ -3,15 +3,12 @@
* 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 WSRunObject_h
#define WSRunObject_h
#ifndef WSRunScanner_h
#define WSRunScanner_h
#include "EditAction.h"
#include "EditorBase.h"
#include "EditorForwards.h"
#include "EditorDOMPoint.h" // for EditorDOMPoint
#include "EditorUtils.h" // for CaretPoint
#include "HTMLEditHelpers.h"
#include "HTMLEditor.h"
#include "HTMLEditUtils.h"
@ -26,8 +23,6 @@
namespace mozilla {
using namespace dom;
/**
* WSScanResult is result of ScanNextVisibleNodeOrBlockBoundaryFrom(),
* ScanPreviousVisibleNodeOrBlockBoundaryFrom(), and their static wrapper
@ -37,6 +32,10 @@ using namespace dom;
*/
class MOZ_STACK_CLASS WSScanResult final {
private:
using Element = dom::Element;
using HTMLBRElement = dom::HTMLBRElement;
using Text = dom::Text;
enum class WSType : uint8_t {
NotInitialized,
// Could be the DOM tree is broken as like crash tests.
@ -414,16 +413,34 @@ class MOZ_STACK_CLASS WSScanResult final {
};
class MOZ_STACK_CLASS WSRunScanner final {
private:
using Element = dom::Element;
using HTMLBRElement = dom::HTMLBRElement;
using Text = dom::Text;
public:
using WSType = WSScanResult::WSType;
enum class IgnoreNonEditableNodes : bool { No, Yes };
enum class StopAtNonEditableNode : bool { No, Yes };
enum class Scan : bool { All, EditableNodes };
[[nodiscard]] constexpr static IgnoreNonEditableNodes
ShouldIgnoreNonEditableSiblingsOrDescendants(Scan aScan) {
return static_cast<IgnoreNonEditableNodes>(static_cast<bool>(aScan));
}
[[nodiscard]] constexpr static StopAtNonEditableNode
ShouldStopAtNonEditableNode(Scan aScan) {
return static_cast<StopAtNonEditableNode>(static_cast<bool>(aScan));
}
template <typename EditorDOMPointType>
WSRunScanner(const Element* aEditingHost,
const EditorDOMPointType& aScanStartPoint,
BlockInlineCheck aBlockInlineCheck)
: mScanStartPoint(aScanStartPoint.template To<EditorDOMPoint>()),
mEditingHost(const_cast<Element*>(aEditingHost)),
mTextFragmentDataAtStart(mScanStartPoint, mEditingHost,
mTextFragmentDataAtStart(Scan::EditableNodes, mScanStartPoint,
aBlockInlineCheck),
mBlockInlineCheck(aBlockInlineCheck) {}
@ -509,27 +526,23 @@ class MOZ_STACK_CLASS WSRunScanner final {
* only invisible white-spaces and there is previous or next text node.
*/
template <typename EditorDOMPointType>
static EditorDOMPointType GetAfterLastVisiblePoint(
Text& aTextNode, const Element* aAncestorLimiter);
static EditorDOMPointType GetAfterLastVisiblePoint(Text& aTextNode);
template <typename EditorDOMPointType>
static EditorDOMPointType GetFirstVisiblePoint(
Text& aTextNode, const Element* aAncestorLimiter);
static EditorDOMPointType GetFirstVisiblePoint(Text& aTextNode);
/**
* GetRangeInTextNodesToForwardDeleteFrom() returns the range to remove
* text when caret is at aPoint.
*/
static Result<EditorDOMRangeInTexts, nsresult>
GetRangeInTextNodesToForwardDeleteFrom(const EditorDOMPoint& aPoint,
const Element& aEditingHost);
GetRangeInTextNodesToForwardDeleteFrom(const EditorDOMPoint& aPoint);
/**
* GetRangeInTextNodesToBackspaceFrom() returns the range to remove text
* when caret is at aPoint.
*/
static Result<EditorDOMRangeInTexts, nsresult>
GetRangeInTextNodesToBackspaceFrom(const EditorDOMPoint& aPoint,
const Element& aEditingHost);
GetRangeInTextNodesToBackspaceFrom(const EditorDOMPoint& aPoint);
/**
* GetRangesForDeletingAtomicContent() returns the range to delete
@ -537,7 +550,7 @@ class MOZ_STACK_CLASS WSRunScanner final {
* be included into the range.
*/
static EditorDOMRange GetRangesForDeletingAtomicContent(
Element* aEditingHost, const nsIContent& aAtomicContent);
const nsIContent& aAtomicContent);
/**
* GetRangeForDeleteBlockElementBoundaries() returns a range starting from end
@ -570,15 +583,14 @@ class MOZ_STACK_CLASS WSRunScanner final {
* is in adjacent text nodes. Returns true if this modifies the range.
*/
static Result<bool, nsresult> ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
const HTMLEditor& aHTMLEditor, nsRange& aRange,
const Element* aEditingHost);
const HTMLEditor& aHTMLEditor, nsRange& aRange);
/**
* GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries() returns
* extended range if range boundaries of aRange are in invisible white-spaces.
*/
static EditorDOMRange GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
Element* aEditingHost, const EditorDOMRange& aRange);
const EditorDOMRange& aRange);
/**
* GetPrecedingBRElementUnlessVisibleContentFound() scans a `<br>` element
@ -589,7 +601,7 @@ class MOZ_STACK_CLASS WSRunScanner final {
template <typename EditorDOMPointType>
MOZ_NEVER_INLINE_DEBUG static HTMLBRElement*
GetPrecedingBRElementUnlessVisibleContentFound(
Element* aEditingHost, const EditorDOMPointType& aPoint,
const Element* aEditingHost, const EditorDOMPointType& aPoint,
BlockInlineCheck aBlockInlineCheck) {
MOZ_ASSERT(aPoint.IsSetAndValid());
// XXX This method behaves differently even in similar point.
@ -604,7 +616,8 @@ class MOZ_STACK_CLASS WSRunScanner final {
}
// TODO: Scan for end boundary is redundant in this case, we should optimize
// it.
TextFragmentData textFragmentData(aPoint, aEditingHost, aBlockInlineCheck);
TextFragmentData textFragmentData(Scan::EditableNodes, aPoint,
aBlockInlineCheck, aEditingHost);
return textFragmentData.StartsFromBRElement()
? textFragmentData.StartReasonBRElementPtr()
: nullptr;
@ -842,7 +855,8 @@ class MOZ_STACK_CLASS WSRunScanner final {
EditorDOMPointType GetInclusiveNextEditableCharPoint(
const EditorDOMPointBase<PT, CT>& aPoint) const {
return TextFragmentDataAtStartRef()
.GetInclusiveNextEditableCharPoint<EditorDOMPointType>(aPoint);
.GetInclusiveNextCharPoint<EditorDOMPointType>(
aPoint, IgnoreNonEditableNodes::Yes);
}
/**
@ -857,7 +871,8 @@ class MOZ_STACK_CLASS WSRunScanner final {
EditorDOMPointType GetPreviousEditableCharPoint(
const EditorDOMPointBase<PT, CT>& aPoint) const {
return TextFragmentDataAtStartRef()
.GetPreviousEditableCharPoint<EditorDOMPointType>(aPoint);
.GetPreviousCharPoint<EditorDOMPointType>(aPoint,
IgnoreNonEditableNodes::Yes);
}
/**
@ -923,20 +938,15 @@ class MOZ_STACK_CLASS WSRunScanner final {
* this returns the data at aPoint.
*
* @param aPoint Scan start point.
* @param aEditableBlockParentOrTopmostEditableInlineElement
* Nearest editable block parent element of
* aPoint if there is. Otherwise, inline editing
* host.
* @param aEditingHost Active editing host.
* @param aNBSPData Optional. If set, this recodes first and last
* NBSP positions.
*/
template <typename EditorDOMPointType>
static BoundaryData ScanCollapsibleWhiteSpaceStartFrom(
const EditorDOMPointType& aPoint,
const Element& aEditableBlockParentOrTopmostEditableInlineElement,
const Element* aEditingHost, NoBreakingSpaceData* aNBSPData,
BlockInlineCheck aBlockInlineCheck);
const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData,
BlockInlineCheck aBlockInlineCheck,
StopAtNonEditableNode aStopAtNonEditableNode,
const Element& aAncestorLimiter);
/**
* ScanCollapsibleWhiteSpaceEndFrom() returns end boundary data of
@ -945,20 +955,15 @@ class MOZ_STACK_CLASS WSRunScanner final {
* this returns the data at aPoint.
*
* @param aPoint Scan start point.
* @param aEditableBlockParentOrTopmostEditableInlineElement
* Nearest editable block parent element of
* aPoint if there is. Otherwise, inline editing
* host.
* @param aEditingHost Active editing host.
* @param aNBSPData Optional. If set, this recodes first and last
* NBSP positions.
*/
template <typename EditorDOMPointType>
static BoundaryData ScanCollapsibleWhiteSpaceEndFrom(
const EditorDOMPointType& aPoint,
const Element& aEditableBlockParentOrTopmostEditableInlineElement,
const Element* aEditingHost, NoBreakingSpaceData* aNBSPData,
BlockInlineCheck aBlockInlineCheck);
const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData,
BlockInlineCheck aBlockInlineCheck,
StopAtNonEditableNode aStopAtNonEditableNode,
const Element& aAncestorLimiter);
BoundaryData() = default;
template <typename EditorDOMPointType>
@ -1065,15 +1070,16 @@ class MOZ_STACK_CLASS WSRunScanner final {
public:
TextFragmentData() = delete;
/**
* If aScanMode is Scan::EditableNodes and aPoint is in an editable node,
* this scans only in the editing host. Therefore, it's same as that
* aAncestorLimiter is specified to the editing host.
*/
template <typename EditorDOMPointType>
TextFragmentData(const WSRunScanner& aWSRunScanner,
const EditorDOMPointType& aPoint)
: TextFragmentData(aPoint, aWSRunScanner.mEditingHost,
aWSRunScanner.mBlockInlineCheck) {}
template <typename EditorDOMPointType>
TextFragmentData(const EditorDOMPointType& aPoint,
const Element* aEditingHost,
BlockInlineCheck aBlockInlineCheck);
TextFragmentData(Scan aScanMode, const EditorDOMPointType& aPoint,
BlockInlineCheck aBlockInlineCheck,
const Element* aAncestorLimiter = nullptr);
bool IsInitialized() const {
return mStart.Initialized() && mEnd.Initialized();
@ -1169,23 +1175,96 @@ class MOZ_STACK_CLASS WSRunScanner final {
return mNBSPData.LastPointRef();
}
template <typename EditorDOMPointType = EditorDOMPointInText, typename PT,
typename CT>
EditorDOMPointType GetInclusiveNextEditableCharPoint(
const EditorDOMPointBase<PT, CT>& aPoint) const;
template <typename EditorDOMPointType = EditorDOMPointInText, typename PT,
typename CT>
EditorDOMPointType GetPreviousEditableCharPoint(
const EditorDOMPointBase<PT, CT>& aPoint) const;
/**
* Return inclusive next point in inclusive next `Text` node from aPoint.
* So, it may be in a collapsed white-space or invisible white-spaces.
*/
template <typename EditorDOMPointType, typename PT, typename CT>
[[nodiscard]] static EditorDOMPointType GetInclusiveNextCharPoint(
const EditorDOMPointBase<PT, CT>& aPoint,
BlockInlineCheck aBlockInlineCheck,
IgnoreNonEditableNodes aIgnoreNonEditableNodes,
const nsIContent* aFollowingLimiterContent = nullptr);
template <typename EditorDOMPointType = EditorDOMPointInText>
EditorDOMPointType GetEndOfCollapsibleASCIIWhiteSpaces(
template <typename EditorDOMPointType, typename PT, typename CT>
[[nodiscard]] EditorDOMPointType GetInclusiveNextCharPoint(
const EditorDOMPointBase<PT, CT>& aPoint,
IgnoreNonEditableNodes aIgnoreNonEditableNodes) const {
return GetInclusiveNextCharPoint<EditorDOMPointType>(
aPoint, mBlockInlineCheck, aIgnoreNonEditableNodes,
GetEndReasonContent());
}
/**
* Return previous point in inclusive previous `Text` node from aPoint.
* So, it may be in a collapsed white-space or invisible white-spaces.
*/
template <typename EditorDOMPointType, typename PT, typename CT>
[[nodiscard]] static EditorDOMPointType GetPreviousCharPoint(
const EditorDOMPointBase<PT, CT>& aPoint,
BlockInlineCheck aBlockInlineCheck,
IgnoreNonEditableNodes aIgnoreNonEditableNodes,
const nsIContent* aPrecedingLimiterContent = nullptr);
template <typename EditorDOMPointType, typename PT, typename CT>
[[nodiscard]] EditorDOMPointType GetPreviousCharPoint(
const EditorDOMPointBase<PT, CT>& aPoint,
IgnoreNonEditableNodes aIgnoreNonEditableNodes) const {
return GetPreviousCharPoint<EditorDOMPointType>(aPoint, mBlockInlineCheck,
aIgnoreNonEditableNodes,
GetStartReasonContent());
}
/**
* Return end of current collapsible ASCII white-spaces.
*
* @param aPointAtASCIIWhiteSpace Must be in a sequence of collapsible
* ASCII white-spaces.
* @param aDirectionToDelete The direction to delete.
*/
template <typename EditorDOMPointType>
[[nodiscard]] static EditorDOMPointType GetEndOfCollapsibleASCIIWhiteSpaces(
const EditorDOMPointInText& aPointAtASCIIWhiteSpace,
nsIEditor::EDirection aDirectionToDelete) const;
template <typename EditorDOMPointType = EditorDOMPointInText>
EditorDOMPointType GetFirstASCIIWhiteSpacePointCollapsedTo(
nsIEditor::EDirection aDirectionToDelete,
BlockInlineCheck aBlockInlineCheck,
IgnoreNonEditableNodes aIgnoreNonEditableNodes,
const nsIContent* aFollowingLimiterContent = nullptr);
template <typename EditorDOMPointType>
[[nodiscard]] EditorDOMPointType GetEndOfCollapsibleASCIIWhiteSpaces(
const EditorDOMPointInText& aPointAtASCIIWhiteSpace,
nsIEditor::EDirection aDirectionToDelete) const;
nsIEditor::EDirection aDirectionToDelete,
IgnoreNonEditableNodes aIgnoreNonEditableNodes) const {
return GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointType>(
aPointAtASCIIWhiteSpace, aDirectionToDelete, mBlockInlineCheck,
aIgnoreNonEditableNodes, GetEndReasonContent());
}
/**
* Return start of current collapsible ASCII white-spaces.
*
* @param aPointAtASCIIWhiteSpace Must be in a sequence of collapsible
* ASCII white-spaces.
* @param aDirectionToDelete The direction to delete.
*/
template <typename EditorDOMPointType>
[[nodiscard]] static EditorDOMPointType
GetFirstASCIIWhiteSpacePointCollapsedTo(
const EditorDOMPointInText& aPointAtASCIIWhiteSpace,
nsIEditor::EDirection aDirectionToDelete,
BlockInlineCheck aBlockInlineCheck,
IgnoreNonEditableNodes aIgnoreNonEditableNodes,
const nsIContent* aPrecedingLimiterContent = nullptr);
template <typename EditorDOMPointType>
[[nodiscard]] EditorDOMPointType GetFirstASCIIWhiteSpacePointCollapsedTo(
const EditorDOMPointInText& aPointAtASCIIWhiteSpace,
nsIEditor::EDirection aDirectionToDelete,
IgnoreNonEditableNodes aIgnoreNonEditableNodes) const {
return GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointType>(
aPointAtASCIIWhiteSpace, aDirectionToDelete, mBlockInlineCheck,
aIgnoreNonEditableNodes, GetStartReasonContent());
}
/**
* GetNonCollapsedRangeInTexts() returns non-empty range in texts which
@ -1429,11 +1508,11 @@ class MOZ_STACK_CLASS WSRunScanner final {
BoundaryData mStart;
BoundaryData mEnd;
NoBreakingSpaceData mNBSPData;
RefPtr<const Element> mEditingHost;
mutable Maybe<EditorDOMRange> mLeadingWhiteSpaceRange;
mutable Maybe<EditorDOMRange> mTrailingWhiteSpaceRange;
mutable Maybe<VisibleWhiteSpacesData> mVisibleWhiteSpacesData;
BlockInlineCheck mBlockInlineCheck;
Scan mScanMode;
};
const TextFragmentData& TextFragmentDataAtStartRef() const {
@ -1470,314 +1549,6 @@ class MOZ_STACK_CLASS WSRunScanner final {
friend class WhiteSpaceVisibilityKeeper;
};
/**
* WhiteSpaceVisibilityKeeper class helps `HTMLEditor` modifying the DOM tree
* with keeps white-space sequence visibility automatically. E.g., invisible
* leading/trailing white-spaces becomes visible, this class members delete
* them. E.g., when splitting visible-white-space sequence, this class may
* replace ASCII white-spaces at split edges with NBSPs.
*/
class WhiteSpaceVisibilityKeeper final {
private:
using AutoTransactionsConserveSelection =
EditorBase::AutoTransactionsConserveSelection;
using EditorType = EditorBase::EditorType;
using PointPosition = WSRunScanner::PointPosition;
using TextFragmentData = WSRunScanner::TextFragmentData;
using VisibleWhiteSpacesData = WSRunScanner::VisibleWhiteSpacesData;
public:
using InsertTextTo = EditorBase::InsertTextTo;
using LineBreakType = HTMLEditor::LineBreakType;
WhiteSpaceVisibilityKeeper() = delete;
explicit WhiteSpaceVisibilityKeeper(
const WhiteSpaceVisibilityKeeper& aOther) = delete;
WhiteSpaceVisibilityKeeper(WhiteSpaceVisibilityKeeper&& aOther) = delete;
/**
* Remove invisible leading white-spaces and trailing white-spaces if there
* are around aPoint.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
DeleteInvisibleASCIIWhiteSpaces(HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aPoint);
/**
* Fix up white-spaces before aStartPoint and after aEndPoint in preparation
* for content to keep the white-spaces visibility after the range is deleted.
* Note that the nodes and offsets are adjusted in response to any dom changes
* we make while adjusting white-spaces.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
PrepareToDeleteRangeAndTrackPoints(HTMLEditor& aHTMLEditor,
EditorDOMPoint* aStartPoint,
EditorDOMPoint* aEndPoint,
const Element& aEditingHost) {
MOZ_ASSERT(aStartPoint->IsSetAndValid());
MOZ_ASSERT(aEndPoint->IsSetAndValid());
AutoTrackDOMPoint trackerStart(aHTMLEditor.RangeUpdaterRef(), aStartPoint);
AutoTrackDOMPoint trackerEnd(aHTMLEditor.RangeUpdaterRef(), aEndPoint);
Result<CaretPoint, nsresult> caretPointOrError =
WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
aHTMLEditor, EditorDOMRange(*aStartPoint, *aEndPoint),
aEditingHost);
NS_WARNING_ASSERTION(
caretPointOrError.isOk(),
"WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
return caretPointOrError;
}
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
PrepareToDeleteRange(HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aStartPoint,
const EditorDOMPoint& aEndPoint,
const Element& aEditingHost) {
MOZ_ASSERT(aStartPoint.IsSetAndValid());
MOZ_ASSERT(aEndPoint.IsSetAndValid());
Result<CaretPoint, nsresult> caretPointOrError =
WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
aHTMLEditor, EditorDOMRange(aStartPoint, aEndPoint), aEditingHost);
NS_WARNING_ASSERTION(
caretPointOrError.isOk(),
"WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
return caretPointOrError;
}
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
PrepareToDeleteRange(HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
const Element& aEditingHost) {
MOZ_ASSERT(aRange.IsPositionedAndValid());
Result<CaretPoint, nsresult> caretPointOrError =
WhiteSpaceVisibilityKeeper::
MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
aHTMLEditor, aRange, aEditingHost);
NS_WARNING_ASSERTION(
caretPointOrError.isOk(),
"WhiteSpaceVisibilityKeeper::"
"MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed");
return caretPointOrError;
}
/**
* PrepareToSplitBlockElement() makes sure that the invisible white-spaces
* not to become visible and returns splittable point.
*
* @param aHTMLEditor The HTML editor.
* @param aPointToSplit The splitting point in aSplittingBlockElement.
* @param aSplittingBlockElement A block element which will be split.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMPoint, nsresult>
PrepareToSplitBlockElement(HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aPointToSplit,
const Element& aSplittingBlockElement);
/**
* MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() merges
* first line in aRightBlockElement into end of aLeftBlockElement which
* is a descendant of aRightBlockElement.
*
* @param aHTMLEditor The HTML editor.
* @param aLeftBlockElement The content will be merged into end of
* this element.
* @param aRightBlockElement The first line in this element will be
* moved to aLeftBlockElement.
* @param aAtRightBlockChild At a child of aRightBlockElement and inclusive
* ancestor of aLeftBlockElement.
* @param aListElementTagName Set some if aRightBlockElement is a list
* element and it'll be merged with another
* list element.
* @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<MoveNodeResult, nsresult>
MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
Element& aRightBlockElement, const EditorDOMPoint& aAtRightBlockChild,
const Maybe<nsAtom*>& aListElementTagName,
const HTMLBRElement* aPrecedingInvisibleBRElement,
const Element& aEditingHost);
/**
* MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() merges
* first line in aRightBlockElement into end of aLeftBlockElement which
* is an ancestor of aRightBlockElement, then, removes aRightBlockElement
* if it becomes empty.
*
* @param aHTMLEditor The HTML editor.
* @param aLeftBlockElement The content will be merged into end of
* this element.
* @param aRightBlockElement The first line in this element will be
* moved to aLeftBlockElement and maybe
* removed when this becomes empty.
* @param aAtLeftBlockChild At a child of aLeftBlockElement and inclusive
* ancestor of aRightBlockElement.
* @param aLeftContentInBlock The content whose inclusive ancestor is
* aLeftBlockElement.
* @param aListElementTagName Set some if aRightBlockElement is a list
* element and it'll be merged with another
* list element.
* @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<MoveNodeResult, nsresult>
MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
Element& aRightBlockElement, const EditorDOMPoint& aAtLeftBlockChild,
nsIContent& aLeftContentInBlock,
const Maybe<nsAtom*>& aListElementTagName,
const HTMLBRElement* aPrecedingInvisibleBRElement,
const Element& aEditingHost);
/**
* MergeFirstLineOfRightBlockElementIntoLeftBlockElement() merges first
* line in aRightBlockElement into end of aLeftBlockElement and removes
* aRightBlockElement when it has only one line.
*
* @param aHTMLEditor The HTML editor.
* @param aLeftBlockElement The content will be merged into end of
* this element.
* @param aRightBlockElement The first line in this element will be
* moved to aLeftBlockElement and maybe
* removed when this becomes empty.
* @param aListElementTagName Set some if aRightBlockElement is a list
* element and its type needs to be changed.
* @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<MoveNodeResult, nsresult>
MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
Element& aRightBlockElement, const Maybe<nsAtom*>& aListElementTagName,
const HTMLBRElement* aPrecedingInvisibleBRElement,
const Element& aEditingHost);
/**
* InsertLineBreak() inserts a line break at (before) aPointToInsert and
* delete unnecessary white-spaces around there and/or replaces white-spaces
* with non-breaking spaces. Note that if the point is in a text node, the
* text node will be split and insert new <br> node between the left node
* and the right node.
*
* @param aPointToInsert The point to insert new line break. Note that
* it'll be inserted before this point. I.e., the
* point will be the point of new line break.
* @return If succeeded, returns the new line break and
* point to put caret.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CreateLineBreakResult,
nsresult>
InsertLineBreak(LineBreakType aLineBreakType, HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aPointToInsert,
const Element& aEditingHost);
/**
* Insert aStringToInsert to aPointToInsert and makes any needed adjustments
* to white-spaces around the insertion point.
*
* @param aStringToInsert The string to insert.
* @param aRangeToBeReplaced The range to be replaced.
* @param aInsertTextTo Whether forcibly creates a new `Text` node in
* specific condition or use existing `Text` if
* available.
*/
template <typename EditorDOMPointType>
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<InsertTextResult, nsresult>
InsertText(HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert,
const EditorDOMPointType& aPointToInsert,
InsertTextTo aInsertTextTo, const Element& aEditingHost) {
return WhiteSpaceVisibilityKeeper::ReplaceText(
aHTMLEditor, aStringToInsert, EditorDOMRange(aPointToInsert),
aInsertTextTo, aEditingHost);
}
/**
* Replace aRangeToReplace with aStringToInsert and makes any needed
* adjustments to white-spaces around both start of the range and end of the
* range.
*
* @param aStringToInsert The string to insert.
* @param aRangeToBeReplaced The range to be replaced.
* @param aInsertTextTo Whether forcibly creates a new `Text` node in
* specific condition or use existing `Text` if
* available.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<InsertTextResult, nsresult>
ReplaceText(HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert,
const EditorDOMRange& aRangeToBeReplaced,
InsertTextTo aInsertTextTo, const Element& aEditingHost);
/**
* Delete previous white-space of aPoint. This automatically keeps visibility
* of white-spaces around aPoint. E.g., may remove invisible leading
* white-spaces.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
DeletePreviousWhiteSpace(HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aPoint,
const Element& aEditingHost);
/**
* Delete inclusive next white-space of aPoint. This automatically keeps
* visiblity of white-spaces around aPoint. E.g., may remove invisible
* trailing white-spaces.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
DeleteInclusiveNextWhiteSpace(HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aPoint,
const Element& aEditingHost);
/**
* Delete aContentToDelete and may remove/replace white-spaces around it.
* Then, if deleting content makes 2 text nodes around it are adjacent
* siblings, this joins them and put selection at the joined point.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
DeleteContentNodeAndJoinTextNodesAroundIt(HTMLEditor& aHTMLEditor,
nsIContent& aContentToDelete,
const EditorDOMPoint& aCaretPoint,
const Element& aEditingHost);
/**
* Try to normalize visible white-space sequence around aPoint.
* This may collapse `Selection` after replaced text. Therefore, the callers
* of this need to restore `Selection` by themselves (this does not do it for
* performance reason of multiple calls).
*/
template <typename EditorDOMPointType>
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult
NormalizeVisibleWhiteSpacesAt(HTMLEditor& aHTMLEditor,
const EditorDOMPointType& aPoint,
const Element& aEditingHost);
private:
/**
* Maybe delete invisible white-spaces for keeping make them invisible and/or
* may replace ASCII white-spaces with NBSPs for making visible white-spaces
* to keep visible.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
HTMLEditor& aHTMLEditor, const EditorDOMRange& aRangeToDelete,
const Element& aEditingHost);
/**
* MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() replaces ASCII white-
* spaces which becomes invisible after split with NBSPs.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult
MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToSplit);
/**
* ReplaceTextAndRemoveEmptyTextNodes() replaces the range between
* aRangeToReplace with aReplaceString simply. Additionally, removes
* empty text nodes in the range.
*
* @param aRangeToReplace Range to replace text.
* @param aReplaceString The new string. Empty string is allowed.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult
ReplaceTextAndRemoveEmptyTextNodes(
HTMLEditor& aHTMLEditor, const EditorDOMRangeInTexts& aRangeToReplace,
const nsAString& aReplaceString);
};
} // namespace mozilla
#endif // #ifndef WSRunObject_h
#endif // #ifndef WSRunScanner_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,342 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef WhiteSpaceVisibilityKeeper_h
#define WhiteSpaceVisibilityKeeper_h
#include "EditAction.h"
#include "EditorBase.h"
#include "EditorForwards.h"
#include "EditorDOMPoint.h" // for EditorDOMPoint
#include "EditorUtils.h" // for CaretPoint
#include "HTMLEditHelpers.h"
#include "HTMLEditor.h"
#include "HTMLEditUtils.h"
#include "WSRunScanner.h"
#include "mozilla/Assertions.h"
#include "mozilla/Maybe.h"
#include "mozilla/Result.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/Text.h"
#include "nsCOMPtr.h"
#include "nsIContent.h"
namespace mozilla {
/**
* WhiteSpaceVisibilityKeeper class helps `HTMLEditor` modifying the DOM tree
* with keeps white-space sequence visibility automatically. E.g., invisible
* leading/trailing white-spaces becomes visible, this class members delete
* them. E.g., when splitting visible-white-space sequence, this class may
* replace ASCII white-spaces at split edges with NBSPs.
*/
class WhiteSpaceVisibilityKeeper final {
private:
using AutoTransactionsConserveSelection =
EditorBase::AutoTransactionsConserveSelection;
using EditorType = EditorBase::EditorType;
using Element = dom::Element;
using HTMLBRElement = dom::HTMLBRElement;
using IgnoreNonEditableNodes = WSRunScanner::IgnoreNonEditableNodes;
using InsertTextTo = EditorBase::InsertTextTo;
using LineBreakType = HTMLEditor::LineBreakType;
using PointPosition = WSRunScanner::PointPosition;
using Scan = WSRunScanner::Scan;
using TextFragmentData = WSRunScanner::TextFragmentData;
using VisibleWhiteSpacesData = WSRunScanner::VisibleWhiteSpacesData;
public:
WhiteSpaceVisibilityKeeper() = delete;
explicit WhiteSpaceVisibilityKeeper(
const WhiteSpaceVisibilityKeeper& aOther) = delete;
WhiteSpaceVisibilityKeeper(WhiteSpaceVisibilityKeeper&& aOther) = delete;
/**
* Remove invisible leading white-spaces and trailing white-spaces if there
* are around aPoint.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
DeleteInvisibleASCIIWhiteSpaces(HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aPoint);
/**
* Fix up white-spaces before aStartPoint and after aEndPoint in preparation
* for content to keep the white-spaces visibility after the range is deleted.
* Note that the nodes and offsets are adjusted in response to any dom changes
* we make while adjusting white-spaces.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
PrepareToDeleteRangeAndTrackPoints(HTMLEditor& aHTMLEditor,
EditorDOMPoint* aStartPoint,
EditorDOMPoint* aEndPoint,
const Element& aEditingHost) {
MOZ_ASSERT(aStartPoint->IsSetAndValid());
MOZ_ASSERT(aEndPoint->IsSetAndValid());
AutoTrackDOMPoint trackerStart(aHTMLEditor.RangeUpdaterRef(), aStartPoint);
AutoTrackDOMPoint trackerEnd(aHTMLEditor.RangeUpdaterRef(), aEndPoint);
Result<CaretPoint, nsresult> caretPointOrError =
WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
aHTMLEditor, EditorDOMRange(*aStartPoint, *aEndPoint),
aEditingHost);
NS_WARNING_ASSERTION(
caretPointOrError.isOk(),
"WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
return caretPointOrError;
}
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
PrepareToDeleteRange(HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aStartPoint,
const EditorDOMPoint& aEndPoint,
const Element& aEditingHost) {
MOZ_ASSERT(aStartPoint.IsSetAndValid());
MOZ_ASSERT(aEndPoint.IsSetAndValid());
Result<CaretPoint, nsresult> caretPointOrError =
WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
aHTMLEditor, EditorDOMRange(aStartPoint, aEndPoint), aEditingHost);
NS_WARNING_ASSERTION(
caretPointOrError.isOk(),
"WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
return caretPointOrError;
}
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
PrepareToDeleteRange(HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
const Element& aEditingHost) {
MOZ_ASSERT(aRange.IsPositionedAndValid());
Result<CaretPoint, nsresult> caretPointOrError =
WhiteSpaceVisibilityKeeper::
MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
aHTMLEditor, aRange, aEditingHost);
NS_WARNING_ASSERTION(
caretPointOrError.isOk(),
"WhiteSpaceVisibilityKeeper::"
"MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed");
return caretPointOrError;
}
/**
* PrepareToSplitBlockElement() makes sure that the invisible white-spaces
* not to become visible and returns splittable point.
*
* @param aHTMLEditor The HTML editor.
* @param aPointToSplit The splitting point in aSplittingBlockElement.
* @param aSplittingBlockElement A block element which will be split.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMPoint, nsresult>
PrepareToSplitBlockElement(HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aPointToSplit,
const Element& aSplittingBlockElement);
/**
* MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() merges
* first line in aRightBlockElement into end of aLeftBlockElement which
* is a descendant of aRightBlockElement.
*
* @param aHTMLEditor The HTML editor.
* @param aLeftBlockElement The content will be merged into end of
* this element.
* @param aRightBlockElement The first line in this element will be
* moved to aLeftBlockElement.
* @param aAtRightBlockChild At a child of aRightBlockElement and inclusive
* ancestor of aLeftBlockElement.
* @param aListElementTagName Set some if aRightBlockElement is a list
* element and it'll be merged with another
* list element.
* @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<MoveNodeResult, nsresult>
MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
Element& aRightBlockElement, const EditorDOMPoint& aAtRightBlockChild,
const Maybe<nsAtom*>& aListElementTagName,
const HTMLBRElement* aPrecedingInvisibleBRElement,
const Element& aEditingHost);
/**
* MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() merges
* first line in aRightBlockElement into end of aLeftBlockElement which
* is an ancestor of aRightBlockElement, then, removes aRightBlockElement
* if it becomes empty.
*
* @param aHTMLEditor The HTML editor.
* @param aLeftBlockElement The content will be merged into end of
* this element.
* @param aRightBlockElement The first line in this element will be
* moved to aLeftBlockElement and maybe
* removed when this becomes empty.
* @param aAtLeftBlockChild At a child of aLeftBlockElement and inclusive
* ancestor of aRightBlockElement.
* @param aLeftContentInBlock The content whose inclusive ancestor is
* aLeftBlockElement.
* @param aListElementTagName Set some if aRightBlockElement is a list
* element and it'll be merged with another
* list element.
* @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<MoveNodeResult, nsresult>
MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
Element& aRightBlockElement, const EditorDOMPoint& aAtLeftBlockChild,
nsIContent& aLeftContentInBlock,
const Maybe<nsAtom*>& aListElementTagName,
const HTMLBRElement* aPrecedingInvisibleBRElement,
const Element& aEditingHost);
/**
* MergeFirstLineOfRightBlockElementIntoLeftBlockElement() merges first
* line in aRightBlockElement into end of aLeftBlockElement and removes
* aRightBlockElement when it has only one line.
*
* @param aHTMLEditor The HTML editor.
* @param aLeftBlockElement The content will be merged into end of
* this element.
* @param aRightBlockElement The first line in this element will be
* moved to aLeftBlockElement and maybe
* removed when this becomes empty.
* @param aListElementTagName Set some if aRightBlockElement is a list
* element and its type needs to be changed.
* @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<MoveNodeResult, nsresult>
MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
Element& aRightBlockElement, const Maybe<nsAtom*>& aListElementTagName,
const HTMLBRElement* aPrecedingInvisibleBRElement,
const Element& aEditingHost);
/**
* InsertLineBreak() inserts a line break at (before) aPointToInsert and
* delete unnecessary white-spaces around there and/or replaces white-spaces
* with non-breaking spaces. Note that if the point is in a text node, the
* text node will be split and insert new <br> node between the left node
* and the right node.
*
* @param aPointToInsert The point to insert new line break. Note that
* it'll be inserted before this point. I.e., the
* point will be the point of new line break.
* @return If succeeded, returns the new line break and
* point to put caret.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CreateLineBreakResult,
nsresult>
InsertLineBreak(LineBreakType aLineBreakType, HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aPointToInsert);
/**
* Insert aStringToInsert to aPointToInsert and makes any needed adjustments
* to white-spaces around the insertion point.
*
* @param aStringToInsert The string to insert.
* @param aRangeToBeReplaced The range to be replaced.
* @param aInsertTextTo Whether forcibly creates a new `Text` node in
* specific condition or use existing `Text` if
* available.
*/
template <typename EditorDOMPointType>
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<InsertTextResult, nsresult>
InsertText(HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert,
const EditorDOMPointType& aPointToInsert,
InsertTextTo aInsertTextTo) {
return WhiteSpaceVisibilityKeeper::ReplaceText(
aHTMLEditor, aStringToInsert, EditorDOMRange(aPointToInsert),
aInsertTextTo);
}
/**
* Replace aRangeToReplace with aStringToInsert and makes any needed
* adjustments to white-spaces around both start of the range and end of the
* range.
*
* @param aStringToInsert The string to insert.
* @param aRangeToBeReplaced The range to be replaced.
* @param aInsertTextTo Whether forcibly creates a new `Text` node in
* specific condition or use existing `Text` if
* available.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<InsertTextResult, nsresult>
ReplaceText(HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert,
const EditorDOMRange& aRangeToBeReplaced,
InsertTextTo aInsertTextTo);
/**
* Delete previous white-space of aPoint. This automatically keeps visibility
* of white-spaces around aPoint. E.g., may remove invisible leading
* white-spaces.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
DeletePreviousWhiteSpace(HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aPoint,
const Element& aEditingHost);
/**
* Delete inclusive next white-space of aPoint. This automatically keeps
* visiblity of white-spaces around aPoint. E.g., may remove invisible
* trailing white-spaces.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
DeleteInclusiveNextWhiteSpace(HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aPoint,
const Element& aEditingHost);
/**
* Delete aContentToDelete and may remove/replace white-spaces around it.
* Then, if deleting content makes 2 text nodes around it are adjacent
* siblings, this joins them and put selection at the joined point.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
DeleteContentNodeAndJoinTextNodesAroundIt(HTMLEditor& aHTMLEditor,
nsIContent& aContentToDelete,
const EditorDOMPoint& aCaretPoint,
const Element& aEditingHost);
/**
* Try to normalize visible white-space sequence around aPoint.
* This may collapse `Selection` after replaced text. Therefore, the callers
* of this need to restore `Selection` by themselves (this does not do it for
* performance reason of multiple calls).
*/
template <typename EditorDOMPointType>
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult
NormalizeVisibleWhiteSpacesAt(HTMLEditor& aHTMLEditor,
const EditorDOMPointType& aPoint,
const Element& aEditingHost);
private:
/**
* Maybe delete invisible white-spaces for keeping make them invisible and/or
* may replace ASCII white-spaces with NBSPs for making visible white-spaces
* to keep visible.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
HTMLEditor& aHTMLEditor, const EditorDOMRange& aRangeToDelete,
const Element& aEditingHost);
/**
* MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() replaces ASCII white-
* spaces which becomes invisible after split with NBSPs.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult
MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToSplit);
/**
* ReplaceTextAndRemoveEmptyTextNodes() replaces the range between
* aRangeToReplace with aReplaceString simply. Additionally, removes
* empty text nodes in the range.
*
* @param aRangeToReplace Range to replace text.
* @param aReplaceString The new string. Empty string is allowed.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult
ReplaceTextAndRemoveEmptyTextNodes(
HTMLEditor& aHTMLEditor, const EditorDOMRangeInTexts& aRangeToReplace,
const nsAString& aReplaceString);
};
} // namespace mozilla
#endif // #ifndef WhiteSpaceVisibilityKeeper_h

View file

@ -78,7 +78,9 @@ UNIFIED_SOURCES += [
"TextEditor.cpp",
"TextEditorDataTransfer.cpp",
"TextEditSubActionHandler.cpp",
"WSRunObject.cpp",
"WhiteSpaceVisibilityKeeper.cpp",
"WSRunScanner.cpp",
"WSRunScannerNestedClasses.cpp",
]
LOCAL_INCLUDES += [

View file

@ -105,7 +105,9 @@ bool SourceSurfaceSharedDataWrapper::Map(MapType aMapType,
MutexAutoLock lock(*mHandleLock);
dataPtr = GetData();
if (mMapCount == 0) {
if (mConsumers > 0) {
SharedSurfacesParent::RemoveTracking(this);
}
if (!dataPtr) {
size_t len = GetAlignedDataLength();
if (!EnsureMapped(len)) {
@ -129,7 +131,7 @@ bool SourceSurfaceSharedDataWrapper::Map(MapType aMapType,
void SourceSurfaceSharedDataWrapper::Unmap() {
if (mHandleLock) {
MutexAutoLock lock(*mHandleLock);
if (--mMapCount == 0) {
if (--mMapCount == 0 && mConsumers > 0) {
SharedSurfacesParent::AddTracking(this);
}
} else {

View file

@ -40,12 +40,7 @@ class SourceSurfaceSharedDataWrapper final : public DataSourceSurface {
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceSharedDataWrapper,
override)
SourceSurfaceSharedDataWrapper()
: mStride(0),
mConsumers(0),
mFormat(SurfaceFormat::UNKNOWN),
mCreatorPid(0),
mCreatorRef(true) {}
SourceSurfaceSharedDataWrapper() = default;
void Init(const IntSize& aSize, int32_t aStride, SurfaceFormat aFormat,
SharedMemory::Handle aHandle, base::ProcessId aCreatorPid);
@ -86,10 +81,7 @@ class SourceSurfaceSharedDataWrapper final : public DataSourceSurface {
return --mConsumers == 0;
}
uint32_t GetConsumers() const {
MOZ_ASSERT(mConsumers > 0);
return mConsumers;
}
uint32_t GetConsumers() const { return mConsumers; }
bool HasCreatorRef() const { return mCreatorRef; }
@ -113,13 +105,13 @@ class SourceSurfaceSharedDataWrapper final : public DataSourceSurface {
// Protects mapping and unmapping of mBuf.
Maybe<Mutex> mHandleLock;
nsExpirationState mExpirationState;
int32_t mStride;
uint32_t mConsumers;
int32_t mStride = 0;
uint32_t mConsumers = 1;
IntSize mSize;
RefPtr<SharedMemory> mBuf;
SurfaceFormat mFormat;
base::ProcessId mCreatorPid;
bool mCreatorRef;
SurfaceFormat mFormat = SurfaceFormat::UNKNOWN;
base::ProcessId mCreatorPid = 0;
bool mCreatorRef = true;
};
/**

View file

@ -200,7 +200,6 @@ void SharedSurfacesParent::AddSameProcess(const wr::ExternalImageId& aId,
auto texture = MakeRefPtr<wr::RenderSharedSurfaceTextureHost>(surface);
wr::RenderThread::Get()->RegisterExternalImage(aId, texture.forget());
surface->AddConsumer();
sInstance->mSurfaces.InsertOrUpdate(id, std::move(surface));
}
@ -269,7 +268,6 @@ void SharedSurfacesParent::Add(const wr::ExternalImageId& aId,
auto texture = MakeRefPtr<wr::RenderSharedSurfaceTextureHost>(surface);
wr::RenderThread::Get()->RegisterExternalImage(aId, texture.forget());
surface->AddConsumer();
sInstance->mSurfaces.InsertOrUpdate(id, std::move(surface));
}
@ -284,6 +282,7 @@ void SharedSurfacesParent::AddTrackingLocked(
SourceSurfaceSharedDataWrapper* aSurface,
const StaticMutexAutoLock& aAutoLock) {
MOZ_ASSERT(!aSurface->GetExpirationState()->IsTracked());
MOZ_ASSERT(aSurface->GetConsumers() > 0);
sInstance->mTracker.AddObjectLocked(aSurface, aAutoLock);
}
@ -356,6 +355,7 @@ bool SharedSurfacesParent::AgeAndExpireOneGeneration() {
void SharedSurfacesParent::ExpireMap(
nsTArray<RefPtr<SourceSurfaceSharedDataWrapper>>& aExpired) {
for (auto& surface : aExpired) {
MOZ_ASSERT(surface->GetConsumers() > 0);
surface->ExpireMap();
}
}

View file

@ -17,7 +17,7 @@ default = []
[dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6"
rev = "aa7bec65b90028e4db6ec8def8589b52097d92f9"
# TODO: remove the replay feature on the next update containing https://github.com/gfx-rs/wgpu/pull/5182
features = ["serde", "replay", "trace", "strict_asserts", "wgsl", "api_log_info", "indirect-validation"]
@ -26,32 +26,32 @@ features = ["serde", "replay", "trace", "strict_asserts", "wgsl", "api_log_info"
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6"
rev = "aa7bec65b90028e4db6ec8def8589b52097d92f9"
features = ["metal"]
# We want the wgpu-core Direct3D backends on Windows.
[target.'cfg(windows)'.dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6"
rev = "aa7bec65b90028e4db6ec8def8589b52097d92f9"
features = ["dx12"]
# We want the wgpu-core Vulkan backend on Linux and Windows.
[target.'cfg(any(windows, all(unix, not(any(target_os = "macos", target_os = "ios")))))'.dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6"
rev = "aa7bec65b90028e4db6ec8def8589b52097d92f9"
features = ["vulkan"]
[dependencies.wgt]
package = "wgpu-types"
git = "https://github.com/gfx-rs/wgpu"
rev = "15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6"
rev = "aa7bec65b90028e4db6ec8def8589b52097d92f9"
[dependencies.wgh]
package = "wgpu-hal"
git = "https://github.com/gfx-rs/wgpu"
rev = "15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6"
rev = "aa7bec65b90028e4db6ec8def8589b52097d92f9"
features = ["oom_panic", "device_lost_panic", "internal_error_panic"]
[target.'cfg(windows)'.dependencies]
@ -59,7 +59,7 @@ windows = { version = "0.58", default-features = false, features = ["Win32_Graph
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2"
metal = "0.30"
metal = { version = "0.30", git = "https://github.com/gfx-rs/metal-rs.git", rev = "ef768ff9d7" }
io-surface = "0.15"
[dependencies]

View file

@ -20,11 +20,11 @@ origin:
# Human-readable identifier for this version/release
# Generally "version NNN", "tag SSS", "bookmark SSS"
release: 15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6 (2025-01-03T00:48:54Z).
release: commit aa7bec65b90028e4db6ec8def8589b52097d92f9
# Revision to pull in
# Must be a long or short commit SHA (long preferred)
revision: 15a77b525c6dc76b39b8bd191d0ddfe21ddbcef6
revision: aa7bec65b90028e4db6ec8def8589b52097d92f9
license: ['MIT', 'Apache-2.0']

View file

@ -144,7 +144,7 @@ pub extern "C" fn wgpu_server_new(owner: *mut c_void, use_dxc: bool) -> *mut Glo
let global = wgc::global::Global::new(
"wgpu",
wgt::InstanceDescriptor {
&wgt::InstanceDescriptor {
backends,
flags: instance_flags,
dx12_shader_compiler,

View file

@ -24,6 +24,7 @@ Specific documentation on a few topics is available at:
SavedFrame/index
feature_checklist
bytecode_checklist
use_counter
Components of SpiderMonkey

68
js/src/doc/use_counter.md Normal file
View file

@ -0,0 +1,68 @@
Adding Use Counter Telemetry to the JS Engine
==============================================
[Use Counters](../dom/use-counters.rst) are used to collect data about the execution
of Firefox. In SpiderMonkey we can use use counters to highlight when certain VM
features are used or to measure how often certain scenarios are encountered.
Because use-counters are intended to find the frequency of a feature's use, a page
reports only _if_ a feature was used, not how many times. E.g. if you used a feature
a million times on a page, and loaded that page once, but you dwere the only person
who ever used that feature then telemetry would report only one use, not a million.
To add a SpiderMonkey Use Counter we have a couple extra steps compared to the
rest of Firefox.
## Adding Telemetry
1. Add an entry to `FOR_EACH_JS_USE_COUNTER` in `js/public/friend/UsageStatistics.h`.
This will add an entry to the `JSUseCounter` enum used in the next step.
2. Call `JSRuntime::setUseCounter(JSObject*, JSUseCounter)` where you would like to
report some use counter telemetry. The object passed is used to determine the
global to which this usage will be attributed. A good default choice would be to
pass `cx->global()` here.
The UseCounter machinery is relatively efficient, and so avoiding double
submission of counters is not necessary. Nevertheless, it is not free, so be
cautious about telemetry on very hot paths.
3. Add an entry to `dom/base/UseCounter.conf`. Use a custom entry in the
JavaScript feature usage section. _Note: the first character after `JS_` should
be lowercase. See Bug 1934649._
5. With a browser-building mozconfig active, run `mach gen-use-counter-metrics`. This
will update `dom/base/use_counter_metrics.yaml`. Note the emails will be incorrect
until Bug 1899418 is fixed.
6. Update the switch in `js/xpconnect/src/XPCJSRuntime.cpp` in function
`SetUseCounterCallback`. Essentially this function redirects from the JS types to
the DOM constants defined via use_counter_metrics.yaml.
At this point you should be able to build the browser and the shell.
## Testing Telemetry
You should write tests for your telemetry. You can do that in the shell using the
`getUseCounterResults()` test helper function.
You can then further test your browser telemetry works interactively using
`about:glean`. Visit a page which will trigger the event you're interested in,
then visit `about:glean`, and open the DevTools console.
In the console you can access these use counters from the `Glean` object:
```
Glean.useCounterDoc.jsOptimizeGetIteratorFuse.testGetValue()
```
Will for example print the current ping value for the JS Optimize Get Iterator Fuse
use counter.
## Landing Telemetry
When reviewing telemetry, follow the instructions automatically added by the data
classification robot. Typically our use counter telemetry is deeply non-identifiable
and thus easily "Category 1 Technical Data", but it is the responsibility of the
developer and reviewer to be sure.

View file

@ -1,10 +0,0 @@
var a = [0, 1];
var iterations = 0;
for (var k in a) {
iterations++;
a.length = 1;
}
assertEq(iterations, 1);
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -1,26 +0,0 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
//-----------------------------------------------------------------------------
var BUGNUMBER = 548671;
var summary =
"Don't use a shared-permanent inherited property to implement " +
"[].length or (function(){}).length";
print(BUGNUMBER + ": " + summary);
/**************
* BEGIN TEST *
**************/
var a = [];
a.p = 1;
var x = Object.create(a);
assertEq(x.length, 0);
assertEq(x.p, 1);
assertEq(a.length, 0);
if (typeof reportCompare === "function")
reportCompare(true, true);
print("All tests passed!");

View file

@ -1,39 +0,0 @@
function basic() {
assertEq([0].at(0), 0);
assertEq([0].at(-1), 0);
assertEq([].at(0), undefined);
assertEq([].at(-1), undefined);
assertEq([].at(1), undefined);
assertEq([0, 1].at(0), 0);
assertEq([0, 1].at(1), 1);
assertEq([0, 1].at(-2), 0);
assertEq([0, 1].at(-1), 1);
assertEq([0, 1].at(2), undefined);
assertEq([0, 1].at(-3), undefined);
assertEq([0, 1].at(-4), undefined);
assertEq([0, 1].at(Infinity), undefined);
assertEq([0, 1].at(-Infinity), undefined);
assertEq([0, 1].at(NaN), 0); // ToInteger(NaN) = 0
}
function obj() {
var o = {length: 0, [0]: "a", at: Array.prototype.at};
assertEq(o.at(0), undefined);
assertEq(o.at(-1), undefined);
o.length = 1;
assertEq(o.at(0), "a");
assertEq(o.at(-1), "a");
assertEq(o.at(1), undefined);
assertEq(o.at(-2), undefined);
}
basic();
obj();
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -1,26 +0,0 @@
// |reftest|
function test(otherGlobal) {
let arrays = [
["with", otherGlobal.Array.prototype.with.call([1,2,3], 1, 3)],
["toSpliced", otherGlobal.Array.prototype.toSpliced.call([1, 2, 3], 0, 1, 4, 5)],
["toReversed", otherGlobal.Array.prototype.toReversed.call([1, 2, 3])],
["toSorted", otherGlobal.Array.prototype.toSorted.call([1, 2, 3], (x, y) => y > x)]
]
// Test that calling each method in a different compartment returns an array, and that
// the returned array's prototype matches the other compartment's Array prototype,
// not this one.
for (const [name, arr] of arrays) {
assertEq(arr instanceof Array, false, name + " returned an instance of Array");
assertEq(arr instanceof otherGlobal.Array, true, name + " did not return an instance of new global's Array");
assertEq(Object.getPrototypeOf(arr) !== Object.getPrototypeOf([1, 2, 3]), true,
name + " returned an object with a prototype from the wrong realm");
}
}
test(newGlobal());
test(newGlobal({newCompartment: true}));
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -1,77 +0,0 @@
// |reftest|
function test(otherGlobal) {
assertEq(TypeError !== otherGlobal.TypeError, true);
assertEq(Object.getPrototypeOf(TypeError) !== Object.getPrototypeOf(otherGlobal.TypeError), true);
assertEq(RangeError !== otherGlobal.RangeError, true);
assertEq(Object.getPrototypeOf(RangeError) !== Object.getPrototypeOf(otherGlobal.RangeError), true);
var arrayLike = {
get "0"() {
throw new Error("Get 0");
},
get "4294967295" () { // 2 ** 32 - 1
throw new Error("Get 2147483648");
},
get "4294967296" () { // 2 ** 32
throw new Error("Get 2147483648");
},
length: 2 ** 32
};
let gToSorted = otherGlobal.Array.prototype.toSorted;
let gToSpliced = otherGlobal.Array.prototype.toSpliced;
let gToReversed = otherGlobal.Array.prototype.toReversed;
let gWith = otherGlobal.Array.prototype.with;
let typeErrorCalls = [
["toSorted - bad comparator", () => gToSorted.call([], 5)],
["toSorted - this is null", () => gToSorted.call(null)],
["toSpliced - array too long", () => {
var oldLen = arrayLike.length;
arrayLike.length = 2**53 - 1;
gToSpliced.call(arrayLike, 0, 0, 1);
arrayLike.length = oldLen;
}]
]
let rangeErrorCalls = [
["toSorted - array too long", () => gToSorted.call(arrayLike)],
["toReversed - array too long", () => gToReversed.call(arrayLike)],
["toSpliced - adding elements would exceed max array length", () => gToSpliced.call(arrayLike, 0, 0)],
["with - index out of range", () => gWith.call([0, 1, 2], 3, 7)],
["with - negative index", () => gWith.call([0, 1, 2], -4, 7)],
["with - array too long", () => gWith.call(arrayLike, 0, 0)]
]
// For each erroneous case, make sure the error comes from
// the other realm (not this realm)
for (const [message, f] of typeErrorCalls) {
try {
f();
} catch (exc) {
assertEq(exc instanceof TypeError, false, message + " threw TypeError from wrong realm");
assertEq(exc instanceof otherGlobal.TypeError, true, message + " didn't throw TypeError from other realm");
assertEq(Object.getPrototypeOf(exc) !== Object.getPrototypeOf(TypeError), true,
message + " TypeError has wrong prototype");
}
}
for (const [message, f] of rangeErrorCalls) {
try {
f();
} catch (exc) {
assertEq(exc instanceof RangeError, false, message + " threw RangeError from wrong realm");
assertEq(exc instanceof otherGlobal.RangeError, true, message + " didn't throw RangeError from other realm");
assertEq(Object.getPrototypeOf(exc) !== Object.getPrototypeOf(RangeError), true,
message + " TypeError has wrong prototype");
}
}
}
test(newGlobal());
test(newGlobal({newCompartment: true}));
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -1,25 +0,0 @@
var BUGNUMBER = 1287520;
var summary = 'Array.prototype.concat should check HasProperty everytime for non-dense array';
print(BUGNUMBER + ": " + summary);
var a = [1, 2, 3];
a.constructor = {
[Symbol.species]: function(...args) {
var p = new Proxy(new Array(...args), {
defineProperty(target, propertyKey, receiver) {
if (propertyKey === "0") delete a[1];
return Reflect.defineProperty(target, propertyKey, receiver);
}
});
return p;
}
};
var p = a.concat();
assertEq(0 in p, true);
assertEq(1 in p, false);
assertEq(2 in p, true);
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -1,97 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
//-----------------------------------------------------------------------------
var BUGNUMBER = 911147;
var summary = 'Array.prototype.fill';
print(BUGNUMBER + ": " + summary);
/**************
* BEGIN TEST *
**************/
assertEq(typeof [].fill, 'function');
assertEq([].fill.length, 1);
// Default values for arguments and absolute values for negative start and end
// arguments are resolved correctly.
assertDeepEq([].fill(1), []);
assertDeepEq([1,1,1].fill(2), [2,2,2]);
assertDeepEq([1,1,1].fill(2, 1), [1,2,2]);
assertDeepEq([1,1,1].fill(2, 1, 2), [1,2,1]);
assertDeepEq([1,1,1].fill(2, -2), [1,2,2]);
assertDeepEq([1,1,1].fill(2, -2, -1), [1,2,1]);
assertDeepEq([1,1,1].fill(2, undefined), [2,2,2]);
assertDeepEq([1,1,1].fill(2, undefined, undefined), [2,2,2]);
assertDeepEq([1,1,1].fill(2, 1, undefined), [1,2,2]);
assertDeepEq([1,1,1].fill(2, undefined, 1), [2,1,1]);
assertDeepEq([1,1,1].fill(2, 2, 1), [1,1,1]);
assertDeepEq([1,1,1].fill(2, -1, 1), [1,1,1]);
assertDeepEq([1,1,1].fill(2, -2, 1), [1,1,1]);
assertDeepEq([1,1,1].fill(2, 1, -2), [1,1,1]);
assertDeepEq([1,1,1].fill(2, 0.1), [2,2,2]);
assertDeepEq([1,1,1].fill(2, 0.9), [2,2,2]);
assertDeepEq([1,1,1].fill(2, 1.1), [1,2,2]);
assertDeepEq([1,1,1].fill(2, 0.1, 0.9), [1,1,1]);
assertDeepEq([1,1,1].fill(2, 0.1, 1.9), [2,1,1]);
assertDeepEq([1,1,1].fill(2, 0.1, 1.9), [2,1,1]);
assertDeepEq([1,1,1].fill(2, -0), [2,2,2]);
assertDeepEq([1,1,1].fill(2, 0, -0), [1,1,1]);
assertDeepEq([1,1,1].fill(2, NaN), [2,2,2]);
assertDeepEq([1,1,1].fill(2, 0, NaN), [1,1,1]);
assertDeepEq([1,1,1].fill(2, false), [2,2,2]);
assertDeepEq([1,1,1].fill(2, true), [1,2,2]);
assertDeepEq([1,1,1].fill(2, "0"), [2,2,2]);
assertDeepEq([1,1,1].fill(2, "1"), [1,2,2]);
assertDeepEq([1,1,1].fill(2, "-2"), [1,2,2]);
assertDeepEq([1,1,1].fill(2, "-2", "-1"), [1,2,1]);
assertDeepEq([1,1,1].fill(2, {valueOf: ()=>1}), [1,2,2]);
assertDeepEq([1,1,1].fill(2, 0, {valueOf: ()=>1}), [2,1,1]);
// fill works generically for objects, too.
assertDeepEq([].fill.call({length: 2}, 2), {0: 2, 1: 2, length: 2});
var setterCalled = false;
var objWithSetter = {set "0"(val) { setterCalled = true}, length: 1};
[].fill.call(objWithSetter, 2);
assertEq(setterCalled, true);
var setHandlerCallCount = 0;
var proxy = new Proxy({length: 3}, {set(t, i, v, r) { setHandlerCallCount++; return true; }});
[].fill.call(proxy, 2);
assertEq(setHandlerCallCount, 3);
var valueOfCallCount = 0;
var typedArray = new Uint8ClampedArray(3);
[].fill.call(typedArray, {valueOf: function() {valueOfCallCount++; return 2000;}});
assertEq(valueOfCallCount, 3);
assertEq(typedArray[0], 0xff);
// All remaining cases should throw.
var objWithGetterOnly = {get "0"() {return 1;}, length: 1};
var objWithReadOnlyProp = {length: 1};
Object.defineProperty(objWithReadOnlyProp, 0, {value: 1, writable: false});
var objWithNonconfigurableProp = {length: 1};
Object.defineProperty(objWithNonconfigurableProp, 0, {value: 1, configurable: false});
var frozenObj = {length: 1};
Object.freeze(frozenObj);
var frozenArray = [1, 1, 1];
Object.freeze(frozenArray);
assertThrowsInstanceOf(() => [].fill.call(objWithGetterOnly, 2), TypeError);
assertThrowsInstanceOf(() => [].fill.call(objWithReadOnlyProp, 2), TypeError);
assertThrowsInstanceOf(() => [].fill.call(objWithNonconfigurableProp, 2), TypeError);
assertThrowsInstanceOf(() => [].fill.call(frozenObj, 2), TypeError);
assertThrowsInstanceOf(() => [].fill.call(frozenArray, 2), TypeError);
assertThrowsInstanceOf(() => [].fill.call("111", 2), TypeError);
assertThrowsInstanceOf(() => [].fill.call(null, 2), TypeError);
assertThrowsInstanceOf(() => [].fill.call(undefined, 2), TypeError);
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -1,138 +0,0 @@
// Test corner cases of for-of iteration over Arrays.
// The current SetObject::construct method uses a ForOfIterator to extract
// values from the array, so we use that mechanism to test ForOfIterator here.
// Test the properties and prototype of a generator object.
function TestManySmallArrays() {
function doIter(f, arr) {
return f(...new Set(arr));
}
function fun(a, b, c) {
var result = 0;
for (var i = 0; i < arguments.length; i++)
result += arguments[i];
return result;
}
var TRUE_SUM = 0;
var N = 100;
var M = 3;
var sum = 0;
for (var i = 0; i < N; i++) {
var arr = new Array(M);
for (var j = 0; j < M; j++) {
arr[j] = j;
TRUE_SUM += j;
}
sum += doIter(fun, arr);
}
assertEq(sum, TRUE_SUM);
}
TestManySmallArrays();
// Test the properties and prototype of a generator object.
function TestSingleSmallArray() {
function doIter(f, arr) {
return f(...new Set(arr));
}
function fun(a, b, c) {
var result = 0;
for (var i = 0; i < arguments.length; i++)
result += arguments[i];
return result;
}
var TRUE_SUM = 0;
var N = 100;
var M = 3;
var arr = new Array(M);
for (var j = 0; j < M; j++) {
arr[j] = j;
TRUE_SUM += j;
}
TRUE_SUM *= N;
var sum = 0;
for (var i = 0; i < N; i++) {
sum += doIter(fun, arr);
}
assertEq(sum, TRUE_SUM);
}
TestSingleSmallArray();
function TestChangeArrayPrototype() {
function doIter(f, arr) {
return f(...new Set(arr));
}
function fun(a, b, c) {
var result = 0;
for (var i = 0; i < arguments.length; i++)
result += arguments[i];
return result;
}
var Proto1 = Object.create(Array.prototype);
var TRUE_SUM = 0;
var N = 100;
var MID = N/2;
var M = 3;
var arr = new Array(M);
var ARR_SUM = 0;
for (var j = 0; j < M; j++) {
arr[j] = j;
ARR_SUM += j;
}
var sum = 0;
for (var i = 0; i < N; i++) {
sum += doIter(fun, arr);
if (i == MID)
arr.__proto__ = Proto1;
TRUE_SUM += ARR_SUM;
}
assertEq(sum, TRUE_SUM);
}
TestChangeArrayPrototype();
function TestChangeManyArrayShape() {
function doIter(f, arr) {
return f(...new Set(arr));
}
function fun(a, b, c) {
var result = 0;
for (var i = 0; i < arguments.length; i++)
result += arguments[i];
return result;
}
var TRUE_SUM = 0;
var N = 100;
var MID = N/2;
var M = 3;
var sum = 0;
for (var i = 0; i < N; i++) {
var arr = new Array(M);
var ARR_SUM = 0;
for (var j = 0; j < M; j++) {
arr[j] = j;
ARR_SUM += j;
}
arr['v_' + i] = i;
sum += doIter(fun, arr);
TRUE_SUM += ARR_SUM;
}
assertEq(sum, TRUE_SUM);
}
TestChangeManyArrayShape();
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -1,58 +0,0 @@
// Test corner cases of for-of iteration over Arrays.
// The current SetObject::construct method uses a ForOfIterator to extract
// values from the array, so we use that mechanism to test ForOfIterator here.
//
// Check case where ArrayIterator.prototype.next changes in the middle of iteration.
//
function TestChangeArrayIteratorNext() {
function doIter(f, arr) {
return f(...new Set(arr));
}
function fun(a, b, c) {
var result = 0;
for (var i = 0; i < arguments.length; i++)
result += arguments[i];
return result;
}
var GET_COUNT = 0;
function getter() {
GET_COUNT++;
if (GET_COUNT == MID)
iterProto.next = NewNext;
return M2;
}
var iter = ([])[Symbol.iterator]();
var iterProto = Object.getPrototypeOf(iter);
var OldNext = iterProto.next;
var NewNext = function () {
return OldNext.apply(this, arguments);
};
var TRUE_SUM = 0;
var N = 100;
var MID = N/2;
var M = 3;
var arr = new Array(M);
var ARR_SUM = 0;
for (var j = 0; j < M; j++) {
arr[j] = j;
ARR_SUM += j;
}
var M2 = (M/2)|0;
Object.defineProperty(arr, M2, {'get':getter});
var sum = 0;
for (var i = 0; i < N; i++) {
sum += doIter(fun, arr);
TRUE_SUM += ARR_SUM;
}
assertEq(sum, TRUE_SUM);
}
TestChangeArrayIteratorNext();
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -1,60 +0,0 @@
// Test corner cases of for-of iteration over Arrays.
// The current SetObject::construct method uses a ForOfIterator to extract
// values from the array, so we use that mechanism to test ForOfIterator here.
//
// Check array length increases changes during iteration.
//
function TestIncreaseArrayLength() {
function doIter(f, arr) {
return f(...new Set(arr));
}
function fun(a, b, c) {
var result = 0;
for (var i = 0; i < arguments.length; i++)
result += arguments[i];
return result;
}
var GET_COUNT = 0;
function getter() {
GET_COUNT++;
if (GET_COUNT == MID) {
ARR_SUM += arr.length;
arr.push(arr.length);
}
return M2;
}
var iter = ([])[Symbol.iterator]();
var iterProto = Object.getPrototypeOf(iter);
var OldNext = iterProto.next;
var NewNext = function () {
return OldNext.apply(this, arguments);
};
var TRUE_SUM = 0;
var N = 100;
var MID = N/2;
var M = 3;
var arr = new Array(M);
var ARR_SUM = 0;
for (var j = 0; j < M; j++) {
arr[j] = j;
ARR_SUM += j;
}
var M2 = (M/2)|0;
Object.defineProperty(arr, M2, {'get':getter});
var sum = 0;
for (var i = 0; i < N; i++) {
sum += doIter(fun, arr);
TRUE_SUM += ARR_SUM;
}
assertEq(sum, TRUE_SUM);
}
TestIncreaseArrayLength();
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -1,64 +0,0 @@
// Test corner cases of for-of iteration over Arrays.
// The current SetObject::construct method uses a ForOfIterator to extract
// values from the array, so we use that mechanism to test ForOfIterator here.
//
// Check array length decreases changes during iteration.
//
function TestDecreaseArrayLength() {
function doIter(f, arr) {
return f(...new Set(arr));
}
function fun(a, b, c) {
var result = 0;
for (var i = 0; i < arguments.length; i++)
result += arguments[i];
return result;
}
var GET_COUNT = 0;
function getter() {
GET_COUNT++;
if (GET_COUNT == MID) {
arr.length = 0;
}
return M2;
}
var iter = ([])[Symbol.iterator]();
var iterProto = Object.getPrototypeOf(iter);
var OldNext = iterProto.next;
var NewNext = function () {
return OldNext.apply(this, arguments);
};
var TRUE_SUM = 0;
var N = 100;
var MID = N/2;
var M = 3;
var arr = new Array(M);
var ARR_SUM = 0;
for (var j = 0; j < M; j++) {
arr[j] = j;
ARR_SUM += j;
}
var M2 = (M/2)|0;
Object.defineProperty(arr, M2, {'get':getter});
var sum = 0;
for (var i = 0; i < N; i++) {
var oldLen = arr.length;
sum += doIter(fun, arr);
var newLen = arr.length;
if (oldLen == newLen)
TRUE_SUM += arr.length > 0 ? ARR_SUM : 0;
else
TRUE_SUM += 1
}
assertEq(sum, TRUE_SUM);
}
TestDecreaseArrayLength();
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -1,183 +0,0 @@
var BUGNUMBER = 1180306;
var summary = 'Array.from should close iterator on error';
print(BUGNUMBER + ": " + summary);
function test(ctor, { mapVal=undefined,
nextVal=undefined,
nextThrowVal=undefined,
modifier=undefined,
exceptionVal=undefined,
exceptionType=undefined,
closed=true }) {
let iterable = {
closed: false,
[Symbol.iterator]() {
let iterator = {
first: true,
next() {
if (this.first) {
this.first = false;
if (nextThrowVal)
throw nextThrowVal;
return nextVal;
}
return { value: undefined, done: true };
},
return() {
iterable.closed = true;
return {};
}
};
if (modifier)
modifier(iterator, iterable);
return iterator;
}
};
if (exceptionVal) {
let caught = false;
try {
ctor.from(iterable, mapVal);
} catch (e) {
assertEq(e, exceptionVal);
caught = true;
}
assertEq(caught, true);
} else if (exceptionType) {
assertThrowsInstanceOf(() => ctor.from(iterable, mapVal), exceptionType);
} else {
ctor.from(iterable, mapVal);
}
assertEq(iterable.closed, closed);
}
// == Error cases with close ==
// ES 2017 draft 22.1.2.1 step 5.e.i.1.
// Cannot test.
// ES 2017 draft 22.1.2.1 step 5.e.vi.2.
test(Array, {
mapVal: () => { throw "map throws"; },
nextVal: { value: 1, done: false },
exceptionVal: "map throws",
closed: true,
});
// ES 2017 draft 22.1.2.1 step 5.e.ix.
class MyArray extends Array {
constructor() {
return new Proxy({}, {
defineProperty() {
throw "defineProperty throws";
}
});
}
}
test(MyArray, {
nextVal: { value: 1, done: false },
exceptionVal: "defineProperty throws",
closed: true,
});
// ES 2021 draft 7.4.6 step 5.
// if GetMethod fails, the thrown value should be ignored.
test(MyArray, {
nextVal: { value: 1, done: false },
modifier: (iterator, iterable) => {
Object.defineProperty(iterator, "return", {
get: function() {
iterable.closed = true;
throw "return getter throws";
}
});
},
exceptionVal: "defineProperty throws",
closed: true,
});
test(MyArray, {
nextVal: { value: 1, done: false },
modifier: (iterator, iterable) => {
Object.defineProperty(iterator, "return", {
get: function() {
iterable.closed = true;
return "non object";
}
});
},
exceptionVal: "defineProperty throws",
closed: true,
});
test(MyArray, {
nextVal: { value: 1, done: false },
modifier: (iterator, iterable) => {
Object.defineProperty(iterator, "return", {
get: function() {
iterable.closed = true;
// Non callable.
return {};
}
});
},
exceptionVal: "defineProperty throws",
closed: true,
});
// ES 2017 draft 7.4.6 steps 6.
// if return method throws, the thrown value should be ignored.
test(MyArray, {
nextVal: { value: 1, done: false },
modifier: (iterator, iterable) => {
iterator.return = function() {
iterable.closed = true;
throw "return throws";
};
},
exceptionVal: "defineProperty throws",
closed: true,
});
test(MyArray, {
nextVal: { value: 1, done: false },
modifier: (iterator, iterable) => {
iterator.return = function() {
iterable.closed = true;
return "non object";
};
},
exceptionVal: "defineProperty throws",
closed: true,
});
// == Error cases without close ==
// ES 2017 draft 22.1.2.1 step 5.e.iii.
test(Array, {
nextThrowVal: "next throws",
exceptionVal: "next throws",
closed: false,
});
test(Array, {
nextVal: { value: {}, get done() { throw "done getter throws"; } },
exceptionVal: "done getter throws",
closed: false,
});
// ES 2017 draft 22.1.2.1 step 5.e.v.
test(Array, {
nextVal: { get value() { throw "value getter throws"; }, done: false },
exceptionVal: "value getter throws",
closed: false,
});
// == Successful cases ==
test(Array, {
nextVal: { value: 1, done: false },
closed: false,
});
if (typeof reportCompare === 'function')
reportCompare(true, true);

View file

@ -1,51 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// Array.from copies arrays.
var src = [1, 2, 3], copy = Array.from(src);
assertEq(copy === src, false);
assertEq(Array.isArray(copy), true);
assertDeepEq(copy, src);
// Non-element properties are not copied.
var a = [0, 1];
a.name = "lisa";
assertDeepEq(Array.from(a), [0, 1]);
// It's a shallow copy.
src = [[0], [1]];
copy = Array.from(src);
assertEq(copy[0], src[0]);
assertEq(copy[1], src[1]);
// Array.from can copy non-iterable objects, if they're array-like.
src = {0: "zero", 1: "one", length: 2};
copy = Array.from(src);
assertEq(Array.isArray(copy), true);
assertDeepEq(copy, ["zero", "one"]);
// Properties past the .length are not copied.
src = {0: "zero", 1: "one", 2: "two", 9: "nine", name: "lisa", length: 2};
assertDeepEq(Array.from(src), ["zero", "one"]);
// If an object has neither an @@iterator method nor .length,
// then it's treated as zero-length.
assertDeepEq(Array.from({}), []);
// Source object property order doesn't matter.
src = {length: 2, 1: "last", 0: "first"};
assertDeepEq(Array.from(src), ["first", "last"]);
// Array.from does not preserve holes.
assertDeepEq(Array.from(Array(3)), [undefined, undefined, undefined]);
assertDeepEq(Array.from([, , 2, 3]), [undefined, undefined, 2, 3]);
assertDeepEq(Array.from([0, , , ,]), [0, undefined, undefined, undefined]);
// Even on non-iterable objects.
assertDeepEq(Array.from({length: 4}), [undefined, undefined, undefined, undefined]);
// Array.from should coerce negative lengths to zero.
assertDeepEq(Array.from({length: -1}), []);
if (typeof reportCompare === 'function')
reportCompare(0, 0);

View file

@ -1,56 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// Array.from can be applied to any constructor.
// For example, the Date builtin constructor.
var d = Array.from.call(Date, ["A", "B"]);
assertEq(Array.isArray(d), false);
assertEq(Object.prototype.toString.call(d), "[object Date]");
assertEq(Object.getPrototypeOf(d), Date.prototype);
assertEq(d.length, 2);
assertEq(d[0], "A");
assertEq(d[1], "B");
// Or Object.
var obj = Array.from.call(Object, []);
assertEq(Array.isArray(obj), false);
assertEq(Object.getPrototypeOf(obj), Object.prototype);
assertEq(Object.getOwnPropertyNames(obj).join(","), "length");
assertEq(obj.length, 0);
// Or any JS function.
function C(arg) {
this.args = arguments;
}
var c = Array.from.call(C, {length: 1, 0: "zero"});
assertEq(c instanceof C, true);
assertEq(c.args.length, 1);
assertEq(c.args[0], 1);
assertEq(c.length, 1);
assertEq(c[0], "zero");
// If the 'this' value passed to Array.from is not a constructor,
// a plain Array is created.
var arr = [3, 4, 5];
var nonconstructors = [
{}, Math, Object.getPrototypeOf, undefined, 17,
() => ({}) // arrow functions are not constructors
];
for (var v of nonconstructors) {
obj = Array.from.call(v, arr);
assertEq(Array.isArray(obj), true);
assertDeepEq(obj, arr);
}
// Array.from does not get confused if global.Array is replaced with another
// constructor.
function NotArray() {
}
var RealArray = Array;
NotArray.from = Array.from;
Array = NotArray;
assertEq(RealArray.from([1]) instanceof RealArray, true);
assertEq(NotArray.from([1]) instanceof NotArray, true);
if (typeof reportCompare === 'function')
reportCompare(0, 0);

View file

@ -1,152 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// Array.from throws if the argument is undefined or null.
assertThrowsInstanceOf(() => Array.from(), TypeError);
assertThrowsInstanceOf(() => Array.from(undefined), TypeError);
assertThrowsInstanceOf(() => Array.from(null), TypeError);
// Array.from throws if an element can't be defined on the new object.
function ObjectWithReadOnlyElement() {
Object.defineProperty(this, "0", {value: null});
this.length = 0;
}
ObjectWithReadOnlyElement.from = Array.from;
assertDeepEq(ObjectWithReadOnlyElement.from([]), new ObjectWithReadOnlyElement);
assertThrowsInstanceOf(() => ObjectWithReadOnlyElement.from([1]), TypeError);
// The same, but via preventExtensions.
function InextensibleObject() {
Object.preventExtensions(this);
}
InextensibleObject.from = Array.from;
assertThrowsInstanceOf(() => InextensibleObject.from([1]), TypeError);
// We will now test this property, that Array.from throws if the .length can't
// be assigned, using several different kinds of object.
var obj;
function init(self) {
obj = self;
self[0] = self[1] = self[2] = self[3] = 0;
}
function testUnsettableLength(C, Exc) {
if (Exc === undefined)
Exc = TypeError; // the usual expected exception type
C.from = Array.from;
obj = null;
assertThrowsInstanceOf(() => C.from([]), Exc);
assertEq(obj instanceof C, true);
for (var i = 0; i < 4; i++)
assertEq(obj[0], 0);
obj = null;
assertThrowsInstanceOf(() => C.from([0, 10, 20, 30]), Exc);
assertEq(obj instanceof C, true);
for (var i = 0; i < 4; i++)
assertEq(obj[i], i * 10);
}
// Array.from throws if the new object's .length can't be assigned because
// there is no .length and the object is inextensible.
function InextensibleObject4() {
init(this);
Object.preventExtensions(this);
}
testUnsettableLength(InextensibleObject4);
// Array.from throws if the new object's .length can't be assigned because it's
// read-only.
function ObjectWithReadOnlyLength() {
init(this);
Object.defineProperty(this, "length", {configurable: true, writable: false, value: 4});
}
testUnsettableLength(ObjectWithReadOnlyLength);
// The same, but using a builtin type.
Uint8Array.from = Array.from;
assertThrowsInstanceOf(() => Uint8Array.from([]), TypeError);
// Array.from throws if the new object's .length can't be assigned because it
// inherits a readonly .length along the prototype chain.
function ObjectWithInheritedReadOnlyLength() {
init(this);
}
Object.defineProperty(ObjectWithInheritedReadOnlyLength.prototype,
"length",
{configurable: true, writable: false, value: 4});
testUnsettableLength(ObjectWithInheritedReadOnlyLength);
// The same, but using an object with a .length getter but no setter.
function ObjectWithGetterOnlyLength() {
init(this);
Object.defineProperty(this, "length", {configurable: true, get: () => 4});
}
testUnsettableLength(ObjectWithGetterOnlyLength);
// The same, but with a setter that throws.
function ObjectWithThrowingLengthSetter() {
init(this);
Object.defineProperty(this, "length", {
configurable: true,
get: () => 4,
set: () => { throw new RangeError("surprise!"); }
});
}
testUnsettableLength(ObjectWithThrowingLengthSetter, RangeError);
// Array.from throws if mapfn is neither callable nor undefined.
assertThrowsInstanceOf(() => Array.from([3, 4, 5], {}), TypeError);
assertThrowsInstanceOf(() => Array.from([3, 4, 5], "also not a function"), TypeError);
assertThrowsInstanceOf(() => Array.from([3, 4, 5], null), TypeError);
// Even if the function would not have been called.
assertThrowsInstanceOf(() => Array.from([], JSON), TypeError);
// If mapfn is not undefined and not callable, the error happens before anything else.
// Before calling the constructor, before touching the arrayLike.
var log = "";
function C() {
log += "C";
obj = this;
}
var p = new Proxy({}, {
has: function () { log += "1"; },
get: function () { log += "2"; },
getOwnPropertyDescriptor: function () { log += "3"; }
});
assertThrowsInstanceOf(() => Array.from.call(C, p, {}), TypeError);
assertEq(log, "");
// If mapfn throws, the new object has already been created.
var arrayish = {
get length() { log += "l"; return 1; },
get 0() { log += "0"; return "q"; }
};
log = "";
var exc = {surprise: "ponies"};
assertThrowsValue(() => Array.from.call(C, arrayish, () => { throw exc; }), exc);
assertEq(log, "lC0");
assertEq(obj instanceof C, true);
// It's a TypeError if the @@iterator property is a primitive (except null and undefined).
for (var primitive of ["foo", 17, Symbol(), true]) {
assertThrowsInstanceOf(() => Array.from({[Symbol.iterator] : primitive}), TypeError);
}
assertDeepEq(Array.from({[Symbol.iterator]: null}), []);
assertDeepEq(Array.from({[Symbol.iterator]: undefined}), []);
// It's a TypeError if the iterator's .next() method returns a primitive.
for (var primitive of [undefined, null, 17]) {
assertThrowsInstanceOf(
() => Array.from({
[Symbol.iterator]() {
return {next() { return primitive; }};
}
}),
TypeError);
}
if (typeof reportCompare === 'function')
reportCompare(0, 0);

View file

@ -1,50 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// Array.from works on arguments objects.
(function () {
assertDeepEq(Array.from(arguments), ["arg0", "arg1", undefined]);
})("arg0", "arg1", undefined);
// If an object has both .length and [@@iterator] properties, [@@iterator] is used.
var a = ['a', 'e', 'i', 'o', 'u'];
a[Symbol.iterator] = function* () {
for (var i = 5; i--; )
yield this[i];
};
var log = '';
function f(x) {
log += x;
return x + x;
}
var b = Array.from(a, f);
assertDeepEq(b, ['uu', 'oo', 'ii', 'ee', 'aa']);
assertEq(log, 'uoiea');
// In fact, if [@@iterator] is present, .length isn't queried at all.
var pa = new Proxy(a, {
has: function (target, id) {
if (id === "length")
throw new Error(".length should not be queried (has)");
return id in target;
},
get: function (target, id) {
if (id === "length")
throw new Error(".length should not be queried (get)");
return target[id];
},
getOwnPropertyDescriptor: function (target, id) {
if (id === "length")
throw new Error(".length should not be queried (getOwnPropertyDescriptor)");
return Object.getOwnPropertyDescriptor(target, id)
}
});
log = "";
b = Array.from(pa, f);
assertDeepEq(b, ['uu', 'oo', 'ii', 'ee', 'aa']);
assertEq(log, 'uoiea');
if (typeof reportCompare === 'function')
reportCompare(0, 0);

View file

@ -1,13 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// Array.from calls a length setter if present.
var hits = 0;
function C() {}
C.prototype = {set length(v) { hits++; }};
C.from = Array.from;
var copy = C.from(["A", "B"]);
assertEq(hits, 1);
if (typeof reportCompare === 'function')
reportCompare(0, 0);

View file

@ -1,41 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// If the mapfn argument to Array.from is undefined, don't map.
assertDeepEq(Array.from([3, 4, 5], undefined), [3, 4, 5]);
assertDeepEq(Array.from([4, 5, 6], undefined, Math), [4, 5, 6]);
// mapfn is called with two arguments: value and index.
var log = [];
function f() {
log.push(Array.from(arguments));
return log.length;
}
assertDeepEq(Array.from(['a', 'e', 'i', 'o', 'u'], f), [1, 2, 3, 4, 5]);
assertDeepEq(log, [['a', 0], ['e', 1], ['i', 2], ['o', 3], ['u', 4]]);
// If the object to be copied is non-iterable, mapfn is still called with two
// arguments.
log = [];
assertDeepEq(Array.from({0: "zero", 1: "one", length: 2}, f), [1, 2]);
assertDeepEq(log, [["zero", 0], ["one", 1]]);
// If the object to be copied is iterable and the constructor is not Array,
// mapfn is still called with two arguments.
log = [];
function C() {}
C.from = Array.from;
var c = new C;
c[0] = 1;
c[1] = 2;
c.length = 2;
assertDeepEq(C.from(["zero", "one"], f), c);
assertDeepEq(log, [["zero", 0], ["one", 1]]);
// The mapfn is called even if the value to be mapped is undefined.
assertDeepEq(Array.from([0, 1, , 3], String), ["0", "1", "undefined", "3"]);
var arraylike = {length: 4, "0": 0, "1": 1, "3": 3};
assertDeepEq(Array.from(arraylike, String), ["0", "1", "undefined", "3"]);
if (typeof reportCompare === 'function')
reportCompare(0, 0);

View file

@ -1,21 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
for (let primitive of [true, 3.14, "hello", Symbol()]) {
let prototype = Object.getPrototypeOf(primitive);
Object.defineProperty(prototype, Symbol.iterator, {
configurable: true,
get() {
"use strict";
assertEq(this, primitive);
return () => [this][Symbol.iterator]();
},
});
assertEq(Array.from(primitive)[0], primitive);
delete prototype[Symbol.iterator];
}
if (typeof reportCompare === 'function')
reportCompare(0, 0);

View file

@ -1,55 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// Two tests involving Array.from and a Proxy.
var log = [];
function LoggingProxy(target) {
var h = {
defineProperty: function (t, id) {
log.push("define", id);
return true;
},
has: function (t, id) {
log.push("has", id);
return id in t;
},
get: function (t, id) {
log.push("get", id);
return t[id];
},
set: function (t, id, v) {
log.push("set", id);
t[id] = v;
return true;
}
};
return new Proxy(target || [], h);
}
// When the new object created by Array.from is a Proxy,
// Array.from calls handler.defineProperty to create new elements
// but handler.set to set the length.
LoggingProxy.from = Array.from;
LoggingProxy.from([3, 4, 5]);
assertDeepEq(log, ["define", "0", "define", "1", "define", "2", "set", "length"]);
// When the argument passed to Array.from is a Proxy, Array.from
// calls handler.get on it.
log = [];
assertDeepEq(Array.from(new LoggingProxy([3, 4, 5])), [3, 4, 5]);
assertDeepEq(log, ["get", Symbol.iterator,
"get", "length", "get", "0",
"get", "length", "get", "1",
"get", "length", "get", "2",
"get", "length"]);
// Array-like iteration only gets the length once.
log = [];
var arr = [5, 6, 7];
arr[Symbol.iterator] = undefined;
assertDeepEq(Array.from(new LoggingProxy(arr)), [5, 6, 7]);
assertDeepEq(log, ["get", Symbol.iterator,
"get", "length", "get", "0", "get", "1", "get", "2"]);
if (typeof reportCompare === 'function')
reportCompare(0, 0);

View file

@ -1,37 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
if (typeof newGlobal === 'function') {
// G.Array.from, where G is any global, produces an array whose prototype
// is G.Array.prototype.
var g = newGlobal();
var ga = g.Array.from([1, 2, 3]);
assertEq(ga instanceof g.Array, true);
// Even if G.Array is not passed in as the 'this' value to the call.
var from = g.Array.from
var ga2 = from([1, 2, 3]);
assertEq(ga2 instanceof g.Array, true);
// Array.from can be applied to a constructor from another realm.
var p = Array.from.call(g.Array, [1, 2, 3]);
assertEq(p instanceof g.Array, true);
var q = g.Array.from.call(Array, [3, 4, 5]);
assertEq(q instanceof Array, true);
// The default 'this' value received by a non-strict mapping function is
// that function's global, not Array.from's global or the caller's global.
var h = newGlobal(), result = undefined;
h.mainGlobal = this;
h.eval("function f() { mainGlobal.result = this; }");
g.Array.from.call(Array, [5, 6, 7], h.f);
// (Give each global in the test a name, for better error messages. But use
// globalName, because window.name is complicated.)
this.globalName = "main";
g.globalName = "g";
h.globalName = "h";
assertEq(result.globalName, "h");
}
if (typeof reportCompare === 'function')
reportCompare(0, 0);

View file

@ -1,23 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// Array.from on a string iterates over the string.
assertDeepEq(Array.from("test string"),
['t', 'e', 's', 't', ' ', 's', 't', 'r', 'i', 'n', 'g']);
// Array.from on a string handles surrogate pairs correctly.
var gclef = "\uD834\uDD1E"; // U+1D11E MUSICAL SYMBOL G CLEF
assertDeepEq(Array.from(gclef), [gclef]);
assertDeepEq(Array.from(gclef + " G"), [gclef, " ", "G"]);
// Array.from on a string calls the @@iterator method.
String.prototype[Symbol.iterator] = function* () { yield 1; yield 2; };
assertDeepEq(Array.from("anything"), [1, 2]);
// If the iterator method is deleted, Strings are still arraylike.
delete String.prototype[Symbol.iterator];
assertDeepEq(Array.from("works"), ['w', 'o', 'r', 'k', 's']);
assertDeepEq(Array.from(gclef), ['\uD834', '\uDD1E']);
if (typeof reportCompare === 'function')
reportCompare(0, 0);

View file

@ -1,13 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// Check superficial features of Array.from.
var desc = Object.getOwnPropertyDescriptor(Array, "from");
assertEq(desc.configurable, true);
assertEq(desc.enumerable, false);
assertEq(desc.writable, true);
assertEq(Array.from.length, 1);
assertThrowsInstanceOf(() => new Array.from(), TypeError); // not a constructor
if (typeof reportCompare === 'function')
reportCompare(0, 0);

View file

@ -1,48 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// The third argument to Array.from is passed as the 'this' value to the
// mapping function.
var hits = 0, obj = {};
function f(x) {
assertEq(this, obj);
hits++;
}
Array.from(["a", "b", "c"], f, obj);
assertEq(hits, 3);
// Without an argument, undefined is passed...
hits = 0;
function gs(x) {
"use strict";
assertEq(this, undefined);
hits++;
}
Array.from("def", gs);
assertEq(hits, 3);
// ...and if the mapping function is non-strict, that means the global is
// passed.
var global = this;
hits = 0;
function g(x) {
assertEq(this, global);
hits++;
}
Array.from("ghi", g);
assertEq(hits, 3);
// A primitive value can be passed.
for (var v of [0, "str", undefined]) {
hits = 0;
var mapfn = function h(x) {
"use strict";
assertEq(this, v);
hits++;
};
Array.from("pq", mapfn, v);
assertEq(hits, 2);
}
if (typeof reportCompare === 'function')
reportCompare(0, 0);

View file

@ -1,42 +0,0 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
* Author: Emilio Cobos Álvarez <ecoal95@gmail.com>
*/
var BUGNUMBER = 1310744;
var summary = "Dense array properties shouldn't be modified when they're frozen";
print(BUGNUMBER + ": " + summary);
var a = Object.freeze([4, 5, 1]);
function assertArrayIsExpected() {
assertEq(a.length, 3);
assertEq(a[0], 4);
assertEq(a[1], 5);
assertEq(a[2], 1);
}
assertThrowsInstanceOf(() => a.reverse(), TypeError);
assertThrowsInstanceOf(() => a.shift(), TypeError);
assertThrowsInstanceOf(() => a.unshift(0), TypeError);
assertThrowsInstanceOf(() => a.sort(function() {}), TypeError);
assertThrowsInstanceOf(() => a.pop(), TypeError);
assertThrowsInstanceOf(() => a.fill(0), TypeError);
assertThrowsInstanceOf(() => a.splice(0, 1, 1), TypeError);
assertThrowsInstanceOf(() => a.push("foo"), TypeError);
assertThrowsInstanceOf(() => { "use strict"; a.length = 5; }, TypeError);
assertThrowsInstanceOf(() => { "use strict"; a[2] = "foo"; }, TypeError);
assertThrowsInstanceOf(() => { "use strict"; delete a[0]; }, TypeError);
assertThrowsInstanceOf(() => a.splice(Math.a), TypeError);
// Shouldn't throw, since this is not strict mode, but shouldn't change the
// value of the property.
a.length = 5;
a[2] = "foo";
assertEq(delete a[0], false);
assertArrayIsExpected();
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -1,18 +0,0 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
* Author: Emilio Cobos Álvarez <ecoal95@gmail.com>
*/
var BUGNUMBER = 1312948;
var summary = "Freezing a dictionary mode object with a length property should make Object.isFrozen report true";
print(BUGNUMBER + ": " + summary);
/* Convert to dictionary mode */
delete Array.prototype.slice;
Object.freeze(Array.prototype);
assertEq(Object.isFrozen(Array.prototype), true);
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -1,9 +0,0 @@
var BUGNUMBER = 1180290;
var summary = 'Array getters should have get prefix';
print(BUGNUMBER + ": " + summary);
assertEq(Object.getOwnPropertyDescriptor(Array, Symbol.species).get.name, "get [Symbol.species]");
if (typeof reportCompare === 'function')
reportCompare(true, true);

View file

@ -1,18 +0,0 @@
var array = [1, 2, 3];
var calls = 0;
var grouped = Object.groupBy(array, () => {
calls++;
return {
toString() {
return "a";
}
}
});
assertEq(calls, 3);
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -1,15 +0,0 @@
var array = [0];
var grouped = Object.groupBy(array, () => "length");
assertDeepEq(grouped, Object.create(null, {
length: {
value: [0],
writable: true,
enumerable: true,
configurable: true,
},
}));
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -1,88 +0,0 @@
function isNeg(x) {
if (Object.is(x, -0) || x < 0) {
return true;
}
return false;
}
{
const a1 = [-Infinity, -2, -1, -0, 0, 1, 2, Infinity];
const expectedObj = { neg: [-Infinity, -2, -1, -0], pos: [0, 1, 2, Infinity] };
Object.setPrototypeOf(expectedObj, null);
const groupedArray = Object.groupBy(a1, x => isNeg(x) ? 'neg' : 'pos');
const mappedArray = Map.groupBy(a1, x => isNeg(x) ? 'neg' : 'pos');
assertEq(Object.getPrototypeOf(groupedArray), null)
assertDeepEq(groupedArray, expectedObj);
assertDeepEq(mappedArray.get("neg"), expectedObj["neg"]);
assertDeepEq(mappedArray.get("pos"), expectedObj["pos"]);
const expectedObj2 = {"undefined": [1,2,3]}
Object.setPrototypeOf(expectedObj2, null);
assertDeepEq(Object.groupBy([1,2,3], () => {}), expectedObj2);
assertDeepEq(Object.groupBy([], () => {}), Object.create(null));
assertDeepEq((Map.groupBy([1,2,3], () => {})).get(undefined), [1,2,3]);
assertEq((Map.groupBy([1,2,3], () => {})).size, 1);
const negMappedArray = Map.groupBy(a1, x => isNeg(x) ? -0 : 0);
assertDeepEq(negMappedArray.get(0), a1);
assertDeepEq(negMappedArray.size, 1);
assertThrowsInstanceOf(() => Object.groupBy([], undefined), TypeError);
assertThrowsInstanceOf(() => Object.groupBy([], null), TypeError);
assertThrowsInstanceOf(() => Object.groupBy([], 0), TypeError);
assertThrowsInstanceOf(() => Object.groupBy([], ""), TypeError);
assertThrowsInstanceOf(() => Map.groupBy([], undefined), TypeError);
assertThrowsInstanceOf(() => Map.groupBy([], null), TypeError);
assertThrowsInstanceOf(() => Map.groupBy([], 0), TypeError);
assertThrowsInstanceOf(() => Map.groupBy([], ""), TypeError);
}
const array = [ 'test' ];
Object.defineProperty(Map.prototype, 4, {
get() {
throw new Error('monkey-patched Map get call');
},
set(v) {
throw new Error('monkey-patched Map set call');
},
has(v) {
throw new Error('monkey-patched Map has call');
}
});
const map1 = Map.groupBy(array, key => key.length);
assertEq('test', map1.get(4)[0])
Object.defineProperty(Array.prototype, '4', {
set(v) {
throw new Error('user observable array set');
},
get() {
throw new Error('user observable array get');
}
});
const map2 = Map.groupBy(array, key => key.length);
const arr = Object.groupBy(array, key => key.length);
assertEq('test', map2.get(4)[0])
assertEq('test', arr[4][0])
Object.defineProperty(Object.prototype, "foo", {
get() { throw new Error("user observable object get"); },
set(v) { throw new Error("user observable object set"); }
});
Object.groupBy([1, 2, 3], () => 'foo');
// Ensure property key is correctly accessed
count = 0;
p = Object.groupBy([1], () => ({ toString() { count++; return 10 } }));
assertEq(count, 1);
Map.groupBy([1], () => ({ toString() { count++; return 10 } }));
assertEq(count, 1);
reportCompare(0, 0);

View file

@ -1,16 +0,0 @@
// Array with trailing hole as explicit "magic elements hole".
assertEq([,].includes(), true);
assertEq([,].includes(undefined), true);
assertEq([,].includes(undefined, 0), true);
assertEq([,].includes(null), false);
assertEq([,].includes(null, 0), false);
// Array with trailing hole with no explicit "magic elements hole".
assertEq(Array(1).includes(), true);
assertEq(Array(1).includes(undefined), true);
assertEq(Array(1).includes(undefined, 0), true);
assertEq(Array(1).includes(null), false);
assertEq(Array(1).includes(null, 0), false);
if (typeof reportCompare === "function")
reportCompare(true, true);

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