use windows::Win32::Foundation::{HWND, LPARAM};
use windows::Win32::System::Threading::GetCurrentThreadId;
use windows::Win32::UI::WindowsAndMessaging::{
CreateWindowExW, DestroyWindow, EnumThreadWindows, GW_HWNDNEXT, GW_OWNER, GetTopWindow,
GetWindow, HWND_BOTTOM, HWND_TOP, IsIconic, IsWindow, IsWindowVisible, SW_HIDE, SW_MINIMIZE,
SW_RESTORE, SW_SHOWNOACTIVATE, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SetWindowPos,
ShowWindow, WINDOW_EX_STYLE, WINDOW_STYLE, WS_EX_TOOLWINDOW, WS_POPUP,
};
use windows::core::{BOOL, PCWSTR, w};
use super::{clear_window_owner, set_window_owner};
pub(super) fn create_probe_window(title: PCWSTR) -> HWND {
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
unsafe {
let hinstance = GetModuleHandleW(None).expect("GetModuleHandleW");
let hwnd = CreateWindowExW(
WINDOW_EX_STYLE(WS_EX_TOOLWINDOW.0),
w!("Static"),
title,
WINDOW_STYLE(WS_POPUP.0),
10,
10,
0,
0,
None,
None,
Some(hinstance.into()),
None,
)
.expect("CreateWindowExW should create a probe window");
let _ = ShowWindow(hwnd, SW_SHOWNOACTIVATE);
hwnd
}
}
thread_local! {
static IME_ANCHOR_MADE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub(super) fn ensure_ime_anchor() {
IME_ANCHOR_MADE.with(|made| {
if made.get() {
return;
}
made.set(true);
let _ = create_probe_window(w!("owner-chain-probe/ime-anchor"));
});
}
fn owns_any_thread_window(hwnd: HWND) -> bool {
struct Probe {
target: HWND,
found: bool,
}
unsafe extern "system" fn visit(candidate: HWND, lparam: LPARAM) -> BOOL {
let probe = unsafe { &mut *(lparam.0 as *mut Probe) };
let owner = unsafe { GetWindow(candidate, GW_OWNER) }.ok();
if owner == Some(probe.target) {
probe.found = true;
return BOOL(0);
}
BOOL(1)
}
let mut probe = Probe {
target: hwnd,
found: false,
};
unsafe {
let _ = EnumThreadWindows(
GetCurrentThreadId(),
Some(visit),
LPARAM(&raw mut probe as isize),
);
}
probe.found
}
pub(super) fn head_owns_nothing(chain: &[HWND]) -> bool {
chain
.first()
.is_some_and(|head| !owns_any_thread_window(*head))
}
pub(super) fn destroy_all(windows: &[HWND]) {
for hwnd in windows {
unsafe {
let _ = DestroyWindow(*hwnd);
}
}
}
fn is_visible(hwnd: HWND) -> bool {
unsafe { IsWindowVisible(hwnd) }.as_bool()
}
fn is_alive(hwnd: HWND) -> bool {
unsafe { IsWindow(Some(hwnd)) }.as_bool()
}
fn is_minimized(hwnd: HWND) -> bool {
unsafe { IsIconic(hwnd) }.as_bool()
}
pub(super) fn owner_of(hwnd: HWND) -> Option<HWND> {
unsafe { GetWindow(hwnd, GW_OWNER) }.ok()
}
const Z_SCAN_ATTEMPTS: usize = 8;
fn relative_z_order(windows: &[HWND]) -> Vec<HWND> {
let mut best: Vec<HWND> = Vec::new();
for _ in 0..Z_SCAN_ATTEMPTS {
let seen = scan_z_once(windows);
if seen.len() == windows.len() {
return seen;
}
if seen.len() > best.len() {
best = seen;
}
}
best
}
fn scan_z_once(windows: &[HWND]) -> Vec<HWND> {
let mut result = Vec::new();
let mut cursor = unsafe { GetTopWindow(None) }.ok();
let mut steps = 0usize;
while let Some(hwnd) = cursor {
if hwnd.is_invalid() {
break;
}
if windows.contains(&hwnd) && !result.contains(&hwnd) {
result.push(hwnd);
if result.len() == windows.len() {
break;
}
}
steps += 1;
if steps > 100_000 {
break;
}
cursor = unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.ok();
}
result
}
pub(super) fn only(shape: &[usize], keep: &[usize]) -> Vec<usize> {
shape.iter().copied().filter(|i| keep.contains(i)).collect()
}
pub(super) fn z_shape(set: &[HWND]) -> Vec<usize> {
relative_z_order(set)
.iter()
.filter_map(|hwnd| set.iter().position(|w| w == hwnd))
.collect()
}
pub(super) fn place_after(hwnd: HWND, after: HWND) {
unsafe {
let _ = SetWindowPos(
hwnd,
Some(after),
0,
0,
0,
0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE,
);
}
}
fn place_at(hwnd: HWND, insert_after: HWND) {
place_after(hwnd, insert_after);
}
fn nudge_chain(chain: &[HWND]) {
if chain.len() >= 2 {
place_after(chain[chain.len() - 1], chain[chain.len() - 2]);
}
}
fn retired_head_nudge(chain: &[HWND]) {
if chain.len() >= 2 {
place_after(chain[0], chain[1]);
}
}
pub(super) fn arrange_z(set: &[HWND], order: &[usize]) {
for pair in order.windows(2) {
place_after(set[pair[1]], set[pair[0]]);
}
}
pub(super) fn link_chain(chain: &[HWND]) {
for pair in chain.windows(2) {
set_window_owner(pair[0], pair[1]).expect("set_window_owner should succeed");
}
}
pub(super) fn unlink_all(windows: &[HWND]) {
for hwnd in windows {
if owner_of(*hwnd).is_some() {
clear_window_owner(*hwnd).expect("clear_window_owner should succeed");
}
}
}
#[test]
fn owner_chain_probe_relink_on_visible_windows() {
let set: Vec<HWND> = (0..4)
.map(|_| create_probe_window(w!("owner-chain-probe/relink")))
.collect();
arrange_z(&set, &[3, 2, 1, 0]);
let before = z_shape(&set);
link_chain(&set);
let owners: Vec<Option<usize>> = set
.iter()
.map(|h| owner_of(*h).and_then(|o| set.iter().position(|w| *w == o)))
.collect();
let after_link = z_shape(&set);
place_at(set[3], HWND_BOTTOM);
let after_nudge_root_bottom = z_shape(&set);
place_at(set[0], HWND_TOP);
let after_nudge_head_top = z_shape(&set);
place_at(set[3], HWND_TOP);
let after_disturb = z_shape(&set);
eprintln!("[probe-1] before(reversed) = {before:?}");
eprintln!("[probe-1] owners(index) = {owners:?}");
eprintln!("[probe-1] after_link(no nudge) = {after_link:?}");
eprintln!("[probe-1] after_nudge_root_bottom = {after_nudge_root_bottom:?}");
eprintln!("[probe-1] after_nudge_head_top = {after_nudge_head_top:?}");
eprintln!("[probe-1] after_disturb_root_top = {after_disturb:?}");
unlink_all(&set);
let after_unlink = z_shape(&set);
eprintln!("[probe-1] after_unlink = {after_unlink:?}");
destroy_all(&set);
assert_eq!(before, vec![3, 2, 1, 0], "助走が宣言の逆順に揃っていること");
assert_eq!(
owners,
vec![Some(1), Some(2), Some(3), None],
"鎖は一直線(星形でも輪でもない)"
);
assert_eq!(
after_link, before,
"【実測】表示中の窓への owner 張り替えは、それだけでは重なりを 1 ミリも動かさない"
);
assert_eq!(
after_nudge_root_bottom,
vec![0, 1, 2, 3],
"【実測】Z を伴う後押し 1 回で鎖全体が宣言順へ収まる"
);
assert_eq!(
after_nudge_head_top,
vec![0, 1, 2, 3],
"収まった後は追加の後押しでも崩れない"
);
assert_eq!(
after_disturb,
vec![0, 1, 2, 3],
"【実測・要件 14.3】最も奥の窓を最前面へ持ち上げても、OS が鎖の順を保つ"
);
assert_eq!(
after_unlink,
vec![0, 1, 2, 3],
"【実測・要件 6】鎖を外しても並べ替えは起きない——束縛が消えるだけ"
);
}
#[test]
fn owner_chain_probe_rechain_to_a_different_order() {
let set: Vec<HWND> = (0..4)
.map(|_| create_probe_window(w!("owner-chain-probe/rechain")))
.collect();
link_chain(&set); place_at(set[3], HWND_BOTTOM);
let first = z_shape(&set);
unlink_all(&set);
let after_unlink = z_shape(&set);
let reversed: Vec<HWND> = set.iter().rev().copied().collect();
link_chain(&reversed); let after_relink_no_nudge = z_shape(&set);
place_at(set[0], HWND_BOTTOM); let after_relink_nudge = z_shape(&set);
eprintln!("[probe-1b] first_chain = {first:?}");
eprintln!("[probe-1b] after_unlink = {after_unlink:?}");
eprintln!("[probe-1b] rechain_no_nudge = {after_relink_no_nudge:?}");
eprintln!("[probe-1b] rechain_after_nudge = {after_relink_nudge:?}");
unlink_all(&set);
destroy_all(&set);
assert_eq!(first, vec![0, 1, 2, 3], "最初の鎖が成立していること");
assert_eq!(after_unlink, first, "外した瞬間には並びは動かない");
assert_eq!(
after_relink_no_nudge, first,
"張り替えだけでは動かない(実測 1 と同じ)"
);
assert_eq!(
after_relink_nudge,
vec![3, 2, 1, 0],
"【実測】全部外してから逆順で張り直し、後押し 1 回で新しい順へ収まる"
);
}
#[test]
fn owner_chain_probe_minimize_and_hide_cascade() {
let a = create_probe_window(w!("owner-chain-probe/casc-a"));
let b = create_probe_window(w!("owner-chain-probe/casc-b"));
let c = create_probe_window(w!("owner-chain-probe/casc-c"));
let set = [a, b, c];
link_chain(&set);
let base = (is_visible(a), is_visible(b), is_visible(c));
unsafe {
let _ = ShowWindow(c, SW_MINIMIZE);
}
let after_min_root = (
is_visible(a),
is_visible(b),
is_visible(c),
is_minimized(c),
is_minimized(a),
);
unsafe {
let _ = ShowWindow(c, SW_RESTORE);
}
let after_restore_root = (is_visible(a), is_visible(b), is_visible(c));
unsafe {
let _ = ShowWindow(b, SW_MINIMIZE);
}
let after_min_mid = (is_visible(a), is_visible(b), is_visible(c));
unsafe {
let _ = ShowWindow(b, SW_RESTORE);
}
unsafe {
let _ = ShowWindow(c, SW_HIDE);
}
let after_hide_root = (is_visible(a), is_visible(b), is_visible(c));
unsafe {
let _ = ShowWindow(c, SW_SHOWNOACTIVATE);
}
eprintln!("[probe-2] base(a,b,c visible) = {base:?}");
eprintln!("[probe-2] after_min_root(a,b,c,ic,ia) = {after_min_root:?}");
eprintln!("[probe-2] after_restore_root(a,b,c) = {after_restore_root:?}");
eprintln!("[probe-2] after_min_mid(a,b,c) = {after_min_mid:?}");
eprintln!("[probe-2] after_hide_root(a,b,c) = {after_hide_root:?}");
unlink_all(&set);
destroy_all(&set);
assert_eq!(base, (true, true, true), "3 枚とも可視で始まること");
assert_eq!(
after_min_root,
(false, false, true, true, false),
"【実測・要件 11.5 の要注意点】所有者を最小化すると鎖の下流の窓がすべて不可視になる(隠れるのであって最小化されるのではない)"
);
assert_eq!(
after_restore_root,
(true, true, true),
"【実測】復元で元に戻る=連動は可逆"
);
assert_eq!(
after_min_mid,
(false, true, true),
"【実測】連動は下流(手前側)へだけ伝う。上流の所有者は影響を受けない"
);
assert_eq!(
after_hide_root,
(true, true, false),
"【実測】`SW_HIDE` は連動しない——伝うのは最小化だけ"
);
}
#[test]
fn owner_chain_probe_destroy_cascade_and_neutralization() {
let a = create_probe_window(w!("owner-chain-probe/dest-a"));
let b = create_probe_window(w!("owner-chain-probe/dest-b"));
let c = create_probe_window(w!("owner-chain-probe/dest-c"));
link_chain(&[a, b, c]);
unsafe {
let _ = DestroyWindow(c);
}
let after_destroy_root = (is_alive(a), is_alive(b), is_alive(c));
destroy_all(&[a, b]);
let d = create_probe_window(w!("owner-chain-probe/dest-d"));
let e = create_probe_window(w!("owner-chain-probe/dest-e"));
let f = create_probe_window(w!("owner-chain-probe/dest-f"));
link_chain(&[d, e, f]);
unsafe {
let _ = DestroyWindow(e);
}
let after_destroy_mid = (is_alive(d), is_alive(e), is_alive(f));
let d_owner_after = owner_of(d).map(|o| o == f);
destroy_all(&[d, f]);
let g = create_probe_window(w!("owner-chain-probe/dest-g"));
let h = create_probe_window(w!("owner-chain-probe/dest-h"));
let i = create_probe_window(w!("owner-chain-probe/dest-i"));
link_chain(&[g, h, i]);
clear_window_owner(g).expect("clear g");
clear_window_owner(h).expect("clear h");
unsafe {
let _ = DestroyWindow(i);
}
let after_unlinked_destroy = (is_alive(g), is_alive(h), is_alive(i));
destroy_all(&[g, h]);
eprintln!("[probe-3] destroy_root(a,b,c alive) = {after_destroy_root:?}");
eprintln!("[probe-3] destroy_mid(d,e,f alive) = {after_destroy_mid:?}");
eprintln!("[probe-3] d_owner_is_f_after_mid_gone = {d_owner_after:?}");
eprintln!("[probe-3] unlinked_destroy(g,h,i) = {after_unlinked_destroy:?}");
assert_eq!(
after_destroy_root,
(false, false, false),
"【実測】鎖の根を破棄すると下流の窓がすべて道連れになる"
);
assert_eq!(
after_destroy_mid,
(false, false, true),
"【実測】途中の窓の破棄は下流だけを巻き込み、上流の所有者は残る"
);
assert_eq!(
after_unlinked_destroy,
(true, true, false),
"【実測・要件 7.2】先に鎖から外してから破棄すれば道連れは完全に消える"
);
}
#[test]
fn owner_chain_probe_splice_in_and_out() {
let a = create_probe_window(w!("owner-chain-probe/spl-a"));
let b = create_probe_window(w!("owner-chain-probe/spl-b"));
let c = create_probe_window(w!("owner-chain-probe/spl-c"));
let x = create_probe_window(w!("owner-chain-probe/spl-x")); let all = [a, b, c, x];
arrange_z(&all, &[0, 1, 2, 3]);
link_chain(&[a, b, c]);
nudge_chain(&[a, b, c]);
let established = z_shape(&all);
clear_window_owner(b).expect("clear b before splice");
let mid_state = z_shape(&all);
set_window_owner(b, x).expect("b <- x");
set_window_owner(x, c).expect("x <- c");
let after_splice_no_nudge = z_shape(&all);
nudge_chain(&[a, b, c]);
let after_splice_nudge = z_shape(&all);
clear_window_owner(b).expect("clear b");
clear_window_owner(x).expect("clear x");
set_window_owner(b, c).expect("b <- c");
nudge_chain(&[a, b, c]);
let after_unsplice = z_shape(&all);
eprintln!("[probe-4] established(a,b,c,x) = {established:?}");
eprintln!("[probe-4] mid_state(edge 1 本切) = {mid_state:?}");
eprintln!("[probe-4] splice_no_nudge = {after_splice_no_nudge:?}");
eprintln!("[probe-4] splice_after_nudge = {after_splice_nudge:?}");
eprintln!("[probe-4] after_unsplice = {after_unsplice:?}");
let chain_order_after_unsplice: Vec<usize> =
after_unsplice.iter().copied().filter(|i| *i < 3).collect();
unlink_all(&all);
destroy_all(&all);
assert_eq!(
only(&established, &[0, 1, 2]),
vec![0, 1, 2],
"助走: 鎖 a,b,c が手前から宣言順で並んでいること"
);
assert_eq!(
only(&mid_state, &[0, 1, 2]),
vec![0, 1, 2],
"【実測・要件 8.2】edge を 1 本切った途中状態でも鎖の順は崩れない"
);
assert_eq!(
only(&after_splice_no_nudge, &[0, 1, 2]),
vec![0, 1, 2],
"張り直しだけでは動かない"
);
assert_eq!(
after_splice_nudge,
vec![0, 1, 3, 2],
"【実測・要件 7.1】後押し 1 回で a,b,x,c = 差し込んだ鎖の順へ収まる"
);
assert_eq!(
chain_order_after_unsplice,
vec![0, 1, 2],
"【実測・要件 7.2】x を抜いても残りの鎖 a,b,c は宣言順を保つ"
);
}
#[test]
fn owner_chain_probe_clear_owner_on_unowned_window_reports_failure() {
let lone = create_probe_window(w!("owner-chain-probe/lone"));
let owner = create_probe_window(w!("owner-chain-probe/lone-owner"));
let unowned_result = clear_window_owner(lone).map_err(|e| e.code());
set_window_owner(lone, owner).expect("set_window_owner");
let owned_result = clear_window_owner(lone).map_err(|e| e.code());
let second_result = clear_window_owner(lone).map_err(|e| e.code());
eprintln!("[probe-5] clear_on_unowned = {unowned_result:?}");
eprintln!("[probe-5] clear_on_owned = {owned_result:?}");
eprintln!("[probe-5] clear_twice = {second_result:?}");
destroy_all(&[lone, owner]);
assert!(
owned_result.is_ok(),
"owner を持つ窓の切離しは成功しなければならない"
);
assert!(
unowned_result.is_err(),
"owner を持たない窓の切離しは失敗として返る(設計はこれを踏まない)"
);
assert_eq!(
unowned_result, second_result,
"二度目の切離しは一度目の未所有と同じ結果"
);
}
#[test]
fn owner_chain_probe_minimal_nudge_variants() {
let s1: Vec<HWND> = (0..4)
.map(|_| create_probe_window(w!("owner-chain-probe/nudge-root-bottom")))
.collect();
arrange_z(&s1, &[3, 2, 1, 0]);
link_chain(&s1);
place_at(s1[3], HWND_BOTTOM);
let root_bottom = z_shape(&s1);
let s2: Vec<HWND> = (0..4)
.map(|_| create_probe_window(w!("owner-chain-probe/nudge-head-top")))
.collect();
arrange_z(&s2, &[3, 2, 1, 0]);
link_chain(&s2);
place_at(s2[0], HWND_TOP);
let head_top = z_shape(&s2);
let s3: Vec<HWND> = (0..4)
.map(|_| create_probe_window(w!("owner-chain-probe/nudge-mid")))
.collect();
arrange_z(&s3, &[3, 2, 1, 0]);
link_chain(&s3);
place_at(s3[2], HWND_TOP);
let mid_top = z_shape(&s3);
let s4: Vec<HWND> = (0..4)
.map(|_| create_probe_window(w!("owner-chain-probe/nudge-none")))
.collect();
arrange_z(&s4, &[3, 2, 1, 0]);
link_chain(&s4);
let no_nudge = z_shape(&s4);
eprintln!("[probe-6] root->BOTTOM = {root_bottom:?}");
eprintln!("[probe-6] head->TOP = {head_top:?}");
eprintln!("[probe-6] mid->TOP = {mid_top:?}");
eprintln!("[probe-6] no nudge = {no_nudge:?} (対照)");
for s in [&s1, &s2, &s3, &s4] {
unlink_all(s);
destroy_all(s);
}
assert_eq!(root_bottom, vec![0, 1, 2, 3], "根への後押しで足りる");
assert_eq!(head_top, vec![0, 1, 2, 3], "先頭への後押しでも足りる");
assert_eq!(mid_top, vec![0, 1, 2, 3], "途中の窓への後押しでも足りる");
assert_eq!(
no_nudge,
vec![3, 2, 1, 0],
"【対照】後押しが無ければ何も起きない——上の 3 本は空虚ではない"
);
}
fn touch_only(hwnd: HWND) {
use windows::Win32::UI::WindowsAndMessaging::SWP_NOZORDER;
unsafe {
let _ = SetWindowPos(
hwnd,
None,
0,
0,
0,
0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER,
);
}
}
#[test]
fn owner_chain_probe_nudge_effect_on_outsiders() {
ensure_ime_anchor();
let chain: Vec<HWND> = (0..3)
.map(|_| create_probe_window(w!("owner-chain-probe/out-chain")))
.collect();
let outsider = create_probe_window(w!("owner-chain-probe/out-outsider"));
let mut all = chain.clone();
all.push(outsider);
arrange_z(&all, &[3, 2, 1, 0]);
let start = z_shape(&all);
link_chain(&chain);
let head_bare = head_owns_nothing(&chain);
touch_only(chain[0]);
let after_touch_head = z_shape(&all);
touch_only(chain[2]);
let after_touch_root = z_shape(&all);
nudge_chain(&chain);
let after_nudge = z_shape(&all);
eprintln!("[probe-7] start(out=3 が最前面) = {start:?}");
eprintln!("[probe-7] head_owns_nothing = {head_bare}");
eprintln!("[probe-7] after_touch_head = {after_touch_head:?}");
eprintln!("[probe-7] after_touch_root = {after_touch_root:?}");
eprintln!("[probe-7] after_nudge(root->anchor) = {after_nudge:?}");
unlink_all(&chain);
destroy_all(&all);
assert_eq!(start, vec![3, 2, 1, 0], "助走: 部外者が最前面・鎖は逆順");
assert!(
head_bare,
"鎖の先頭が窓を所有している=受け皿が効かず、本番より易しい配置で測っている(§13.2)"
);
assert_eq!(
after_touch_head, start,
"【実測】`SWP_NOZORDER` の後押しでは再整列が起きない——Z を伴う指令でなければならない"
);
assert_eq!(after_touch_root, start, "根を触っても同じ");
assert_eq!(
only(&after_nudge, &[0, 1, 2]),
vec![0, 1, 2],
"【実測・§13.3】Z を伴う後押しでも**所有側を動かせば**鎖は宣言順へ収まる"
);
}
#[test]
fn owner_chain_probe_nudge_referencing_only_our_own_windows() {
ensure_ime_anchor();
let chain: Vec<HWND> = (0..3)
.map(|_| create_probe_window(w!("owner-chain-probe/own-chain")))
.collect();
let front_outsider = create_probe_window(w!("owner-chain-probe/own-front"));
let back_outsider = create_probe_window(w!("owner-chain-probe/own-back"));
let mut all = chain.clone();
all.push(front_outsider);
all.push(back_outsider);
arrange_z(&all, &[3, 2, 1, 0, 4]);
let start = z_shape(&all);
link_chain(&chain);
let head_bare = head_owns_nothing(&chain);
retired_head_nudge(&chain);
let after_retired = z_shape(&all);
nudge_chain(&chain);
let after_adopted = z_shape(&all);
eprintln!("[probe-9] start = {start:?}");
eprintln!("[probe-9] head_owns_nothing = {head_bare}");
eprintln!("[probe-9] after_retired(head->second) = {after_retired:?}");
eprintln!("[probe-9] after_adopted(root->anchor) = {after_adopted:?}");
unlink_all(&chain);
destroy_all(&all);
assert_eq!(
start,
vec![3, 2, 1, 0, 4],
"助走: 部外者で前後を挟み鎖は逆順"
);
assert!(
head_bare,
"鎖の先頭が窓を所有している=受け皿が効かず、本番より易しい配置で測っている(§13.2)"
);
assert_eq!(
after_retired, start,
"【実測・§13.2 の撤回そのもの】撤回済みの形(先頭を 2 番目の直後へ)は 1 枚も動かさない。当初ここが収まると記録していたのは、受け皿が無く IME 窓が先頭に所有されていたためである"
);
assert_eq!(
only(&after_adopted, &[0, 1, 2]),
vec![0, 1, 2],
"【実測・§13.3】自分の窓だけを参照する後押しでも、**所有側を動かせば**鎖は宣言順へ収まる"
);
assert_eq!(
only(&after_adopted, &[3, 4]),
vec![3, 4],
"【実測・要件 6.1/6.2】鎖の外どうしの相対順は変わらない"
);
}