Update On Tue Jan 21 19:51:51 CET 2025
This commit is contained in:
parent
e049474a96
commit
490a5a2db1
2029 changed files with 22345 additions and 158165 deletions
113
Cargo.lock
generated
113
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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`
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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",
|
||||
{
|
||||
|
|
|
@ -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"',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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::]",
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -6,3 +6,7 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -12,5 +12,5 @@ SharedLibrary("crashinjectdll")
|
|||
|
||||
DEFFILE = "crashinjectdll.def"
|
||||
|
||||
USE_STATIC_LIBS = True
|
||||
USE_STATIC_MSVCRT = True
|
||||
NO_PGO = True
|
||||
|
|
|
@ -11,7 +11,7 @@ if CONFIG["ENABLE_TESTS"]:
|
|||
SOURCES += [
|
||||
"crashinject.cpp",
|
||||
]
|
||||
USE_STATIC_LIBS = True
|
||||
USE_STATIC_MSVCRT = True
|
||||
|
||||
NO_PGO = True
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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(
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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
|
@ -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")
|
||||
|
|
|
@ -69,6 +69,7 @@ DevToolsModules(
|
|||
"picker-constants.js",
|
||||
"plural-form.js",
|
||||
"protocol.js",
|
||||
"string.js",
|
||||
"system.js",
|
||||
"ThreadSafeDevToolsUtils.js",
|
||||
"throttle.js",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
`
|
||||
);
|
||||
|
|
|
@ -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() "
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
955
editor/libeditor/WSRunScanner.cpp
Normal file
955
editor/libeditor/WSRunScanner.cpp
Normal 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
|
|
@ -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
|
1380
editor/libeditor/WSRunScannerNestedClasses.cpp
Normal file
1380
editor/libeditor/WSRunScannerNestedClasses.cpp
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
342
editor/libeditor/WhiteSpaceVisibilityKeeper.h
Normal file
342
editor/libeditor/WhiteSpaceVisibilityKeeper.h
Normal 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
|
|
@ -78,7 +78,9 @@ UNIFIED_SOURCES += [
|
|||
"TextEditor.cpp",
|
||||
"TextEditorDataTransfer.cpp",
|
||||
"TextEditSubActionHandler.cpp",
|
||||
"WSRunObject.cpp",
|
||||
"WhiteSpaceVisibilityKeeper.cpp",
|
||||
"WSRunScanner.cpp",
|
||||
"WSRunScannerNestedClasses.cpp",
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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']
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
68
js/src/doc/use_counter.md
Normal 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.
|
|
@ -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);
|
|
@ -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!");
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue