mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 16:07:24 +00:00
fix(renderer): reconcile native Window lifecycle with protocol Page ownership
Use the canonical Window.document and Location objects across related Pages, retain producer identity and inherited about-document settings, and preserve the Page dispatch policy for cross-document history traversal. Route stop and close through exact Document owner actions and apply them through ordered protocol target lanes. Keep unload reentry checks and bootstrap output ordering. Window.opener=null preserves its accessor as specified; adjust the two conflicting protocol expectations while keeping the renderer regression. Register the main realm owner before native HistoryEntry bootstrap so inherited origins remain in committed history; replace an initial-empty entry consistently in both navigate events and Location seeds. Empty URL reuse can retain a focus activation with navigation_requested=false while preserving every document/history/load assertion. The renderer enum producer and exhaustive protocol consumer change together. Formatting, strict workspace Clippy and the complete nextest suite passed against unchanged final source with 32 workers and zero retries.
This commit is contained in:
@@ -136,8 +136,8 @@ pub use moli_renderer_v8::{
|
||||
RendererServiceWorkerVersionStatus, RendererSetDocumentContentResult,
|
||||
RendererSharedWorkerConsoleMessage, RendererSharedWorkerTargetEvent,
|
||||
RendererSharedWorkerTargetInfo, RendererSyntheticResponseBody, RendererTextSearchMatch,
|
||||
RendererTouchEventInput, RendererTouchPoint, RendererVisualStateToken, RendererWindowDocumentSource,
|
||||
RuntimeConsoleMessageSnapshot,
|
||||
RendererTouchEventInput, RendererTouchPoint, RendererVisualStateToken,
|
||||
RendererWindowDocumentSource, RuntimeConsoleMessageSnapshot,
|
||||
};
|
||||
pub use moli_renderer_v8::{
|
||||
RendererAppManifest, RendererAppManifestDisplayMode, RendererAppManifestError,
|
||||
|
||||
@@ -1407,6 +1407,32 @@ impl CdpConnection {
|
||||
.clear_pending_navigation_history_update()
|
||||
}
|
||||
|
||||
pub(crate) fn stop_renderer_location_navigation_for_owner(
|
||||
&mut self,
|
||||
owner: &CommandOwnerScope,
|
||||
source_document: moli_core::RendererDocumentLifecycleIdentity,
|
||||
) {
|
||||
let Some(mut resolved) = self.target_session_owner_mut_for_owner(owner) else {
|
||||
return;
|
||||
};
|
||||
let target = resolved.target_mut();
|
||||
// stop() aborts traversal signals and API promises, but script cannot
|
||||
// cancel the browser's physical cross-document history traversal.
|
||||
if target
|
||||
.owner_state
|
||||
.navigation_history_state
|
||||
.is_pending_traversal()
|
||||
{
|
||||
return;
|
||||
}
|
||||
if target
|
||||
.runtime_slot
|
||||
.stop_pending_document_navigation(source_document)
|
||||
{
|
||||
target.owner_state.clear_pending_navigation_history_update();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn mark_target_crashed_for_owner_async(
|
||||
&mut self,
|
||||
owner: &CommandOwnerScope,
|
||||
|
||||
@@ -239,6 +239,13 @@ impl TargetNavigationHistoryState {
|
||||
self.pending_update = None;
|
||||
}
|
||||
|
||||
pub(crate) fn is_pending_traversal(&self) -> bool {
|
||||
matches!(
|
||||
self.pending_update,
|
||||
Some(PendingNavigationHistoryUpdate::TraverseToEntry(_))
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn is_pending_reload(&self) -> bool {
|
||||
matches!(
|
||||
self.pending_update,
|
||||
@@ -287,8 +294,8 @@ impl TargetNavigationHistoryState {
|
||||
}
|
||||
let mutation = match self.pending_update {
|
||||
Some(
|
||||
PendingNavigationHistoryUpdate::ReplaceCurrent
|
||||
| PendingNavigationHistoryUpdate::ReplaceInitialEmptyDocument,
|
||||
PendingNavigationHistoryUpdate::ReplaceCurrent(_)
|
||||
| PendingNavigationHistoryUpdate::ReplaceInitialEmptyDocument(_),
|
||||
) => moli_page_types::NavigationHistoryMutation::Replace,
|
||||
_ => moli_page_types::NavigationHistoryMutation::Push,
|
||||
};
|
||||
|
||||
@@ -680,6 +680,25 @@ impl TargetPageSlot {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cancel_uncommitted_document_navigation(&mut self) -> bool {
|
||||
let Some(token) = self
|
||||
.pending_navigation_request
|
||||
.as_ref()
|
||||
.filter(|request| !request.committed)
|
||||
.map(|request| request.token.clone())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
self.cancel_pending_document_navigation();
|
||||
if matches!(&self.pending_renderer_page,
|
||||
Some(PendingRendererPageBinding::DocumentNavigation { navigation, .. })
|
||||
if navigation == &token)
|
||||
{
|
||||
self.pending_renderer_page = None;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn document_navigation_cancellation_handle(
|
||||
&self,
|
||||
token: &DocumentNavigationToken,
|
||||
|
||||
@@ -665,6 +665,20 @@ impl TargetRuntimeSlot {
|
||||
self.page_slot.has_pending_document_navigation()
|
||||
}
|
||||
|
||||
pub(crate) fn stop_pending_document_navigation(
|
||||
&mut self,
|
||||
source_document: RendererDocumentLifecycleIdentity,
|
||||
) -> bool {
|
||||
if self
|
||||
.committed_renderer_document_binding()
|
||||
.map(CommittedRendererDocumentBinding::renderer_document_identity)
|
||||
!= Some(source_document)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
self.page_slot.cancel_uncommitted_document_navigation()
|
||||
}
|
||||
|
||||
pub(crate) fn moli_memory_diagnostics(&self) -> Value {
|
||||
json!({
|
||||
"hasLoadedPage": self.has_loaded_page(),
|
||||
|
||||
@@ -209,6 +209,14 @@ impl PreparedProtocolOutputs {
|
||||
) -> Self {
|
||||
let mut prepared = Self::empty();
|
||||
match action {
|
||||
RendererOwnerAction::StopTopLevelLocationNavigation { source_document } => {
|
||||
crate::domains::page::PagePreparedOutputs::from_renderer_location_navigation_stop(
|
||||
conn,
|
||||
owner,
|
||||
source_document,
|
||||
)
|
||||
.append_to_location_navigation_stop_output_sink(&mut prepared);
|
||||
}
|
||||
RendererOwnerAction::FileChooser(activation) => {
|
||||
crate::domains::input::InputPreparedOutputs::from_renderer_file_chooser_activation(
|
||||
conn, owner, activation,
|
||||
|
||||
@@ -32,6 +32,7 @@ pub(in crate::domains) enum ProtocolOutputSlot {
|
||||
WebMcp,
|
||||
PendingSubresourceContinueEvents,
|
||||
TopLevelLocationNavigation,
|
||||
StopTopLevelLocationNavigation,
|
||||
TopLevelHistoryTraversal,
|
||||
FileChooser,
|
||||
Download,
|
||||
@@ -83,6 +84,7 @@ impl ProtocolOutputSlot {
|
||||
| Self::SessionHistoryUpdate
|
||||
| Self::SameDocumentNavigation
|
||||
| Self::TopLevelLocationNavigation
|
||||
| Self::StopTopLevelLocationNavigation
|
||||
| Self::TopLevelHistoryTraversal => ProtocolOutputDelivery::OwnerAction,
|
||||
Self::WindowOpen
|
||||
| Self::WebMcp
|
||||
@@ -117,6 +119,7 @@ impl ProtocolOutputSlot {
|
||||
ProtocolOutputResponseOrder::AfterResponse
|
||||
}
|
||||
Self::PendingSubresourceContinueEvents
|
||||
| Self::StopTopLevelLocationNavigation
|
||||
// Blink's file-input activation probe queues
|
||||
// Page.fileChooserOpened synchronously. A script may continue into
|
||||
// document.open(), but Chromium still flushes the chooser event
|
||||
@@ -246,6 +249,7 @@ impl ProtocolOutputSlot {
|
||||
| Self::SessionHistoryUpdate
|
||||
| Self::SameDocumentNavigation
|
||||
| Self::TopLevelLocationNavigation
|
||||
| Self::StopTopLevelLocationNavigation
|
||||
| Self::TopLevelHistoryTraversal => {
|
||||
crate::domains::page::project_page_output_async(self, conn, context, payloads)
|
||||
.await;
|
||||
@@ -288,6 +292,7 @@ mod tests {
|
||||
BeforeResponse,
|
||||
),
|
||||
(TopLevelLocationNavigation, OwnerAction, AfterResponse),
|
||||
(StopTopLevelLocationNavigation, OwnerAction, BeforeResponse),
|
||||
(TopLevelHistoryTraversal, OwnerAction, AfterResponse),
|
||||
(FileChooser, OwnerAction, BeforeResponse),
|
||||
(Download, OwnerAction, AfterResponse),
|
||||
|
||||
@@ -553,6 +553,10 @@ pub(crate) struct PagePreparedOutputs {
|
||||
moli_page_types::SessionHistoryUpdate,
|
||||
)>,
|
||||
top_level_location_navigation: Option<PagePreparedTopLevelLocationNavigation>,
|
||||
stopped_location_navigations: Vec<(
|
||||
crate::conn::TargetPageResidenceIdentity,
|
||||
RendererDocumentLifecycleIdentity,
|
||||
)>,
|
||||
top_level_history_traversal: Option<RendererPendingTopLevelHistoryTraversal>,
|
||||
}
|
||||
|
||||
|
||||
@@ -2320,17 +2320,9 @@ pub(super) fn start_session_owner_navigation_from_renderer(
|
||||
conn.mark_next_auxiliary_navigation_history_for_owner(owner, kind);
|
||||
}
|
||||
let result_projection = NavigationResultProjection::Cdp(result_payload);
|
||||
// Top-level Navigation API traversals hand admission to the browser,
|
||||
// unlike ordinary renderer navigations whose entry points own the
|
||||
// source check. Keep this separate from the event's initiator.
|
||||
let beforeunload = if navigation_history
|
||||
.as_ref()
|
||||
.is_some_and(|history| history.navigation_type() == Some("traverse"))
|
||||
{
|
||||
BeforeUnloadCheck::Required
|
||||
} else {
|
||||
BeforeUnloadCheck::RendererHandled
|
||||
};
|
||||
// Renderer entry points check the exact outgoing document tree before
|
||||
// handing the request to the browser, including named Window reuse.
|
||||
let beforeunload = BeforeUnloadCheck::RendererHandled;
|
||||
start_navigate_to_url_command_with_background_policy_and_request(
|
||||
conn,
|
||||
None,
|
||||
@@ -2925,9 +2917,13 @@ fn start_navigate_to_url_command_with_background_policy(
|
||||
allow_background_navigation,
|
||||
request_load_policy,
|
||||
initiator,
|
||||
// Browser commands and renderer History API handoffs reach this
|
||||
// entry point before checking the outgoing document tree.
|
||||
BeforeUnloadCheck::Required,
|
||||
// Renderer History API handoffs have already checked the outgoing
|
||||
// tree. Browser commands still own that check here.
|
||||
if matches!(initiator, NavigationStartInitiator::Renderer) {
|
||||
BeforeUnloadCheck::RendererHandled
|
||||
} else {
|
||||
BeforeUnloadCheck::Required
|
||||
},
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -36,7 +36,7 @@ pub(super) async fn commit_loaded_navigation_async(
|
||||
response_from_cache,
|
||||
main_document_body,
|
||||
initial_runtime_realms,
|
||||
mut renderer_output_predecessor,
|
||||
renderer_output_predecessor,
|
||||
main_document_commit,
|
||||
progress_gate,
|
||||
network_error_page,
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
use super::*;
|
||||
|
||||
impl PagePreparedOutputs {
|
||||
pub(crate) fn from_renderer_location_navigation_stop(
|
||||
conn: &CdpConnection,
|
||||
owner: &CommandOwnerScope,
|
||||
source_document: RendererDocumentLifecycleIdentity,
|
||||
) -> Self {
|
||||
let Some(residence) = conn.target_page_residence_identity_for_owner(owner) else {
|
||||
return Self::default();
|
||||
};
|
||||
Self {
|
||||
stopped_location_navigations: vec![(residence, source_document)],
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::domains) fn append_to_location_navigation_stop_output_sink(
|
||||
self,
|
||||
sink: &mut (impl ProtocolOutputSink + ?Sized),
|
||||
) {
|
||||
if !self.stopped_location_navigations.is_empty() {
|
||||
sink.push_produced_slot(ProtocolOutputSlot::StopTopLevelLocationNavigation);
|
||||
sink.push_prepared_payload(PagePreparedOutputSlot::from_outputs(self).into());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_renderer_auxiliary_window_navigation(
|
||||
conn: &CdpConnection,
|
||||
owner: &CommandOwnerScope,
|
||||
@@ -382,6 +406,8 @@ impl PagePreparedOutputs {
|
||||
}
|
||||
self.same_document_navigations
|
||||
.extend(other.same_document_navigations);
|
||||
self.stopped_location_navigations
|
||||
.extend(other.stopped_location_navigations);
|
||||
if self.top_level_location_navigation.is_none() {
|
||||
self.top_level_location_navigation = other.top_level_location_navigation;
|
||||
}
|
||||
@@ -510,6 +536,7 @@ impl PagePreparedOutputs {
|
||||
child_frame_activities: Vec::new(),
|
||||
same_document_navigations: Vec::new(),
|
||||
session_history_updates: Vec::new(),
|
||||
stopped_location_navigations: Vec::new(),
|
||||
top_level_location_navigation: None,
|
||||
top_level_history_traversal: None,
|
||||
}
|
||||
@@ -539,6 +566,7 @@ impl PagePreparedOutputs {
|
||||
child_frame_activities: Vec::new(),
|
||||
same_document_navigations: Vec::new(),
|
||||
session_history_updates: Vec::new(),
|
||||
stopped_location_navigations: Vec::new(),
|
||||
top_level_location_navigation: None,
|
||||
top_level_history_traversal: None,
|
||||
}
|
||||
@@ -590,6 +618,7 @@ impl PagePreparedOutputs {
|
||||
child_frame_activities: vec![activity],
|
||||
same_document_navigations: Vec::new(),
|
||||
session_history_updates: Vec::new(),
|
||||
stopped_location_navigations: Vec::new(),
|
||||
top_level_location_navigation: None,
|
||||
top_level_history_traversal: None,
|
||||
}
|
||||
@@ -616,6 +645,7 @@ impl PagePreparedOutputs {
|
||||
})
|
||||
.collect(),
|
||||
session_history_updates: Vec::new(),
|
||||
stopped_location_navigations: Vec::new(),
|
||||
top_level_location_navigation: None,
|
||||
top_level_history_traversal: None,
|
||||
}
|
||||
@@ -637,6 +667,7 @@ impl PagePreparedOutputs {
|
||||
child_frame_activities: Vec::new(),
|
||||
same_document_navigations: Vec::new(),
|
||||
session_history_updates: Vec::new(),
|
||||
stopped_location_navigations: Vec::new(),
|
||||
top_level_location_navigation: navigation
|
||||
.map(|navigation| PagePreparedTopLevelLocationNavigation::new(owner, navigation)),
|
||||
top_level_history_traversal: None,
|
||||
@@ -935,6 +966,17 @@ pub(in crate::domains) async fn project_page_output_async(
|
||||
prepared_outputs,
|
||||
);
|
||||
}
|
||||
ProtocolOutputSlot::StopTopLevelLocationNavigation => {
|
||||
if let Some(slot) = prepared_outputs.page_mut() {
|
||||
for (residence, source_document) in
|
||||
std::mem::take(&mut slot.outputs.stopped_location_navigations)
|
||||
{
|
||||
if conn.target_page_residence_identity_is_current(&residence) {
|
||||
conn.stop_renderer_location_navigation_for_owner(&owner, source_document);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ProtocolOutputSlot::TopLevelHistoryTraversal => {
|
||||
let mut events = Vec::new();
|
||||
emit_top_level_history_traversal_activity_background_events_async(
|
||||
|
||||
@@ -687,6 +687,7 @@ pub(crate) async fn complete_popup_target_navigation_owner_action_async(
|
||||
&[],
|
||||
request_kind,
|
||||
None,
|
||||
None,
|
||||
auxiliary_navigation,
|
||||
document_response,
|
||||
initial_document_environment,
|
||||
|
||||
+2
-2
@@ -1277,7 +1277,7 @@ async fn rust_cdp_chromium_target_named_reuse_updates_dom_opener_before_returnin
|
||||
&mut ctx,
|
||||
&parent_session,
|
||||
269_027,
|
||||
"child.opener=null;Object.getOwnPropertyDescriptor(child,'opener').value===null",
|
||||
"child.opener=null;child.opener===null && typeof Object.getOwnPropertyDescriptor(child,'opener').get==='function'",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(disowned["result"]["result"]["value"], true);
|
||||
@@ -1285,7 +1285,7 @@ async fn rust_cdp_chromium_target_named_reuse_updates_dom_opener_before_returnin
|
||||
&mut ctx,
|
||||
&sibling_session,
|
||||
269_028,
|
||||
"open('', 'child').opener===null",
|
||||
"open('', 'child').opener===window",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(shadowed["result"]["result"]["value"], true);
|
||||
|
||||
+101
-87
@@ -15,7 +15,7 @@ use super::conn::{
|
||||
};
|
||||
use crate::automation::{AutomationCommand, AutomationResult, DevToolsError};
|
||||
use crate::domains::activity::{
|
||||
ProtocolSchedulerWork, ProtocolSchedulerWorkKind, RuntimeCommandOutputBarrierCompletion,
|
||||
ProtocolSchedulerWork, RuntimeCommandOutputBarrierCompletion,
|
||||
RuntimeCommandOutputBarrierPermit, RuntimeCommandOutputBarriers,
|
||||
};
|
||||
use moli_core::{
|
||||
@@ -920,7 +920,30 @@ impl TestContext {
|
||||
// renderer Page build, so composing both futures inline can
|
||||
// exceed Rust's default test-thread stack before the owner
|
||||
// turn gets a chance to yield.
|
||||
let completed = Box::pin(pending.wait()).await;
|
||||
let executes_javascript = pending.runtime_command_executes_page_javascript();
|
||||
let mut completion = Box::pin(pending.wait());
|
||||
let completed = if executes_javascript {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
completed = &mut completion => break completed,
|
||||
input = Box::pin(self.receive_one_test_scheduler_input()) => {
|
||||
let (_, input) = input.expect(
|
||||
"pending command lost its renderer scheduler inputs"
|
||||
);
|
||||
// Receiving is cancellation-safe; projecting
|
||||
// a publication is not. Finish its actions
|
||||
// even if the command becomes ready meanwhile.
|
||||
let mut work = VecDeque::from([input]);
|
||||
Box::pin(self.route_test_scheduler_work_queue(&mut work)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Page construction must install its owner binding
|
||||
// before admitting the renderer's first publication.
|
||||
completion.await
|
||||
};
|
||||
step = Box::pin(self.conn.complete_pending_command_dispatch_with_context(
|
||||
completed,
|
||||
command_context,
|
||||
@@ -1374,75 +1397,76 @@ impl TestContext {
|
||||
return ready;
|
||||
}
|
||||
|
||||
let mut work = VecDeque::new();
|
||||
let Some((input_kind, input)) = Box::pin(self.receive_one_test_scheduler_input()).await
|
||||
else {
|
||||
return TestSchedulerTurnOutcome::Idle;
|
||||
};
|
||||
let mut work = VecDeque::from([input]);
|
||||
Box::pin(self.route_test_scheduler_work_queue(&mut work)).await;
|
||||
TestSchedulerTurnOutcome::Processed(input_kind)
|
||||
}
|
||||
|
||||
async fn receive_one_test_scheduler_input(
|
||||
&mut self,
|
||||
) -> Option<(TestSchedulerInputKind, TestSchedulerWork)> {
|
||||
let background_navigation_scheduler_enabled = self.background_navigation_scheduler_enabled;
|
||||
let input_kind = if !self.pending_runtime_deferred_replies.is_empty() {
|
||||
if !self.pending_runtime_deferred_replies.is_empty() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
maybe_response = self.runtime_inspector_response_ready_rx.recv() => {
|
||||
let Some(response) = maybe_response else {
|
||||
return TestSchedulerTurnOutcome::Idle;
|
||||
};
|
||||
work.push_back(TestSchedulerWork::RuntimeDeferredReplyReady(response));
|
||||
TestSchedulerInputKind::RuntimeDeferredReply
|
||||
maybe_response.map(|response| (
|
||||
TestSchedulerInputKind::RuntimeDeferredReply,
|
||||
TestSchedulerWork::RuntimeDeferredReplyReady(response),
|
||||
))
|
||||
}
|
||||
maybe_completion = self.background_navigation_completion_rx.recv(), if background_navigation_scheduler_enabled => {
|
||||
let Some(completion) = maybe_completion else {
|
||||
return TestSchedulerTurnOutcome::Idle;
|
||||
};
|
||||
work.push_back(TestSchedulerWork::BackgroundNavigationCompletion(completion));
|
||||
TestSchedulerInputKind::BackgroundNavigationCompletion
|
||||
maybe_completion.map(|completion| (
|
||||
TestSchedulerInputKind::BackgroundNavigationCompletion,
|
||||
TestSchedulerWork::BackgroundNavigationCompletion(completion),
|
||||
))
|
||||
}
|
||||
maybe_event = self.background_event_rx.recv(), if background_navigation_scheduler_enabled => {
|
||||
let Some(event) = maybe_event else {
|
||||
return TestSchedulerTurnOutcome::Idle;
|
||||
};
|
||||
work.push_back(TestSchedulerWork::BackgroundEvent(event));
|
||||
TestSchedulerInputKind::BackgroundEvent
|
||||
maybe_event.map(|event| (
|
||||
TestSchedulerInputKind::BackgroundEvent,
|
||||
TestSchedulerWork::BackgroundEvent(event),
|
||||
))
|
||||
}
|
||||
maybe_publication = self.renderer_publication_rx.recv() => {
|
||||
let Some(publication) = maybe_publication else {
|
||||
return TestSchedulerTurnOutcome::Idle;
|
||||
};
|
||||
work.push_back(TestSchedulerWork::RendererPublication(publication));
|
||||
TestSchedulerInputKind::RendererPublication
|
||||
maybe_publication.map(|publication| (
|
||||
TestSchedulerInputKind::RendererPublication,
|
||||
TestSchedulerWork::RendererPublication(publication),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tokio::select! {
|
||||
biased;
|
||||
maybe_completion = self.background_navigation_completion_rx.recv(), if background_navigation_scheduler_enabled => {
|
||||
let Some(completion) = maybe_completion else {
|
||||
return TestSchedulerTurnOutcome::Idle;
|
||||
};
|
||||
work.push_back(TestSchedulerWork::BackgroundNavigationCompletion(completion));
|
||||
TestSchedulerInputKind::BackgroundNavigationCompletion
|
||||
maybe_completion.map(|completion| (
|
||||
TestSchedulerInputKind::BackgroundNavigationCompletion,
|
||||
TestSchedulerWork::BackgroundNavigationCompletion(completion),
|
||||
))
|
||||
}
|
||||
maybe_event = self.background_event_rx.recv(), if background_navigation_scheduler_enabled => {
|
||||
let Some(event) = maybe_event else {
|
||||
return TestSchedulerTurnOutcome::Idle;
|
||||
};
|
||||
work.push_back(TestSchedulerWork::BackgroundEvent(event));
|
||||
TestSchedulerInputKind::BackgroundEvent
|
||||
maybe_event.map(|event| (
|
||||
TestSchedulerInputKind::BackgroundEvent,
|
||||
TestSchedulerWork::BackgroundEvent(event),
|
||||
))
|
||||
}
|
||||
maybe_publication = self.renderer_publication_rx.recv() => {
|
||||
let Some(publication) = maybe_publication else {
|
||||
return TestSchedulerTurnOutcome::Idle;
|
||||
};
|
||||
work.push_back(TestSchedulerWork::RendererPublication(publication));
|
||||
TestSchedulerInputKind::RendererPublication
|
||||
maybe_publication.map(|publication| (
|
||||
TestSchedulerInputKind::RendererPublication,
|
||||
TestSchedulerWork::RendererPublication(publication),
|
||||
))
|
||||
}
|
||||
maybe_response = self.runtime_inspector_response_ready_rx.recv() => {
|
||||
let Some(response) = maybe_response else {
|
||||
return TestSchedulerTurnOutcome::Idle;
|
||||
};
|
||||
work.push_back(TestSchedulerWork::RuntimeDeferredReplyReady(response));
|
||||
TestSchedulerInputKind::RuntimeDeferredReply
|
||||
maybe_response.map(|response| (
|
||||
TestSchedulerInputKind::RuntimeDeferredReply,
|
||||
TestSchedulerWork::RuntimeDeferredReplyReady(response),
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
Box::pin(self.route_test_scheduler_work_queue(&mut work)).await;
|
||||
TestSchedulerTurnOutcome::Processed(input_kind)
|
||||
}
|
||||
}
|
||||
|
||||
async fn route_protocol_events_like_scheduler(
|
||||
@@ -1565,47 +1589,9 @@ impl TestContext {
|
||||
work: &mut VecDeque<TestSchedulerWork>,
|
||||
) {
|
||||
loop {
|
||||
let selected_index = match self.pending_protocol_scheduler_work.front() {
|
||||
Some(front) if front.is_ready() => 0,
|
||||
Some(front)
|
||||
if front.kind() == ProtocolSchedulerWorkKind::MainDocumentLoadOwnerAction =>
|
||||
{
|
||||
let Some(index) =
|
||||
self.pending_protocol_scheduler_work
|
||||
.iter()
|
||||
.position(|candidate| {
|
||||
candidate.is_ready()
|
||||
&& candidate.navigation_dependency()
|
||||
== crate::ProtocolNavigationDependency::ReplacesPendingLoad
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
// Production checks the pending load observer out of the
|
||||
// FIFO while it waits for the renderer. An unconstrained
|
||||
// location owner action may then run and replace that
|
||||
// exact source Document, which completes the observer as
|
||||
// Superseded. Keeping the pending observer at the front in
|
||||
// this protocol-only harness would deadlock the action
|
||||
// needed to make it terminal.
|
||||
index
|
||||
}
|
||||
Some(_) | None => return,
|
||||
};
|
||||
if !self.background_navigation_scheduler_enabled
|
||||
&& self
|
||||
.pending_protocol_scheduler_work
|
||||
.get(selected_index)
|
||||
.is_some_and(ProtocolSchedulerWork::requires_background_navigation_scheduler)
|
||||
{
|
||||
// The default protocol fixture has no owner task lane. Keep
|
||||
// independent popup navigation resident rather than invoking
|
||||
// the production function's synchronous fallback while the
|
||||
// exact renderer cursor is still being projected. Tests that
|
||||
// assert navigation progress opt into the production-shaped
|
||||
// background scheduler and drive its typed completions.
|
||||
let Some(selected_index) = self.next_ready_protocol_work_index() else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let protocol_work = self
|
||||
.pending_protocol_scheduler_work
|
||||
.remove(selected_index)
|
||||
@@ -1624,6 +1610,34 @@ impl TestContext {
|
||||
}
|
||||
}
|
||||
|
||||
fn next_ready_protocol_work_index(&self) -> Option<usize> {
|
||||
// Match production's per-target ordering. An unfinished opener load
|
||||
// cannot block an independent popup, and a replacement must reach
|
||||
// the owner that supersedes its pending load.
|
||||
let mut blocked_targets = Vec::new();
|
||||
for (index, candidate) in self.pending_protocol_scheduler_work.iter().enumerate() {
|
||||
let target = candidate.navigation_gate_target_id();
|
||||
if target.is_some_and(|target| blocked_targets.contains(&target))
|
||||
&& candidate.navigation_dependency()
|
||||
!= crate::ProtocolNavigationDependency::ReplacesPendingLoad
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if !candidate.is_ready()
|
||||
|| (!self.background_navigation_scheduler_enabled
|
||||
&& candidate.requires_background_navigation_scheduler())
|
||||
{
|
||||
// An unscoped predecessor orders all later work. Fixtures
|
||||
// that need popup navigation enable the background channels;
|
||||
// otherwise retain that action on its target's lane.
|
||||
blocked_targets.push(target?);
|
||||
continue;
|
||||
}
|
||||
return Some(index);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
async fn ingest_renderer_publication_like_scheduler(
|
||||
&mut self,
|
||||
publication: RendererOutputTransportMessage,
|
||||
|
||||
@@ -90,8 +90,8 @@ mod navigation_restore;
|
||||
mod navigation_result;
|
||||
mod navigation_seed;
|
||||
mod navigation_serialize;
|
||||
pub(crate) use navigation_serialize::current_document_referrer_policy;
|
||||
pub(crate) use navigation_entry::navigation_entry_public_token;
|
||||
pub(crate) use navigation_serialize::current_document_referrer_policy;
|
||||
pub(crate) use navigation_serialize::{
|
||||
document_referrer_policy_for_native_document, history_document_id_for_holder,
|
||||
publish_top_level_navigation_history,
|
||||
@@ -183,17 +183,15 @@ pub(crate) use css_fontface_runtime::{
|
||||
pub(crate) use form_navigation::FormNavigationHistory;
|
||||
pub(crate) use history_mutation::update_history_for_document_open;
|
||||
pub(crate) use location_navigation::{
|
||||
HyperlinkNavigationOptions, LocationNavigationKind, blocks_ancestor_location_navigation,
|
||||
dispatch_top_level_navigation_event_with_source_element,
|
||||
HyperlinkNavigationOptions, LocationNavigationKind, navigate_location_object_for_hyperlink,
|
||||
is_native_location,
|
||||
history_entry_seed_for_cross_document_location, is_native_location,
|
||||
meta_refresh_navigation_kind, navigate_borrowed_location,
|
||||
navigate_location_object_for_element_fragment, navigate_location_object_for_hyperlink,
|
||||
navigate_location_object_with_child_navigate_event,
|
||||
history_entry_seed_for_cross_document_location,
|
||||
|
||||
navigate_location_object_with_child_navigate_event_and_initiator_url,
|
||||
navigate_location_object_with_source_element, navigate_top_level_meta_refresh,
|
||||
navigate_top_level_same_document_from_browser, resolve_cross_window_location_target,
|
||||
navigate_location_object_for_element_fragment,
|
||||
};
|
||||
pub(crate) use navigation_cancellation::{
|
||||
NavigationCancellationReason, inform_about_canceled_navigation_for_window,
|
||||
@@ -367,9 +365,8 @@ pub(crate) use self::indexed_db::{
|
||||
flush_blocked_indexed_db_requests, flush_indexed_db_connection_notification,
|
||||
flush_indexed_db_task_by_id, flush_indexed_db_transaction_starts, flush_next_indexed_db_task,
|
||||
indexed_db_has_pending_tasks, install_worker_indexed_db_runtime_state,
|
||||
retire_indexed_db_context, set_indexed_db_manager_for_context,
|
||||
set_worker_indexed_db_task_wake_for_context,
|
||||
retain_indexed_db_state_in_retired_realm,
|
||||
retain_indexed_db_state_in_retired_realm, retire_indexed_db_context,
|
||||
set_indexed_db_manager_for_context, set_worker_indexed_db_task_wake_for_context,
|
||||
};
|
||||
#[cfg(test)]
|
||||
pub(crate) use self::indexed_db::{
|
||||
@@ -474,7 +471,6 @@ pub(crate) use self::runtime_state::install_webassembly_runtime_state;
|
||||
#[cfg(feature = "wpt-extensions")]
|
||||
pub(crate) use self::runtime_state::install_wpt_webdriver_runtime_state;
|
||||
pub(crate) use self::runtime_state::set_window_origin_runtime_state;
|
||||
pub(crate) use self::runtime_state::window_realm_secure_context_available;
|
||||
pub(crate) use self::runtime_state::{
|
||||
ORIGINAL_WEBASSEMBLY_COMPILE_ERROR_CONSTRUCTOR_SLOT,
|
||||
ORIGINAL_WEBASSEMBLY_GLOBAL_VALUE_GETTER_SLOT, ORIGINAL_WEBASSEMBLY_INSTANCE_CONSTRUCTOR_SLOT,
|
||||
@@ -1199,7 +1195,6 @@ pub(crate) fn live_ranges_detached_text_split<'s>(
|
||||
pub(crate) use self::window_accessors::{
|
||||
bind_current_child_window_document, current_window_style_viewport,
|
||||
retain_window_document_in_retired_realm, window_parent_or_top,
|
||||
|
||||
};
|
||||
|
||||
pub(crate) use session_history::{
|
||||
|
||||
@@ -545,16 +545,6 @@ pub(crate) fn new_dom_exception_value<'s>(
|
||||
scope,
|
||||
"DOMException",
|
||||
)
|
||||
.or_else(|| {
|
||||
// A native exception must materialize its own realm's intrinsic.
|
||||
// Reading the public Window property can run author code or enter
|
||||
// an access check again after navigation detaches its global.
|
||||
crate::context_bootstrap::exposed_interfaces::ensure_intrinsic_interface_prototype(
|
||||
scope,
|
||||
"DOMException",
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
{
|
||||
let exception = v8::Object::new(scope);
|
||||
initialize_dom_exception(scope, exception, message, name);
|
||||
|
||||
@@ -27,7 +27,6 @@ use super::navigation_window::{
|
||||
runtime_window_is_global, window_history_for_holder, window_location_for_holder,
|
||||
window_navigation_for_holder,
|
||||
};
|
||||
use super::*;
|
||||
use crate::context_bootstrap::navigation_entry::wrappers as entry_wrappers;
|
||||
use crate::structured_clone::{deserialize_history_state, serialize_history_state};
|
||||
use crate::webidl;
|
||||
@@ -210,7 +209,7 @@ fn mutate_history_object<'s>(
|
||||
// Admission follows argument conversion, state serialization and URL
|
||||
// validation, but precedes navigation cancellation and event dispatch.
|
||||
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#shared-history-push/replace-state-steps
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, owner) else {
|
||||
return;
|
||||
};
|
||||
let Some(dispatch_scope) = runtime_window_dispatch_scope(scope, owner) else {
|
||||
@@ -411,7 +410,7 @@ fn history_api_base_url<'s>(
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
current_url: &url::Url,
|
||||
) -> url::Url {
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, owner) else {
|
||||
return current_url.clone();
|
||||
};
|
||||
let host = unsafe { &*host_ptr };
|
||||
@@ -439,7 +438,7 @@ fn history_document_url<'s>(
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
current_href: &str,
|
||||
) -> Option<url::Url> {
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, owner) else {
|
||||
return url::Url::parse(current_href).ok();
|
||||
};
|
||||
let host = unsafe { &*host_ptr };
|
||||
@@ -466,7 +465,7 @@ fn effective_history_mutation_kind<'s>(
|
||||
if !matches!(kind, HistoryMutationKind::Push) {
|
||||
return kind;
|
||||
}
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, owner) else {
|
||||
return kind;
|
||||
};
|
||||
let host = unsafe { &*host_ptr };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::super::navigation_window::{navigation_document_is_active, runtime_window_owner};
|
||||
use super::super::{context_host_ptr_from_global_bridge, throw_dom_exception_value};
|
||||
use super::super::throw_dom_exception_value;
|
||||
|
||||
/// History API algorithms call this after WebIDL argument conversion.
|
||||
pub(in crate::context_bootstrap) fn require_fully_active_history_owner<'s>(
|
||||
@@ -10,7 +10,7 @@ pub(in crate::context_bootstrap) fn require_fully_active_history_owner<'s>(
|
||||
let is_fully_active = if let Some(popup_id) =
|
||||
crate::native_bridge::lightweight_popup_id_from_window(scope, owner)
|
||||
{
|
||||
context_host_ptr_from_global_bridge(scope)
|
||||
super::super::window_accessors::window_host_ptr(scope, owner)
|
||||
.is_some_and(|host_ptr| unsafe { &*host_ptr }.lightweight_popup_is_open(popup_id))
|
||||
} else {
|
||||
navigation_document_is_active(scope, owner)
|
||||
|
||||
@@ -84,6 +84,7 @@ struct LocationNavigationOptions {
|
||||
dispatch_child_navigate_event_for_all_kinds: bool,
|
||||
force_exact_same_document_navigation: bool,
|
||||
explicit_initiator_url: Option<url::Url>,
|
||||
explicit_navigation_initiator: Option<crate::runtime::RendererNavigationInitiator>,
|
||||
user_initiated: bool,
|
||||
source_can_access_target: Option<bool>,
|
||||
hyperlink: bool,
|
||||
@@ -93,7 +94,7 @@ struct LocationNavigationOptions {
|
||||
pub(crate) struct HyperlinkNavigationOptions {
|
||||
pub(crate) user_initiated: bool,
|
||||
pub(crate) source_can_access_target: bool,
|
||||
pub(crate) initiator_url: url::Url,
|
||||
pub(crate) initiator: crate::runtime::RendererNavigationInitiator,
|
||||
pub(crate) named_popup: Option<crate::native_bridge::element::NamedHyperlinkPopup>,
|
||||
}
|
||||
|
||||
@@ -115,7 +116,8 @@ pub(crate) fn navigate_location_object_for_hyperlink<'s>(
|
||||
source_can_access_target: Some(options.source_can_access_target),
|
||||
hyperlink: true,
|
||||
force_exact_same_document_navigation: true,
|
||||
explicit_initiator_url: Some(options.initiator_url),
|
||||
explicit_initiator_url: Some(options.initiator.url().clone()),
|
||||
explicit_navigation_initiator: Some(options.initiator),
|
||||
named_hyperlink_popup: options.named_popup,
|
||||
..LocationNavigationOptions::default()
|
||||
},
|
||||
@@ -237,12 +239,7 @@ pub(crate) fn navigate_top_level_same_document_from_browser(
|
||||
// fragment-only string through Location: Chromium pushes a same-document
|
||||
// history entry and runs the Navigation/popstate surfaces even though the
|
||||
// serialized URL does not change.
|
||||
navigate_location_object_for_same_document(
|
||||
scope,
|
||||
location,
|
||||
&target,
|
||||
kind,
|
||||
);
|
||||
navigate_location_object_for_same_document(scope, location, &target, kind);
|
||||
true
|
||||
}
|
||||
|
||||
@@ -371,6 +368,28 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>(
|
||||
source_element: Option<v8::Local<'s, v8::Object>>,
|
||||
options: LocationNavigationOptions,
|
||||
) {
|
||||
if let Some(context) = location.get_creation_context(scope)
|
||||
&& context != scope.get_current_context()
|
||||
&& is_native_location(scope, location)
|
||||
&& crate::util::context_host_ptr_from_context_slot(context)
|
||||
!= context_host_ptr_from_global_bridge(scope)
|
||||
{
|
||||
// URL parsing retains the entry settings before entering the native
|
||||
// receiver's realm. The incumbent source remains available for the
|
||||
// initiator, origin and about-document inheritance checks below.
|
||||
let raw_target =
|
||||
raw_target.map(|raw| resolve_cross_window_location_target(scope, &raw).unwrap_or(raw));
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
navigate_location_object_with_source_element_and_child_navigate_event(
|
||||
scope,
|
||||
location,
|
||||
kind,
|
||||
raw_target,
|
||||
source_element,
|
||||
options,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// The Location setters and methods return before URL parsing when their
|
||||
// relevant Document is null, including when argument conversion removed it.
|
||||
if !location_has_relevant_document(scope, location) {
|
||||
@@ -393,6 +412,7 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>(
|
||||
dispatch_child_navigate_event_for_all_kinds,
|
||||
force_exact_same_document_navigation,
|
||||
explicit_initiator_url,
|
||||
explicit_navigation_initiator,
|
||||
user_initiated,
|
||||
source_can_access_target,
|
||||
hyperlink,
|
||||
@@ -425,7 +445,13 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>(
|
||||
let navigation_source = if matches!(kind, LocationNavigationKind::Reload) {
|
||||
None
|
||||
} else {
|
||||
capture_location_navigation_source(scope, owner, &resolved)
|
||||
let captured = capture_location_navigation_source(scope, owner, &resolved);
|
||||
match (explicit_navigation_initiator, captured) {
|
||||
(Some(initiator), captured) => {
|
||||
Some((initiator, captured.and_then(|(_, environment)| environment)))
|
||||
}
|
||||
(None, captured) => captured,
|
||||
}
|
||||
};
|
||||
if resolved.scheme() == "javascript" {
|
||||
let source_context = scope
|
||||
@@ -464,6 +490,14 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>(
|
||||
});
|
||||
let owner = runtime_window_owner(scope, location);
|
||||
let source_can_access_target = source_can_access_target.unwrap_or_else(|| {
|
||||
if runtime_window_is_global(scope, owner)
|
||||
&& let Some(context) = owner.get_creation_context(scope)
|
||||
{
|
||||
let source = scope
|
||||
.get_incumbent_context()
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
return crate::native_bridge::window_contexts_allow_access(source, context);
|
||||
}
|
||||
let Some(host_ptr) = context_host_ptr_for_navigation_owner(scope, owner) else {
|
||||
return true;
|
||||
};
|
||||
@@ -477,6 +511,16 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>(
|
||||
target,
|
||||
)
|
||||
});
|
||||
// Child navigations capture the source origin and recheck the target when
|
||||
// their queued task executes. Preserve that check instead of rejecting the
|
||||
// Location write synchronously as for a top-level related Page.
|
||||
if resolved.scheme() == "javascript"
|
||||
&& !source_can_access_target
|
||||
&& runtime_window_is_global(scope, owner)
|
||||
{
|
||||
crate::native_bridge::throw_cross_origin_location_security_error(scope);
|
||||
return;
|
||||
}
|
||||
let event_source_element = source_element.filter(|_| source_can_access_target);
|
||||
if navigation_unload_event_active(scope, owner)
|
||||
|| super::navigation_cancellation::window_navigation_is_stopping(scope, owner)
|
||||
@@ -549,14 +593,16 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>(
|
||||
let replaces_initial_about_blank =
|
||||
super::navigation_window::navigation_document_is_initial_empty(scope, owner)
|
||||
|| child_handle.is_some_and(|handle| {
|
||||
context_host_ptr_for_navigation_owner(scope, owner).is_some_and(|host_ptr| {
|
||||
unsafe { &*host_ptr }.child_current_document_is_initial_empty(handle)
|
||||
})
|
||||
}) || popup_id.is_some_and(|popup_id| {
|
||||
context_host_ptr_for_navigation_owner(scope, owner).is_some_and(|host_ptr| {
|
||||
unsafe { &*host_ptr }.lightweight_popup_current_document_is_initial_empty(popup_id)
|
||||
})
|
||||
});
|
||||
context_host_ptr_for_navigation_owner(scope, owner).is_some_and(|host_ptr| {
|
||||
unsafe { &*host_ptr }.child_current_document_is_initial_empty(handle)
|
||||
})
|
||||
})
|
||||
|| popup_id.is_some_and(|popup_id| {
|
||||
context_host_ptr_for_navigation_owner(scope, owner).is_some_and(|host_ptr| {
|
||||
unsafe { &*host_ptr }
|
||||
.lightweight_popup_current_document_is_initial_empty(popup_id)
|
||||
})
|
||||
});
|
||||
let effective_kind = match kind {
|
||||
LocationNavigationKind::Assign if replaces_initial_about_blank => {
|
||||
LocationNavigationKind::Replace
|
||||
@@ -574,6 +620,9 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>(
|
||||
};
|
||||
let mut navigate_outcome = navigation.map(|navigation| {
|
||||
let _ = cancel_active_navigation_event(scope, navigation);
|
||||
super::navigation_result::cancel_active_cross_document_navigation(
|
||||
scope, navigation, None,
|
||||
);
|
||||
cancel_pending_precommit_same_document_navigation(scope, navigation);
|
||||
cancel_pending_precommit_history_traversal(scope, navigation);
|
||||
cancel_active_intercepted_same_document_navigation(scope, navigation);
|
||||
@@ -794,6 +843,16 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>(
|
||||
return;
|
||||
}
|
||||
|
||||
let kind = if kind == LocationNavigationKind::Assign
|
||||
&& resolved.scheme() != "javascript"
|
||||
&& runtime_window_is_global(scope, owner)
|
||||
&& context_host_ptr_for_navigation_owner(scope, owner)
|
||||
.is_some_and(|host| unsafe { &*host }.main_document_is_initial_empty())
|
||||
{
|
||||
LocationNavigationKind::Replace
|
||||
} else {
|
||||
kind
|
||||
};
|
||||
let child_handle = if runtime_window_is_global(scope, owner) {
|
||||
None
|
||||
} else {
|
||||
@@ -804,11 +863,7 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>(
|
||||
&& let Some(navigation) =
|
||||
super::navigation_window::window_navigation_for_holder(scope, owner)
|
||||
{
|
||||
if popup_id.is_some() {
|
||||
super::navigation_result::cancel_active_cross_document_navigation(
|
||||
scope, navigation, None,
|
||||
);
|
||||
}
|
||||
super::navigation_result::cancel_active_cross_document_navigation(scope, navigation, None);
|
||||
let _ = cancel_active_navigation_event(scope, navigation);
|
||||
cancel_pending_precommit_same_document_navigation(scope, navigation);
|
||||
cancel_pending_precommit_history_traversal(scope, navigation);
|
||||
@@ -863,14 +918,12 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>(
|
||||
);
|
||||
return;
|
||||
}
|
||||
if popup_id.is_some() {
|
||||
super::navigation_result::track_cross_document_location_navigation(
|
||||
scope,
|
||||
navigation,
|
||||
outcome.signal,
|
||||
resolved.as_str(),
|
||||
);
|
||||
}
|
||||
super::navigation_result::track_cross_document_location_navigation(
|
||||
scope,
|
||||
navigation,
|
||||
outcome.signal,
|
||||
resolved.as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(popup_id) = popup_id {
|
||||
@@ -996,6 +1049,7 @@ pub(crate) fn is_native_location<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
let receiver = moli_webapi_declare::web_api_object_target(scope, receiver).unwrap_or(receiver);
|
||||
crate::util::get_private_value(scope, receiver, WINDOW_LOCATION_HREF_SLOT).is_some()
|
||||
}
|
||||
|
||||
@@ -1093,7 +1147,7 @@ fn capture_location_navigation_source<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
fn blocks_ancestor_location_navigation<'s>(
|
||||
pub(crate) fn blocks_ancestor_location_navigation<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
destination: &url::Url,
|
||||
|
||||
@@ -16,7 +16,7 @@ use super::{
|
||||
use crate::native_bridge::{
|
||||
JsContextHost, OwnerDispatchScope, WindowDocumentTaskTarget, WindowExecutionContextBinding,
|
||||
};
|
||||
use crate::util::{context_host_ptr_from_global_bridge, get_private_value, set_private_value};
|
||||
use crate::util::{get_private_value, set_private_value};
|
||||
|
||||
const WINDOW_STOP_ACTIVE_SLOT: &str = "__lmWindowStopActive";
|
||||
|
||||
@@ -133,7 +133,7 @@ pub(crate) fn stop_navigation_for_window_and_descendants<'s>(
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
binding: WindowExecutionContextBinding,
|
||||
) {
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, window) else {
|
||||
return;
|
||||
};
|
||||
let host = unsafe { &*host_ptr };
|
||||
@@ -216,7 +216,7 @@ pub(super) fn clear_pending_cross_document_navigation_for_window<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, window) else {
|
||||
return;
|
||||
};
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
@@ -238,7 +238,7 @@ pub(super) fn clear_pending_cross_document_navigation_for_window<'s>(
|
||||
}
|
||||
match dispatch_scope {
|
||||
Some(crate::native_bridge::OwnerDispatchScope::Top) => {
|
||||
host.clear_pending_location_navigation();
|
||||
host.cancel_pending_location_navigation();
|
||||
}
|
||||
Some(crate::native_bridge::OwnerDispatchScope::Child(handle)) => {
|
||||
host.cancel_pending_child_browsing_context_navigation(handle);
|
||||
|
||||
@@ -181,7 +181,9 @@ pub(super) fn navigation_document_origin<'s>(
|
||||
) -> String {
|
||||
super::navigation_window::runtime_window_dispatch_scope(scope, owner)
|
||||
.and_then(|dispatch_scope| {
|
||||
crate::util::context_host_ptr_from_global_bridge(scope)
|
||||
owner
|
||||
.get_creation_context(scope)
|
||||
.and_then(crate::util::context_host_ptr_from_context_slot)
|
||||
.and_then(|host| unsafe { &*host }.window_document_origin(dispatch_scope))
|
||||
})
|
||||
.or_else(|| {
|
||||
|
||||
@@ -118,6 +118,14 @@ pub(super) fn dispatch_cross_document_navigation_event<'s>(
|
||||
return false;
|
||||
}
|
||||
if !outcome.intercepted {
|
||||
if !same_document && download_request.is_none() {
|
||||
super::navigation_result::track_cross_document_location_navigation(
|
||||
scope,
|
||||
navigation,
|
||||
outcome.signal,
|
||||
href,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
cancel_pending_same_document_navigation_finishes(scope, navigation);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use super::history_runtime::native;
|
||||
use super::navigation_window::{
|
||||
runtime_window_dispatch_scope,
|
||||
};
|
||||
use super::navigation_window::runtime_window_dispatch_scope;
|
||||
use crate::context_bootstrap::navigation_entry::wrappers as entry_wrappers;
|
||||
use crate::util::{context_host_ptr_from_global_bridge, serialize_v8_iter_array};
|
||||
use moli_history::HistoryEntryRef;
|
||||
|
||||
@@ -105,7 +105,11 @@ pub(super) fn build_current_navigation_entry_from_seed<'s>(
|
||||
HistoryEntry {
|
||||
url: "about:blank".to_owned(),
|
||||
inherited_origin: None,
|
||||
document_origin: super::navigation_entry::navigation_document_origin(scope, owner, "about:blank"),
|
||||
document_origin: super::navigation_entry::navigation_document_origin(
|
||||
scope,
|
||||
owner,
|
||||
"about:blank",
|
||||
),
|
||||
referrer_policy: None,
|
||||
history_state: state.clone(),
|
||||
navigation_state: state,
|
||||
|
||||
@@ -285,9 +285,7 @@ pub(super) fn navigation_reload_callback<'s>(
|
||||
if navigation_can_update_current_entry(scope, args.this())
|
||||
&& let Some(navigation) = window_navigation_for_holder(scope, owner)
|
||||
{
|
||||
if crate::native_bridge::lightweight_popup_id_from_window(scope, owner).is_some() {
|
||||
cancel_active_cross_document_navigation(scope, navigation, None);
|
||||
}
|
||||
cancel_active_cross_document_navigation(scope, navigation, None);
|
||||
let _ = cancel_active_navigation_event(scope, navigation);
|
||||
cancel_pending_precommit_same_document_navigation(scope, navigation);
|
||||
cancel_pending_precommit_history_traversal(scope, navigation);
|
||||
@@ -397,6 +395,48 @@ pub(super) fn navigation_reload_callback<'s>(
|
||||
rv.set(pending.object.into());
|
||||
return;
|
||||
}
|
||||
if super::navigation_window::runtime_window_is_global(scope, owner)
|
||||
&& let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, owner)
|
||||
&& let Some(context) = owner.get_creation_context(scope)
|
||||
&& let Some(entry_seed) =
|
||||
super::navigation_seed::history_entry_seed_for_reload(scope, owner)
|
||||
&& let Ok(url) = url::Url::parse(¤t_href)
|
||||
{
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
if !unsafe { &mut *host_ptr }.dispatch_main_document_tree_beforeunload(scope) {
|
||||
rv.set(
|
||||
reload_canceled_after_dispatch_result(
|
||||
scope,
|
||||
navigation,
|
||||
&outcome,
|
||||
¤t_href,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
let about_document_state = unsafe { &*host_ptr }.capture_about_document_state(
|
||||
crate::native_bridge::OwnerDispatchScope::Top,
|
||||
&url,
|
||||
None,
|
||||
);
|
||||
unsafe { &mut *host_ptr }.record_pending_location_navigation_with_kind(
|
||||
url,
|
||||
Some(entry_seed),
|
||||
moli_fetch::BrowserNavigationRequestKind::Reload,
|
||||
about_document_state,
|
||||
);
|
||||
rv.set(
|
||||
navigation_cross_document_pending_result(
|
||||
scope,
|
||||
navigation,
|
||||
outcome.signal,
|
||||
¤t_href,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if let Some(popup_id) = crate::native_bridge::lightweight_popup_id_from_window(scope, owner)
|
||||
&& let Some(host_ptr) = context_host_ptr_from_global_bridge(scope)
|
||||
&& let Some(entry_seed) =
|
||||
|
||||
@@ -5,14 +5,12 @@ use super::navigation_events::{
|
||||
NavigationDispatchOutcome, dispatch_navigation_traverse_event_with_outcome,
|
||||
};
|
||||
use super::navigation_result::{
|
||||
cancel_active_cross_document_navigation, navigation_dom_exception, navigation_pending_result,
|
||||
cancel_active_cross_document_navigation, navigation_dom_exception,
|
||||
navigation_rejected_dom_exception_result, track_cross_document_traversal_navigation,
|
||||
};
|
||||
use super::navigation_seed::history_entry_seed_for_traversal;
|
||||
use super::navigation_window::{navigation_document_is_active, window_navigation_for_holder};
|
||||
use super::navigation_window::{
|
||||
runtime_window_is_global, window_history_for_holder, window_task_target_for_runtime_owner,
|
||||
};
|
||||
use super::navigation_window::{window_history_for_holder, window_task_target_for_runtime_owner};
|
||||
use super::*;
|
||||
use crate::native_bridge::{
|
||||
PendingCrossDocumentTraversal, PendingHistoryTraversalAction, PendingNavigationResult,
|
||||
@@ -45,7 +43,7 @@ pub(super) fn queue_navigation_traversal_with_result<'s>(
|
||||
"SecurityError",
|
||||
));
|
||||
}
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, target.owner) else {
|
||||
return Some(navigation_rejected_dom_exception_result(
|
||||
scope,
|
||||
"Navigation was canceled",
|
||||
@@ -60,7 +58,7 @@ pub(super) fn queue_navigation_traversal_with_result<'s>(
|
||||
"AbortError",
|
||||
));
|
||||
};
|
||||
if let Some((target_url, mut seed)) = history_entry_seed_for_traversal(
|
||||
if let Some((_target_url, mut seed)) = history_entry_seed_for_traversal(
|
||||
scope,
|
||||
target.owner,
|
||||
target.current_index,
|
||||
@@ -68,10 +66,6 @@ pub(super) fn queue_navigation_traversal_with_result<'s>(
|
||||
) {
|
||||
seed.session_history.target_step = target.joint_step;
|
||||
super::session_history::capture_for_navigation(scope, target.owner, &mut seed);
|
||||
if runtime_window_is_global(scope, target.owner) {
|
||||
host.record_pending_location_navigation(target_url, Some(seed.clone()), None);
|
||||
return Some(navigation_pending_result(scope));
|
||||
}
|
||||
let target_key = traversal_target_entry(scope, &target)
|
||||
.map(|entry| entry.borrow().key.as_str().to_owned());
|
||||
let receiver_context = target
|
||||
@@ -124,7 +118,7 @@ pub(super) fn queue_history_traversal_by_delta<'s>(
|
||||
delta: i64,
|
||||
) {
|
||||
let owner = super::navigation_window::runtime_window_owner(scope, history);
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, owner) else {
|
||||
return;
|
||||
};
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
@@ -175,6 +169,7 @@ fn apply_history_traversal_by_delta(
|
||||
crate::native_bridge::OwnerDispatchScope::Top,
|
||||
crate::native_bridge::OwnerDispatchScope::Top,
|
||||
)
|
||||
&& host.dispatch_main_document_tree_beforeunload(scope)
|
||||
{
|
||||
host.record_pending_top_level_history_traversal(delta);
|
||||
}
|
||||
@@ -322,14 +317,18 @@ pub(in crate::context_bootstrap) fn apply_pending_cross_document_traversal<'s, '
|
||||
}
|
||||
// close() queues retirement of this exact Window. Keep its dispatched
|
||||
// event and API results alive until the close task aborts them together.
|
||||
let closing_popup = matches!(dispatch_scope,
|
||||
crate::native_bridge::OwnerDispatchScope::LightweightPopup(popup_id)
|
||||
if host.lightweight_popup_is_closing(popup_id))
|
||||
&& host.current_window_document_task_target_for_dispatch_scope(dispatch_scope)
|
||||
== retiring_document;
|
||||
let closing_window = match dispatch_scope {
|
||||
crate::native_bridge::OwnerDispatchScope::Top => host.browsing_context_is_closed(),
|
||||
crate::native_bridge::OwnerDispatchScope::LightweightPopup(popup_id) => {
|
||||
host.lightweight_popup_is_closing(popup_id)
|
||||
}
|
||||
crate::native_bridge::OwnerDispatchScope::Child(_) => false,
|
||||
} && host
|
||||
.current_window_document_task_target_for_dispatch_scope(dispatch_scope)
|
||||
== retiring_document;
|
||||
if outcome.proceed
|
||||
&& outcome.abort_error.is_none()
|
||||
&& (is_current(scope, host) || closing_popup)
|
||||
&& (is_current(scope, host) || closing_window)
|
||||
&& let Some(navigation) = navigation
|
||||
{
|
||||
track_cross_document_traversal_navigation(
|
||||
@@ -344,7 +343,7 @@ pub(in crate::context_bootstrap) fn apply_pending_cross_document_traversal<'s, '
|
||||
// cross-document traversals cannot be canceled by script. Only continue
|
||||
// while the original Window and Document are still active.
|
||||
if !is_current(scope, host) {
|
||||
if !closing_popup {
|
||||
if !closing_window {
|
||||
reject_cross_document_traversal(scope, &traversal);
|
||||
}
|
||||
return false;
|
||||
@@ -366,7 +365,42 @@ pub(in crate::context_bootstrap) fn apply_pending_cross_document_traversal<'s, '
|
||||
queued
|
||||
}
|
||||
crate::native_bridge::OwnerDispatchScope::Top => {
|
||||
unreachable!("top-level traversals are handed off to the browser")
|
||||
if host.page_script_environment().is_none_or(|environment| {
|
||||
environment.top_level_navigation_dispatch()
|
||||
== crate::RendererTopLevelNavigationDispatch::DelegateToBrowser
|
||||
}) {
|
||||
let Some(step) = traversal.seed.session_history.target_step else {
|
||||
reject_cross_document_traversal(scope, &traversal);
|
||||
return false;
|
||||
};
|
||||
let binding = super::session_history::binding(scope, host, owner);
|
||||
let Some(delta) = host.session_histories.get_mut(binding.popup).delta_to(step)
|
||||
else {
|
||||
reject_cross_document_traversal(scope, &traversal);
|
||||
return false;
|
||||
};
|
||||
if let Some(key) = traversal.target_key {
|
||||
host.top_level_navigation_history()
|
||||
.select_joint_traversal(key, step);
|
||||
}
|
||||
host.record_pending_top_level_history_traversal(delta);
|
||||
return true;
|
||||
}
|
||||
let Ok(url) = url::Url::parse(&target_url) else {
|
||||
reject_cross_document_traversal(scope, &traversal);
|
||||
return false;
|
||||
};
|
||||
if let Some(key) = traversal.target_key
|
||||
&& let Some(step) = traversal.seed.session_history.target_step
|
||||
{
|
||||
host.top_level_navigation_history()
|
||||
.select_joint_traversal(key, step);
|
||||
}
|
||||
// The standalone adapter has no browser history controller to
|
||||
// consume a delta. Retain its concrete destination and seed after
|
||||
// the shared navigate/beforeunload dispatch above.
|
||||
host.record_pending_location_navigation(url, Some(traversal.seed), None);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ fn navigation_owner_local_window_id<'s>(
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) -> Option<u64> {
|
||||
let dispatch_scope = runtime_window_dispatch_scope(scope, owner)?;
|
||||
let host_ptr = context_host_ptr_from_global_bridge(scope)?;
|
||||
let host_ptr = super::window_accessors::window_host_ptr(scope, owner)?;
|
||||
let owner = unsafe { &*host_ptr }.current_window_execution_context_owner(dispatch_scope)?;
|
||||
Some(match owner {
|
||||
crate::native_bridge::WindowExecutionContextOwner::Frame(local_window_id) => {
|
||||
@@ -63,7 +63,7 @@ fn navigation_owner_document<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) -> Option<crate::document_runtime::DomHandle> {
|
||||
let host_ptr = context_host_ptr_from_global_bridge(scope)?;
|
||||
let host_ptr = super::window_accessors::window_host_ptr(scope, owner)?;
|
||||
super::window_accessors::window_document_handle(scope, owner, unsafe { &*host_ptr })
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ pub(super) fn navigation_document_is_initial_empty<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, owner) else {
|
||||
return false;
|
||||
};
|
||||
let host = unsafe { &*host_ptr };
|
||||
@@ -270,7 +270,7 @@ pub(super) fn navigation_document_has_disabled_entries<'s>(
|
||||
if navigation_document_has_opaque_origin(scope, owner) {
|
||||
return true;
|
||||
}
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, owner) else {
|
||||
return false;
|
||||
};
|
||||
let host = unsafe { &*host_ptr };
|
||||
@@ -279,6 +279,9 @@ pub(super) fn navigation_document_has_disabled_entries<'s>(
|
||||
if let Some(popup_id) = crate::native_bridge::lightweight_popup_id_from_window(scope, owner) {
|
||||
return !host.lightweight_popup_has_committed_navigation(popup_id);
|
||||
}
|
||||
if runtime_window_is_global(scope, owner) {
|
||||
return !host.main_document_has_committed_navigation();
|
||||
}
|
||||
child_browsing_context_handle_for_runtime_owner(scope, owner)
|
||||
.is_some_and(|handle| !host.child_has_committed_navigation(handle))
|
||||
}
|
||||
@@ -287,7 +290,7 @@ pub(super) fn navigation_document_has_opaque_origin<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, owner) else {
|
||||
return false;
|
||||
};
|
||||
let host = unsafe { &*host_ptr };
|
||||
@@ -311,7 +314,7 @@ pub(crate) fn navigation_unload_event_active<'s>(
|
||||
) -> bool {
|
||||
get_private_value(scope, owner, WINDOW_UNLOAD_EVENT_ACTIVE_SLOT)
|
||||
.is_some_and(|value| value.is_true())
|
||||
|| context_host_ptr_from_global_bridge(scope).is_some_and(|host_ptr| {
|
||||
|| super::window_accessors::window_host_ptr(scope, owner).is_some_and(|host_ptr| {
|
||||
let host = unsafe { &*host_ptr };
|
||||
super::window_accessors::window_document_handle(scope, owner, host)
|
||||
.is_some_and(|document| host.has_document_unload_counter(document))
|
||||
@@ -341,7 +344,7 @@ pub(super) fn navigation_document_base_url<'s>(
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
fallback_href: &str,
|
||||
) -> Option<url::Url> {
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, owner) else {
|
||||
return url::Url::parse(fallback_href).ok();
|
||||
};
|
||||
let host = unsafe { &*host_ptr };
|
||||
@@ -374,16 +377,17 @@ fn navigation_document_is_live<'s>(
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
if let Some(popup_id) = crate::native_bridge::lightweight_popup_id_from_window(scope, owner) {
|
||||
return context_host_ptr_from_global_bridge(scope)
|
||||
return super::window_accessors::window_host_ptr(scope, owner)
|
||||
.is_some_and(|host_ptr| unsafe { &*host_ptr }.lightweight_popup_is_open(popup_id));
|
||||
}
|
||||
if runtime_window_is_global(scope, owner) {
|
||||
return true;
|
||||
return super::window_accessors::window_host_ptr(scope, owner)
|
||||
.is_some_and(|host_ptr| !unsafe { &*host_ptr }.browsing_context_is_closed());
|
||||
}
|
||||
let Some(handle) = child_browsing_context_handle_for_runtime_owner(scope, owner) else {
|
||||
return false;
|
||||
};
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::window_accessors::window_host_ptr(scope, owner) else {
|
||||
return false;
|
||||
};
|
||||
let host = unsafe { &*host_ptr };
|
||||
|
||||
@@ -234,13 +234,15 @@ pub(super) fn length<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) -> usize {
|
||||
if super::navigation_window::navigation_document_is_initial_empty(scope, owner) {
|
||||
return 1;
|
||||
}
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
return 1;
|
||||
};
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
if super::navigation_window::runtime_window_is_global(scope, owner)
|
||||
&& !host.main_document_has_committed_navigation()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
let binding = binding(scope, host, owner);
|
||||
host.session_histories
|
||||
.get_mut(binding.popup)
|
||||
|
||||
@@ -92,17 +92,9 @@ pub(super) fn apply_step(
|
||||
if let Some(index) = root {
|
||||
let target = &plan.targets[index];
|
||||
if runtime_window_is_global(scope, target.owner) {
|
||||
let Some(delta) = host.session_histories.get_mut(binding.popup).delta_to(step) else {
|
||||
return;
|
||||
};
|
||||
if let Some(entry) = history_entries(scope, target.history)
|
||||
.and_then(|entries| entries.get(target.target_index as usize).cloned())
|
||||
{
|
||||
let key = entry.borrow().key.as_str().to_owned();
|
||||
host.top_level_navigation_history()
|
||||
.select_joint_traversal(key, step);
|
||||
if let Some(traversal) = cross_document_traversal(scope, host, target, step, method) {
|
||||
apply_pending_cross_document_traversal(scope, host, traversal);
|
||||
}
|
||||
host.record_pending_top_level_history_traversal(delta);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -130,38 +122,8 @@ pub(super) fn apply_step(
|
||||
host.retain_active_history_delta_position(binding.popup);
|
||||
let accepted = if let Some(index) = root {
|
||||
let target = &plan.targets[index];
|
||||
let pending = window_task_target_for_runtime_owner(scope, host, target.owner).zip(
|
||||
history_entry_seed_for_traversal(
|
||||
scope,
|
||||
target.owner,
|
||||
target.current_index,
|
||||
target.target_index,
|
||||
),
|
||||
);
|
||||
if let Some((exact, (_url, mut seed))) = pending {
|
||||
seed.session_history.target_step = Some(step);
|
||||
let key = history_entries(scope, target.history)
|
||||
.and_then(|entries| entries.get(target.target_index as usize).cloned())
|
||||
.map(|entry| entry.borrow().key.as_str().to_owned());
|
||||
let (info, results) = method.map_or_else(
|
||||
|| (None, Vec::new()),
|
||||
|method| (method.info, method.results),
|
||||
);
|
||||
apply_pending_cross_document_traversal(
|
||||
scope,
|
||||
host,
|
||||
PendingCrossDocumentTraversal {
|
||||
target: exact,
|
||||
target_index: target.target_index,
|
||||
target_key: key,
|
||||
seed,
|
||||
info,
|
||||
results,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
cross_document_traversal(scope, host, target, step, method)
|
||||
.is_some_and(|traversal| apply_pending_cross_document_traversal(scope, host, traversal))
|
||||
} else {
|
||||
let history =
|
||||
window_history_for_holder(scope, owner).expect("admitted traversal has a history");
|
||||
@@ -263,3 +225,35 @@ pub(crate) fn finish_without_document_commit(
|
||||
finish(scope, host_ptr, root, popup, step);
|
||||
}
|
||||
}
|
||||
|
||||
fn cross_document_traversal<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
host: &JsContextHost,
|
||||
target: &super::navigation_traversal_execution::TraversalTarget<'s>,
|
||||
step: SessionHistoryStepId,
|
||||
method: Option<PendingHistoryTraversal>,
|
||||
) -> Option<PendingCrossDocumentTraversal> {
|
||||
let exact = window_task_target_for_runtime_owner(scope, host, target.owner)?;
|
||||
let (_, mut seed) = history_entry_seed_for_traversal(
|
||||
scope,
|
||||
target.owner,
|
||||
target.current_index,
|
||||
target.target_index,
|
||||
)?;
|
||||
seed.session_history.target_step = Some(step);
|
||||
let key = history_entries(scope, target.history)
|
||||
.and_then(|entries| entries.get(target.target_index as usize).cloned())
|
||||
.map(|entry| entry.borrow().key.as_str().to_owned());
|
||||
let (info, results) = method.map_or_else(
|
||||
|| (None, Vec::new()),
|
||||
|method| (method.info, method.results),
|
||||
);
|
||||
Some(PendingCrossDocumentTraversal {
|
||||
target: exact,
|
||||
target_index: target.target_index,
|
||||
target_key: key,
|
||||
seed,
|
||||
info,
|
||||
results,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ pub(super) use interceptors::{
|
||||
window_named_property_query,
|
||||
};
|
||||
pub(crate) use surface::{
|
||||
window_parent_or_top, window_closed_getter, window_opener_getter, window_parent_getter, window_top_getter,
|
||||
window_closed_getter, window_opener_getter, window_parent_getter, window_parent_or_top,
|
||||
window_top_getter,
|
||||
};
|
||||
pub(super) use surface::{
|
||||
window_custom_elements_getter, window_device_pixel_ratio_getter, window_frames_getter,
|
||||
@@ -50,5 +51,4 @@ pub(super) use surface::{
|
||||
window_outer_height_getter, window_outer_width_getter, window_performance_getter,
|
||||
window_screen_getter, window_scroll_x_getter, window_scroll_y_getter, window_self_getter,
|
||||
window_speech_synthesis_getter, window_visual_viewport_getter, window_window_getter,
|
||||
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::helpers::{
|
||||
same_origin_window_receiver,
|
||||
window_child_context_handle, window_document_handle, window_has_discarded_child_browsing_context,
|
||||
window_hidden_value, window_host_ptr, window_is_closed, window_receiver,
|
||||
same_origin_window_receiver, window_child_context_handle, window_document_handle,
|
||||
window_has_discarded_child_browsing_context, window_hidden_value, window_host_ptr,
|
||||
window_is_closed, window_receiver,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ use crate::{
|
||||
util::{get_private_object, set_private_value},
|
||||
};
|
||||
|
||||
pub(in crate::context_bootstrap) const WINDOW_DOCUMENT_SLOT: &str =
|
||||
"__moliWindowAssociatedDocument";
|
||||
pub(in crate::context_bootstrap) use super::super::shared::WINDOW_DOCUMENT_SLOT;
|
||||
|
||||
pub(in crate::context_bootstrap) fn window_document_getter_template<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
@@ -19,9 +18,9 @@ pub(in crate::context_bootstrap) fn window_document_getter_template<'s>(
|
||||
None => v8::FunctionTemplate::new(scope, window_document_getter),
|
||||
};
|
||||
template.remove_prototype();
|
||||
// V8 checks the receiver against the actual access Context before
|
||||
// invoking the callback, including when the getter is borrowed.
|
||||
template.set_accept_any_receiver(false);
|
||||
// WindowProxy checks ordinary property reads before using the cache.
|
||||
// Explicit getter calls must reach our receiver check in the callee's
|
||||
// realm so a borrowed getter creates errors with the correct intrinsic.
|
||||
template
|
||||
}
|
||||
|
||||
@@ -30,9 +29,7 @@ pub(crate) fn retain_window_document_in_retired_realm(
|
||||
) -> Option<()> {
|
||||
let context = scope.get_current_context();
|
||||
let host_ptr = crate::util::context_host_ptr_from_context_slot(context)?;
|
||||
let _ = WindowEnvironmentSettings::bind_current_main_document_for_retirement(scope, unsafe {
|
||||
&*host_ptr
|
||||
});
|
||||
let _ = WindowEnvironmentSettings::bind_current_main_document(scope, unsafe { &*host_ptr });
|
||||
let window = context.global(scope);
|
||||
if get_private_object(scope, window, WINDOW_DOCUMENT_SLOT).is_some() {
|
||||
return Some(());
|
||||
@@ -94,6 +91,12 @@ pub(in crate::context_bootstrap) fn window_document_getter<'s>(
|
||||
rv.set(document.into());
|
||||
return;
|
||||
}
|
||||
if crate::native_bridge::lightweight_popup_id_from_window(scope, receiver).is_some() {
|
||||
// Popup bootstrap may precede its Document projection. The opener's
|
||||
// host Document is never this Window's associated Document.
|
||||
rv.set_null();
|
||||
return;
|
||||
}
|
||||
let handle = if let Some(settings) = WindowEnvironmentSettings::for_current_realm(scope) {
|
||||
settings.document_handle()
|
||||
} else if let Some(handle) = window_child_context_handle(scope, receiver) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::helpers::{
|
||||
window_child_context_handle, window_has_discarded_child_browsing_context, window_hidden_value, window_host_ptr, window_is_closed,
|
||||
window_receiver,
|
||||
window_child_context_handle, window_has_discarded_child_browsing_context, window_hidden_value,
|
||||
window_host_ptr, window_is_closed, window_receiver,
|
||||
};
|
||||
use super::*;
|
||||
use crate::{
|
||||
@@ -176,7 +176,6 @@ pub(crate) fn window_parent_or_top<'s>(
|
||||
}
|
||||
|
||||
pub(crate) fn window_opener_getter<'s>(
|
||||
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
@@ -193,17 +192,23 @@ pub(crate) fn window_opener_getter<'s>(
|
||||
return;
|
||||
};
|
||||
rv.set_null();
|
||||
if window_is_closed(scope, receiver) || window_has_discarded_child_browsing_context(scope, receiver) {
|
||||
if window_is_closed(scope, receiver)
|
||||
|| window_has_discarded_child_browsing_context(scope, receiver)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let Some(host_ptr) = window_host_ptr(scope, receiver) {
|
||||
let host = unsafe { &*host_ptr };
|
||||
if let Some(popup_id) = lightweight_popup_id_from_window(scope, receiver) {
|
||||
if let Some(opener) = host.lightweight_popup_opener_window(scope, popup_id) { rv.set(opener.into()); }
|
||||
if let Some(opener) = host.lightweight_popup_opener_window(scope, popup_id) {
|
||||
rv.set(opener.into());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if let Some(handle) = window_child_context_handle(scope, receiver) {
|
||||
if let Some(opener) = host.child_browsing_context_opener(scope, handle) { rv.set(opener.into()); }
|
||||
if let Some(opener) = host.child_browsing_context_opener(scope, handle) {
|
||||
rv.set(opener.into());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ pub(super) use base64::{
|
||||
pub(super) use dialogs::entered_window_api_base_url;
|
||||
pub(super) use dialogs::{window_alert_callback, window_confirm_callback, window_prompt_callback};
|
||||
pub(crate) use dialogs::{
|
||||
window_blur_callback, window_close_callback, window_const_false_callback, window_focus_callback,
|
||||
window_noop_callback, window_open_callback, window_stop_callback,
|
||||
window_blur_callback, window_close_callback, window_const_false_callback,
|
||||
window_focus_callback, window_noop_callback, window_open_callback, window_stop_callback,
|
||||
};
|
||||
pub(crate) use navigator::{
|
||||
LegacyStorageQuotaCallbackOutcome, LegacyStorageQuotaCallbackTask,
|
||||
|
||||
@@ -12,7 +12,6 @@ use crate::{
|
||||
SpecialBrowsingContextTarget, navigate_existing_browsing_context_target,
|
||||
navigate_iframe_target,
|
||||
},
|
||||
entered_child_window_handle,
|
||||
},
|
||||
runtime::{
|
||||
RendererPendingJavaScriptDialog, RendererPendingPopupActivation,
|
||||
@@ -88,8 +87,15 @@ pub(crate) fn window_close_callback<'s>(
|
||||
});
|
||||
if let Some(host_ptr) = receiver_context.and_then(context_host_ptr_from_context_slot) {
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
if let Some(window) = host.auxiliary_window() {
|
||||
host.request_auxiliary_window_close(window);
|
||||
if let Some(window) = host.auxiliary_window()
|
||||
&& host.request_auxiliary_window_close(window)
|
||||
&& let Some(context) = receiver_context
|
||||
{
|
||||
// Closing has its own synchronous beforeunload check, including
|
||||
// close() called from pagehide. The native close transition above
|
||||
// rejects reentry; the queued close task owns final retirement.
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
host.dispatch_main_document_tree_beforeunload_for_close(scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,9 +148,15 @@ pub(crate) fn window_stop_callback<'s>(
|
||||
if !crate::context_bootstrap::require_same_origin_window_receiver(scope, args.this(), false) {
|
||||
return;
|
||||
}
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = super::super::window_accessors::window_host_ptr(scope, args.this()) else {
|
||||
return;
|
||||
};
|
||||
let Some(context) = args.this().get_creation_context(scope) else {
|
||||
return;
|
||||
};
|
||||
// The receiver was authorized above. Its native binding belongs to its
|
||||
// own host, including when stop() is borrowed from another Window.
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let receiver = match crate::native_bridge::WindowOperationReceiver::capture_and_authorize(
|
||||
scope,
|
||||
args.this(),
|
||||
@@ -462,7 +474,7 @@ pub(crate) fn window_open_callback<'s>(
|
||||
// The entered Window identifies the creator even when `noopener` removes
|
||||
// the opener relationship. Its child marker is more precise than the
|
||||
// ambient dispatch scope while a child callback is running.
|
||||
let source_child_handle = window_open_receiver_child_handle(scope, entered_window);
|
||||
let source_child_handle = window_receiver_child_handle(scope, entered_window);
|
||||
let popup_source_scope = source_child_handle
|
||||
.map(OwnerDispatchScope::Child)
|
||||
.unwrap_or(source_scope);
|
||||
@@ -480,7 +492,10 @@ pub(crate) fn window_open_callback<'s>(
|
||||
_ => crate::RendererPopupDisposition::Foreground,
|
||||
};
|
||||
if host.has_browser_owned_auxiliary_page_factory()
|
||||
&& (!suppress_opener || url.as_deref().is_none_or(|url| Url::parse(url).is_ok_and(|url| moli_url::is_about_blank(&url))))
|
||||
&& (!suppress_opener
|
||||
|| url
|
||||
.as_deref()
|
||||
.is_none_or(|url| Url::parse(url).is_ok_and(|url| moli_url::is_about_blank(&url))))
|
||||
{
|
||||
let creator_child_handle = window_receiver_child_handle(scope, entered_window);
|
||||
match host.open_renderer_owned_auxiliary_window(
|
||||
@@ -496,6 +511,14 @@ pub(crate) fn window_open_callback<'s>(
|
||||
true,
|
||||
) {
|
||||
Ok(opened) => {
|
||||
if !parsed.raw_url.is_empty() && !opened.prepare_navigation_activation(scope) {
|
||||
if suppress_opener {
|
||||
rv.set_null();
|
||||
} else {
|
||||
rv.set(opened.window_proxy.into());
|
||||
}
|
||||
return;
|
||||
}
|
||||
let disposition = if opened.pending_page.is_some()
|
||||
|| host.protocol_user_gesture_activation()
|
||||
|| host.current_input_event().is_some()
|
||||
@@ -552,7 +575,7 @@ pub(crate) fn window_open_callback<'s>(
|
||||
url.as_deref(),
|
||||
entered_base_url,
|
||||
creator_policy_container,
|
||||
true,
|
||||
false,
|
||||
),
|
||||
_ => host.open_lightweight_popup_window(
|
||||
scope,
|
||||
@@ -582,7 +605,9 @@ pub(crate) fn window_open_callback<'s>(
|
||||
let popup_id = opened_popup.popup_id;
|
||||
if opened_popup.created_new_browsing_context {
|
||||
host.set_lightweight_popup_is_popup(popup_id, parsed_features.is_popup());
|
||||
} else if url.is_none() || !opened_popup.allows_navigation_activation(scope, host) {
|
||||
} else if (url.is_none() && host.lightweight_popup_has_pending_document_load(popup_id))
|
||||
|| !opened_popup.allows_navigation_activation(scope, host)
|
||||
{
|
||||
if suppress_opener {
|
||||
rv.set(v8::null(scope).into());
|
||||
} else {
|
||||
@@ -796,7 +821,8 @@ fn open_dialog(
|
||||
if !allows_modals {
|
||||
return None;
|
||||
}
|
||||
let source_url = window_open_entered_document_url(scope, host).to_string();
|
||||
let source_url =
|
||||
window_open_document_url(host, window_open_entry_scope(scope, host)).to_string();
|
||||
let dialog_id = host.allocate_javascript_dialog_id();
|
||||
host.open_modal_javascript_dialog(
|
||||
target,
|
||||
|
||||
@@ -926,7 +926,11 @@ impl DocumentLifecycleRecord {
|
||||
self.domcontentloaded_transition_token = None;
|
||||
self.complete_transition_token = None;
|
||||
self.readiness = Some(DocumentReadinessState::Complete);
|
||||
self.load = DocumentLoadEventProgress::Ready;
|
||||
self.load = if self.load_delivery_kind == DocumentLoadDeliveryKind::Main {
|
||||
DocumentLoadEventProgress::Suppressed
|
||||
} else {
|
||||
DocumentLoadEventProgress::Ready
|
||||
};
|
||||
self.completely_loaded = true;
|
||||
true
|
||||
}
|
||||
@@ -1129,6 +1133,10 @@ impl DocumentLifecycleRecord {
|
||||
Some(finished)
|
||||
}
|
||||
|
||||
pub(super) fn unload_has_started(&self) -> bool {
|
||||
self.unload != DocumentUnloadEventProgress::Pending
|
||||
}
|
||||
|
||||
pub(super) fn begin_unload_dispatch(&mut self) -> bool {
|
||||
if !self.child_load_event_has_started()
|
||||
|| self.unload != DocumentUnloadEventProgress::Pending
|
||||
|
||||
@@ -179,6 +179,38 @@ impl FrameOwnerStore {
|
||||
realm_id
|
||||
}
|
||||
|
||||
pub(crate) fn mark_main_document_initial_empty(&mut self) {
|
||||
let owner = self
|
||||
.current_main_owner_snapshot()
|
||||
.expect("an initial main Document must have an installed owner");
|
||||
let document = self
|
||||
.documents
|
||||
.get_mut(&owner.document_id)
|
||||
.expect("the current main Document must retain its owner record");
|
||||
document.creation_kind = DocumentCreationKind::InitialEmpty;
|
||||
document.has_committed_navigation = false;
|
||||
}
|
||||
|
||||
pub(crate) fn main_document_has_committed_navigation(&self) -> bool {
|
||||
self.current_main_owner_snapshot()
|
||||
.and_then(|owner| self.documents.get(&owner.document_id))
|
||||
.is_some_and(|document| document.has_committed_navigation)
|
||||
}
|
||||
|
||||
pub(crate) fn complete_current_main_initial_empty_document(&mut self) -> bool {
|
||||
let Some(owner) = self.current_main_document_task_owner() else {
|
||||
return false;
|
||||
};
|
||||
self.documents
|
||||
.get_mut(&owner.document_id)
|
||||
.filter(|document| moli_url::is_about_blank(&document.url))
|
||||
.is_some_and(|document| {
|
||||
document
|
||||
.lifecycle_progress
|
||||
.complete_initial_empty_document()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_child_frame(
|
||||
&mut self,
|
||||
child_handle: DomHandle,
|
||||
@@ -397,25 +429,27 @@ impl FrameOwnerStore {
|
||||
.lifecycle_progress
|
||||
.document_open_load_continuation();
|
||||
let completely_loaded = retired_document.lifecycle_progress.is_completely_loaded();
|
||||
let has_committed_navigation = if matches!(creation_kind, DocumentCreationKind::DocumentOpen { .. }) {
|
||||
retired_document.has_committed_navigation
|
||||
} else {
|
||||
!creation_kind.is_initial_empty()
|
||||
};
|
||||
let has_committed_navigation =
|
||||
if matches!(creation_kind, DocumentCreationKind::DocumentOpen { .. }) {
|
||||
retired_document.has_committed_navigation
|
||||
} else {
|
||||
!creation_kind.is_initial_empty()
|
||||
};
|
||||
retired_document.lifecycle = DocumentLifecycleState::Replaced;
|
||||
retired_document.lifecycle_progress.retire();
|
||||
retired_document.active_requests.clear();
|
||||
|
||||
let document_id = transition.current_owner().document_id;
|
||||
let lifecycle_progress = if matches!(creation_kind, DocumentCreationKind::DocumentOpen { .. }) {
|
||||
self.new_loading_document_lifecycle_for_document_open(
|
||||
DocumentLoadDeliveryKind::Main,
|
||||
load_continuation,
|
||||
completely_loaded,
|
||||
)
|
||||
} else {
|
||||
self.new_loading_document_lifecycle(DocumentLoadDeliveryKind::Main)
|
||||
};
|
||||
let lifecycle_progress =
|
||||
if matches!(creation_kind, DocumentCreationKind::DocumentOpen { .. }) {
|
||||
self.new_loading_document_lifecycle_for_document_open(
|
||||
DocumentLoadDeliveryKind::Main,
|
||||
load_continuation,
|
||||
completely_loaded,
|
||||
)
|
||||
} else {
|
||||
self.new_loading_document_lifecycle(DocumentLoadDeliveryKind::Main)
|
||||
};
|
||||
self.documents.insert(
|
||||
document_id,
|
||||
DocumentRecord {
|
||||
@@ -2085,6 +2119,12 @@ impl FrameOwnerStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn current_main_document_unload_has_started(&self) -> bool {
|
||||
self.current_main_document_task_owner()
|
||||
.and_then(|owner| self.documents.get(&owner.document_id))
|
||||
.is_some_and(|document| document.lifecycle_progress.unload_has_started())
|
||||
}
|
||||
|
||||
pub(crate) fn begin_current_main_document_unload(&mut self) -> Option<FrameDocumentTaskOwner> {
|
||||
let owner = self.current_main_document_task_owner()?;
|
||||
self.documents
|
||||
|
||||
@@ -381,6 +381,53 @@ fn initial_main_document_commit_prepares_owners_before_replacement_without_turn_
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_initial_document_stays_provisional_through_open_until_navigation_commits() {
|
||||
let mut store = FrameOwnerStore::default();
|
||||
store.ensure_main_frame(
|
||||
handle(1),
|
||||
url("about:blank"),
|
||||
url("https://creator.test/"),
|
||||
"https://creator.test".to_owned(),
|
||||
policy_container(),
|
||||
policy_context(),
|
||||
None,
|
||||
);
|
||||
store.mark_main_document_initial_empty();
|
||||
assert!(!store.main_document_has_committed_navigation());
|
||||
assert!(store.complete_current_main_initial_empty_document());
|
||||
assert!(!store.complete_current_main_initial_empty_document());
|
||||
let initial = store.current_main_document_task_owner().unwrap();
|
||||
assert_eq!(
|
||||
store.current_document_is_completely_loaded(initial.document_owner()),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
store.current_main_document_complete_transition_is_ready(initial),
|
||||
Some(false)
|
||||
);
|
||||
store
|
||||
.replace_main_document(
|
||||
handle(1),
|
||||
url("https://creator.test/written"),
|
||||
url("https://creator.test/written"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!store.main_document_has_committed_navigation());
|
||||
let transition = store
|
||||
.prepare_initial_main_document_owner_transition()
|
||||
.unwrap();
|
||||
store
|
||||
.replace_initial_main_document(
|
||||
transition,
|
||||
handle(2),
|
||||
url("https://creator.test/committed"),
|
||||
url("https://creator.test/committed"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(store.main_document_has_committed_navigation());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_document_replacement_rotates_document_owner_without_replacing_window_or_realm() {
|
||||
let mut store = FrameOwnerStore::default();
|
||||
|
||||
@@ -232,10 +232,9 @@ pub use runtime::{
|
||||
CompletedWorkerRuntimeInspectorCommandDispatch, DetachedParserScriptFetchContinuation,
|
||||
DevToolsSessionKey, ExternalRawDocumentBodyStream, JsRuntime, JsRuntimeOwner, PageId,
|
||||
PendingHtmlPage, PendingWorkerRuntimeInspectorSessionResponse, PreparedRendererDocument,
|
||||
RendererAboutDocumentState,
|
||||
RendererAccessibilityPayloadsForObjectId, RendererActivityDiagnostics,
|
||||
RendererAgentAttachmentId, RendererAutofillAddressField, RendererAutofillCreditCard,
|
||||
RendererAutofillTriggerOutcome, RendererAutofillTriggerRequest,
|
||||
RendererAboutDocumentState, RendererAccessibilityPayloadsForObjectId,
|
||||
RendererActivityDiagnostics, RendererAgentAttachmentId, RendererAutofillAddressField,
|
||||
RendererAutofillCreditCard, RendererAutofillTriggerOutcome, RendererAutofillTriggerRequest,
|
||||
RendererAuxiliaryDocumentResponse, RendererAuxiliaryNavigationKind, RendererAuxiliaryWindow,
|
||||
RendererBrowserContextRuntime, RendererBrowserContextRuntimeId,
|
||||
RendererBrowserContextRuntimeOwner, RendererBrowserContextRuntimeOwnerAccess,
|
||||
@@ -286,11 +285,12 @@ pub use runtime::{
|
||||
RendererNativeCommandReadyResponse, RendererNativeCommandResponseGuard,
|
||||
RendererNativeCommandTerminal, RendererNativeOperation, RendererNativeOperationStep,
|
||||
RendererNativeProtocolError, RendererNativeProtocolNotification,
|
||||
RendererNativeProtocolResponse, RendererNativeProtocolStateUpdate, RendererNavigationInitiator, RendererNavigationHistory, RendererNavigationHistoryRequest,
|
||||
RendererOutputCursor, RendererOutputFence, RendererOutputFenceLeaseId, RendererOutputItem,
|
||||
RendererOutputPublication, RendererOutputPublicationOrdering, RendererOutputRecord,
|
||||
RendererOutputResidenceIdentity, RendererOutputStreamCloseReason, RendererOutputStreamControl,
|
||||
RendererOutputStreamEpoch, RendererOutputStreamIdentity, RendererOutputTransportDiagnostics,
|
||||
RendererNativeProtocolResponse, RendererNativeProtocolStateUpdate, RendererNavigationHistory,
|
||||
RendererNavigationHistoryRequest, RendererNavigationInitiator, RendererOutputCursor,
|
||||
RendererOutputFence, RendererOutputFenceLeaseId, RendererOutputItem, RendererOutputPublication,
|
||||
RendererOutputPublicationOrdering, RendererOutputRecord, RendererOutputResidenceIdentity,
|
||||
RendererOutputStreamCloseReason, RendererOutputStreamControl, RendererOutputStreamEpoch,
|
||||
RendererOutputStreamIdentity, RendererOutputTransportDiagnostics,
|
||||
RendererOutputTransportMessage, RendererOutputTransportReceiver,
|
||||
RendererOutputTransportSendError, RendererOutputTransportSender, RendererOwnerAction,
|
||||
RendererOwnerCommand, RendererOwnerHandle, RendererOwnerLocalHostId, RendererOwnerReply,
|
||||
|
||||
@@ -1229,6 +1229,7 @@ impl JsContextHost {
|
||||
host_ptr: *mut Self,
|
||||
document_handle: DomHandle,
|
||||
) {
|
||||
let _unload = unsafe { &*host_ptr }.enter_document_unload(document_handle);
|
||||
let handles = unsafe { &*host_ptr }.child_browsing_context_handles_in_document_order();
|
||||
for handle in handles {
|
||||
if unsafe { &*host_ptr }
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ impl JsContextHost {
|
||||
self.retire_window_realm_resources(stale.runtime_observable_context_token);
|
||||
self.retire_window_execution_contexts_for_context_token(
|
||||
stale.runtime_observable_context_token,
|
||||
config.resource_owner_id,
|
||||
Some(config.resource_owner_id),
|
||||
);
|
||||
if self.child_window_proxy_frame_is_current(handle, &stale.frame_id) {
|
||||
stale_context.detach_global();
|
||||
|
||||
@@ -65,6 +65,7 @@ impl CrossOriginWindowProperty {
|
||||
/// on the Window or reading its private slots from the accessing realm.
|
||||
pub(crate) struct CrossOriginWindowAccessor<'s> {
|
||||
surface: v8::Local<'s, v8::Object>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
}
|
||||
|
||||
impl<'s> CrossOriginWindowAccessor<'s> {
|
||||
@@ -110,26 +111,35 @@ impl<'s> CrossOriginWindowAccessor<'s> {
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<Self> {
|
||||
let context = receiver.get_creation_context(scope)?;
|
||||
let accessing_context = cross_origin_accessing_context(scope);
|
||||
if !receiver.strict_equals(context.global(scope).into())
|
||||
&& crate::native_bridge::lightweight_popup_id_from_window(scope, receiver).is_some()
|
||||
{
|
||||
let accessing_context = scope.get_current_context();
|
||||
if synthetic_window_context_can_access(scope, accessing_context, receiver) {
|
||||
return None;
|
||||
}
|
||||
return window_cross_origin_access_surface(scope, receiver)
|
||||
.map(|surface| Self { surface });
|
||||
return window_cross_origin_backing_surface(scope, receiver)
|
||||
.map(|surface| Self { surface, receiver });
|
||||
}
|
||||
if !receiver.strict_equals(context.global(scope).into())
|
||||
|| super::super::window_contexts_allow_access(scope.get_current_context(), context)
|
||||
|| contexts_can_script_access(scope, accessing_context, context)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
// Only native Window realms install this context slot. A copied bridge
|
||||
// property, inherited Window prototype, or author Proxy cannot supply it.
|
||||
if context
|
||||
.get_slot::<crate::util::MainDefaultWindowContext>()
|
||||
.is_some()
|
||||
{
|
||||
return Some(Self {
|
||||
surface: context.get_extras_binding_object(scope),
|
||||
receiver,
|
||||
});
|
||||
}
|
||||
context.get_slot::<WindowCrossOriginAccessSurface>()?;
|
||||
let surface = window_cross_origin_access_surface(scope, receiver)?;
|
||||
Some(Self { surface })
|
||||
let surface = window_cross_origin_backing_surface(scope, receiver)?;
|
||||
Some(Self { surface, receiver })
|
||||
}
|
||||
|
||||
pub(crate) fn get(
|
||||
@@ -137,6 +147,14 @@ impl<'s> CrossOriginWindowAccessor<'s> {
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
property: CrossOriginWindowProperty,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
if self
|
||||
.receiver
|
||||
.get_creation_context(scope)?
|
||||
.get_slot::<crate::util::MainDefaultWindowContext>()
|
||||
.is_some()
|
||||
{
|
||||
return main_page::window_property_value(scope, self.receiver, property);
|
||||
}
|
||||
self.surface
|
||||
.get(scope, v8str(scope, property.name()).into())
|
||||
}
|
||||
@@ -220,6 +238,23 @@ impl ChildWindowProxyRecords {
|
||||
.map(|window| v8::Local::new(scope, window))
|
||||
}
|
||||
|
||||
fn cross_origin_handler_data<'s>(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
handle: DomHandle,
|
||||
) -> Option<(v8::Local<'s, v8::Object>, v8::Local<'s, v8::Object>)> {
|
||||
let window = self.live_window(scope, handle)?;
|
||||
let context = window.get_creation_context(scope)?;
|
||||
let target_scope = &mut v8::ContextScope::new(scope, context);
|
||||
let surface = get_private_value(
|
||||
target_scope,
|
||||
window,
|
||||
CROSS_ORIGIN_WINDOW_ACCESS_SURFACE_SLOT,
|
||||
)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())?;
|
||||
Some((surface, window))
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn has_live_window(
|
||||
&self,
|
||||
handle: DomHandle,
|
||||
@@ -880,6 +915,8 @@ struct CrossOriginLocationProxyHandlerDeclaration {
|
||||
set: (),
|
||||
#[webapi(method, length = 2, callback = cross_origin_location_proxy_descriptor_callback)]
|
||||
get_own_property_descriptor: (),
|
||||
#[webapi(method, length = 1, callback = cross_origin_location_proxy_own_keys_callback)]
|
||||
own_keys: (),
|
||||
#[webapi(method, length = 2, callback = cross_origin_window_denied_callback)]
|
||||
delete_property: (),
|
||||
#[webapi(method, length = 3, callback = cross_origin_window_denied_callback)]
|
||||
@@ -1502,6 +1539,18 @@ impl JsContextHost {
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
if self.auxiliary_window().is_some()
|
||||
&& let Some(environment) = self.page_script_environment()
|
||||
{
|
||||
// Named reuse changes the related Page's opener synchronously.
|
||||
// Its script environment also records an explicitly disowned
|
||||
// opener; an older endpoint must not restore that relationship.
|
||||
let opener = environment.opener_in_scope(scope)?;
|
||||
let host = opener
|
||||
.get_creation_context(scope)
|
||||
.and_then(crate::util::context_host_ptr_from_context_slot)?;
|
||||
return (!unsafe { &*host }.browsing_context_is_closed()).then_some(opener);
|
||||
}
|
||||
let (endpoint, opener) = self.top_window_opener.as_ref()?;
|
||||
let opener = v8::Local::new(scope, opener);
|
||||
self.window_opener_endpoint_is_live(scope, *endpoint, opener)
|
||||
@@ -1559,6 +1608,15 @@ impl JsContextHost {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn child_browsing_context_top_window_for_current_realm<'s>(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
handle: DomHandle,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
self.child_window_proxy_records
|
||||
.browsing_context_top(scope, handle)
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn child_browsing_context_parent_window<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
@@ -2361,6 +2419,7 @@ fn wrap_cross_origin_location_proxy<'s>(
|
||||
get: (),
|
||||
set: (),
|
||||
get_own_property_descriptor: (),
|
||||
own_keys: (),
|
||||
delete_property: (),
|
||||
define_property: (),
|
||||
}
|
||||
@@ -2518,6 +2577,11 @@ fn window_cross_origin_access_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
holder: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
if let Some((surface, _)) = child_window_cross_origin_handler_data(scope, holder)
|
||||
&& main_page::is_main_surface(scope, surface)
|
||||
{
|
||||
return Some(surface);
|
||||
}
|
||||
if crate::native_bridge::lightweight_popup_id_from_window(scope, holder).is_some() {
|
||||
let surface = get_private_value(scope, holder, CROSS_ORIGIN_WINDOW_ACCESS_SURFACE_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())?;
|
||||
@@ -2555,6 +2619,23 @@ fn window_cross_origin_access_surface<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
// Native getter projection reads the target's backing surface. Public
|
||||
// descriptors use the caller surface and must validate their actual receiver.
|
||||
fn window_cross_origin_backing_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
holder: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let context = holder.get_creation_context(scope)?;
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
get_private_value(scope, holder, CROSS_ORIGIN_WINDOW_ACCESS_SURFACE_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
.or_else(|| {
|
||||
context
|
||||
.get_slot::<WindowCrossOriginAccessSurface>()
|
||||
.and_then(|surface| surface.0.to_local(scope))
|
||||
})
|
||||
}
|
||||
|
||||
fn window_cross_origin_caller_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
storage: v8::Local<'s, v8::Object>,
|
||||
@@ -2635,7 +2716,9 @@ fn child_window_cross_origin_handler_data<'s>(
|
||||
if let Some(identity) = host.window_execution_context_identity_for_access_check(holder_context)
|
||||
&& host.window_execution_context_identity_is_current(identity)
|
||||
{
|
||||
if !host.window_execution_context_identity_is_default_world(identity) {
|
||||
let (_, default_context) =
|
||||
host.window_execution_context(scope, identity.owner(), identity.dispatch_scope())?;
|
||||
if default_context != holder_context {
|
||||
return None;
|
||||
}
|
||||
if identity.dispatch_scope() == super::super::OwnerDispatchScope::Top {
|
||||
@@ -2759,7 +2842,7 @@ fn child_window_cross_origin_named_enumerator<'s>(
|
||||
property_names.index_filter(v8::IndexFilter::SkipIndices);
|
||||
property_names.key_conversion(v8::KeyConversionMode::ConvertToString);
|
||||
let names = window_cross_origin_access_surface(scope, callback_args.holder())
|
||||
.and_then(|surface| surface.get_own_property_names(scope, property_names.build()))
|
||||
.and_then(|surface| surface.get_property_names(scope, property_names.build()))
|
||||
.unwrap_or_else(|| v8::Array::new(scope, 0));
|
||||
rv.set(names);
|
||||
}
|
||||
@@ -2813,7 +2896,16 @@ fn child_window_cross_origin_named_child_value<'s>(
|
||||
if cross_origin_window_has_discarded_browsing_context(scope, surface) {
|
||||
return None;
|
||||
}
|
||||
let parent = if let Some(handle) = child_handle_from_object(scope, surface) {
|
||||
let main_host = main_page::is_main_surface(scope, surface)
|
||||
.then(|| {
|
||||
let window = get_private_value(scope, surface, CROSS_ORIGIN_WINDOW_SELF_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())?;
|
||||
crate::util::context_host_ptr_from_context_slot(window.get_creation_context(scope)?)
|
||||
})
|
||||
.flatten();
|
||||
let parent = if main_host.is_some() {
|
||||
super::super::OwnerDispatchScope::Top
|
||||
} else if let Some(handle) = child_handle_from_object(scope, surface) {
|
||||
super::super::OwnerDispatchScope::Child(handle)
|
||||
} else if let Some(id) = cross_origin_lightweight_popup_id(scope, surface) {
|
||||
super::super::OwnerDispatchScope::LightweightPopup(id)
|
||||
@@ -2822,7 +2914,10 @@ fn child_window_cross_origin_named_child_value<'s>(
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let host_ptr = context_host_ptr_from_global_bridge(scope)?;
|
||||
let host_ptr = main_host.or_else(|| context_host_ptr_from_global_bridge(scope))?;
|
||||
if unsafe { &*host_ptr }.browsing_context_is_closed() {
|
||||
return None;
|
||||
}
|
||||
let child_handle = unsafe { &*host_ptr }
|
||||
.child_browsing_context_named_child_handle_for_scope(parent, &key_name)?;
|
||||
unsafe { &mut *host_ptr }
|
||||
@@ -2838,7 +2933,9 @@ fn child_window_cross_origin_indexed_getter<'s>(
|
||||
) -> v8::Intercepted {
|
||||
if let Some(surface) = window_cross_origin_access_surface(scope, args.holder())
|
||||
&& main_page::is_main_surface(scope, surface)
|
||||
&& !surface.has_own_property(scope, v8_string(scope, &index.to_string()).unwrap().into()).unwrap_or(false)
|
||||
&& !surface
|
||||
.has_own_property(scope, v8_string(scope, &index.to_string()).unwrap().into())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return v8::Intercepted::kNo;
|
||||
}
|
||||
@@ -2923,9 +3020,7 @@ fn child_window_cross_origin_indexed_enumerator<'s>(
|
||||
) {
|
||||
let holder = args.holder();
|
||||
let count = window_cross_origin_access_surface(scope, holder)
|
||||
.filter(|surface| {
|
||||
!cross_origin_window_has_discarded_browsing_context(scope, *surface)
|
||||
})
|
||||
.filter(|surface| !cross_origin_window_has_discarded_browsing_context(scope, *surface))
|
||||
.and_then(|surface| {
|
||||
cross_origin_surface_get(scope, holder, surface, v8str(scope, "length").into())
|
||||
})
|
||||
@@ -3052,7 +3147,10 @@ fn cross_origin_location_proxy_get_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if is_cross_origin_location_href_key_value(scope, args.get(1)) {
|
||||
if !v8::Local::<v8::Name>::try_from(args.get(1))
|
||||
.is_ok_and(|key| cross_origin_location_key_is_allowed(scope, key))
|
||||
|| is_cross_origin_location_href_key_value(scope, args.get(1))
|
||||
{
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
return;
|
||||
}
|
||||
@@ -3061,10 +3159,9 @@ fn cross_origin_location_proxy_get_callback<'s>(
|
||||
return;
|
||||
};
|
||||
let receiver = v8::Local::<v8::Object>::try_from(args.get(2)).unwrap_or(target);
|
||||
let surface = main_page::location_access_surface(scope, target).unwrap_or(target);
|
||||
let accessing_context = cross_origin_accessing_context(scope);
|
||||
let scope = &mut v8::ContextScope::new(scope, accessing_context);
|
||||
match surface.get_with_receiver(scope, args.get(1), receiver) {
|
||||
match target.get_with_receiver(scope, args.get(1), receiver) {
|
||||
Some(value) => rv.set(value),
|
||||
None => rv.set_undefined(),
|
||||
}
|
||||
@@ -3081,27 +3178,50 @@ fn cross_origin_location_proxy_descriptor_callback<'s>(
|
||||
let Ok(key) = v8::Local::<v8::Name>::try_from(args.get(1)) else {
|
||||
return;
|
||||
};
|
||||
let main_surface = main_page::location_access_surface(scope, target);
|
||||
let surface = main_surface.unwrap_or(target);
|
||||
if !cross_origin_location_key_is_allowed(scope, key) {
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
return;
|
||||
}
|
||||
let accessing_context = cross_origin_accessing_context(scope);
|
||||
let scope = &mut v8::ContextScope::new(scope, accessing_context);
|
||||
let Some(descriptor) = surface.get_own_property_descriptor(scope, key) else {
|
||||
let Some(descriptor) = target.get_own_property_descriptor(scope, key) else {
|
||||
return;
|
||||
};
|
||||
if main_surface.is_some()
|
||||
&& let Ok(descriptor) = v8::Local::<v8::Object>::try_from(descriptor)
|
||||
{
|
||||
// Main Location's target holds placeholders. Its descriptors are
|
||||
// projected into each accessor realm and must remain configurable.
|
||||
let _ = descriptor.set(
|
||||
scope,
|
||||
v8str(scope, "configurable").into(),
|
||||
v8::Boolean::new(scope, true).into(),
|
||||
);
|
||||
}
|
||||
rv.set(descriptor);
|
||||
}
|
||||
|
||||
fn cross_origin_location_key_is_allowed<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
key: v8::Local<'s, v8::Name>,
|
||||
) -> bool {
|
||||
["href", "replace", "then"]
|
||||
.into_iter()
|
||||
.any(|name| key == v8str(scope, name))
|
||||
|| [
|
||||
v8::Symbol::get_to_string_tag(scope),
|
||||
v8::Symbol::get_has_instance(scope),
|
||||
v8::Symbol::get_is_concat_spreadable(scope),
|
||||
]
|
||||
.into_iter()
|
||||
.any(|symbol| key == symbol)
|
||||
}
|
||||
|
||||
fn cross_origin_location_proxy_own_keys_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
_args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let keys = [
|
||||
v8str(scope, "href").into(),
|
||||
v8str(scope, "replace").into(),
|
||||
v8str(scope, "then").into(),
|
||||
v8::Symbol::get_to_string_tag(scope).into(),
|
||||
v8::Symbol::get_has_instance(scope).into(),
|
||||
v8::Symbol::get_is_concat_spreadable(scope).into(),
|
||||
];
|
||||
rv.set(v8::Array::new_with_elements(scope, &keys).into());
|
||||
}
|
||||
|
||||
fn cross_origin_location_proxy_set_receiver<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
target: v8::Local<'s, v8::Object>,
|
||||
@@ -3126,16 +3246,7 @@ fn cross_origin_location_proxy_set_callback<'s>(
|
||||
return;
|
||||
};
|
||||
if !is_cross_origin_location_href_key_value(scope, args.get(1)) {
|
||||
let receiver = v8::Local::<v8::Object>::try_from(args.get(3)).unwrap_or(target);
|
||||
rv.set(
|
||||
v8::Boolean::new(
|
||||
scope,
|
||||
target
|
||||
.set_with_receiver(scope, args.get(1), args.get(2), receiver)
|
||||
.unwrap_or(false),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
return;
|
||||
}
|
||||
let Some(receiver) = cross_origin_location_proxy_set_receiver(scope, target, args.get(3))
|
||||
@@ -3255,7 +3366,11 @@ fn cross_origin_window_has_discarded_browsing_context<'s>(
|
||||
get_cross_origin_proxy_private_value(scope, window, CROSS_ORIGIN_WINDOW_SELF_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
.unwrap_or(window);
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let Some(host_ptr) = identity
|
||||
.get_creation_context(scope)
|
||||
.and_then(crate::util::context_host_ptr_from_context_slot)
|
||||
.or_else(|| context_host_ptr_from_global_bridge(scope))
|
||||
else {
|
||||
return true;
|
||||
};
|
||||
// DOM handles survive removal/reinsertion, and a WindowProxy survives
|
||||
@@ -3463,14 +3578,6 @@ fn cross_origin_location_navigate<'s>(
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
) -> bool {
|
||||
if main_page::is_main_location(scope, receiver) {
|
||||
return main_page::navigate(
|
||||
scope,
|
||||
receiver,
|
||||
value,
|
||||
crate::context_bootstrap::LocationNavigationKind::Assign,
|
||||
);
|
||||
}
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
return false;
|
||||
};
|
||||
@@ -3547,22 +3654,6 @@ fn cross_origin_location_replace_callback<'s>(
|
||||
);
|
||||
return;
|
||||
}
|
||||
if main_page::is_main_location(scope, args.this()) {
|
||||
let Some(parsed) = webidl::parse_args::<CrossOriginLocationReplaceArgs>(scope, &args)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(value) = v8_string(scope, &parsed.url) else {
|
||||
return;
|
||||
};
|
||||
main_page::navigate(
|
||||
scope,
|
||||
args.this(),
|
||||
value.into(),
|
||||
crate::context_bootstrap::LocationNavigationKind::Replace,
|
||||
);
|
||||
return;
|
||||
}
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
return;
|
||||
};
|
||||
|
||||
+69
-141
@@ -4,12 +4,49 @@ use crate::context_bootstrap::{
|
||||
window_opener_getter, window_parent_getter, window_top_getter,
|
||||
};
|
||||
|
||||
const LOCATION: &str = "__moliMainWindowCrossOriginLocationObject";
|
||||
const LOCATION_SURFACE: &str = "__moliMainWindowCrossOriginLocationSurface";
|
||||
const MAIN_LOCATION: &str = "__moliMainWindowCrossOriginLocation";
|
||||
const INDEXED_COUNT: &str = "__moliMainWindowCrossOriginIndexedCount";
|
||||
const WINDOW_LOCATION_SLOT: &str = "__moliWindowLocation";
|
||||
|
||||
pub(super) fn window_property_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
property: CrossOriginWindowProperty,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
if matches!(property, CrossOriginWindowProperty::Location) {
|
||||
return location_for_window(scope, window).map(Into::into);
|
||||
}
|
||||
let context = window.get_creation_context(scope)?;
|
||||
let host = crate::util::context_host_ptr_from_context_slot(context);
|
||||
let closed = host.is_none_or(|host| unsafe { &*host }.browsing_context_is_closed());
|
||||
Some(match property {
|
||||
CrossOriginWindowProperty::Closed => v8::Boolean::new(scope, closed).into(),
|
||||
CrossOriginWindowProperty::Length => {
|
||||
let length = if closed {
|
||||
0
|
||||
} else {
|
||||
unsafe { &*host? }.child_browsing_context_count()
|
||||
};
|
||||
v8::Integer::new_from_unsigned(scope, length as u32).into()
|
||||
}
|
||||
CrossOriginWindowProperty::Opener => {
|
||||
if closed {
|
||||
v8::null(scope).into()
|
||||
} else {
|
||||
unsafe { &*host? }
|
||||
.top_window_opener(scope)
|
||||
.map_or_else(|| v8::null(scope).into(), Into::into)
|
||||
}
|
||||
}
|
||||
CrossOriginWindowProperty::Parent | CrossOriginWindowProperty::Top if closed => {
|
||||
v8::null(scope).into()
|
||||
}
|
||||
CrossOriginWindowProperty::Parent
|
||||
| CrossOriginWindowProperty::Top
|
||||
| CrossOriginWindowProperty::Window => window.into(),
|
||||
CrossOriginWindowProperty::Location => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain)]
|
||||
struct MainWindowCrossOriginSurface {
|
||||
@@ -51,51 +88,12 @@ fn cached_access_surface<'s>(
|
||||
let accessing_context = cross_origin_accessing_context(scope);
|
||||
cached_cross_origin_window_surface(scope, target_context, accessing_context, |scope| {
|
||||
let window = target_context.global(scope);
|
||||
let location = shared_cross_origin_location(scope, window)
|
||||
let location = location_for_window(scope, window)
|
||||
.expect("main Window cross-origin Location should initialize");
|
||||
build_accessing_surface(scope, window, location)
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_shared_location<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
anchor: v8::Local<'s, v8::Object>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
if let Some(location) = get_private_value(scope, anchor, LOCATION)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
{
|
||||
return location;
|
||||
}
|
||||
let target = new_null_prototype_object(scope);
|
||||
let marker = v8::Boolean::new(scope, true).into();
|
||||
set_private_value(scope, target, CROSS_ORIGIN_LOCATION_PROXY_SLOT, marker);
|
||||
set_private_value(scope, target, MAIN_LOCATION, window.into());
|
||||
// Preserve the target's keys without exposing realm-specific
|
||||
// functions. Configurable placeholders allow each observer's
|
||||
// get/descriptor projection to obey Proxy invariants.
|
||||
let undefined = v8::undefined(scope).into();
|
||||
for name in CROSS_ORIGIN_LOCATION_DENIED_PROPERTIES
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(["href", "replace", "then"])
|
||||
{
|
||||
let key = v8str(scope, name).into();
|
||||
let _ = target.define_own_property(scope, key, undefined, v8::PropertyAttribute::DONT_ENUM);
|
||||
}
|
||||
install_cross_origin_symbol_slots(scope, target, "Location");
|
||||
let location = wrap_cross_origin_location_proxy(scope, target)
|
||||
.expect("main Window cross-origin Location proxy should initialize");
|
||||
set_private_value(
|
||||
scope,
|
||||
target,
|
||||
CROSS_ORIGIN_LOCATION_PROXY_SELF_SLOT,
|
||||
location.into(),
|
||||
);
|
||||
set_private_value(scope, anchor, LOCATION, location.into());
|
||||
location
|
||||
}
|
||||
|
||||
fn build_accessing_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
@@ -127,49 +125,12 @@ fn build_accessing_surface<'s>(
|
||||
CROSS_ORIGIN_WINDOW_LOCATION_SLOT,
|
||||
location.into(),
|
||||
);
|
||||
install_cross_origin_symbol_slots(scope, surface, "Window");
|
||||
set_private_value(scope, surface, CROSS_ORIGIN_WINDOW_SELF_SLOT, window.into());
|
||||
install_cross_origin_symbol_slots(scope, surface);
|
||||
|
||||
let location_surface = new_null_prototype_object(scope);
|
||||
install_cross_origin_symbol_slots(scope, location_surface, "Location");
|
||||
install_cross_origin_denied_accessors(
|
||||
scope,
|
||||
location_surface,
|
||||
CROSS_ORIGIN_LOCATION_DENIED_PROPERTIES,
|
||||
);
|
||||
let setter = v8::Function::builder(cross_origin_location_navigate_setter_callback)
|
||||
.length(1)
|
||||
.constructor_behavior(v8::ConstructorBehavior::Throw)
|
||||
.build(scope)
|
||||
.expect("cross-origin Location href setter should initialize");
|
||||
setter.set_name(v8str(scope, "set href"));
|
||||
let href = v8str(scope, "href").into();
|
||||
define_get_set_property(
|
||||
scope,
|
||||
location_surface,
|
||||
href,
|
||||
undefined,
|
||||
setter.into(),
|
||||
cross_origin_property_attributes(),
|
||||
"href",
|
||||
)
|
||||
.expect("cross-origin Location href descriptor should initialize");
|
||||
install_cross_origin_location_methods(scope, location_surface);
|
||||
set_cross_origin_object_slot(scope, location_surface, "then", undefined);
|
||||
set_private_value(scope, surface, LOCATION_SURFACE, location_surface.into());
|
||||
surface
|
||||
}
|
||||
|
||||
pub(super) fn location_access_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let window = main_location_window(scope, receiver)?;
|
||||
let context = window.get_creation_context(scope)?;
|
||||
let surface = cached_access_surface(scope, context);
|
||||
get_private_value(scope, surface, LOCATION_SURFACE)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
}
|
||||
|
||||
pub(super) fn is_main_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
surface: v8::Local<'s, v8::Object>,
|
||||
@@ -193,9 +154,11 @@ fn refresh_indexed_children<'s>(
|
||||
host.sync_child_browsing_context_subtree(scope, host.document_handle());
|
||||
let count = host.child_browsing_context_count() as u32;
|
||||
for index in 0..count {
|
||||
if let Some(handle) = host.child_browsing_context_handle_by_index(index as usize)
|
||||
&& let Some(window) =
|
||||
host.child_browsing_context_window_proxy_for_top(scope, handle)
|
||||
if let Some(handle) = host.child_browsing_context_handle_by_index_for_document(
|
||||
host.document_handle(),
|
||||
index as usize,
|
||||
) && let Some(window) =
|
||||
host.child_browsing_context_window_proxy_for_current_realm(scope, handle)
|
||||
{
|
||||
let key = v8_string(scope, &index.to_string()).unwrap();
|
||||
let _ = surface.define_own_property(
|
||||
@@ -215,39 +178,6 @@ fn refresh_indexed_children<'s>(
|
||||
set_private_value(scope, surface, INDEXED_COUNT, count);
|
||||
}
|
||||
|
||||
fn main_location_window<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
get_cross_origin_proxy_private_value(scope, receiver, MAIN_LOCATION)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
}
|
||||
|
||||
pub(super) fn is_main_location<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
main_location_window(scope, receiver).is_some()
|
||||
}
|
||||
|
||||
pub(super) fn navigate<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
kind: crate::context_bootstrap::LocationNavigationKind,
|
||||
) -> bool {
|
||||
let Some(window) = main_location_window(scope, receiver) else {
|
||||
return false;
|
||||
};
|
||||
// The stable WindowProxy follows its successor, but a Location belongs to
|
||||
// the Window realm that created it. An old Location must not gain the new
|
||||
// Document's navigation authority through that proxy.
|
||||
if receiver.get_creation_context(scope) != window.get_creation_context(scope) {
|
||||
return false;
|
||||
}
|
||||
navigate_window(scope, window, value, kind, true)
|
||||
}
|
||||
|
||||
fn navigate_window<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
@@ -270,19 +200,29 @@ fn navigate_window<'s>(
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
return false;
|
||||
}
|
||||
let raw = if kind != crate::context_bootstrap::LocationNavigationKind::Reload {
|
||||
let Some(context) = window.get_creation_context(scope) else {
|
||||
return false;
|
||||
};
|
||||
let accessing_context = cross_origin_accessing_context(scope);
|
||||
let raw = if context != accessing_context
|
||||
&& kind != crate::context_bootstrap::LocationNavigationKind::Reload
|
||||
{
|
||||
crate::context_bootstrap::resolve_cross_window_location_target(scope, &raw).unwrap_or(raw)
|
||||
} else {
|
||||
raw
|
||||
};
|
||||
let Some(context) = window.get_creation_context(scope) else {
|
||||
if let Ok(destination) = url::Url::parse(&raw)
|
||||
&& crate::context_bootstrap::blocks_ancestor_location_navigation(
|
||||
scope,
|
||||
window,
|
||||
&destination,
|
||||
)
|
||||
{
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let Some(location) = window
|
||||
.get(scope, v8str(scope, "location").into())
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
else {
|
||||
let Some(location) = crate::context_bootstrap::window_location_for_holder(scope, window) else {
|
||||
return false;
|
||||
};
|
||||
crate::context_bootstrap::navigate_location_object_with_child_navigate_event(
|
||||
@@ -316,15 +256,14 @@ fn require_window_receiver<'s>(
|
||||
None
|
||||
}
|
||||
|
||||
fn shared_cross_origin_location<'s>(
|
||||
pub(super) fn location_for_window<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let context = window.get_creation_context(scope)?;
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let anchor = context.get_extras_binding_object(scope);
|
||||
let window = context.global(scope);
|
||||
Some(ensure_shared_location(scope, anchor, window))
|
||||
get_private_value(scope, window, WINDOW_LOCATION_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
}
|
||||
|
||||
fn location_getter<'s>(
|
||||
@@ -335,18 +274,7 @@ fn location_getter<'s>(
|
||||
let Some(window) = require_window_receiver(scope, &args, "get", "location") else {
|
||||
return;
|
||||
};
|
||||
if caller_can_access_window(scope, window) {
|
||||
if let Some(location) = get_private_value(scope, window, WINDOW_LOCATION_SLOT) {
|
||||
rv.set(location);
|
||||
return;
|
||||
}
|
||||
throw_type_error(
|
||||
scope,
|
||||
"Failed to get the 'location' property on 'Window': Illegal invocation",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if let Some(location) = shared_cross_origin_location(scope, window) {
|
||||
if let Some(location) = location_for_window(scope, window) {
|
||||
rv.set(location.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +215,13 @@ impl JsContextHost {
|
||||
}
|
||||
|
||||
pub(crate) fn child_browsing_context_host_is_active(&self, handle: DomHandle) -> bool {
|
||||
if self
|
||||
.dom_host()
|
||||
.owner_document_handle(handle)
|
||||
.is_none_or(|document| !self.document_has_browsing_context(document))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if self
|
||||
.dom_host()
|
||||
.owner_document_handle(handle)
|
||||
|
||||
@@ -419,13 +419,16 @@ impl JsContextHost {
|
||||
}
|
||||
};
|
||||
let root_name_matches = match root {
|
||||
OwnerDispatchScope::Top => !self.browsing_context_is_closed()
|
||||
&& self.browsing_context_name().get() == name,
|
||||
OwnerDispatchScope::LightweightPopup(id) => self.lightweight_popup_record(id)
|
||||
OwnerDispatchScope::Top => {
|
||||
!self.browsing_context_is_closed() && self.browsing_context_name().get() == name
|
||||
}
|
||||
OwnerDispatchScope::LightweightPopup(id) => self
|
||||
.lightweight_popup_record(id)
|
||||
.is_some_and(|record| record.is_open() && record.name.get() == name),
|
||||
OwnerDispatchScope::Child(_) => false,
|
||||
};
|
||||
if root_window.is_some() && root_name_matches
|
||||
if root_window.is_some()
|
||||
&& root_name_matches
|
||||
&& related_to.is_none_or(|source| self.browsing_contexts_are_familiar(source, root))
|
||||
{
|
||||
return Some(root);
|
||||
|
||||
@@ -15,6 +15,7 @@ fn slotchange_microtask_callback(
|
||||
impl JsContextHost {
|
||||
pub(crate) fn mark_main_document_initial_empty(&mut self) {
|
||||
assert!(moli_url::is_about_blank(self.document_url()));
|
||||
self.frame_owner_store.mark_main_document_initial_empty();
|
||||
self.initial_empty_main_document = true;
|
||||
}
|
||||
|
||||
@@ -22,6 +23,16 @@ impl JsContextHost {
|
||||
self.initial_empty_main_document
|
||||
}
|
||||
|
||||
pub(crate) fn main_document_has_committed_navigation(&self) -> bool {
|
||||
self.frame_owner_store
|
||||
.main_document_has_committed_navigation()
|
||||
}
|
||||
|
||||
pub(crate) fn complete_current_main_initial_empty_document(&mut self) -> bool {
|
||||
self.frame_owner_store
|
||||
.complete_current_main_initial_empty_document()
|
||||
}
|
||||
|
||||
pub(crate) fn rebind_initial_main_document_senders(
|
||||
&mut self,
|
||||
resource: RendererResourceCompletionSender,
|
||||
@@ -47,14 +58,18 @@ impl JsContextHost {
|
||||
&self,
|
||||
origin: &str,
|
||||
inherited_opaque_origin_matches: bool,
|
||||
inherited_document_domain: Option<&str>,
|
||||
inherited_origin: Option<&super::WindowAccessOrigin>,
|
||||
policy: &crate::document_runtime::DocumentPolicyContainer,
|
||||
) -> crate::frame_owner_model::FrameDocumentLocalWindowTransition {
|
||||
let same_origin = self.document_domain_override.get().as_deref()
|
||||
== inherited_document_domain
|
||||
&& (inherited_opaque_origin_matches
|
||||
|| moli_url::WebOrigin::from_serialized(&self.main_document_security_origin())
|
||||
.same_origin(&moli_url::WebOrigin::from_serialized(origin)));
|
||||
let same_origin = inherited_opaque_origin_matches
|
||||
|| if let Some(origin @ super::WindowAccessOrigin::Tuple { .. }) = inherited_origin {
|
||||
self.main_window_access_origin()
|
||||
.is_some_and(|current| current.can_access(origin))
|
||||
} else {
|
||||
self.document_domain_override.get().is_none()
|
||||
&& moli_url::WebOrigin::from_serialized(&self.main_document_security_origin())
|
||||
.same_origin(&moli_url::WebOrigin::from_serialized(origin))
|
||||
};
|
||||
crate::frame_owner_model::FrameDocumentLocalWindowTransition::for_document_commit(
|
||||
self.initial_empty_main_document,
|
||||
same_origin,
|
||||
@@ -143,11 +158,14 @@ impl JsContextHost {
|
||||
pub(crate) fn request_auxiliary_window_close(
|
||||
&mut self,
|
||||
window: crate::runtime::RendererAuxiliaryWindow,
|
||||
) {
|
||||
) -> bool {
|
||||
if window.close() {
|
||||
self.append_live_turn_items(vec![crate::runtime::RendererOutputItem::OwnerAction(
|
||||
crate::runtime::RendererOwnerAction::CloseAuxiliaryWindow(window),
|
||||
)]);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,21 @@ impl JsContextHost {
|
||||
pub(crate) fn dispatch_main_document_tree_beforeunload(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
) -> bool {
|
||||
self.dispatch_main_document_tree_beforeunload_with_close(scope, false)
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_main_document_tree_beforeunload_for_close(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
) -> bool {
|
||||
self.dispatch_main_document_tree_beforeunload_with_close(scope, true)
|
||||
}
|
||||
|
||||
fn dispatch_main_document_tree_beforeunload_with_close(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
closing: bool,
|
||||
) -> bool {
|
||||
let Some(owner) = self.current_main_document_task_owner() else {
|
||||
return false;
|
||||
@@ -30,9 +45,14 @@ impl JsContextHost {
|
||||
};
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let window = context.global(scope);
|
||||
if crate::context_bootstrap::navigation_unload_event_active(scope, window) {
|
||||
if !closing && crate::context_bootstrap::navigation_unload_event_active(scope, window) {
|
||||
return false;
|
||||
}
|
||||
let was_closed = self.browsing_context_is_closed();
|
||||
let is_current = |host: &Self| {
|
||||
host.main_document_task_owner_is_current(owner)
|
||||
&& host.browsing_context_is_closed() == was_closed
|
||||
};
|
||||
let descendants = self.child_document_descendants_unload_snapshot(self.document_handle());
|
||||
// Keep the navigation target protected while checking its descendants:
|
||||
// a descendant's beforeunload must not erase the target with open/write.
|
||||
@@ -42,11 +62,14 @@ impl JsContextHost {
|
||||
let previous =
|
||||
crate::context_bootstrap::replace_navigation_unload_event_active(scope, window, true);
|
||||
crate::context_bootstrap::dispatch_beforeunload_for_runtime_owner(scope, window);
|
||||
self.dispatch_child_documents_beforeunload(scope, descendants, None, |host| {
|
||||
host.main_document_task_owner_is_current(owner)
|
||||
});
|
||||
self.dispatch_child_documents_beforeunload(scope, descendants, None, is_current);
|
||||
crate::context_bootstrap::replace_navigation_unload_event_active(scope, window, previous);
|
||||
self.main_document_task_owner_is_current(owner)
|
||||
is_current(self)
|
||||
}
|
||||
|
||||
pub(crate) fn main_document_unload_has_started(&self) -> bool {
|
||||
self.frame_owner_store
|
||||
.current_main_document_unload_has_started()
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_main_document_unload_for_navigation_commit(
|
||||
|
||||
@@ -1460,7 +1460,7 @@ impl JsContextHost {
|
||||
self.shutdown_workers();
|
||||
self.output_journal = None;
|
||||
self.root_document_lifecycle = None;
|
||||
self.child_default_context_bootstrap = None;
|
||||
self.window_default_context_bootstrap = None;
|
||||
self.command_turn_output = None;
|
||||
self.lightweight_popup_browsing_contexts.clear();
|
||||
self.internal_inspector_value_references.clear();
|
||||
|
||||
@@ -487,6 +487,24 @@ impl JsContextHost {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cancel_pending_location_navigation(&mut self) {
|
||||
self.clear_pending_location_navigation();
|
||||
// A request already handed to the browser no longer occupies the
|
||||
// renderer's pending slot. Cancel through the same ordered owner
|
||||
// channel, retaining the Document that authorized this action.
|
||||
if let Some(source_document) = self
|
||||
.root_document_lifecycle
|
||||
.as_ref()
|
||||
.map(RendererDocumentLifecycleJournalHandle::identity)
|
||||
{
|
||||
self.append_live_turn_owner_action(
|
||||
crate::runtime::RendererOwnerAction::StopTopLevelLocationNavigation {
|
||||
source_document,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn record_pending_top_level_history_traversal(&mut self, delta: i64) {
|
||||
self.clear_pending_top_level_navigation();
|
||||
let traversal = RendererPendingTopLevelHistoryTraversal { delta };
|
||||
|
||||
@@ -247,6 +247,10 @@ impl PendingLightweightPopupHistory {
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
final_url: &Url,
|
||||
) {
|
||||
let Some(context) = window.get_creation_context(scope) else {
|
||||
return;
|
||||
};
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let mut seed = match self.entry_seed() {
|
||||
Some(seed) => seed.clone(),
|
||||
None => {
|
||||
@@ -525,7 +529,7 @@ enum LightweightPopupLifecycle {
|
||||
}
|
||||
|
||||
pub(super) struct LightweightPopupBrowsingContextRecord {
|
||||
name: crate::runtime::RendererBrowsingContextName,
|
||||
pub(in crate::native_bridge::context_host) name: crate::runtime::RendererBrowsingContextName,
|
||||
auxiliary_window: crate::runtime::RendererAuxiliaryWindow,
|
||||
name_lookup_allowed: bool,
|
||||
window_proxy: v8::Global<v8::Object>,
|
||||
@@ -546,7 +550,7 @@ impl LightweightPopupBrowsingContextRecord {
|
||||
matches!(self.lifecycle, LightweightPopupLifecycle::Open(_))
|
||||
}
|
||||
|
||||
fn is_open(&self) -> bool {
|
||||
pub(in crate::native_bridge::context_host) fn is_open(&self) -> bool {
|
||||
self.is_live() && !self.is_closing && !self.auxiliary_window.is_closed()
|
||||
}
|
||||
}
|
||||
@@ -666,7 +670,7 @@ impl LightweightPopupStorageScope {
|
||||
}
|
||||
|
||||
impl JsContextHost {
|
||||
fn lightweight_popup_record(
|
||||
pub(in crate::native_bridge::context_host) fn lightweight_popup_record(
|
||||
&self,
|
||||
popup_id: u64,
|
||||
) -> Option<&LightweightPopupBrowsingContextRecord> {
|
||||
@@ -796,7 +800,11 @@ impl JsContextHost {
|
||||
let Some(transition) = self.take_lightweight_popup_close_transition(popup_id) else {
|
||||
return false;
|
||||
};
|
||||
let window_handle = self.lightweight_popup_record(popup_id).expect("closed popup record retained").auxiliary_window.clone();
|
||||
let window_handle = self
|
||||
.lightweight_popup_record(popup_id)
|
||||
.expect("closed popup record retained")
|
||||
.auxiliary_window
|
||||
.clone();
|
||||
self.request_auxiliary_window_close(window_handle);
|
||||
self.popup_nested_histories
|
||||
.remove(&OwnerDispatchScope::LightweightPopup(popup_id));
|
||||
@@ -902,9 +910,12 @@ impl JsContextHost {
|
||||
|
||||
pub(crate) fn named_lightweight_popup_id(&self, target_name: &str) -> Option<u64> {
|
||||
let name = trackable_lightweight_popup_window_name(target_name)?;
|
||||
self.lightweight_popup_browsing_contexts.iter().find_map(|(id, record)| {
|
||||
(record.is_open() && record.name_lookup_allowed && record.name.get() == name).then_some(*id)
|
||||
})
|
||||
self.lightweight_popup_browsing_contexts
|
||||
.iter()
|
||||
.find_map(|(id, record)| {
|
||||
(record.is_open() && record.name_lookup_allowed && record.name.get() == name)
|
||||
.then_some(*id)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn open_lightweight_popup_window<'s>(
|
||||
@@ -959,7 +970,7 @@ impl JsContextHost {
|
||||
})
|
||||
}
|
||||
|
||||
fn popup_sandbox_policy_for_source(
|
||||
pub(in crate::native_bridge::context_host) fn popup_sandbox_policy_for_source(
|
||||
&self,
|
||||
source_scope: OwnerDispatchScope,
|
||||
) -> Option<DocumentSandboxPolicy> {
|
||||
@@ -1012,41 +1023,42 @@ impl JsContextHost {
|
||||
return None;
|
||||
}
|
||||
let creator_is_secure_context = self.popup_creator_is_secure_context(scope, creator_scope);
|
||||
let window = self.lightweight_popup_window(scope, popup_id)?;
|
||||
if update_existing_opener && let Some(opener) = opener {
|
||||
set_private_value(scope, window, LIGHTWEIGHT_POPUP_OPENER_SLOT, opener.into());
|
||||
let endpoint = lightweight_popup_initiator_endpoint(scope, Some(opener), opener_child_handle);
|
||||
if let Some(record) = self.lightweight_popup_record_mut(popup_id) {
|
||||
record.opener = endpoint;
|
||||
record.opener_window = Some(v8::Global::new(scope, opener));
|
||||
}
|
||||
let window = self.lightweight_popup_window(scope, popup_id)?;
|
||||
if update_existing_opener && let Some(opener) = opener {
|
||||
set_private_value(scope, window, LIGHTWEIGHT_POPUP_OPENER_SLOT, opener.into());
|
||||
let endpoint =
|
||||
lightweight_popup_initiator_endpoint(scope, Some(opener), opener_child_handle);
|
||||
if let Some(record) = self.lightweight_popup_record_mut(popup_id) {
|
||||
record.opener = endpoint;
|
||||
record.opener_window = Some(v8::Global::new(scope, opener));
|
||||
}
|
||||
let Some(href) = href else {
|
||||
return Some(OpenedLightweightPopup {
|
||||
window,
|
||||
popup_id,
|
||||
created_new_browsing_context: false,
|
||||
document_response: None,
|
||||
});
|
||||
};
|
||||
let (document_response, receiver) = self.auxiliary_document_response_channel(href);
|
||||
let window = self.reopen_lightweight_popup_window(
|
||||
scope,
|
||||
}
|
||||
let Some(href) = href else {
|
||||
return Some(OpenedLightweightPopup {
|
||||
window,
|
||||
popup_id,
|
||||
opener,
|
||||
opener_child_handle,
|
||||
href,
|
||||
creator_base_url.clone(),
|
||||
creator_policy_container.clone(),
|
||||
creator_is_secure_context,
|
||||
receiver,
|
||||
)?;
|
||||
Some(OpenedLightweightPopup {
|
||||
window,
|
||||
popup_id,
|
||||
created_new_browsing_context: false,
|
||||
document_response,
|
||||
})
|
||||
created_new_browsing_context: false,
|
||||
document_response: None,
|
||||
});
|
||||
};
|
||||
let (document_response, receiver) = self.auxiliary_document_response_channel(href);
|
||||
let window = self.reopen_lightweight_popup_window(
|
||||
scope,
|
||||
popup_id,
|
||||
opener,
|
||||
opener_child_handle,
|
||||
href,
|
||||
creator_base_url.clone(),
|
||||
creator_policy_container.clone(),
|
||||
creator_is_secure_context,
|
||||
receiver,
|
||||
)?;
|
||||
Some(OpenedLightweightPopup {
|
||||
window,
|
||||
popup_id,
|
||||
created_new_browsing_context: false,
|
||||
document_response,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn create_lightweight_popup_window<'s>(
|
||||
@@ -2310,7 +2322,10 @@ impl JsContextHost {
|
||||
&self,
|
||||
popup_id: u64,
|
||||
) -> Option<LightweightPopupDocumentOwner> {
|
||||
if !self.lightweight_popup_is_open(popup_id) {
|
||||
if !self
|
||||
.lightweight_popup_record(popup_id)
|
||||
.is_some_and(|record| record.is_live())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
self.lightweight_popup_document_record(popup_id)
|
||||
@@ -5928,60 +5943,6 @@ fn lightweight_popup_window_name_setter<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
fn lightweight_popup_opener_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let open = lightweight_popup_id_from_window(scope, args.this())
|
||||
.and_then(|id| {
|
||||
context_host_ptr_from_global_bridge(scope)
|
||||
.map(|host| unsafe { &*host }.lightweight_popup_is_open(id))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if open
|
||||
&& let Some(opener) = get_private_value(scope, args.this(), LIGHTWEIGHT_POPUP_OPENER_SLOT)
|
||||
{
|
||||
rv.set(opener);
|
||||
return;
|
||||
}
|
||||
rv.set_null();
|
||||
}
|
||||
|
||||
fn lightweight_popup_opener_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
set_private_value(
|
||||
scope,
|
||||
args.this(),
|
||||
LIGHTWEIGHT_POPUP_OPENER_SLOT,
|
||||
args.get(0),
|
||||
);
|
||||
if args.get(0).is_null()
|
||||
&& let Some(popup_id) = lightweight_popup_id_from_window(scope, args.this())
|
||||
&& let Some(host) = context_host_ptr_from_global_bridge(scope)
|
||||
&& let Some(record) = unsafe { &mut *host }.lightweight_popup_record_mut(popup_id)
|
||||
{
|
||||
record.opener = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn lightweight_popup_closed_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let closed = lightweight_popup_id_from_window(scope, args.this())
|
||||
.and_then(|id| {
|
||||
context_host_ptr_from_global_bridge(scope)
|
||||
.and_then(|host| unsafe { &*host }.lightweight_popup_record(id))
|
||||
})
|
||||
.is_none_or(|record| !record.is_open());
|
||||
rv.set(v8::Boolean::new(scope, closed).into());
|
||||
}
|
||||
|
||||
fn lightweight_popup_window_opener_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
|
||||
@@ -98,7 +98,7 @@ impl JsContextHost {
|
||||
}
|
||||
self.retire_window_execution_contexts_for_context_token(
|
||||
retired.realm_token,
|
||||
config.resource_owner_id,
|
||||
Some(config.resource_owner_id),
|
||||
);
|
||||
context.detach_global();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,30 @@ pub(crate) struct OpenedRendererWindow<'s> {
|
||||
Option<crate::runtime::RendererCapturedDocumentEnvironment>,
|
||||
}
|
||||
|
||||
impl<'s> OpenedRendererWindow<'s> {
|
||||
pub(crate) fn prepare_navigation_activation(&self, scope: &mut v8::PinScope<'s, '_>) -> bool {
|
||||
if self
|
||||
.window
|
||||
.as_ref()
|
||||
.is_some_and(|window| window.is_closed())
|
||||
|| crate::context_bootstrap::navigation_unload_event_active(scope, self.window_proxy)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if self.pending_page.is_some() {
|
||||
return true;
|
||||
}
|
||||
let Some(context) = self.window_proxy.get_creation_context(scope) else {
|
||||
return false;
|
||||
};
|
||||
let Some(host_ptr) = crate::util::context_host_ptr_from_context_slot(context) else {
|
||||
return false;
|
||||
};
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
unsafe { &mut *host_ptr }.dispatch_main_document_tree_beforeunload(scope)
|
||||
}
|
||||
}
|
||||
|
||||
impl JsContextHost {
|
||||
pub(crate) fn has_browser_owned_auxiliary_page_factory(&self) -> bool {
|
||||
self.browser_context_runtime
|
||||
@@ -165,13 +189,29 @@ impl JsContextHost {
|
||||
} else {
|
||||
about_blank_url()
|
||||
};
|
||||
policy.document_referrer = Url::parse(&policy.document_referrer)
|
||||
.ok()
|
||||
.and_then(|source| {
|
||||
moli_fetch::referrer_value(
|
||||
&source,
|
||||
if exposes_opener {
|
||||
&source
|
||||
} else {
|
||||
&initial_url
|
||||
},
|
||||
None,
|
||||
policy.referrer_policy.as_deref(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let base_url = if exposes_opener {
|
||||
lightweight_popup_initial_base_url(&initial_url, creator_base_url)
|
||||
} else {
|
||||
initial_url.clone()
|
||||
};
|
||||
let sandbox = creator_child_handle
|
||||
.and_then(|handle| self.child_browsing_context_popup_opener_sandbox_policy(handle));
|
||||
let sandbox = creator_child_handle.and_then(|handle| {
|
||||
self.popup_sandbox_policy_for_source(OwnerDispatchScope::Child(handle))
|
||||
});
|
||||
if policy.sandbox.allows_popups_to_escape {
|
||||
policy.sandbox = DocumentSandboxPolicy::default();
|
||||
}
|
||||
@@ -202,6 +242,19 @@ impl JsContextHost {
|
||||
let loader = source_loader
|
||||
.request_client()
|
||||
.fork_with_isolated_page_network_policy();
|
||||
let about_document_state = self
|
||||
.capture_about_document_state(
|
||||
creator_child_handle.map_or(OwnerDispatchScope::Top, OwnerDispatchScope::Child),
|
||||
&initial_url,
|
||||
None,
|
||||
)
|
||||
.map(|state| {
|
||||
crate::runtime::RendererAboutDocumentState::new(
|
||||
state.origin().clone(),
|
||||
base_url.clone(),
|
||||
policy.clone(),
|
||||
)
|
||||
});
|
||||
let env = crate::runtime::PageVmEnvConfig {
|
||||
root_frame_id: None,
|
||||
main_document_commit: None,
|
||||
@@ -235,6 +288,8 @@ impl JsContextHost {
|
||||
layout_configuration: Default::default(),
|
||||
wpt_extensions_enabled: false,
|
||||
navigation_bootstrap_entry: None,
|
||||
navigation_history_source: None,
|
||||
about_document_state,
|
||||
reserved_service_worker_client_id: None,
|
||||
};
|
||||
let mut dom_host = crate::dom::native::DomHost::from_dom(
|
||||
|
||||
@@ -162,14 +162,14 @@ impl JsContextHost {
|
||||
.collect::<Vec<_>>();
|
||||
realm_tokens.sort_unstable();
|
||||
realm_tokens.dedup();
|
||||
let resource_owner_id = self
|
||||
.window_default_context_bootstrap
|
||||
.as_ref()
|
||||
.map(|config| config.resource_owner_id);
|
||||
|
||||
for realm_token in realm_tokens {
|
||||
self.retire_window_realm_resources(realm_token);
|
||||
let resource_owner_id = self
|
||||
.child_default_context_bootstrap
|
||||
.as_ref()
|
||||
.map(|config| config.resource_owner_id);
|
||||
self.retire_window_execution_context_registry(realm_token, resource_owner_id);
|
||||
self.retire_window_execution_contexts_for_context_token(realm_token, resource_owner_id);
|
||||
}
|
||||
|
||||
for owner in owners {
|
||||
@@ -490,14 +490,6 @@ impl JsContextHost {
|
||||
}
|
||||
|
||||
pub(crate) fn retire_window_execution_contexts_for_context_token(
|
||||
&mut self,
|
||||
context_token: RuntimeObservableContextToken,
|
||||
resource_owner_id: crate::resource_owner::ResourceOwnerId,
|
||||
) -> usize {
|
||||
self.retire_window_execution_context_registry(context_token, Some(resource_owner_id))
|
||||
}
|
||||
|
||||
fn retire_window_execution_context_registry(
|
||||
&mut self,
|
||||
context_token: RuntimeObservableContextToken,
|
||||
resource_owner_id: Option<crate::resource_owner::ResourceOwnerId>,
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ impl WindowEnvironmentSettings {
|
||||
scope.get_current_context().get_slot::<Self>()
|
||||
}
|
||||
|
||||
pub(crate) fn bind_current_main_document_for_retirement(
|
||||
pub(crate) fn bind_current_main_document(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
host: &JsContextHost,
|
||||
) -> Option<()> {
|
||||
|
||||
@@ -21,8 +21,7 @@ pub(crate) fn window_contexts_allow_access(
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let Some(accessed_host_ptr) =
|
||||
crate::util::context_host_ptr_from_context_slot(accessed_context)
|
||||
let Some(accessed_host_ptr) = crate::util::context_host_ptr_from_context_slot(accessed_context)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
@@ -236,7 +235,6 @@ impl JsContextHost {
|
||||
return Some(owner.origin);
|
||||
}
|
||||
WindowSecurityOrigin::for_context(context)
|
||||
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn document_has_same_origin_as_entry<'s>(
|
||||
@@ -285,7 +283,6 @@ impl JsContextHost {
|
||||
origin
|
||||
.zip(entry_origin)
|
||||
.is_some_and(|(origin, entry)| origin.has_same_origin(&entry))
|
||||
|
||||
}
|
||||
|
||||
pub(crate) fn install_window_context_security_origin(
|
||||
@@ -821,7 +818,9 @@ impl JsContextHost {
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn main_window_access_origin(&self) -> Option<WindowAccessOrigin> {
|
||||
pub(in crate::native_bridge::context_host) fn main_window_access_origin(
|
||||
&self,
|
||||
) -> Option<WindowAccessOrigin> {
|
||||
if !self
|
||||
.document_policy_container()
|
||||
.sandbox
|
||||
|
||||
@@ -265,12 +265,8 @@ fn navigate_element_popup_target(
|
||||
.or_else(|| creator.policy_container.referrer_policy.clone())
|
||||
},
|
||||
);
|
||||
creator.policy_container.document_referrer = if relations.suppress_opener {
|
||||
navigation_initiator
|
||||
.document_referrer(&url::Url::parse(resolved_url).expect("resolved hyperlink URL"))
|
||||
} else {
|
||||
navigation_initiator.outgoing_referrer()
|
||||
};
|
||||
creator.policy_container.document_referrer = navigation_initiator
|
||||
.document_referrer(&url::Url::parse(resolved_url).expect("resolved hyperlink URL"));
|
||||
let opener = (!relations.suppress_opener).then_some(creator.opener);
|
||||
let source_child_handle = match dispatch_scope {
|
||||
crate::native_bridge::OwnerDispatchScope::Child(handle) => Some(handle),
|
||||
@@ -306,6 +302,9 @@ fn navigate_element_popup_target(
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if !opened.prepare_navigation_activation(scope) {
|
||||
return true;
|
||||
}
|
||||
let disposition = if opened.pending_page.is_some()
|
||||
|| runtime.protocol_user_gesture_activation()
|
||||
|| runtime.current_input_event().is_some()
|
||||
@@ -455,6 +454,14 @@ pub(in crate::native_bridge) fn choose_form_navigation_target(
|
||||
return None;
|
||||
}
|
||||
let relations = element_popup_relations(unsafe { &*runtime_ptr }, form, target_name);
|
||||
if let Some(ExistingHyperlinkTarget::RelatedPage(window)) =
|
||||
existing_hyperlink_target(scope, runtime_ptr, source, form, Some(target_name))
|
||||
&& crate::context_bootstrap::navigation_unload_event_active(scope, window)
|
||||
{
|
||||
// A related Page being unloaded is still the named target. Do not
|
||||
// fall through to creating a second legacy popup with the same name.
|
||||
return None;
|
||||
}
|
||||
let mut creator = element_popup_creator(scope, runtime_ptr, form)?;
|
||||
creator.policy_container.document_referrer = if relations.suppress_referrer {
|
||||
String::new()
|
||||
@@ -486,6 +493,7 @@ pub(in crate::native_bridge) fn choose_form_navigation_target(
|
||||
None,
|
||||
creator.base_url,
|
||||
creator.policy_container,
|
||||
false,
|
||||
),
|
||||
_ => runtime.open_lightweight_popup_window(
|
||||
scope,
|
||||
@@ -496,6 +504,7 @@ pub(in crate::native_bridge) fn choose_form_navigation_target(
|
||||
None,
|
||||
creator.base_url,
|
||||
creator.policy_container,
|
||||
false,
|
||||
),
|
||||
}?;
|
||||
if !opened.allows_navigation_activation(scope, runtime) {
|
||||
@@ -641,19 +650,26 @@ pub(crate) fn navigate_existing_browsing_context_target<'s>(
|
||||
if navigated { Some(target_window) } else { None }
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum ExistingHyperlinkTarget<'s> {
|
||||
Local(crate::native_bridge::OwnerDispatchScope),
|
||||
RelatedPage(v8::Local<'s, v8::Object>),
|
||||
}
|
||||
|
||||
/// Resolve an existing target without reading script-visible WindowProxy properties.
|
||||
fn existing_hyperlink_target(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
fn existing_hyperlink_target<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
runtime_ptr: *mut JsContextHost,
|
||||
source: crate::native_bridge::OwnerDispatchScope,
|
||||
source_handle: DomHandle,
|
||||
target_name: Option<&str>,
|
||||
) -> Option<crate::native_bridge::OwnerDispatchScope> {
|
||||
) -> Option<ExistingHyperlinkTarget<'s>> {
|
||||
use crate::native_bridge::OwnerDispatchScope;
|
||||
let runtime = unsafe { &*runtime_ptr };
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
let Some(name) = target_name else {
|
||||
return Some(source);
|
||||
return Some(ExistingHyperlinkTarget::Local(source));
|
||||
};
|
||||
match SpecialBrowsingContextTarget::parse(name) {
|
||||
let local = match SpecialBrowsingContextTarget::parse(name) {
|
||||
Some(SpecialBrowsingContextTarget::Current) => Some(source),
|
||||
Some(
|
||||
special @ (SpecialBrowsingContextTarget::Top | SpecialBrowsingContextTarget::Parent),
|
||||
@@ -668,9 +684,23 @@ fn existing_hyperlink_target(
|
||||
Some(target)
|
||||
}
|
||||
Some(SpecialBrowsingContextTarget::Blank) => None,
|
||||
None => unsafe { &mut *runtime_ptr }
|
||||
.browsing_context_target_by_name_for_navigation(scope, name, source),
|
||||
None => runtime.browsing_context_target_by_name_for_navigation(scope, name, source),
|
||||
};
|
||||
if let Some(local) = local {
|
||||
return Some(ExistingHyperlinkTarget::Local(local));
|
||||
}
|
||||
if SpecialBrowsingContextTarget::parse(name).is_some()
|
||||
|| element_popup_relations(runtime, source_handle, name).suppress_opener
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let related = runtime
|
||||
.page_script_environment()?
|
||||
.named_related_window(name, runtime.browsing_context_name())?;
|
||||
related
|
||||
.window_proxy_in_scope(scope)
|
||||
.ok()
|
||||
.map(ExistingHyperlinkTarget::RelatedPage)
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn navigate_element_target_browsing_context(
|
||||
@@ -689,7 +719,9 @@ pub(in crate::native_bridge) fn navigate_element_target_browsing_context(
|
||||
let Some(source) = unsafe { &*runtime_ptr }.owner_dispatch_scope_for_node(source_handle) else {
|
||||
return false;
|
||||
};
|
||||
let Some(target) = existing_hyperlink_target(scope, runtime_ptr, source, target_name) else {
|
||||
let Some(target) =
|
||||
existing_hyperlink_target(scope, runtime_ptr, source, source_handle, target_name)
|
||||
else {
|
||||
return target_name.is_some_and(|name| {
|
||||
navigate_element_popup_target(
|
||||
scope,
|
||||
@@ -702,8 +734,10 @@ pub(in crate::native_bridge) fn navigate_element_target_browsing_context(
|
||||
});
|
||||
};
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
if url::Url::parse(resolved_url)
|
||||
.is_ok_and(|destination| runtime.blocks_ancestor_navigation(source, target, &destination))
|
||||
if let ExistingHyperlinkTarget::Local(target) = &target
|
||||
&& url::Url::parse(resolved_url).is_ok_and(|destination| {
|
||||
runtime.blocks_ancestor_navigation(source, *target, &destination)
|
||||
})
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -711,29 +745,77 @@ pub(in crate::native_bridge) fn navigate_element_target_browsing_context(
|
||||
return false;
|
||||
};
|
||||
let initiator_url = runtime.document_url_for_handle(document);
|
||||
let Some(source_element) = crate::util::node_wrapper_from_handle(scope, source_handle) else {
|
||||
let initiator = crate::runtime::RendererNavigationInitiator::new(
|
||||
runtime
|
||||
.document_referrer_source_url_for_dispatch_scope(source)
|
||||
.unwrap_or(initiator_url),
|
||||
runtime
|
||||
.document_resource_loader_for_dispatch_scope(source)
|
||||
.map(|loader| loader.fetch_context().request_origin())
|
||||
.unwrap_or(moli_url::WebOrigin::Opaque),
|
||||
if element_popup_relations(runtime, source_handle, target_name.unwrap_or("_self"))
|
||||
.suppress_referrer
|
||||
{
|
||||
Some("no-referrer".to_owned())
|
||||
} else {
|
||||
element_popup_referrer_policy(runtime, source_handle)
|
||||
.map(str::to_owned)
|
||||
.or_else(|| {
|
||||
runtime
|
||||
.document_policy_container_for_inheritance(source)
|
||||
.and_then(|policy| policy.referrer_policy)
|
||||
})
|
||||
},
|
||||
);
|
||||
let Some(source_element) =
|
||||
runtime
|
||||
.native_bridge_mut()
|
||||
.wrap_handle(scope, runtime_ptr, source_handle)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
// Resolve the native target realm before reading its private Location slot.
|
||||
// The source's access check controls event dispatch and sourceElement exposure.
|
||||
let context = match target {
|
||||
OwnerDispatchScope::Child(handle) => runtime
|
||||
.ensure_prebootstrapped_child_default_context(scope, handle)
|
||||
let context = match &target {
|
||||
ExistingHyperlinkTarget::Local(OwnerDispatchScope::Child(handle)) => runtime
|
||||
.ensure_prebootstrapped_child_default_context(scope, *handle)
|
||||
.ok(),
|
||||
_ => runtime
|
||||
.current_registered_window_execution_context_identity(target)
|
||||
.and_then(|identity| runtime.window_execution_context(scope, identity.owner(), target))
|
||||
ExistingHyperlinkTarget::Local(OwnerDispatchScope::LightweightPopup(id)) => runtime
|
||||
.lightweight_popup_window(scope, *id)
|
||||
.and_then(|window| window.get_creation_context(scope)),
|
||||
ExistingHyperlinkTarget::Local(target) => runtime
|
||||
.current_registered_window_execution_context_identity(*target)
|
||||
.and_then(|identity| runtime.window_execution_context(scope, identity.owner(), *target))
|
||||
.map(|(_, context)| context),
|
||||
ExistingHyperlinkTarget::RelatedPage(window) => window.get_creation_context(scope),
|
||||
};
|
||||
let Some(context) = context else { return false };
|
||||
let native_popup_context = matches!(target, ExistingHyperlinkTarget::RelatedPage(_))
|
||||
|| matches!(target, ExistingHyperlinkTarget::Local(OwnerDispatchScope::LightweightPopup(id))
|
||||
if runtime.lightweight_popup_window(scope, id)
|
||||
.is_some_and(|window| window.strict_equals(context.global(scope).into())));
|
||||
let can_access = runtime
|
||||
.current_registered_window_execution_context_identity(source)
|
||||
.is_some_and(|identity| {
|
||||
runtime.window_execution_context_can_access_dispatch_scope(identity, target)
|
||||
if native_popup_context {
|
||||
runtime
|
||||
.window_execution_context(scope, identity.owner(), source)
|
||||
.is_some_and(|(_, source_context)| {
|
||||
crate::native_bridge::window_contexts_allow_access(source_context, context)
|
||||
})
|
||||
} else {
|
||||
let ExistingHyperlinkTarget::Local(target) = target else {
|
||||
unreachable!("related Pages use native context access checks")
|
||||
};
|
||||
runtime.window_execution_context_can_access_dispatch_scope(identity, target)
|
||||
}
|
||||
});
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let window = match target {
|
||||
OwnerDispatchScope::LightweightPopup(id) => runtime.lightweight_popup_window(scope, id),
|
||||
let window = match &target {
|
||||
ExistingHyperlinkTarget::Local(OwnerDispatchScope::LightweightPopup(id)) => {
|
||||
runtime.lightweight_popup_window(scope, *id)
|
||||
}
|
||||
ExistingHyperlinkTarget::RelatedPage(window) => Some(*window),
|
||||
_ => Some(context.global(scope)),
|
||||
};
|
||||
let Some(location) = window
|
||||
@@ -749,11 +831,13 @@ pub(in crate::native_bridge) fn navigate_element_target_browsing_context(
|
||||
crate::context_bootstrap::HyperlinkNavigationOptions {
|
||||
user_initiated,
|
||||
source_can_access_target: can_access,
|
||||
initiator_url,
|
||||
initiator,
|
||||
named_popup: target_name
|
||||
.filter(|name| {
|
||||
matches!(target, OwnerDispatchScope::LightweightPopup(_))
|
||||
&& SpecialBrowsingContextTarget::parse(name).is_none()
|
||||
matches!(
|
||||
target,
|
||||
ExistingHyperlinkTarget::Local(OwnerDispatchScope::LightweightPopup(_))
|
||||
) && SpecialBrowsingContextTarget::parse(name).is_none()
|
||||
})
|
||||
.map(|name| NamedHyperlinkPopup {
|
||||
source_handle,
|
||||
|
||||
@@ -449,6 +449,10 @@ pub(crate) fn contexts_share_wrapper_world(
|
||||
right: v8::Local<'_, v8::Context>,
|
||||
) -> bool {
|
||||
left == right
|
||||
// Related native Pages share the isolate's default wrapper world,
|
||||
// even though each native DOM host owns a separate reflector cache.
|
||||
|| (left.get_slot::<SharedDefaultWorldWrapperCache>().is_some()
|
||||
&& right.get_slot::<SharedDefaultWorldWrapperCache>().is_some())
|
||||
|| match (
|
||||
left.get_slot::<RefCell<BridgeContextWrapperCache>>(),
|
||||
right.get_slot::<RefCell<BridgeContextWrapperCache>>(),
|
||||
@@ -587,16 +591,40 @@ pub(crate) fn clear_context_wrapper_cache_for_teardown(
|
||||
.map(|token| token.as_u64());
|
||||
if let Some(cache) = context.get_slot::<RefCell<BridgeContextWrapperCache>>() {
|
||||
let mut entries = cache.borrow_mut();
|
||||
for entry in entries.wrappers.values_mut() {
|
||||
if all_entries || entry.creation_realm.map(NonZeroU64::get) == realm {
|
||||
entry.wrapper.retain_in_realm(scope);
|
||||
entries
|
||||
.retired_wrappers
|
||||
.retain(|_, wrapper| !wrapper.is_empty());
|
||||
entries
|
||||
.retired_live_collection_wrappers
|
||||
.retain(|_, wrapper| !wrapper.is_empty());
|
||||
// A retained realm must preserve wrappers that JS still reaches without
|
||||
// making every detached node reachable through the realm itself.
|
||||
let mut retired = Vec::new();
|
||||
entries.wrappers.retain(|id, entry| {
|
||||
if !all_entries && entry.creation_realm.map(NonZeroU64::get) != realm {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for entry in entries.live_collection_wrappers.values_mut() {
|
||||
if all_entries || entry.creation_realm.map(NonZeroU64::get) == realm {
|
||||
entry.wrapper.retain_in_realm(scope);
|
||||
if let Some(wrapper) = entry.wrapper.to_local(scope) {
|
||||
retired.push((id, v8::Weak::new(scope, wrapper)));
|
||||
}
|
||||
}
|
||||
false
|
||||
});
|
||||
entries.retired_wrappers.extend(retired);
|
||||
let mut retired_collections = Vec::new();
|
||||
entries
|
||||
.live_collection_wrappers
|
||||
.retain(|descriptor, entry| {
|
||||
if !all_entries && entry.creation_realm.map(NonZeroU64::get) != realm {
|
||||
return true;
|
||||
}
|
||||
if let Some(wrapper) = entry.wrapper.to_local(scope) {
|
||||
retired_collections.push((descriptor.clone(), v8::Weak::new(scope, wrapper)));
|
||||
}
|
||||
false
|
||||
});
|
||||
entries
|
||||
.retired_live_collection_wrappers
|
||||
.extend(retired_collections);
|
||||
drop(entries);
|
||||
crate::util::retain_context_v8_handle_state_for_safe_release(context, cache);
|
||||
}
|
||||
@@ -620,6 +648,24 @@ pub(super) struct BridgeIdentityStore {
|
||||
}
|
||||
|
||||
impl BridgeIdentityStore {
|
||||
fn current_wrapper_cache(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
) -> Rc<RefCell<BridgeContextWrapperCache>> {
|
||||
if scope
|
||||
.get_current_context()
|
||||
.get_slot::<SharedDefaultWorldWrapperCache>()
|
||||
.is_some()
|
||||
{
|
||||
// Reflector IDs are local to this native host. A cross-Page call
|
||||
// in the same default world must use this store's canonical cache,
|
||||
// not the calling Page's cache with potentially colliding IDs.
|
||||
self.default_world_wrapper_cache.clone()
|
||||
} else {
|
||||
context_wrapper_cache(scope)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn install_default_world_wrapper_cache(&self, context: v8::Local<'_, v8::Context>) {
|
||||
let _ = context.set_slot(self.default_world_wrapper_cache.clone());
|
||||
let _ = context.set_slot(Rc::new(SharedDefaultWorldWrapperCache));
|
||||
@@ -655,13 +701,13 @@ impl BridgeIdentityStore {
|
||||
if matches!(self.bridge_handle(reflector_id), Some(BridgeHandle::Window)) {
|
||||
return context_window_wrapper(scope);
|
||||
}
|
||||
context_wrapper_cache(scope)
|
||||
self.current_wrapper_cache(scope)
|
||||
.borrow()
|
||||
.wrappers
|
||||
.get(&reflector_id)
|
||||
.and_then(|entry| entry.wrapper.to_local(scope))
|
||||
.or_else(|| {
|
||||
context_wrapper_cache(scope)
|
||||
self.current_wrapper_cache(scope)
|
||||
.borrow()
|
||||
.retired_wrappers
|
||||
.get(&reflector_id)
|
||||
@@ -679,7 +725,7 @@ impl BridgeIdentityStore {
|
||||
set_context_window_wrapper(scope, wrapper);
|
||||
return;
|
||||
}
|
||||
let cache = context_wrapper_cache(scope);
|
||||
let cache = self.current_wrapper_cache(scope);
|
||||
let mut cache = cache.borrow_mut();
|
||||
if scope
|
||||
.get_current_context()
|
||||
@@ -712,13 +758,13 @@ impl BridgeIdentityStore {
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
descriptor: &LiveCollectionDescriptor,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
context_wrapper_cache(scope)
|
||||
self.current_wrapper_cache(scope)
|
||||
.borrow()
|
||||
.live_collection_wrappers
|
||||
.get(descriptor)
|
||||
.and_then(|entry| entry.wrapper.to_local(scope))
|
||||
.or_else(|| {
|
||||
context_wrapper_cache(scope)
|
||||
self.current_wrapper_cache(scope)
|
||||
.borrow()
|
||||
.retired_live_collection_wrappers
|
||||
.get(descriptor)
|
||||
@@ -732,7 +778,7 @@ impl BridgeIdentityStore {
|
||||
descriptor: LiveCollectionDescriptor,
|
||||
wrapper: v8::Local<'_, v8::Object>,
|
||||
) {
|
||||
let cache = context_wrapper_cache(scope);
|
||||
let cache = self.current_wrapper_cache(scope);
|
||||
let mut cache = cache.borrow_mut();
|
||||
if scope
|
||||
.get_current_context()
|
||||
|
||||
@@ -113,13 +113,6 @@ impl<V> DenseReflectorMap<V> {
|
||||
pub(super) fn values(&self) -> impl Iterator<Item = &V> {
|
||||
self.dense.iter().flatten().chain(self.sparse.values())
|
||||
}
|
||||
|
||||
pub(super) fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
|
||||
self.dense
|
||||
.iter_mut()
|
||||
.flatten()
|
||||
.chain(self.sparse.values_mut())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -24,12 +24,18 @@ impl RendererCreateStreamingRawPageRequest {
|
||||
local_executor: JsLocalExecutor,
|
||||
runtime_hooks: PageVmRuntimeHooks,
|
||||
) -> Result<StreamingNavigationPageCreationResult> {
|
||||
let navigation_bootstrap_entry = self.navigation_history
|
||||
let navigation_bootstrap_entry = self
|
||||
.navigation_history
|
||||
.as_ref()
|
||||
.map(|history| history.resolve(&self.final_url))
|
||||
.transpose()?;
|
||||
self.bootstrap_with_navigation_seed(page_id, local_executor, runtime_hooks, navigation_bootstrap_entry)
|
||||
.await
|
||||
self.bootstrap_with_navigation_seed(
|
||||
page_id,
|
||||
local_executor,
|
||||
runtime_hooks,
|
||||
navigation_bootstrap_entry,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(in crate::runtime) async fn bootstrap_with_navigation_seed(
|
||||
@@ -85,9 +91,12 @@ impl RendererCreateStreamingRawPageRequest {
|
||||
let document_policy_container = about_document_state
|
||||
.as_ref()
|
||||
.map(|state| state.policy_container().clone())
|
||||
.unwrap_or_else(|| DocumentPolicyContainer::from_navigation_response_headers(
|
||||
&response_headers, &final_url,
|
||||
))
|
||||
.unwrap_or_else(|| {
|
||||
DocumentPolicyContainer::from_navigation_response_headers(
|
||||
&response_headers,
|
||||
&final_url,
|
||||
)
|
||||
})
|
||||
.with_content_security_policy_bypass(document_settings.bypass_content_security_policy);
|
||||
let document_default_language =
|
||||
crate::document_language::document_default_language_from_headers(&response_headers);
|
||||
@@ -119,7 +128,9 @@ impl RendererCreateStreamingRawPageRequest {
|
||||
main_document_commit,
|
||||
top_level_storage_key: None,
|
||||
navigation_bootstrap_entry,
|
||||
navigation_history_source: navigation_history.as_ref().map(|history| history.source_history()),
|
||||
navigation_history_source: navigation_history
|
||||
.as_ref()
|
||||
.map(|history| history.source_history()),
|
||||
reserved_service_worker_client_id: reserved_service_worker_client
|
||||
.map(RendererReservedServiceWorkerClient::release),
|
||||
};
|
||||
|
||||
@@ -342,6 +342,12 @@ impl LivePageEntry {
|
||||
dispatch: RendererTopLevelNavigationDispatch,
|
||||
) {
|
||||
self.top_level_navigation_dispatch = dispatch;
|
||||
if let Some(environment) = self
|
||||
.active_page_vm()
|
||||
.and_then(PageVm::renderer_page_script_environment)
|
||||
{
|
||||
environment.set_top_level_navigation_dispatch(dispatch);
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::runtime) fn top_level_navigation_dispatch(
|
||||
|
||||
@@ -64,7 +64,10 @@ impl PageVm {
|
||||
);
|
||||
vm.install_page_task_capabilities(capabilities);
|
||||
vm.set_root_document_lifecycle(lifecycle.clone());
|
||||
vm.set_document_ready_state(crate::dom::native::DocumentReadyState::Complete)?;
|
||||
// This Document completed synchronously before window.open returned.
|
||||
// Complete the native lifecycle too, so parser scheduling cannot emit
|
||||
// a second load that observers mistake for the requested navigation.
|
||||
vm.complete_related_initial_empty_document()?;
|
||||
for milestone in [
|
||||
RendererDocumentLifecycleMilestone::DomContentLoaded,
|
||||
RendererDocumentLifecycleMilestone::Load,
|
||||
|
||||
@@ -1133,20 +1133,26 @@ impl PageVm {
|
||||
|
||||
pub(in crate::runtime) fn validate_prepared_document_response(
|
||||
&mut self,
|
||||
request: crate::runtime::owner::RendererCreateStreamingRawPageRequest,
|
||||
mut request: crate::runtime::owner::RendererCreateStreamingRawPageRequest,
|
||||
mut bootstrap: crate::script_vm::RendererDocumentIsolateBootstrap,
|
||||
reservation: crate::runtime::owner_local_store::RendererDocumentIsolateReservation,
|
||||
) -> Result<PageVmValidatedFollowedNavigationCommit> {
|
||||
request.validate_bootstrap_configuration()?;
|
||||
let navigation_bootstrap_entry = match request
|
||||
// Browser requests retain the destination identity while resolving the
|
||||
// latest source state at commit. A session-history position alone is
|
||||
// only a fallback for requests without that explicit history handle.
|
||||
let navigation_bootstrap_entry = if let Some(history) = request.navigation_history.as_ref()
|
||||
{
|
||||
Some(history.resolve(&request.final_url)?)
|
||||
} else if let Some(position) = request
|
||||
.main_document_commit
|
||||
.as_ref()
|
||||
.and_then(|commit| commit.session_history_position)
|
||||
{
|
||||
Some(position) => self
|
||||
.vm_mut()
|
||||
.capture_inherited_history_for_browser_commit(&request.final_url, position)?,
|
||||
None => None,
|
||||
self.vm_mut()
|
||||
.capture_inherited_history_for_browser_commit(&request.final_url, position)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if moli_url::is_about_blank(&request.final_url)
|
||||
&& let Some(replacement) = request.document_replacement.as_ref()
|
||||
@@ -1167,6 +1173,14 @@ impl PageVm {
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(state) = bootstrap
|
||||
.initial_document_environment
|
||||
.as_ref()
|
||||
.and_then(|environment| environment.about_document_state.clone())
|
||||
&& let Some(history) = request.navigation_history.take()
|
||||
{
|
||||
request.navigation_history = Some(history.with_about_document_state(Some(state)));
|
||||
}
|
||||
ensure!(
|
||||
request.lifecycle_decider.is_none(),
|
||||
"a Page replacement cannot use a Page-creation lifecycle decider"
|
||||
|
||||
@@ -1956,6 +1956,12 @@ impl PageVm {
|
||||
let Some(vm) = self.vm.as_mut() else {
|
||||
return;
|
||||
};
|
||||
// A native auxiliary Window is closed on the protocol owner's task.
|
||||
// Settle its Navigation API while the exact realm and callbacks remain
|
||||
// registered, before clearing the context's resources.
|
||||
if let Err(error) = vm.cancel_closed_main_window_navigation() {
|
||||
tracing::warn!(%error, "failed to cancel closed Window navigation before retirement");
|
||||
}
|
||||
vm.detach_default_inspector_context_for_context_teardown();
|
||||
vm.close_page_context_resources_for_context_teardown();
|
||||
}
|
||||
@@ -4658,11 +4664,10 @@ impl PageVm {
|
||||
mode,
|
||||
AuxiliaryEnvironmentApply::Navigation | AuxiliaryEnvironmentApply::InheritedNavigation
|
||||
) {
|
||||
self.vm_mut()
|
||||
.install_navigation_bootstrap_from_history(
|
||||
env.navigation_bootstrap_entry.clone(),
|
||||
env.navigation_history_source.clone(),
|
||||
);
|
||||
self.vm_mut().install_navigation_bootstrap_from_history(
|
||||
env.navigation_bootstrap_entry.clone(),
|
||||
env.navigation_history_source.clone(),
|
||||
);
|
||||
if env.navigation_bootstrap_entry.is_none()
|
||||
&& let Some(position) = env
|
||||
.main_document_commit
|
||||
|
||||
@@ -64,6 +64,9 @@ pub enum RendererOwnerAction {
|
||||
update: moli_page_types::SessionHistoryUpdate,
|
||||
},
|
||||
TopLevelLocationNavigation(RendererDocumentSourcedTopLevelLocationNavigation),
|
||||
StopTopLevelLocationNavigation {
|
||||
source_document: RendererDocumentLifecycleIdentity,
|
||||
},
|
||||
TopLevelHistoryTraversal(RendererPendingTopLevelHistoryTraversal),
|
||||
SubresourceFetchPause {
|
||||
source_document: RendererDocumentLifecycleIdentity,
|
||||
|
||||
@@ -313,7 +313,8 @@ fn owner_action_transport_charge_bytes(action: &RendererOwnerAction) -> usize {
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.saturating_add(headers_charge(event.request_headers())),
|
||||
RendererOwnerAction::TopLevelHistoryTraversal(_) => 0,
|
||||
RendererOwnerAction::TopLevelHistoryTraversal(_)
|
||||
| RendererOwnerAction::StopTopLevelLocationNavigation { .. } => 0,
|
||||
RendererOwnerAction::SubresourceFetchPause { info, .. } => {
|
||||
info.renderer_transport_charge_bytes()
|
||||
}
|
||||
|
||||
@@ -809,9 +809,9 @@ mod inspector;
|
||||
mod promise_rejection_task;
|
||||
mod script_preparation_error;
|
||||
pub(crate) use inspector::{dispatch_inspector_io_owner_wake, dispatch_inspector_main_owner_wake};
|
||||
mod canvas_blob_serialization;
|
||||
mod document_context_transition;
|
||||
mod initial_window_document;
|
||||
mod canvas_blob_serialization;
|
||||
mod isolated_worlds;
|
||||
mod main_document_lifecycle;
|
||||
mod main_document_lifecycle_body;
|
||||
@@ -2019,6 +2019,7 @@ pub(crate) struct ScriptVmInitialDocumentEnvironment {
|
||||
pub(crate) policy_container: crate::document_runtime::DocumentPolicyContainer,
|
||||
fallback_base_url: Option<url::Url>,
|
||||
storage_key: Option<moli_storage_key::MoliStorageKey>,
|
||||
pub(crate) about_document_state: Option<crate::runtime::RendererAboutDocumentState>,
|
||||
}
|
||||
|
||||
pub(crate) use document_environment::ScriptVmCapturedDocumentEnvironment;
|
||||
@@ -2050,6 +2051,9 @@ impl ScriptVmPageRealmBootstrap {
|
||||
.initial_document_environment
|
||||
.take()
|
||||
});
|
||||
let about_document_state = initial_environment
|
||||
.as_ref()
|
||||
.and_then(|environment| environment.about_document_state.clone());
|
||||
let document_handle = dom_host.document_handle();
|
||||
if let Some(base) = initial_environment
|
||||
.as_ref()
|
||||
@@ -2182,6 +2186,9 @@ impl ScriptVmPageRealmBootstrap {
|
||||
.bind_deferred_context_host_release_queue(
|
||||
renderer_document_isolate.deferred_context_host_release_queue(),
|
||||
);
|
||||
context_host
|
||||
.borrow_mut()
|
||||
.set_main_about_document_state(about_document_state);
|
||||
if let Some(root_frame_id) = root_frame_id.as_deref() {
|
||||
let name = context_host
|
||||
.borrow()
|
||||
@@ -2485,10 +2492,32 @@ impl ScriptVmDefaultWorldBootstrap {
|
||||
>,
|
||||
about_document_state: Option<crate::runtime::RendererAboutDocumentState>,
|
||||
) -> std::result::Result<Self, ScriptVmBootstrapError> {
|
||||
let about_document_state = renderer_document_isolate_bootstrap
|
||||
.initial_document_environment
|
||||
.as_ref()
|
||||
.and_then(|environment| environment.about_document_state.clone())
|
||||
.or(about_document_state);
|
||||
let top_level_storage_key = about_document_state
|
||||
.as_ref()
|
||||
.and_then(crate::runtime::RendererAboutDocumentState::storage_key)
|
||||
.or(top_level_storage_key);
|
||||
let initial_environment = if renderer_document_isolate_bootstrap
|
||||
.initial_document_environment
|
||||
.is_none()
|
||||
{
|
||||
about_document_state
|
||||
.as_ref()
|
||||
.map(|state| ScriptVmInitialDocumentEnvironment {
|
||||
security_token: None,
|
||||
origin: state.origin().serialized_origin(),
|
||||
policy_container: state.policy_container().clone(),
|
||||
fallback_base_url: Some(state.base_url().clone()),
|
||||
storage_key: state.storage_key(),
|
||||
about_document_state: Some(state.clone()),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut bootstrap = ScriptVmPageRealmBootstrap::new_from_dom_host(
|
||||
bootstrap_dom_host,
|
||||
bypass_content_security_policy,
|
||||
@@ -2505,7 +2534,7 @@ impl ScriptVmDefaultWorldBootstrap {
|
||||
main_document_commit,
|
||||
top_level_storage_key,
|
||||
reserved_service_worker_client_id,
|
||||
None,
|
||||
initial_environment,
|
||||
)?;
|
||||
if let Some(state) = &about_document_state {
|
||||
bootstrap
|
||||
|
||||
@@ -85,6 +85,23 @@ impl ScriptVmInitialDocumentEnvironment {
|
||||
host.inherited_document_domain(child)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let about_document_state = crate::util::context_host_ptr_from_context_slot(context)
|
||||
.and_then(|host| {
|
||||
let host = unsafe { &*host };
|
||||
let owner = host.window_dispatch_scope_for_context(scope, context)?;
|
||||
host.capture_about_document_state(
|
||||
owner,
|
||||
&Url::parse("about:blank").expect("valid about:blank URL"),
|
||||
None,
|
||||
)
|
||||
})
|
||||
.map(|state| {
|
||||
crate::runtime::RendererAboutDocumentState::new(
|
||||
state.origin().clone(),
|
||||
base_url.clone(),
|
||||
policy_container.clone(),
|
||||
)
|
||||
});
|
||||
Ok(Self {
|
||||
security_token,
|
||||
document_domain_override,
|
||||
@@ -92,6 +109,7 @@ impl ScriptVmInitialDocumentEnvironment {
|
||||
policy_container,
|
||||
fallback_base_url: Some(base_url),
|
||||
storage_key: Some(storage_key),
|
||||
about_document_state,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -423,23 +441,47 @@ impl ScriptVm {
|
||||
Some(host.document_base_url_for_handle(host.document_handle()))
|
||||
};
|
||||
let mut policy_container = host.document_policy_container().clone();
|
||||
let source = if preserve_navigation_referrer {
|
||||
url::Url::parse(&policy_container.document_referrer).ok()
|
||||
} else {
|
||||
Some(host.document_url().clone())
|
||||
};
|
||||
policy_container.document_referrer = source
|
||||
.as_ref()
|
||||
.and_then(|source| {
|
||||
moli_fetch::referrer_value(
|
||||
source,
|
||||
host.document_url(),
|
||||
None,
|
||||
policy_container.referrer_policy.as_deref(),
|
||||
)
|
||||
})
|
||||
if preserve_navigation_referrer && host.main_document_is_initial_empty() {
|
||||
// The synchronous initial Document may expose the creator's
|
||||
// full URL. A browser reload reapplies the navigation policy
|
||||
// to that retained source for its actual about:blank target.
|
||||
policy_container.document_referrer =
|
||||
Url::parse(&policy_container.document_referrer)
|
||||
.ok()
|
||||
.and_then(|source| {
|
||||
moli_fetch::referrer_value(
|
||||
&source,
|
||||
host.document_url(),
|
||||
None,
|
||||
policy_container.referrer_policy.as_deref(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
} else if !preserve_navigation_referrer {
|
||||
policy_container.document_referrer = moli_fetch::referrer_value(
|
||||
host.document_url(),
|
||||
host.document_url(),
|
||||
None,
|
||||
policy_container.referrer_policy.as_deref(),
|
||||
)
|
||||
.unwrap_or_default();
|
||||
}
|
||||
let token = scope.get_current_context().get_security_token(scope);
|
||||
let about_document_state = host
|
||||
.capture_about_document_state(
|
||||
crate::native_bridge::OwnerDispatchScope::Top,
|
||||
&Url::parse("about:blank").expect("valid about:blank URL"),
|
||||
None,
|
||||
)
|
||||
.map(|state| {
|
||||
crate::runtime::RendererAboutDocumentState::new(
|
||||
state.origin().clone(),
|
||||
fallback_base_url
|
||||
.clone()
|
||||
.unwrap_or_else(|| host.document_url().clone()),
|
||||
policy_container.clone(),
|
||||
)
|
||||
});
|
||||
Ok(ScriptVmInitialDocumentEnvironment {
|
||||
security_token: Some(v8::Global::new(scope, token)),
|
||||
document_domain_override: host.inherited_document_domain(None),
|
||||
@@ -447,6 +489,7 @@ impl ScriptVm {
|
||||
policy_container,
|
||||
fallback_base_url,
|
||||
storage_key: Some(host.top_web_storage_scope().storage_key().clone()),
|
||||
about_document_state,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -277,6 +277,7 @@ struct RendererPageScriptEnvironmentInner {
|
||||
auxiliary_window: OnceCell<crate::runtime::RendererAuxiliaryWindow>,
|
||||
opener: RefCell<Option<v8::Global<v8::Object>>>,
|
||||
document_host: RefCell<Weak<RefCell<JsContextHost>>>,
|
||||
top_level_navigation_dispatch: Cell<crate::RendererTopLevelNavigationDispatch>,
|
||||
closed: Cell<bool>,
|
||||
}
|
||||
|
||||
@@ -360,6 +361,9 @@ impl RendererPageScriptEnvironment {
|
||||
auxiliary_window: OnceCell::new(),
|
||||
opener: RefCell::new(None),
|
||||
document_host: RefCell::new(Weak::new()),
|
||||
top_level_navigation_dispatch: Cell::new(
|
||||
crate::RendererTopLevelNavigationDispatch::FollowInStandaloneAdapter,
|
||||
),
|
||||
closed: Cell::new(false),
|
||||
}),
|
||||
};
|
||||
@@ -380,6 +384,19 @@ impl RendererPageScriptEnvironment {
|
||||
*self.inner.document_host.borrow_mut() = Rc::downgrade(host);
|
||||
}
|
||||
|
||||
pub(crate) fn set_top_level_navigation_dispatch(
|
||||
&self,
|
||||
dispatch: crate::RendererTopLevelNavigationDispatch,
|
||||
) {
|
||||
self.inner.top_level_navigation_dispatch.set(dispatch);
|
||||
}
|
||||
|
||||
pub(crate) fn top_level_navigation_dispatch(
|
||||
&self,
|
||||
) -> crate::RendererTopLevelNavigationDispatch {
|
||||
self.inner.top_level_navigation_dispatch.get()
|
||||
}
|
||||
|
||||
pub(crate) fn enqueue_related_page_turn_completion(&self) {
|
||||
let host = self.inner.document_host.borrow().clone();
|
||||
self.inner
|
||||
|
||||
@@ -424,7 +424,7 @@ impl ScriptVm {
|
||||
host.retire_window_realm_resources(context.runtime_observable_context_token);
|
||||
host.retire_window_execution_contexts_for_context_token(
|
||||
context.runtime_observable_context_token,
|
||||
self.resource_owner_id,
|
||||
Some(self.resource_owner_id),
|
||||
);
|
||||
}
|
||||
let reuses_window_proxy = self
|
||||
@@ -562,7 +562,6 @@ impl ScriptVm {
|
||||
context.local_window_id,
|
||||
),
|
||||
);
|
||||
self.clear_context_wrapper_cache_for_context_ptr(&context.context, false);
|
||||
}
|
||||
{
|
||||
let mut host = self._context_host.borrow_mut();
|
||||
@@ -570,7 +569,7 @@ impl ScriptVm {
|
||||
host.retire_window_realm_resources(context.runtime_observable_context_token);
|
||||
host.retire_window_execution_contexts_for_context_token(
|
||||
context.runtime_observable_context_token,
|
||||
self.resource_owner_id,
|
||||
Some(self.resource_owner_id),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -646,7 +645,7 @@ impl ScriptVm {
|
||||
host.retire_window_realm_resources(context.runtime_observable_context_token);
|
||||
host.retire_window_execution_contexts_for_context_token(
|
||||
context.runtime_observable_context_token,
|
||||
self.resource_owner_id,
|
||||
Some(self.resource_owner_id),
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
|
||||
@@ -25,17 +25,23 @@ impl ScriptVm {
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let inherited_domain =
|
||||
inherited.map(|environment| environment.document_domain_override.get());
|
||||
let inherited_origin = inherited.and_then(|environment| {
|
||||
let mut origin = environment.about_document_state.as_ref()?.origin().clone();
|
||||
if let crate::native_bridge::WindowAccessOrigin::Tuple {
|
||||
document_domain, ..
|
||||
} = &mut origin
|
||||
{
|
||||
*document_domain = environment.document_domain_override.get();
|
||||
}
|
||||
Some(origin)
|
||||
});
|
||||
Ok(self
|
||||
._context_host
|
||||
.borrow()
|
||||
.main_document_local_window_transition_for_commit(
|
||||
origin,
|
||||
inherited_opaque_origin_matches,
|
||||
inherited_domain
|
||||
.as_ref()
|
||||
.and_then(|domain| domain.as_deref()),
|
||||
inherited_origin.as_ref(),
|
||||
policy,
|
||||
))
|
||||
}
|
||||
@@ -121,6 +127,12 @@ impl ScriptVm {
|
||||
environment.document_domain_override.clone(),
|
||||
);
|
||||
}
|
||||
host.set_main_about_document_state(
|
||||
inherited
|
||||
.as_ref()
|
||||
.and_then(|environment| environment.about_document_state.clone())
|
||||
.or_else(|| env.about_document_state.clone()),
|
||||
);
|
||||
host.rebind_initial_main_document_senders(
|
||||
resource_completion,
|
||||
page_task_tx.top_level_navigation_handoff_sender(),
|
||||
@@ -161,19 +173,27 @@ impl ScriptVm {
|
||||
let scope = &mut scope.init();
|
||||
let context = v8::Local::new(scope, context);
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
crate::context_bootstrap::sync_global_location_runtime_state(
|
||||
scope,
|
||||
document_url.as_str(),
|
||||
);
|
||||
let global = context.global(scope);
|
||||
if let Some(document) = global
|
||||
.get(scope, crate::util::v8str(scope, "document").into())
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
let window = context.global(scope);
|
||||
{
|
||||
crate::context_bootstrap::sync_document_location_runtime_state_from_window(
|
||||
scope, document, global,
|
||||
let mut host = vm._context_host.borrow_mut();
|
||||
crate::native_bridge::WindowEnvironmentSettings::bind_current_main_document(scope, &host)
|
||||
.ok_or_else(|| anyhow!("retained Window has no current Document settings"))?;
|
||||
let host_ptr = &mut *host as *mut JsContextHost;
|
||||
let document = host.native_bridge_mut()
|
||||
.wrap_handle(scope, host_ptr, document_handle)
|
||||
.ok_or_else(|| anyhow!("retained Window Document could not be wrapped"))?;
|
||||
crate::util::set_private_value(
|
||||
scope,
|
||||
window,
|
||||
crate::context_bootstrap::WINDOW_DOCUMENT_SLOT,
|
||||
document.into(),
|
||||
);
|
||||
}
|
||||
crate::context_bootstrap::reset_window_location_history_navigation_runtime_state(
|
||||
scope,
|
||||
window,
|
||||
document_url.as_str(),
|
||||
)?;
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
|
||||
@@ -11,6 +11,28 @@ use crate::{
|
||||
};
|
||||
|
||||
impl ScriptVm {
|
||||
pub(crate) fn cancel_closed_main_window_navigation(&mut self) -> Result<()> {
|
||||
if !self._context_host.borrow().browsing_context_is_closed() {
|
||||
return Ok(());
|
||||
}
|
||||
self.with_default_context_scope(|scope, host_ptr| {
|
||||
let window = scope.get_current_context().global(scope);
|
||||
// Closing from pagehide/unload must not settle the outgoing
|
||||
// Document's Navigation API promises after its unload has begun.
|
||||
if !unsafe { &*host_ptr }.main_document_unload_has_started() {
|
||||
inform_about_canceled_navigation_for_window(
|
||||
scope,
|
||||
window,
|
||||
NavigationCancellationReason::WindowClose,
|
||||
);
|
||||
}
|
||||
crate::native_bridge::JsContextHost::dispatch_main_document_unload_for_navigation_commit(
|
||||
scope, host_ptr,
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn current_popup_close_owner(
|
||||
&self,
|
||||
popup_id: u64,
|
||||
|
||||
@@ -570,7 +570,8 @@ impl PendingWindowRealmBootstrapRegistration {
|
||||
realm_token: crate::native_bridge::RuntimeObservableContextToken,
|
||||
resource_owner_id: ResourceOwnerId,
|
||||
) -> Result<Option<Self>> {
|
||||
let Some((owner, dispatch_scope, access_policy)) = mode.registration() else {
|
||||
let Some((owner, dispatch_scope, access_policy)) = mode.registration(unsafe { &*host })
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
if !unsafe { &mut *host }.register_window_execution_context_realm(
|
||||
@@ -599,7 +600,7 @@ impl Drop for PendingWindowRealmBootstrapRegistration {
|
||||
if !self.committed {
|
||||
unsafe { &mut *self.host }.retire_window_execution_contexts_for_context_token(
|
||||
self.realm_token,
|
||||
self.resource_owner_id,
|
||||
Some(self.resource_owner_id),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -608,13 +609,20 @@ impl Drop for PendingWindowRealmBootstrapRegistration {
|
||||
impl WindowContextBootstrapMode {
|
||||
fn registration(
|
||||
self,
|
||||
host: &JsContextHost,
|
||||
) -> Option<(
|
||||
crate::native_bridge::WindowExecutionContextOwner,
|
||||
crate::native_bridge::OwnerDispatchScope,
|
||||
crate::native_bridge::WindowExecutionContextAccessPolicy,
|
||||
)> {
|
||||
match self {
|
||||
Self::MainDefault => None,
|
||||
Self::MainDefault => Some((
|
||||
host.current_window_execution_context_owner(
|
||||
crate::native_bridge::OwnerDispatchScope::Top,
|
||||
)?,
|
||||
crate::native_bridge::OwnerDispatchScope::Top,
|
||||
crate::native_bridge::WindowExecutionContextAccessPolicy::EnforceWebOrigin,
|
||||
)),
|
||||
Self::PopupDefault {
|
||||
popup_id,
|
||||
local_window_id,
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
use super::*;
|
||||
|
||||
impl ScriptVm {
|
||||
pub(crate) fn complete_related_initial_empty_document(&mut self) -> Result<()> {
|
||||
anyhow::ensure!(
|
||||
self._context_host
|
||||
.borrow_mut()
|
||||
.complete_current_main_initial_empty_document(),
|
||||
"related initial Document rejected synchronous completion"
|
||||
);
|
||||
self.document_runtime.note_dom_content_loaded_dispatched();
|
||||
self.set_document_ready_state(crate::dom::native::DocumentReadyState::Complete)
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptVmDefaultWorldBootstrap {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn related_page_in_scope(
|
||||
@@ -43,6 +56,7 @@ impl ScriptVmDefaultWorldBootstrap {
|
||||
storage_key: None,
|
||||
origin: inherited_origin.clone(),
|
||||
policy_container: env.document_policy_container.clone(),
|
||||
about_document_state: env.about_document_state.clone(),
|
||||
}),
|
||||
)?;
|
||||
// Noopener blank pages commit a navigation; only pages exposing an
|
||||
@@ -107,6 +121,7 @@ impl ScriptVmPageRealmBootstrap {
|
||||
root_frame_id,
|
||||
context_host,
|
||||
prebootstrapped_child_default_contexts,
|
||||
popup_default_contexts,
|
||||
page_context_cancel_tx,
|
||||
post_domcontentloaded_page_task_tx,
|
||||
page_runtime_wake_tx,
|
||||
@@ -193,6 +208,7 @@ impl ScriptVmPageRealmBootstrap {
|
||||
root_frame_id,
|
||||
context_host,
|
||||
prebootstrapped_child_default_contexts,
|
||||
popup_default_contexts,
|
||||
page_context_cancel_tx,
|
||||
post_domcontentloaded_page_task_tx,
|
||||
page_runtime_wake_tx,
|
||||
|
||||
@@ -71,10 +71,7 @@ fn queue_agent_pending_promise_rejections(scope: &mut v8::PinScope<'_, '_>) -> u
|
||||
.get_slot_mut::<AgentPendingPromiseRejections>()
|
||||
.map(|pending| std::mem::take(&mut pending.0))
|
||||
.unwrap_or_default();
|
||||
pending
|
||||
.into_iter()
|
||||
.map(queue_promise_rejection_state)
|
||||
.sum()
|
||||
pending.into_iter().map(queue_promise_rejection_state).sum()
|
||||
}
|
||||
|
||||
pub(super) fn promise_reject_dispatch_slot(
|
||||
|
||||
@@ -115,8 +115,6 @@ mod window_open_named_targets;
|
||||
mod window_open_referrer;
|
||||
mod window_open_special_targets;
|
||||
mod worker_listener_invocation;
|
||||
|
||||
|
||||
mod error_event_init;
|
||||
mod message_event_init;
|
||||
mod window_current_event_private;
|
||||
|
||||
@@ -132,7 +132,14 @@ setTimeout(() => {
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(vm.take_pending_popup_activations().is_empty(), "{kind}");
|
||||
// Named reuse may publish a focus/activation record. Its browser
|
||||
// handoff must explicitly prohibit navigation and another load.
|
||||
assert!(
|
||||
vm.take_pending_popup_activations()
|
||||
.iter()
|
||||
.all(|activation| !activation.navigation_requested()),
|
||||
"{kind}"
|
||||
);
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
|
||||
@@ -3122,7 +3122,9 @@ async fn borrowed_cross_origin_window_accessors_follow_the_receiver() {
|
||||
} catch (error) {
|
||||
plain = error instanceof TypeError ? "type-error" : String(error && error.name);
|
||||
}
|
||||
setLocation.call(window, "#pr957-receiver");
|
||||
// A relative fragment on srcdoc resolves against the inherited API base URL.
|
||||
// Use its explicit URL to isolate which Window the borrowed setter navigates.
|
||||
setLocation.call(window, "about:srcdoc#pr957-receiver");
|
||||
return JSON.stringify({
|
||||
plain,
|
||||
normalParentIsSelf: w.parent === w,
|
||||
|
||||
@@ -2252,7 +2252,6 @@ mod domrect_receiver_consolidation;
|
||||
mod dynamic_inline_scripts;
|
||||
mod element_click;
|
||||
mod eme_owner_interface_exposure_interfaces;
|
||||
mod encoded_video_chunk_shell;
|
||||
mod encoded_video_chunk;
|
||||
mod event_receivers;
|
||||
mod extracted;
|
||||
|
||||
@@ -917,10 +917,14 @@ pub(crate) fn window_post_message_callback<'s>(
|
||||
.as_ref()
|
||||
.map(|(identity, _, _)| *identity)
|
||||
.or_else(|| {
|
||||
active_lightweight_popup_id(scope)
|
||||
.map(PendingWindowMessageEndpoint::LightweightPopup)
|
||||
.and_then(|endpoint| current_window_message_source_identity(scope, host, endpoint))
|
||||
.or_else(|| incumbent_window_message_source_identity(scope, host))
|
||||
incumbent_window_message_source_identity(scope, host)
|
||||
.or_else(|| {
|
||||
active_lightweight_popup_id(scope)
|
||||
.map(PendingWindowMessageEndpoint::LightweightPopup)
|
||||
.and_then(|endpoint| {
|
||||
current_window_message_source_identity(scope, host, endpoint)
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
host.current_window_message_source().and_then(|endpoint| {
|
||||
current_window_message_source_identity(scope, host, endpoint)
|
||||
@@ -1889,6 +1893,31 @@ fn dispatch_window_message_event<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
fn top_window_message_source_for_target<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
host: &JsContextHost,
|
||||
target: PendingWindowMessageEndpoint,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
let PendingWindowMessageEndpoint::ChildWindow(handle) = target else {
|
||||
return host
|
||||
.page_default_context(scope)
|
||||
.map(|context| context.global(scope).into());
|
||||
};
|
||||
let top = host.child_browsing_context_top_window_for_current_realm(scope, handle);
|
||||
if moli_trace::window_message_trace_enabled() {
|
||||
tracing::info!(
|
||||
target: "moli_window_message_trace",
|
||||
handle = handle.index(),
|
||||
relation_found = top.is_some(),
|
||||
top_is_target_global = top.is_some_and(|top| {
|
||||
top.strict_equals(scope.get_current_context().global(scope).into())
|
||||
}),
|
||||
stage = "top_window_message_source_resolved",
|
||||
);
|
||||
}
|
||||
top.map(Into::into)
|
||||
}
|
||||
|
||||
fn child_window_message_source<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
host: &mut JsContextHost,
|
||||
|
||||
@@ -144,11 +144,6 @@ impl WindowOpenFeatures {
|
||||
self.noreferrer
|
||||
}
|
||||
|
||||
/// Returns whether the features request an independent popup window.
|
||||
pub fn is_popup(&self) -> bool {
|
||||
self.is_popup
|
||||
}
|
||||
|
||||
/// Returns the normalized enabled features used by the popup runtime.
|
||||
pub fn enabled_feature_strings(&self) -> Vec<String> {
|
||||
let mut enabled = Vec::new();
|
||||
|
||||
Reference in New Issue
Block a user