ignore late page/title events from suspended tabs (white screen on tab restore)
when a loading tab was suspended its webview was destroyed while events were still queued, and a finished load from the dying webview could overwrite the tab's stored URL with an intermediate redirect URL. on switch-back the tab then reloaded the wrong page and stayed blank. each webview instance now gets a generation (epoch): events carry it and are dropped when they don't match, and suspending a tab invalidates its epoch. verified tab cycling still restores pages normally.
This commit is contained in:
parent
38a7b3a626
commit
0c6b62ae3b
1 changed files with 49 additions and 6 deletions
|
|
@ -39,10 +39,12 @@ enum UserEvent {
|
||||||
ChromeIpc(String),
|
ChromeIpc(String),
|
||||||
TitleChanged {
|
TitleChanged {
|
||||||
tab_id: u32,
|
tab_id: u32,
|
||||||
|
epoch: u32,
|
||||||
title: String,
|
title: String,
|
||||||
},
|
},
|
||||||
PageLoaded {
|
PageLoaded {
|
||||||
tab_id: u32,
|
tab_id: u32,
|
||||||
|
epoch: u32,
|
||||||
url: String,
|
url: String,
|
||||||
},
|
},
|
||||||
DownloadStarted {
|
DownloadStarted {
|
||||||
|
|
@ -73,6 +75,10 @@ struct Tab {
|
||||||
url: String,
|
url: String,
|
||||||
kir_enabled: bool,
|
kir_enabled: bool,
|
||||||
webview: Option<WebView>,
|
webview: Option<WebView>,
|
||||||
|
// Identifies the current webview instance for this tab. Events (page load,
|
||||||
|
// title) carry the epoch they were emitted from so that late events from a
|
||||||
|
// destroyed webview (suspended tab) cannot corrupt the tab state.
|
||||||
|
epoch: u32,
|
||||||
back_stack: Vec<String>,
|
back_stack: Vec<String>,
|
||||||
forward_stack: Vec<String>,
|
forward_stack: Vec<String>,
|
||||||
pending_nav_from_stack: bool,
|
pending_nav_from_stack: bool,
|
||||||
|
|
@ -94,6 +100,7 @@ struct BrowserState {
|
||||||
downloads_panel_open: bool,
|
downloads_panel_open: bool,
|
||||||
active: usize,
|
active: usize,
|
||||||
next_id: u32,
|
next_id: u32,
|
||||||
|
next_epoch: u32,
|
||||||
private: bool,
|
private: bool,
|
||||||
user_agent: Option<String>,
|
user_agent: Option<String>,
|
||||||
web_context: Option<WebContext>,
|
web_context: Option<WebContext>,
|
||||||
|
|
@ -133,6 +140,13 @@ impl BrowserState {
|
||||||
self.next_id += 1;
|
self.next_id += 1;
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
fn alloc_epoch(&mut self) -> u32 {
|
||||||
|
self.next_epoch = self.next_epoch.wrapping_add(1);
|
||||||
|
if self.next_epoch == 0 {
|
||||||
|
self.next_epoch = 1;
|
||||||
|
}
|
||||||
|
self.next_epoch
|
||||||
|
}
|
||||||
fn find_index(&self, tab_id: u32) -> Option<usize> {
|
fn find_index(&self, tab_id: u32) -> Option<usize> {
|
||||||
self.tabs.iter().position(|t| t.id == tab_id)
|
self.tabs.iter().position(|t| t.id == tab_id)
|
||||||
}
|
}
|
||||||
|
|
@ -1182,6 +1196,7 @@ pub fn launch_webkit(args: &Cli) -> Result<()> {
|
||||||
downloads_panel_open: false,
|
downloads_panel_open: false,
|
||||||
active: 0,
|
active: 0,
|
||||||
next_id: 1,
|
next_id: 1,
|
||||||
|
next_epoch: 1,
|
||||||
private: args.private,
|
private: args.private,
|
||||||
user_agent: if args.ua_chrome {
|
user_agent: if args.ua_chrome {
|
||||||
Some(chrome_user_agent())
|
Some(chrome_user_agent())
|
||||||
|
|
@ -1385,16 +1400,23 @@ fn handle_event(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match ev {
|
match ev {
|
||||||
UserEvent::ChromeIpc(msg) => handle_chrome_ipc(&msg, window, proxy, state)?,
|
UserEvent::ChromeIpc(msg) => handle_chrome_ipc(&msg, window, proxy, state)?,
|
||||||
UserEvent::TitleChanged { tab_id, title } => {
|
UserEvent::TitleChanged {
|
||||||
|
tab_id,
|
||||||
|
epoch,
|
||||||
|
title,
|
||||||
|
} => {
|
||||||
if let Some(i) = state.find_index(tab_id) {
|
if let Some(i) = state.find_index(tab_id) {
|
||||||
if !title.trim().is_empty() {
|
if state.tabs[i].epoch == epoch && !title.trim().is_empty() {
|
||||||
state.tabs[i].title = title;
|
state.tabs[i].title = title;
|
||||||
push_tabs(state);
|
push_tabs(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UserEvent::PageLoaded { tab_id, url } => {
|
UserEvent::PageLoaded { tab_id, epoch, url } => {
|
||||||
if let Some(i) = state.find_index(tab_id) {
|
if let Some(i) = state.find_index(tab_id) {
|
||||||
|
if state.tabs[i].epoch != epoch {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
if !url.is_empty() && url != "about:blank" {
|
if !url.is_empty() && url != "about:blank" {
|
||||||
let previous = state.tabs[i].url.clone();
|
let previous = state.tabs[i].url.clone();
|
||||||
if url != previous {
|
if url != previous {
|
||||||
|
|
@ -1701,6 +1723,7 @@ fn handle_chrome_ipc(
|
||||||
state.tabs[i].kir_enabled = !state.tabs[i].kir_enabled;
|
state.tabs[i].kir_enabled = !state.tabs[i].kir_enabled;
|
||||||
if i != state.active && !state.tabs[i].kir_enabled {
|
if i != state.active && !state.tabs[i].kir_enabled {
|
||||||
state.tabs[i].webview = None;
|
state.tabs[i].webview = None;
|
||||||
|
state.tabs[i].epoch = 0;
|
||||||
}
|
}
|
||||||
apply_visibility(state);
|
apply_visibility(state);
|
||||||
push_tabs(state);
|
push_tabs(state);
|
||||||
|
|
@ -1883,6 +1906,7 @@ fn build_tab_webview(
|
||||||
user_agent: Option<&str>,
|
user_agent: Option<&str>,
|
||||||
mut web_context: Option<&mut WebContext>,
|
mut web_context: Option<&mut WebContext>,
|
||||||
tab_id: u32,
|
tab_id: u32,
|
||||||
|
epoch: u32,
|
||||||
url: &str,
|
url: &str,
|
||||||
bounds: Rect,
|
bounds: Rect,
|
||||||
) -> Result<(WebView, WebViewHostMode)> {
|
) -> Result<(WebView, WebViewHostMode)> {
|
||||||
|
|
@ -1902,6 +1926,7 @@ fn build_tab_webview(
|
||||||
user_agent,
|
user_agent,
|
||||||
&init_script,
|
&init_script,
|
||||||
tab_id,
|
tab_id,
|
||||||
|
epoch,
|
||||||
url,
|
url,
|
||||||
bounds,
|
bounds,
|
||||||
)
|
)
|
||||||
|
|
@ -1919,6 +1944,7 @@ fn build_tab_webview(
|
||||||
user_agent,
|
user_agent,
|
||||||
&init_script,
|
&init_script,
|
||||||
tab_id,
|
tab_id,
|
||||||
|
epoch,
|
||||||
url,
|
url,
|
||||||
bounds,
|
bounds,
|
||||||
)
|
)
|
||||||
|
|
@ -1935,6 +1961,7 @@ fn build_tab_webview(
|
||||||
user_agent,
|
user_agent,
|
||||||
&init_script,
|
&init_script,
|
||||||
tab_id,
|
tab_id,
|
||||||
|
epoch,
|
||||||
url,
|
url,
|
||||||
bounds,
|
bounds,
|
||||||
)
|
)
|
||||||
|
|
@ -1957,6 +1984,7 @@ fn build_tab_webview(
|
||||||
user_agent,
|
user_agent,
|
||||||
&init_script,
|
&init_script,
|
||||||
tab_id,
|
tab_id,
|
||||||
|
epoch,
|
||||||
url,
|
url,
|
||||||
bounds,
|
bounds,
|
||||||
)
|
)
|
||||||
|
|
@ -1982,6 +2010,7 @@ fn build_tab_webview(
|
||||||
user_agent,
|
user_agent,
|
||||||
&init_script,
|
&init_script,
|
||||||
tab_id,
|
tab_id,
|
||||||
|
epoch,
|
||||||
url,
|
url,
|
||||||
bounds,
|
bounds,
|
||||||
)
|
)
|
||||||
|
|
@ -2009,6 +2038,7 @@ fn tab_webview_builder<'a>(
|
||||||
user_agent: Option<&str>,
|
user_agent: Option<&str>,
|
||||||
init_script: &str,
|
init_script: &str,
|
||||||
tab_id: u32,
|
tab_id: u32,
|
||||||
|
epoch: u32,
|
||||||
url: &str,
|
url: &str,
|
||||||
bounds: Rect,
|
bounds: Rect,
|
||||||
) -> WebViewBuilder<'a> {
|
) -> WebViewBuilder<'a> {
|
||||||
|
|
@ -2036,11 +2066,15 @@ fn tab_webview_builder<'a>(
|
||||||
false
|
false
|
||||||
})
|
})
|
||||||
.with_document_title_changed_handler(move |title| {
|
.with_document_title_changed_handler(move |title| {
|
||||||
let _ = title_proxy.send_event(UserEvent::TitleChanged { tab_id, title });
|
let _ = title_proxy.send_event(UserEvent::TitleChanged {
|
||||||
|
tab_id,
|
||||||
|
epoch,
|
||||||
|
title,
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.with_on_page_load_handler(move |event, url| {
|
.with_on_page_load_handler(move |event, url| {
|
||||||
if matches!(event, PageLoadEvent::Finished) {
|
if matches!(event, PageLoadEvent::Finished) {
|
||||||
let _ = page_proxy.send_event(UserEvent::PageLoaded { tab_id, url });
|
let _ = page_proxy.send_event(UserEvent::PageLoaded { tab_id, epoch, url });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.with_download_started_handler(move |url, destination| {
|
.with_download_started_handler(move |url, destination| {
|
||||||
|
|
@ -2126,6 +2160,7 @@ fn open_tab(
|
||||||
url: String,
|
url: String,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let tab_id = state.alloc_id();
|
let tab_id = state.alloc_id();
|
||||||
|
let epoch = state.alloc_epoch();
|
||||||
let chrome_h = chrome_height(state);
|
let chrome_h = chrome_height(state);
|
||||||
let tab_bounds = content_bounds(window, chrome_h, state.host_mode);
|
let tab_bounds = content_bounds(window, chrome_h, state.host_mode);
|
||||||
let (webview, effective_host_mode) = build_tab_webview(
|
let (webview, effective_host_mode) = build_tab_webview(
|
||||||
|
|
@ -2138,6 +2173,7 @@ fn open_tab(
|
||||||
state.user_agent.as_deref(),
|
state.user_agent.as_deref(),
|
||||||
state.web_context.as_mut(),
|
state.web_context.as_mut(),
|
||||||
tab_id,
|
tab_id,
|
||||||
|
epoch,
|
||||||
&url,
|
&url,
|
||||||
tab_bounds,
|
tab_bounds,
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -2149,6 +2185,7 @@ fn open_tab(
|
||||||
url,
|
url,
|
||||||
kir_enabled: false,
|
kir_enabled: false,
|
||||||
webview: Some(webview),
|
webview: Some(webview),
|
||||||
|
epoch,
|
||||||
back_stack: Vec::new(),
|
back_stack: Vec::new(),
|
||||||
forward_stack: Vec::new(),
|
forward_stack: Vec::new(),
|
||||||
pending_nav_from_stack: false,
|
pending_nav_from_stack: false,
|
||||||
|
|
@ -2227,6 +2264,7 @@ fn ensure_tab_loaded(
|
||||||
let chrome_h = chrome_height(state);
|
let chrome_h = chrome_height(state);
|
||||||
let tab_bounds = content_bounds(window, chrome_h, state.host_mode);
|
let tab_bounds = content_bounds(window, chrome_h, state.host_mode);
|
||||||
let tab_id = state.tabs[index].id;
|
let tab_id = state.tabs[index].id;
|
||||||
|
let epoch = state.alloc_epoch();
|
||||||
let tab_url = state.tabs[index].url.clone();
|
let tab_url = state.tabs[index].url.clone();
|
||||||
let (webview, effective_host_mode) = build_tab_webview(
|
let (webview, effective_host_mode) = build_tab_webview(
|
||||||
window,
|
window,
|
||||||
|
|
@ -2238,18 +2276,23 @@ fn ensure_tab_loaded(
|
||||||
state.user_agent.as_deref(),
|
state.user_agent.as_deref(),
|
||||||
state.web_context.as_mut(),
|
state.web_context.as_mut(),
|
||||||
tab_id,
|
tab_id,
|
||||||
|
epoch,
|
||||||
&tab_url,
|
&tab_url,
|
||||||
tab_bounds,
|
tab_bounds,
|
||||||
)?;
|
)?;
|
||||||
state.host_mode = effective_host_mode;
|
state.host_mode = effective_host_mode;
|
||||||
state.tabs[index].webview = Some(webview);
|
state.tabs[index].webview = Some(webview);
|
||||||
|
state.tabs[index].epoch = epoch;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn suspend_background_tabs(state: &mut BrowserState) {
|
fn suspend_background_tabs(state: &mut BrowserState) {
|
||||||
for (i, tab) in state.tabs.iter_mut().enumerate() {
|
for (i, tab) in state.tabs.iter_mut().enumerate() {
|
||||||
if i != state.active && !tab.kir_enabled {
|
if i != state.active && !tab.kir_enabled && tab.webview.is_some() {
|
||||||
tab.webview = None;
|
tab.webview = None;
|
||||||
|
// Invalidate pending events from the destroyed webview so they
|
||||||
|
// cannot overwrite this tab's url/title while it is suspended.
|
||||||
|
tab.epoch = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue