Rewild Your Web
18
fork

Configure Feed

Select the types of activity you want to include in your feed.

servo: media session control api on web-view

Signed-off-by: webbeef <me@webbeef.org>

webbeef 92a580a4 9f6b387c

+208 -49
+25 -8
patches/components/constellation/constellation.rs.patch
··· 308 308 ScriptToConstellationMessage::MediaSessionEvent(pipeline_id, event) => { 309 309 // Unlikely at this point, but we may receive events coming from 310 310 // different media sessions, so we set the active media session based 311 - @@ -2045,6 +2173,392 @@ 311 + @@ -1991,7 +2119,12 @@ 312 + } 313 + self.active_media_session = Some(pipeline_id); 314 + self.embedder_proxy 315 + - .send(EmbedderMsg::MediaSessionEvent(webview_id, event)); 316 + + .send(EmbedderMsg::MediaSessionEvent(webview_id, event.clone())); 317 + + // Also route to embedded webview parent iframe. 318 + + self.handle_embedded_webview_notification( 319 + + webview_id, 320 + + EmbeddedWebViewEventType::MediaSessionEvent(event), 321 + + ); 322 + }, 323 + #[cfg(feature = "webgpu")] 324 + ScriptToConstellationMessage::RequestAdapter(response_sender, options, ids) => self 325 + @@ -2045,6 +2178,395 @@ 312 326 let _ = event_loop.send(ScriptThreadMessage::TriggerGarbageCollection); 313 327 } 314 328 }, ··· 382 396 + )); 383 397 + } 384 398 + }, 399 + + ScriptToConstellationMessage::EmbeddedWebViewMediaSessionAction(action) => { 400 + + self.handle_media_session_action_msg(action); 401 + + }, 385 402 + ScriptToConstellationMessage::ForwardEventToEmbeddedWebView( 386 403 + embedded_webview_id, 387 404 + event, ··· 685 702 + .into_iter() 686 703 + .map(|(origin, name)| (origin.ascii_serialization(), name)) 687 704 + .collect(); 688 - + warn!( 705 + + debug!( 689 706 + "[P2P BC] Peer {id} connected/discovered, {} open channels to sync", 690 707 + channels.len() 691 708 + ); ··· 701 718 } 702 719 } 703 720 704 - @@ -2364,6 +2878,29 @@ 721 + @@ -2364,6 +2886,29 @@ 705 722 TransferState::TransferInProgress(queue) => queue.push_back(task), 706 723 TransferState::CompletionFailed(queue) => queue.push_back(task), 707 724 TransferState::CompletionRequested(_, queue) => queue.push_back(task), ··· 731 748 } 732 749 } 733 750 734 - @@ -3243,6 +3780,13 @@ 751 + @@ -3243,6 +3788,13 @@ 735 752 /// <https://html.spec.whatwg.org/multipage/#destroy-a-top-level-traversable> 736 753 fn handle_close_top_level_browsing_context(&mut self, webview_id: WebViewId) { 737 754 debug!("{webview_id}: Closing"); ··· 745 762 let browsing_context_id = BrowsingContextId::from(webview_id); 746 763 // Step 5. Remove traversable from the user agent's top-level traversable set. 747 764 let browsing_context = 748 - @@ -3519,8 +4063,27 @@ 765 + @@ -3519,8 +4071,27 @@ 749 766 opener_webview_id, 750 767 opener_pipeline_id, 751 768 response_sender, ··· 773 790 let Some((webview_id_sender, webview_id_receiver)) = generic_channel::channel() else { 774 791 warn!("Failed to create channel"); 775 792 let _ = response_sender.send(None); 776 - @@ -3619,6 +4182,361 @@ 793 + @@ -3619,6 +4190,361 @@ 777 794 }); 778 795 } 779 796 ··· 1135 1152 #[servo_tracing::instrument(skip_all)] 1136 1153 fn handle_refresh_cursor(&self, pipeline_id: PipelineId) { 1137 1154 let Some(pipeline) = self.pipelines.get(&pipeline_id) else { 1138 - @@ -4744,7 +5662,7 @@ 1155 + @@ -4744,7 +5670,7 @@ 1139 1156 } 1140 1157 1141 1158 #[servo_tracing::instrument(skip_all)] ··· 1144 1161 // Send a flat projection of the history to embedder. 1145 1162 // The final vector is a concatenation of the URLs of the past 1146 1163 // entries, the current entry and the future entries. 1147 - @@ -4847,9 +5765,23 @@ 1164 + @@ -4847,9 +5773,23 @@ 1148 1165 ); 1149 1166 self.embedder_proxy.send(EmbedderMsg::HistoryChanged( 1150 1167 webview_id,
+45 -11
patches/components/constellation/pairing.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,770 @@ 3 + @@ -0,0 +1,804 @@ 4 4 +// SPDX-License-Identifier: AGPL-3.0-or-later 5 5 + 6 6 +//! P2P pairing service integration with the constellation. ··· 81 81 + local_info: Arc<Mutex<Option<LocalPeerInfo>>>, 82 82 + event_receiver: Option<crossbeam_channel::Receiver<PairingEvent>>, 83 83 + /// Remote broadcast channels announced by each paired peer. 84 - + /// Maps peer_id → set of (origin, channel_name). 84 + + /// Maps a endpoint ID to a set of (origin, channel_name). 85 85 + remote_channels: Arc<Mutex<HashMap<String, HashSet<(String, String)>>>>, 86 86 +} 87 87 + ··· 475 475 + .iter() 476 476 + .filter(|p| p.status == beaver_p2p::EndpointStatus::PairedConnected) 477 477 + .collect(); 478 - + debug!( 478 + + warn!( 479 479 + "[P2P BC] broadcast_message: {} total peers, {} connected", 480 480 + peers.len(), 481 481 + connected.len() ··· 635 635 + /// Send `BroadcastChannelOpen` for each locally open channel to a specific peer. 636 636 + /// Called when a paired peer connects or reconnects so it knows about our channels. 637 637 + pub(crate) fn sync_channels_to_peer(&self, peer_id: &str, channels: &[(String, String)]) { 638 - + debug!( 638 + + warn!( 639 639 + "[P2P BC] Syncing {} channels to peer {peer_id}", 640 640 + channels.len() 641 641 + ); 642 - + for (origin, name) in channels { 643 - + self.send_message( 644 - + peer_id, 645 - + &P2pMessage::BroadcastChannelOpen { 642 + + 643 + + // Serialize all messages upfront. 644 + + let messages: Vec<Vec<u8>> = channels 645 + + .iter() 646 + + .filter_map(|(origin, name)| { 647 + + P2pMessage::BroadcastChannelOpen { 646 648 + origin: origin.clone(), 647 649 + name: name.clone(), 648 - + }, 649 - + ); 650 - + } 650 + + } 651 + + .to_bytes() 652 + + .ok() 653 + + }) 654 + + .collect(); 655 + + 656 + + let endpoint_id: iroh::EndpointId = match peer_id.parse() { 657 + + Ok(id) => id, 658 + + Err(e) => { 659 + + error!("Invalid endpoint ID for sync: {e}"); 660 + + return; 661 + + }, 662 + + }; 663 + + 664 + + // Send all messages in a single task so the first one establishes 665 + + // the connection and subsequent ones reuse it. 666 + + let manager = self.manager.clone(); 667 + + net::async_runtime::spawn_task(async move { 668 + + let mgr = { 669 + + let guard = manager.lock().await; 670 + + match guard.as_ref() { 671 + + Some(mgr) => mgr.clone(), 672 + + None => { 673 + + error!("Cannot sync channels: pairing service not started"); 674 + + return; 675 + + }, 676 + + } 677 + + }; 678 + + for message in &messages { 679 + + if let Err(e) = mgr.send_message(&endpoint_id, message).await { 680 + + error!("Failed to sync channel to {endpoint_id}: {e}"); 681 + + break; 682 + + } 683 + + } 684 + + }); 651 685 + } 652 686 +} 653 687 +
+4 -1
patches/components/constellation/tracing.rs.patch
··· 36 36 Self::ActivateDocument => target!("ActivateDocument"), 37 37 Self::SetDocumentState(..) => target!("SetDocumentState"), 38 38 Self::SetFinalUrl(..) => target!("SetFinalUrl"), 39 - @@ -186,6 +194,46 @@ 39 + @@ -186,6 +194,49 @@ 40 40 target!("RespondToScreenshotReadinessRequest") 41 41 }, 42 42 Self::TriggerGarbageCollection => target!("TriggerGarbageCollection"), ··· 50 50 + }, 51 51 + Self::EmbeddedWebViewTakeScreenshot(..) => { 52 52 + target!("EmbeddedWebViewTakeScreenshot") 53 + + }, 54 + + Self::EmbeddedWebViewMediaSessionAction(..) => { 55 + + target!("EmbeddedWebViewMediaSessionAction") 53 56 + }, 54 57 + Self::ForwardEventToEmbeddedWebView(..) => { 55 58 + target!("ForwardEventToEmbeddedWebView")
+87 -4
patches/components/script/dom/html/htmlembeddedwebview.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,988 @@ 3 + @@ -0,0 +1,1071 @@ 4 4 +/* This Source Code Form is subject to the terms of the Mozilla Public 5 5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 6 6 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ ··· 20 20 + ContextMenuElementInformationFlags, ContextMenuItem, EmbeddedWebViewScreenshotError, 21 21 + EmbeddedWebViewScreenshotRequest, EmbeddedWebViewScreenshotResult, EmbedderControlId, 22 22 + EmbedderControlRequest, EmbedderControlResponse, InputMethodType, LoadStatus, 23 - + PermissionFeature, PromptResponse, RgbColor, ScreenshotImageType, SimpleDialogRequest, 23 + + MediaSessionEvent, MediaSessionPlaybackState, PermissionFeature, PromptResponse, RgbColor, 24 + + ScreenshotImageType, SimpleDialogRequest, 24 25 +}; 25 26 +use js::jsval::UndefinedValue; 26 27 +use script_bindings::codegen::GenericBindings::CustomEventBinding::CustomEventMethods; ··· 32 33 + EmbedderColorParameters, EmbedderContextMenuItem, EmbedderContextMenuParameters, 33 34 + EmbedderControlHideEventDetail, EmbedderControlPosition, EmbedderControlShowEventDetail, 34 35 + EmbedderDialogShowEventDetail, EmbedderFileParameters, EmbedderInputMethodParameters, 35 - + EmbedderNotificationShowEventDetail, EmbedderPermissionParameters, EmbedderSelectOption, 36 - + EmbedderSelectParameters, ScreenshotOptions, 36 + + EmbedderMediaSessionEventDetail, EmbedderNotificationShowEventDetail, 37 + + EmbedderPermissionParameters, EmbedderSelectOption, EmbedderSelectParameters, 38 + + ScreenshotOptions, 37 39 +}; 38 40 +use crate::dom::bindings::error::{Error, Fallible}; 39 41 +use crate::dom::bindings::inheritance::Castable; ··· 107 109 + EmbeddedWebViewEventType::EmbedderControlHide { .. } => Atom::from("embedcontrolhide"), 108 110 + EmbeddedWebViewEventType::SimpleDialogShow(_) => Atom::from("embeddialogshow"), 109 111 + EmbeddedWebViewEventType::NotificationShow(_) => Atom::from("embednotificationshow"), 112 + + EmbeddedWebViewEventType::MediaSessionEvent(_) => Atom::from("embedmediasessionevent"), 110 113 + }; 111 114 + 112 115 + let cx = GlobalScope::get_cx(); ··· 441 444 + }; 442 445 + event_detail.safe_to_jsval(cx, detail.handle_mut(), can_gc); 443 446 + }, 447 + + EmbeddedWebViewEventType::MediaSessionEvent(session_event) => { 448 + + let event_detail = match session_event { 449 + + MediaSessionEvent::SetMetadata(metadata) => EmbedderMediaSessionEventDetail { 450 + + eventType: DOMString::from("metadata"), 451 + + title: Some(DOMString::from(metadata.title.clone())), 452 + + artist: Some(DOMString::from(metadata.artist.clone())), 453 + + album: Some(DOMString::from(metadata.album.clone())), 454 + + playbackState: None, 455 + + duration: Finite::wrap(0.0), 456 + + playbackRate: Finite::wrap(0.0), 457 + + position: Finite::wrap(0.0), 458 + + }, 459 + + MediaSessionEvent::PlaybackStateChange(state) => { 460 + + let state_str = match state { 461 + + MediaSessionPlaybackState::None_ => "none", 462 + + MediaSessionPlaybackState::Playing => "playing", 463 + + MediaSessionPlaybackState::Paused => "paused", 464 + + }; 465 + + EmbedderMediaSessionEventDetail { 466 + + eventType: DOMString::from("playbackstate"), 467 + + title: None, 468 + + artist: None, 469 + + album: None, 470 + + playbackState: Some(DOMString::from(state_str)), 471 + + duration: Finite::wrap(0.0), 472 + + playbackRate: Finite::wrap(0.0), 473 + + position: Finite::wrap(0.0), 474 + + } 475 + + }, 476 + + MediaSessionEvent::SetPositionState(position) => { 477 + + EmbedderMediaSessionEventDetail { 478 + + eventType: DOMString::from("positionstate"), 479 + + title: None, 480 + + artist: None, 481 + + album: None, 482 + + playbackState: None, 483 + + duration: Finite::wrap(position.duration), 484 + + playbackRate: Finite::wrap(position.playback_rate), 485 + + position: Finite::wrap(position.position), 486 + + } 487 + + }, 488 + + }; 489 + + event_detail.safe_to_jsval(cx, detail.handle_mut(), can_gc); 490 + + }, 444 491 + } 445 492 + 446 493 + let global = self.owner_global(); ··· 933 980 + AllowOrDeny::Deny 934 981 + }; 935 982 + let _ = sender.send(response); 983 + + Ok(()) 984 + + } 985 + + 986 + + /// Send a media session action (play, pause, etc.) to the active media session. 987 + + pub(crate) fn embedded_media_session_action(&self, action: DOMString) -> Fallible<()> { 988 + + if !self.is_embedded_webview() { 989 + + return Err(Error::InvalidState(Some( 990 + + "This iframe is not an embedded webview".to_string(), 991 + + ))); 992 + + } 993 + + 994 + + use embedder_traits::MediaSessionActionType; 995 + + 996 + + let action_type = match &*action.str() { 997 + + "play" => MediaSessionActionType::Play, 998 + + "pause" => MediaSessionActionType::Pause, 999 + + "seekbackward" => MediaSessionActionType::SeekBackward, 1000 + + "seekforward" => MediaSessionActionType::SeekForward, 1001 + + "previoustrack" => MediaSessionActionType::PreviousTrack, 1002 + + "nexttrack" => MediaSessionActionType::NextTrack, 1003 + + "skipad" => MediaSessionActionType::SkipAd, 1004 + + "stop" => MediaSessionActionType::Stop, 1005 + + "seekto" => MediaSessionActionType::SeekTo, 1006 + + other => { 1007 + + return Err(Error::InvalidState(Some(format!( 1008 + + "Unknown media session action: {other}", 1009 + + )))); 1010 + + }, 1011 + + }; 1012 + + 1013 + + let window = self.owner_window(); 1014 + + window 1015 + + .as_global_scope() 1016 + + .script_to_constellation_chan() 1017 + + .send(ScriptToConstellationMessage::EmbeddedWebViewMediaSessionAction(action_type)) 1018 + + .unwrap(); 936 1019 + Ok(()) 937 1020 + } 938 1021 +}
+19 -18
patches/components/script/dom/html/htmliframeelement.rs.patch
··· 271 271 } 272 272 } 273 273 274 - @@ -657,7 +843,158 @@ 274 + @@ -657,6 +843,157 @@ 275 275 self.webview_id.get() 276 276 } 277 277 ··· 376 376 + 377 377 + /// Returns true if this iframe is hosting an embedded webview (created with "embed" attribute). 378 378 + /// Embedded webviews have their own top-level WebViewId and window.parent === window.self. 379 - #[inline] 379 + + #[inline] 380 380 + pub(crate) fn is_embedded_webview(&self) -> bool { 381 381 + self.is_embedded_webview.get() 382 382 + } ··· 426 426 + self.page_zoom.set(zoom); 427 427 + } 428 428 + 429 - + #[inline] 429 + #[inline] 430 430 pub(crate) fn sandboxing_flag_set(&self) -> SandboxingFlagSet { 431 431 self.sandboxing_flag_set 432 - .get() 433 - @@ -1018,6 +1355,85 @@ 432 + @@ -1018,6 +1355,89 @@ 434 433 435 434 // https://html.spec.whatwg.org/multipage/#attr-iframe-loading 436 435 make_setter!(SetLoading, "loading"); ··· 513 512 + fn RespondToPermissionPrompt(&self, control_id: DOMString, allowed: bool) -> Fallible<()> { 514 513 + self.embedded_respond_to_permission_prompt(control_id, allowed) 515 514 + } 515 + + 516 + + fn MediaSessionAction(&self, action: DOMString) -> Fallible<()> { 517 + + self.embedded_media_session_action(action) 518 + + } 516 519 } 517 520 518 521 impl VirtualMethods for HTMLIFrameElement { 519 - @@ -1074,10 +1490,38 @@ 522 + @@ -1074,8 +1494,36 @@ 520 523 // is in a document tree and has a browsing context, which is what causes 521 524 // the child browsing context to be created. 522 525 if self.upcast::<Node>().is_connected_with_browsing_context() { ··· 542 545 + debug!("iframe src set while in browsing context."); 543 546 + self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, cx); 544 547 + } 545 - } 546 - }, 548 + + } 549 + + }, 547 550 + local_name!("embed") => { 548 551 + // The embed attribute determines whether this iframe hosts an embedded webview. 549 552 + // Warn if it's changed after the iframe is already connected, as this is not supported. ··· 552 555 + "The 'embed' attribute on iframe should not be changed after insertion. \ 553 556 + The iframe mode (nested vs embedded webview) is determined at insertion time." 554 557 + ); 555 - + } 556 - + }, 558 + } 559 + }, 557 560 local_name!("loading") => { 558 - // https://html.spec.whatwg.org/multipage/#attr-iframe-loading 559 - // > When the loading attribute's state is changed to the Eager state, the user agent must run these steps: 560 - @@ -1140,6 +1584,23 @@ 561 + @@ -1140,6 +1588,23 @@ 561 562 562 563 debug!("<iframe> running post connection steps"); 563 564 ··· 581 582 // Step 1. Create a new child navigable for insertedNode. 582 583 self.create_nested_browsing_context(cx); 583 584 584 - @@ -1163,11 +1624,25 @@ 585 + @@ -1163,11 +1628,25 @@ 585 586 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) { 586 587 self.super_type().unwrap().unbind_from_tree(context, can_gc); 587 588 ··· 594 595 + let global = window.as_global_scope(); 595 596 + let msg = ScriptToConstellationMessage::RemoveEmbeddedWebView(embedded_webview_id); 596 597 + global.script_to_constellation_chan().send(msg).unwrap(); 597 - + 598 + 599 + - // The iframe HTML element removing steps, given removedNode, are to destroy a child navigable given removedNode 600 + - self.destroy_child_navigable(&mut cx); 598 601 + window 599 602 + .paint_api() 600 603 + .remove_embedded_webview(embedded_webview_id); ··· 602 605 + } else { 603 606 + // TODO: https://github.com/servo/servo/issues/42837 604 607 + let mut cx = unsafe { temp_cx() }; 605 - 606 - - // The iframe HTML element removing steps, given removedNode, are to destroy a child navigable given removedNode 607 - - self.destroy_child_navigable(&mut cx); 608 + + 608 609 + // The iframe HTML element removing steps, given removedNode, are to destroy a child navigable given removedNode 609 610 + self.destroy_child_navigable(&mut cx); 610 611 + }
+18 -1
patches/components/script_bindings/webidls/EmbeddedWebView.webidl.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,156 @@ 3 + @@ -0,0 +1,173 @@ 4 4 +/* This Source Code Form is subject to the terms of the Mozilla Public 5 5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 6 6 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ ··· 43 43 + 44 44 + // Permission prompt response 45 45 + [Throws] undefined respondToPermissionPrompt(DOMString controlId, boolean allowed); 46 + + 47 + + // Media session control 48 + + [Throws] undefined mediaSessionAction(DOMString action); 46 49 +}; 47 50 + 48 51 + ··· 157 160 + DOMString tag = ""; // Tag for deduplication/replacement 158 161 + DOMString? iconUrl = null; // Icon URL 159 162 +}; 163 + + 164 + +dictionary EmbedderMediaSessionEventDetail { 165 + + required DOMString eventType; // "metadata", "playbackstate", "positionstate" 166 + + // Metadata fields (when eventType == "metadata") 167 + + DOMString? title = null; 168 + + DOMString? artist = null; 169 + + DOMString? album = null; 170 + + // Playback state (when eventType == "playbackstate") 171 + + DOMString? playbackState = null; // "none", "playing", "paused" 172 + + // Position state (when eventType == "positionstate") 173 + + double duration = 0; 174 + + double playbackRate = 0; 175 + + double position = 0; 176 + +};
+3 -1
patches/components/shared/constellation/from_script_message.rs.patch
··· 119 119 /// Mark a new document as active 120 120 ActivateDocument, 121 121 /// Set the document state for a pipeline (used by screenshot / reftests) 122 - @@ -726,6 +775,75 @@ 122 + @@ -726,6 +775,77 @@ 123 123 RespondToScreenshotReadinessRequest(ScreenshotReadinessResponse), 124 124 /// Request the constellation to force garbage collection in all `ScriptThread`'s. 125 125 TriggerGarbageCollection, ··· 144 144 + >, 145 145 + >, 146 146 + ), 147 + + /// Send a media session action to the active media session in an embedded webview. 148 + + EmbeddedWebViewMediaSessionAction(embedder_traits::MediaSessionActionType), 147 149 + /// Forward an input event to an embedded webview after the parent's DOM hit testing 148 150 + /// determined that the event target is an embedded iframe element. 149 151 + ForwardEventToEmbeddedWebView(WebViewId, InputEventAndId),
+7 -5
patches/components/shared/constellation/lib.rs.patch
··· 15 15 - MediaSessionActionType, NewWebViewDetails, PaintHitTestResult, Theme, TraversalId, 16 16 - ViewportDetails, WebDriverCommandMsg, 17 17 + EmbedderControlId, EmbedderControlRequest, EmbedderControlResponse, InputEventAndId, 18 - + JavaScriptEvaluationId, LoadStatus, MediaSessionActionType, NewWebViewDetails, Notification, 19 - + PaintHitTestResult, ServoErrorType, SimpleDialogRequest, Theme, TraversalId, ViewportDetails, 20 - + WebDriverCommandMsg, 18 + + JavaScriptEvaluationId, LoadStatus, MediaSessionActionType, MediaSessionEvent, 19 + + NewWebViewDetails, Notification, PaintHitTestResult, ServoErrorType, SimpleDialogRequest, 20 + + Theme, TraversalId, ViewportDetails, WebDriverCommandMsg, 21 21 }; 22 22 pub use from_script_message::*; 23 23 use malloc_size_of_derive::MallocSizeOf; 24 - @@ -36,9 +37,147 @@ 24 + @@ -36,9 +37,149 @@ 25 25 use servo_url::{ImmutableOrigin, ServoUrl}; 26 26 pub use structured_data::*; 27 27 use strum::IntoStaticStr; ··· 77 77 + SimpleDialogShow(SimpleDialogRequest), 78 78 + /// A notification should be shown to the user. 79 79 + NotificationShow(Notification), 80 + + /// A media session event (metadata, playback state, position). 81 + + MediaSessionEvent(MediaSessionEvent), 80 82 +} 81 83 + 82 84 +/// Information about the local P2P endpoint. ··· 170 172 /// Messages to the Constellation from the embedding layer, whether from `ServoRenderer` or 171 173 /// from `libservo` itself. 172 174 #[derive(IntoStaticStr)] 173 - @@ -118,6 +257,9 @@ 175 + @@ -118,6 +259,9 @@ 174 176 UpdatePinchZoomInfos(PipelineId, PinchZoomInfos), 175 177 /// Activate or deactivate accessibility features for the given `WebView`. 176 178 SetAccessibilityActive(WebViewId, bool),