mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Auto merge of #11544 - jdm:privatebrowsing, r=asajeffrey
Implement private browsing for mozbrowser <!-- Please describe your changes on the following line: --> Support the `mozprivatebrowsing` attribute on mozbrowser iframes. This separates the non-private and private sessions in terms of cookies, HSTS lists, cached HTTP credentials, HTTP connection pools, and web storage. The private session is shared between all private mozbrowsers, and lasts until shutdown. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] There are tests for these changes <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/11544) <!-- Reviewable:end -->
This commit is contained in:
commit
6166d8e74f
14 changed files with 294 additions and 138 deletions
|
@ -108,7 +108,10 @@ pub struct Constellation<Message, LTF, STF> {
|
|||
compositor_proxy: Box<CompositorProxy>,
|
||||
|
||||
/// Channels through which messages can be sent to the resource-related threads.
|
||||
resource_threads: ResourceThreads,
|
||||
public_resource_threads: ResourceThreads,
|
||||
|
||||
/// Channels through which messages can be sent to the resource-related threads.
|
||||
private_resource_threads: ResourceThreads,
|
||||
|
||||
/// A channel through which messages can be sent to the image cache thread.
|
||||
image_cache_thread: ImageCacheThread,
|
||||
|
@ -201,7 +204,9 @@ pub struct InitialConstellationState {
|
|||
/// A channel to the font cache thread.
|
||||
pub font_cache_thread: FontCacheThread,
|
||||
/// A channel to the resource thread.
|
||||
pub resource_threads: ResourceThreads,
|
||||
pub public_resource_threads: ResourceThreads,
|
||||
/// A channel to the resource thread.
|
||||
pub private_resource_threads: ResourceThreads,
|
||||
/// A channel to the time profiler thread.
|
||||
pub time_profiler_chan: time::ProfilerChan,
|
||||
/// A channel to the memory profiler thread.
|
||||
|
@ -331,7 +336,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
compositor_proxy: state.compositor_proxy,
|
||||
devtools_chan: state.devtools_chan,
|
||||
bluetooth_thread: state.bluetooth_thread,
|
||||
resource_threads: state.resource_threads,
|
||||
public_resource_threads: state.public_resource_threads,
|
||||
private_resource_threads: state.private_resource_threads,
|
||||
image_cache_thread: state.image_cache_thread,
|
||||
font_cache_thread: state.font_cache_thread,
|
||||
pipelines: HashMap::new(),
|
||||
|
@ -404,9 +410,16 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
parent_info: Option<(PipelineId, SubpageId, FrameType)>,
|
||||
initial_window_size: Option<TypedSize2D<PagePx, f32>>,
|
||||
script_channel: Option<IpcSender<ConstellationControlMsg>>,
|
||||
load_data: LoadData) {
|
||||
load_data: LoadData,
|
||||
is_private: bool) {
|
||||
if self.shutting_down { return; }
|
||||
|
||||
let resource_threads = if is_private {
|
||||
self.private_resource_threads.clone()
|
||||
} else {
|
||||
self.public_resource_threads.clone()
|
||||
};
|
||||
|
||||
let parent_visibility = if let Some((parent_pipeline_id, _, _)) = parent_info {
|
||||
self.pipelines.get(&parent_pipeline_id).map(|pipeline| pipeline.visible)
|
||||
} else {
|
||||
|
@ -425,7 +438,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
bluetooth_thread: self.bluetooth_thread.clone(),
|
||||
image_cache_thread: self.image_cache_thread.clone(),
|
||||
font_cache_thread: self.font_cache_thread.clone(),
|
||||
resource_threads: self.resource_threads.clone(),
|
||||
resource_threads: resource_threads,
|
||||
time_profiler_chan: self.time_profiler_chan.clone(),
|
||||
mem_profiler_chan: self.mem_profiler_chan.clone(),
|
||||
window_size: initial_window_size,
|
||||
|
@ -435,6 +448,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
pipeline_namespace_id: self.next_pipeline_namespace_id(),
|
||||
parent_visibility: parent_visibility,
|
||||
webrender_api_sender: self.webrender_api_sender.clone(),
|
||||
is_private: is_private,
|
||||
});
|
||||
|
||||
let (pipeline, child_process) = match result {
|
||||
|
@ -850,7 +864,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
self.image_cache_thread.exit();
|
||||
|
||||
debug!("Exiting core resource threads.");
|
||||
if let Err(e) = self.resource_threads.send(net_traits::CoreResourceMsg::Exit(core_sender)) {
|
||||
if let Err(e) = self.public_resource_threads.send(net_traits::CoreResourceMsg::Exit(core_sender)) {
|
||||
warn!("Exit resource thread failed ({})", e);
|
||||
}
|
||||
|
||||
|
@ -863,12 +877,12 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
}
|
||||
|
||||
debug!("Exiting storage resource threads.");
|
||||
if let Err(e) = self.resource_threads.send(StorageThreadMsg::Exit(storage_sender)) {
|
||||
if let Err(e) = self.public_resource_threads.send(StorageThreadMsg::Exit(storage_sender)) {
|
||||
warn!("Exit storage thread failed ({})", e);
|
||||
}
|
||||
|
||||
debug!("Exiting file manager resource threads.");
|
||||
if let Err(e) = self.resource_threads.send(FileManagerThreadMsg::Exit) {
|
||||
if let Err(e) = self.public_resource_threads.send(FileManagerThreadMsg::Exit) {
|
||||
warn!("Exit storage thread failed ({})", e);
|
||||
}
|
||||
|
||||
|
@ -951,7 +965,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
|
||||
let new_pipeline_id = PipelineId::new();
|
||||
let load_data = LoadData::new(failure_url, None, None);
|
||||
self.new_pipeline(new_pipeline_id, parent_info, window_size, None, load_data);
|
||||
self.new_pipeline(new_pipeline_id, parent_info, window_size, None, load_data, false);
|
||||
|
||||
self.push_pending_frame(new_pipeline_id, Some(pipeline_id));
|
||||
|
||||
|
@ -965,7 +979,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
let root_pipeline_id = PipelineId::new();
|
||||
debug_assert!(PipelineId::fake_root_pipeline_id() == root_pipeline_id);
|
||||
self.new_pipeline(root_pipeline_id, None, Some(window_size), None,
|
||||
LoadData::new(url.clone(), None, None));
|
||||
LoadData::new(url.clone(), None, None), false);
|
||||
self.handle_load_start_msg(&root_pipeline_id);
|
||||
self.push_pending_frame(root_pipeline_id, None);
|
||||
self.compositor_proxy.send(ToCompositorMsg::ChangePageUrl(root_pipeline_id, url));
|
||||
|
@ -1030,7 +1044,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
.and_then(|old_subpage_id| self.subpage_map.get(&(load_info.containing_pipeline_id, old_subpage_id)))
|
||||
.cloned();
|
||||
|
||||
let (load_data, script_chan, window_size) = {
|
||||
let (load_data, script_chan, window_size, is_private) = {
|
||||
let old_pipeline = old_pipeline_id
|
||||
.and_then(|old_pipeline_id| self.pipelines.get(&old_pipeline_id));
|
||||
|
||||
|
@ -1054,11 +1068,14 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
// then reuse the script thread in creating the new pipeline
|
||||
let source_url = &source_pipeline.url;
|
||||
|
||||
let is_private = load_info.is_private || source_pipeline.is_private;
|
||||
|
||||
// FIXME(#10968): this should probably match the origin check in
|
||||
// HTMLIFrameElement::contentDocument.
|
||||
let same_script = source_url.host() == load_data.url.host() &&
|
||||
source_url.port() == load_data.url.port() &&
|
||||
load_info.sandbox == IFrameSandboxState::IFrameUnsandboxed;
|
||||
load_info.sandbox == IFrameSandboxState::IFrameUnsandboxed &&
|
||||
source_pipeline.is_private == is_private;
|
||||
|
||||
// Reuse the script thread if the URL is same-origin
|
||||
let script_chan = if same_script {
|
||||
|
@ -1077,16 +1094,17 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
old_pipeline.freeze();
|
||||
}
|
||||
|
||||
(load_data, script_chan, window_size)
|
||||
|
||||
(load_data, script_chan, window_size, is_private)
|
||||
};
|
||||
|
||||
|
||||
// Create the new pipeline, attached to the parent and push to pending frames
|
||||
self.new_pipeline(load_info.new_pipeline_id,
|
||||
Some((load_info.containing_pipeline_id, load_info.new_subpage_id, load_info.frame_type)),
|
||||
window_size,
|
||||
script_chan,
|
||||
load_data);
|
||||
load_data,
|
||||
is_private);
|
||||
|
||||
self.subpage_map.insert((load_info.containing_pipeline_id, load_info.new_subpage_id),
|
||||
load_info.new_pipeline_id);
|
||||
|
@ -1217,7 +1235,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
// Create the new pipeline
|
||||
let window_size = self.pipelines.get(&source_id).and_then(|source| source.size);
|
||||
let new_pipeline_id = PipelineId::new();
|
||||
self.new_pipeline(new_pipeline_id, None, window_size, None, load_data);
|
||||
self.new_pipeline(new_pipeline_id, None, window_size, None, load_data, false);
|
||||
self.push_pending_frame(new_pipeline_id, Some(source_id));
|
||||
|
||||
// Send message to ScriptThread that will suspend all timers
|
||||
|
@ -1906,25 +1924,6 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
|||
ReadyToSave::Ready
|
||||
}
|
||||
|
||||
/// Checks whether the pipeline or its ancestors are private
|
||||
#[allow(dead_code)]
|
||||
fn check_is_pipeline_private(&self, mut pipeline_id: PipelineId) -> bool {
|
||||
loop {
|
||||
match self.pipelines.get(&pipeline_id) {
|
||||
Some(pipeline) if pipeline.is_private => return true,
|
||||
Some(pipeline) => match pipeline.parent_info {
|
||||
None => return false,
|
||||
Some((_, _, FrameType::MozBrowserIFrame)) => return false,
|
||||
Some((parent_id, _, _)) => pipeline_id = parent_id,
|
||||
},
|
||||
None => {
|
||||
warn!("Finding private ancestor for pipeline {} after closure.", pipeline_id);
|
||||
return false;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close a frame (and all children)
|
||||
fn close_frame(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) {
|
||||
// Store information about the pipelines to be closed. Then close the
|
||||
|
|
|
@ -64,6 +64,7 @@ pub struct Pipeline {
|
|||
/// animations cause composites to be continually scheduled.
|
||||
pub running_animations: bool,
|
||||
pub children: Vec<FrameId>,
|
||||
/// Whether this pipeline is considered distinct from public pipelines.
|
||||
pub is_private: bool,
|
||||
/// Whether this pipeline should be treated as visible for the purposes of scheduling and
|
||||
/// resource management.
|
||||
|
@ -119,6 +120,8 @@ pub struct InitialPipelineState {
|
|||
pub parent_visibility: Option<bool>,
|
||||
/// Optional webrender api (if enabled).
|
||||
pub webrender_api_sender: Option<webrender_traits::RenderApiSender>,
|
||||
/// Whether this pipeline is considered private.
|
||||
pub is_private: bool,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
|
@ -254,6 +257,7 @@ impl Pipeline {
|
|||
pipeline_chan,
|
||||
state.compositor_proxy,
|
||||
chrome_to_paint_chan,
|
||||
state.is_private,
|
||||
state.load_data.url,
|
||||
state.window_size,
|
||||
state.parent_visibility.unwrap_or(true));
|
||||
|
@ -269,6 +273,7 @@ impl Pipeline {
|
|||
layout_chan: IpcSender<LayoutControlMsg>,
|
||||
compositor_proxy: Box<CompositorProxy + 'static + Send>,
|
||||
chrome_to_paint_chan: Sender<ChromeToPaintMsg>,
|
||||
is_private: bool,
|
||||
url: Url,
|
||||
size: Option<TypedSize2D<PagePx, f32>>,
|
||||
visible: bool)
|
||||
|
@ -285,8 +290,8 @@ impl Pipeline {
|
|||
children: vec!(),
|
||||
size: size,
|
||||
running_animations: false,
|
||||
is_private: false,
|
||||
visible: visible,
|
||||
is_private: is_private,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,11 +19,13 @@ use http_loader::{self, HttpState};
|
|||
use hyper::client::pool::Pool;
|
||||
use hyper::header::{ContentType, Header, SetCookie};
|
||||
use hyper::mime::{Mime, SubLevel, TopLevel};
|
||||
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
|
||||
use ipc_channel::ipc::{self, IpcReceiver, IpcSender, IpcReceiverSet};
|
||||
use mime_classifier::{ApacheBugFlag, MIMEClassifier, NoSniffFlag};
|
||||
use net_traits::LoadContext;
|
||||
use net_traits::ProgressMsg::Done;
|
||||
use net_traits::filemanager_thread::FileManagerThreadMsg;
|
||||
use net_traits::request::{Request, RequestInit};
|
||||
use net_traits::storage_thread::StorageThreadMsg;
|
||||
use net_traits::{AsyncResponseTarget, Metadata, ProgressMsg, ResponseAction, CoreResourceThread};
|
||||
use net_traits::{CoreResourceMsg, CookieSource, FetchResponseMsg, FetchTaskTarget, LoadConsumer};
|
||||
use net_traits::{LoadData, LoadResponse, NetworkError, ResourceId};
|
||||
|
@ -56,6 +58,14 @@ pub enum ProgressSender {
|
|||
Listener(AsyncResponseTarget),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ResourceGroup {
|
||||
cookie_jar: Arc<RwLock<CookieStorage>>,
|
||||
auth_cache: Arc<RwLock<AuthCache>>,
|
||||
hsts_list: Arc<RwLock<HstsList>>,
|
||||
connector: Arc<Pool<Connector>>,
|
||||
}
|
||||
|
||||
impl ProgressSender {
|
||||
//XXXjdm return actual error
|
||||
pub fn send(&self, msg: ProgressMsg) -> Result<(), ()> {
|
||||
|
@ -158,56 +168,117 @@ fn start_sending_opt(start_chan: LoadConsumer, metadata: Metadata,
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a tuple of (public, private) senders to the new threads.
|
||||
pub fn new_resource_threads(user_agent: String,
|
||||
devtools_chan: Option<Sender<DevtoolsControlMsg>>,
|
||||
profiler_chan: ProfilerChan) -> ResourceThreads {
|
||||
ResourceThreads::new(new_core_resource_thread(user_agent, devtools_chan, profiler_chan),
|
||||
StorageThreadFactory::new(),
|
||||
FileManagerThreadFactory::new(TFD_PROVIDER))
|
||||
profiler_chan: ProfilerChan) -> (ResourceThreads, ResourceThreads) {
|
||||
let (public_core, private_core) = new_core_resource_thread(user_agent, devtools_chan, profiler_chan);
|
||||
let storage: IpcSender<StorageThreadMsg> = StorageThreadFactory::new();
|
||||
let filemanager: IpcSender<FileManagerThreadMsg> = FileManagerThreadFactory::new(TFD_PROVIDER);
|
||||
(ResourceThreads::new(public_core, storage.clone(), filemanager.clone()),
|
||||
ResourceThreads::new(private_core, storage, filemanager))
|
||||
}
|
||||
|
||||
|
||||
/// Create a CoreResourceThread
|
||||
pub fn new_core_resource_thread(user_agent: String,
|
||||
devtools_chan: Option<Sender<DevtoolsControlMsg>>,
|
||||
profiler_chan: ProfilerChan) -> CoreResourceThread {
|
||||
let hsts_preload = HstsList::from_servo_preload();
|
||||
let (setup_chan, setup_port) = ipc::channel().unwrap();
|
||||
let setup_chan_clone = setup_chan.clone();
|
||||
profiler_chan: ProfilerChan)
|
||||
-> (CoreResourceThread, CoreResourceThread) {
|
||||
let (public_setup_chan, public_setup_port) = ipc::channel().unwrap();
|
||||
let (private_setup_chan, private_setup_port) = ipc::channel().unwrap();
|
||||
let public_setup_chan_clone = public_setup_chan.clone();
|
||||
let private_setup_chan_clone = private_setup_chan.clone();
|
||||
spawn_named("ResourceManager".to_owned(), move || {
|
||||
let resource_manager = CoreResourceManager::new(
|
||||
user_agent, hsts_preload, devtools_chan, profiler_chan
|
||||
user_agent, devtools_chan, profiler_chan
|
||||
);
|
||||
|
||||
let mut channel_manager = ResourceChannelManager {
|
||||
from_client: setup_port,
|
||||
resource_manager: resource_manager
|
||||
resource_manager: resource_manager,
|
||||
};
|
||||
channel_manager.start(setup_chan_clone);
|
||||
channel_manager.start(public_setup_chan_clone,
|
||||
private_setup_chan_clone,
|
||||
public_setup_port,
|
||||
private_setup_port);
|
||||
});
|
||||
setup_chan
|
||||
(public_setup_chan, private_setup_chan)
|
||||
}
|
||||
|
||||
struct ResourceChannelManager {
|
||||
from_client: IpcReceiver<CoreResourceMsg>,
|
||||
resource_manager: CoreResourceManager
|
||||
resource_manager: CoreResourceManager,
|
||||
}
|
||||
|
||||
fn create_resource_groups() -> (ResourceGroup, ResourceGroup) {
|
||||
let mut hsts_list = HstsList::from_servo_preload();
|
||||
let mut auth_cache = AuthCache::new();
|
||||
let mut cookie_jar = CookieStorage::new();
|
||||
if let Some(ref config_dir) = opts::get().config_dir {
|
||||
read_json_from_file(&mut auth_cache, config_dir, "auth_cache.json");
|
||||
read_json_from_file(&mut hsts_list, config_dir, "hsts_list.json");
|
||||
read_json_from_file(&mut cookie_jar, config_dir, "cookie_jar.json");
|
||||
}
|
||||
let resource_group = ResourceGroup {
|
||||
cookie_jar: Arc::new(RwLock::new(cookie_jar)),
|
||||
auth_cache: Arc::new(RwLock::new(auth_cache)),
|
||||
hsts_list: Arc::new(RwLock::new(hsts_list.clone())),
|
||||
connector: create_http_connector(),
|
||||
};
|
||||
let private_resource_group = ResourceGroup {
|
||||
cookie_jar: Arc::new(RwLock::new(CookieStorage::new())),
|
||||
auth_cache: Arc::new(RwLock::new(AuthCache::new())),
|
||||
hsts_list: Arc::new(RwLock::new(HstsList::new())),
|
||||
connector: create_http_connector(),
|
||||
};
|
||||
(resource_group, private_resource_group)
|
||||
}
|
||||
|
||||
impl ResourceChannelManager {
|
||||
fn start(&mut self, control_sender: CoreResourceThread) {
|
||||
#[allow(unsafe_code)]
|
||||
fn start(&mut self,
|
||||
public_control_sender: CoreResourceThread,
|
||||
private_control_sender: CoreResourceThread,
|
||||
public_receiver: IpcReceiver<CoreResourceMsg>,
|
||||
private_receiver: IpcReceiver<CoreResourceMsg>) {
|
||||
let (public_resource_group, private_resource_group) = create_resource_groups();
|
||||
|
||||
let mut rx_set = IpcReceiverSet::new().unwrap();
|
||||
let private_id = rx_set.add(private_receiver).unwrap();
|
||||
let public_id = rx_set.add(public_receiver).unwrap();
|
||||
|
||||
loop {
|
||||
match self.from_client.recv().unwrap() {
|
||||
for (id, data) in rx_set.select().unwrap().into_iter().map(|m| m.unwrap()) {
|
||||
let (group, sender) = if id == private_id {
|
||||
(&private_resource_group, &private_control_sender)
|
||||
} else {
|
||||
assert_eq!(id, public_id);
|
||||
(&public_resource_group, &public_control_sender)
|
||||
};
|
||||
if let Ok(msg) = data.to() {
|
||||
if !self.process_msg(msg, group, &sender) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns false if the thread should exit.
|
||||
fn process_msg(&mut self,
|
||||
msg: CoreResourceMsg,
|
||||
group: &ResourceGroup,
|
||||
control_sender: &CoreResourceThread) -> bool {
|
||||
match msg {
|
||||
CoreResourceMsg::Load(load_data, consumer, id_sender) =>
|
||||
self.resource_manager.load(load_data, consumer, id_sender, control_sender.clone()),
|
||||
self.resource_manager.load(load_data, consumer, id_sender, control_sender.clone(), group),
|
||||
CoreResourceMsg::Fetch(init, sender) =>
|
||||
self.resource_manager.fetch(init, sender),
|
||||
self.resource_manager.fetch(init, sender, group),
|
||||
CoreResourceMsg::WebsocketConnect(connect, connect_data) =>
|
||||
self.resource_manager.websocket_connect(connect, connect_data),
|
||||
self.resource_manager.websocket_connect(connect, connect_data, group),
|
||||
CoreResourceMsg::SetCookiesForUrl(request, cookie_list, source) =>
|
||||
self.resource_manager.set_cookies_for_url(request, cookie_list, source),
|
||||
self.resource_manager.set_cookies_for_url(request, cookie_list, source, group),
|
||||
CoreResourceMsg::GetCookiesForUrl(url, consumer, source) => {
|
||||
let cookie_jar = &self.resource_manager.cookie_jar;
|
||||
let mut cookie_jar = cookie_jar.write().unwrap();
|
||||
let mut cookie_jar = group.cookie_jar.write().unwrap();
|
||||
consumer.send(cookie_jar.cookies_for_url(&url, source)).unwrap();
|
||||
}
|
||||
CoreResourceMsg::Cancel(res_id) => {
|
||||
|
@ -221,25 +292,24 @@ impl ResourceChannelManager {
|
|||
}
|
||||
CoreResourceMsg::Exit(sender) => {
|
||||
if let Some(ref config_dir) = opts::get().config_dir {
|
||||
match self.resource_manager.auth_cache.read() {
|
||||
match group.auth_cache.read() {
|
||||
Ok(auth_cache) => write_json_to_file(&*auth_cache, config_dir, "auth_cache.json"),
|
||||
Err(_) => warn!("Error writing auth cache to disk"),
|
||||
}
|
||||
match self.resource_manager.cookie_jar.read() {
|
||||
match group.cookie_jar.read() {
|
||||
Ok(jar) => write_json_to_file(&*jar, config_dir, "cookie_jar.json"),
|
||||
Err(_) => warn!("Error writing cookie jar to disk"),
|
||||
}
|
||||
match self.resource_manager.hsts_list.read() {
|
||||
match group.hsts_list.read() {
|
||||
Ok(hsts) => write_json_to_file(&*hsts, config_dir, "hsts_list.json"),
|
||||
Err(_) => warn!("Error writing hsts list to disk"),
|
||||
}
|
||||
}
|
||||
let _ = sender.send(());
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -381,50 +451,37 @@ pub struct AuthCache {
|
|||
|
||||
pub struct CoreResourceManager {
|
||||
user_agent: String,
|
||||
cookie_jar: Arc<RwLock<CookieStorage>>,
|
||||
auth_cache: Arc<RwLock<AuthCache>>,
|
||||
mime_classifier: Arc<MIMEClassifier>,
|
||||
devtools_chan: Option<Sender<DevtoolsControlMsg>>,
|
||||
profiler_chan: ProfilerChan,
|
||||
hsts_list: Arc<RwLock<HstsList>>,
|
||||
connector: Arc<Pool<Connector>>,
|
||||
cancel_load_map: HashMap<ResourceId, Sender<()>>,
|
||||
next_resource_id: ResourceId,
|
||||
}
|
||||
|
||||
impl CoreResourceManager {
|
||||
pub fn new(user_agent: String,
|
||||
mut hsts_list: HstsList,
|
||||
devtools_channel: Option<Sender<DevtoolsControlMsg>>,
|
||||
profiler_chan: ProfilerChan) -> CoreResourceManager {
|
||||
let mut auth_cache = AuthCache::new();
|
||||
let mut cookie_jar = CookieStorage::new();
|
||||
if let Some(ref config_dir) = opts::get().config_dir {
|
||||
read_json_from_file(&mut auth_cache, config_dir, "auth_cache.json");
|
||||
read_json_from_file(&mut hsts_list, config_dir, "hsts_list.json");
|
||||
read_json_from_file(&mut cookie_jar, config_dir, "cookie_jar.json");
|
||||
}
|
||||
CoreResourceManager {
|
||||
user_agent: user_agent,
|
||||
cookie_jar: Arc::new(RwLock::new(cookie_jar)),
|
||||
auth_cache: Arc::new(RwLock::new(auth_cache)),
|
||||
mime_classifier: Arc::new(MIMEClassifier::new()),
|
||||
devtools_chan: devtools_channel,
|
||||
profiler_chan: profiler_chan,
|
||||
hsts_list: Arc::new(RwLock::new(hsts_list)),
|
||||
connector: create_http_connector(),
|
||||
cancel_load_map: HashMap::new(),
|
||||
next_resource_id: ResourceId(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cookies_for_url(&mut self, request: Url, cookie_list: String, source: CookieSource) {
|
||||
fn set_cookies_for_url(&mut self,
|
||||
request: Url,
|
||||
cookie_list: String,
|
||||
source: CookieSource,
|
||||
resource_group: &ResourceGroup) {
|
||||
let header = Header::parse_header(&[cookie_list.into_bytes()]);
|
||||
if let Ok(SetCookie(cookies)) = header {
|
||||
for bare_cookie in cookies {
|
||||
if let Some(cookie) = cookie::Cookie::new_wrapped(bare_cookie, &request, source) {
|
||||
let cookie_jar = &self.cookie_jar;
|
||||
let mut cookie_jar = cookie_jar.write().unwrap();
|
||||
let mut cookie_jar = resource_group.cookie_jar.write().unwrap();
|
||||
cookie_jar.push(cookie, source);
|
||||
}
|
||||
}
|
||||
|
@ -435,7 +492,8 @@ impl CoreResourceManager {
|
|||
load_data: LoadData,
|
||||
consumer: LoadConsumer,
|
||||
id_sender: Option<IpcSender<ResourceId>>,
|
||||
resource_thread: CoreResourceThread) {
|
||||
resource_thread: CoreResourceThread,
|
||||
resource_grp: &ResourceGroup) {
|
||||
fn from_factory(factory: fn(LoadData, LoadConsumer, Arc<MIMEClassifier>, CancellationListener))
|
||||
-> Box<FnBox(LoadData,
|
||||
LoadConsumer,
|
||||
|
@ -461,16 +519,16 @@ impl CoreResourceManager {
|
|||
"file" => from_factory(file_loader::factory),
|
||||
"http" | "https" | "view-source" => {
|
||||
let http_state = HttpState {
|
||||
hsts_list: self.hsts_list.clone(),
|
||||
cookie_jar: self.cookie_jar.clone(),
|
||||
auth_cache: self.auth_cache.clone(),
|
||||
blocked_content: BLOCKED_CONTENT_RULES.clone(),
|
||||
hsts_list: resource_grp.hsts_list.clone(),
|
||||
cookie_jar: resource_grp.cookie_jar.clone(),
|
||||
auth_cache: resource_grp.auth_cache.clone()
|
||||
};
|
||||
http_loader::factory(self.user_agent.clone(),
|
||||
http_state,
|
||||
self.devtools_chan.clone(),
|
||||
self.profiler_chan.clone(),
|
||||
self.connector.clone())
|
||||
resource_grp.connector.clone())
|
||||
},
|
||||
"data" => from_factory(data_loader::factory),
|
||||
"about" => from_factory(about_loader::factory),
|
||||
|
@ -488,11 +546,14 @@ impl CoreResourceManager {
|
|||
cancel_listener));
|
||||
}
|
||||
|
||||
fn fetch(&self, init: RequestInit, sender: IpcSender<FetchResponseMsg>) {
|
||||
fn fetch(&self,
|
||||
init: RequestInit,
|
||||
sender: IpcSender<FetchResponseMsg>,
|
||||
group: &ResourceGroup) {
|
||||
let http_state = HttpState {
|
||||
hsts_list: self.hsts_list.clone(),
|
||||
cookie_jar: self.cookie_jar.clone(),
|
||||
auth_cache: self.auth_cache.clone(),
|
||||
hsts_list: group.hsts_list.clone(),
|
||||
cookie_jar: group.cookie_jar.clone(),
|
||||
auth_cache: group.auth_cache.clone(),
|
||||
blocked_content: BLOCKED_CONTENT_RULES.clone(),
|
||||
};
|
||||
let ua = self.user_agent.clone();
|
||||
|
@ -510,7 +571,8 @@ impl CoreResourceManager {
|
|||
|
||||
fn websocket_connect(&self,
|
||||
connect: WebSocketCommunicate,
|
||||
connect_data: WebSocketConnectData) {
|
||||
websocket_loader::init(connect, connect_data, self.cookie_jar.clone());
|
||||
connect_data: WebSocketConnectData,
|
||||
resource_grp: &ResourceGroup) {
|
||||
websocket_loader::init(connect, connect_data, resource_grp.cookie_jar.clone());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ use msg::constellation_msg::{PipelineId, ReferrerPolicy};
|
|||
use request::{Request, RequestInit};
|
||||
use response::{HttpsState, Response};
|
||||
use std::io::Error as IOError;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::thread;
|
||||
use storage_thread::StorageThreadMsg;
|
||||
use url::Url;
|
||||
|
@ -649,9 +648,10 @@ pub fn unwrap_websocket_protocol(wsp: Option<&header::WebSocketProtocol>) -> Opt
|
|||
#[derive(Clone, PartialEq, Eq, Copy, Hash, Debug, Deserialize, Serialize, HeapSizeOf)]
|
||||
pub struct ResourceId(pub u32);
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub enum ConstellationMsg {
|
||||
/// Queries whether a pipeline or its ancestors are private
|
||||
IsPrivate(PipelineId, Sender<bool>),
|
||||
IsPrivate(PipelineId, IpcSender<bool>),
|
||||
}
|
||||
|
||||
/// Network errors that have to be exported out of the loaders
|
||||
|
|
|
@ -562,6 +562,21 @@ impl HTMLIFrameElementMethods for HTMLIFrameElement {
|
|||
make_getter!(Height, "height");
|
||||
// https://html.spec.whatwg.org/multipage/#dom-dim-height
|
||||
make_dimension_setter!(SetHeight, "height");
|
||||
|
||||
// check-tidy: no specs after this line
|
||||
fn SetMozprivatebrowsing(&self, value: bool) {
|
||||
let element = self.upcast::<Element>();
|
||||
element.set_bool_attribute(&Atom::from("mozprivatebrowsing"), value);
|
||||
}
|
||||
|
||||
fn Mozprivatebrowsing(&self) -> bool {
|
||||
if window_from_node(self).is_mozbrowser() {
|
||||
let element = self.upcast::<Element>();
|
||||
element.has_attribute(&Atom::from("mozprivatebrowsing"))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualMethods for HTMLIFrameElement {
|
||||
|
|
|
@ -33,6 +33,9 @@ partial interface HTMLIFrameElement {
|
|||
partial interface HTMLIFrameElement {
|
||||
[Func="::dom::window::Window::global_is_mozbrowser"]
|
||||
attribute boolean mozbrowser;
|
||||
|
||||
[Func="::dom::window::Window::global_is_mozbrowser"]
|
||||
attribute boolean mozprivatebrowsing;
|
||||
};
|
||||
|
||||
HTMLIFrameElement implements BrowserElement;
|
||||
|
|
|
@ -209,12 +209,13 @@ fn create_constellation(opts: opts::Opts,
|
|||
webrender_api_sender: Option<webrender_traits::RenderApiSender>) -> Sender<ConstellationMsg> {
|
||||
let bluetooth_thread: IpcSender<BluetoothMethodMsg> = BluetoothThreadFactory::new();
|
||||
|
||||
let resource_threads = new_resource_threads(opts.user_agent.clone(),
|
||||
let (public_resource_threads, private_resource_threads) =
|
||||
new_resource_threads(opts.user_agent.clone(),
|
||||
devtools_chan.clone(),
|
||||
time_profiler_chan.clone());
|
||||
let image_cache_thread = new_image_cache_thread(resource_threads.sender(),
|
||||
let image_cache_thread = new_image_cache_thread(public_resource_threads.sender(),
|
||||
webrender_api_sender.as_ref().map(|wr| wr.create_api()));
|
||||
let font_cache_thread = FontCacheThread::new(resource_threads.sender(),
|
||||
let font_cache_thread = FontCacheThread::new(public_resource_threads.sender(),
|
||||
webrender_api_sender.as_ref().map(|wr| wr.create_api()));
|
||||
|
||||
let initial_state = InitialConstellationState {
|
||||
|
@ -223,7 +224,8 @@ fn create_constellation(opts: opts::Opts,
|
|||
bluetooth_thread: bluetooth_thread,
|
||||
image_cache_thread: image_cache_thread,
|
||||
font_cache_thread: font_cache_thread,
|
||||
resource_threads: resource_threads,
|
||||
public_resource_threads: public_resource_threads,
|
||||
private_resource_threads: private_resource_threads,
|
||||
time_profiler_chan: time_profiler_chan,
|
||||
mem_profiler_chan: mem_profiler_chan,
|
||||
supports_clipboard: supports_clipboard,
|
||||
|
|
|
@ -40,7 +40,7 @@ impl LoadOrigin for ResourceTest {
|
|||
fn test_exit() {
|
||||
let (tx, _rx) = ipc::channel().unwrap();
|
||||
let (sender, receiver) = ipc::channel().unwrap();
|
||||
let resource_thread = new_core_resource_thread("".to_owned(), None, ProfilerChan(tx));
|
||||
let (resource_thread, _) = new_core_resource_thread("".to_owned(), None, ProfilerChan(tx));
|
||||
resource_thread.send(CoreResourceMsg::Exit(sender)).unwrap();
|
||||
receiver.recv().unwrap();
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ fn test_exit() {
|
|||
fn test_bad_scheme() {
|
||||
let (tx, _rx) = ipc::channel().unwrap();
|
||||
let (sender, receiver) = ipc::channel().unwrap();
|
||||
let resource_thread = new_core_resource_thread("".to_owned(), None, ProfilerChan(tx));
|
||||
let (resource_thread, _) = new_core_resource_thread("".to_owned(), None, ProfilerChan(tx));
|
||||
let (start_chan, start) = ipc::channel().unwrap();
|
||||
let url = Url::parse("bogus://whatever").unwrap();
|
||||
resource_thread.send(CoreResourceMsg::Load(LoadData::new(LoadContext::Browsing, url, &ResourceTest),
|
||||
|
@ -228,7 +228,7 @@ fn test_cancelled_listener() {
|
|||
|
||||
let (tx, _rx) = ipc::channel().unwrap();
|
||||
let (exit_sender, exit_receiver) = ipc::channel().unwrap();
|
||||
let resource_thread = new_core_resource_thread("".to_owned(), None, ProfilerChan(tx));
|
||||
let (resource_thread, _) = new_core_resource_thread("".to_owned(), None, ProfilerChan(tx));
|
||||
let (sender, receiver) = ipc::channel().unwrap();
|
||||
let (id_sender, id_receiver) = ipc::channel().unwrap();
|
||||
let (sync_sender, sync_receiver) = ipc::channel().unwrap();
|
||||
|
|
|
@ -6616,6 +6616,12 @@
|
|||
"url": "/_mozilla/mozilla/mozbrowser/mozbrowsertitlechangedeagerly_event.html"
|
||||
}
|
||||
],
|
||||
"mozilla/mozbrowser/private_browsing.html": [
|
||||
{
|
||||
"path": "mozilla/mozbrowser/private_browsing.html",
|
||||
"url": "/_mozilla/mozilla/mozbrowser/private_browsing.html"
|
||||
}
|
||||
],
|
||||
"mozilla/mozbrowser/redirect.html": [
|
||||
{
|
||||
"path": "mozilla/mozbrowser/redirect.html",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<html><body><div id="test">Normal iFrame</div></body>
|
||||
<script>alert(document.cookie)</script>
|
||||
</html>
|
|
@ -0,0 +1,4 @@
|
|||
<span id="cookie"></span>
|
||||
<script>
|
||||
document.querySelector('#cookie').innerHTML = document.cookie;
|
||||
</script>
|
|
@ -0,0 +1,13 @@
|
|||
<html>
|
||||
<body>
|
||||
<div id="test">Private iFrame</div>
|
||||
</body>
|
||||
<script>document.cookie = "private=active;path=/";</script>
|
||||
<iframe src="iframe_privateContent_grandchild.html"></iframe>
|
||||
<script>
|
||||
var iframe = document.querySelector('iframe');
|
||||
iframe.onload = function() {
|
||||
alert(document.cookie + ' ' + iframe.contentDocument.querySelector('#cookie').textContent);
|
||||
};
|
||||
</script>
|
||||
</html>
|
|
@ -0,0 +1,44 @@
|
|||
<head>
|
||||
<meta charset="utf8" />
|
||||
<title>Private browsing</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
async_test(function(t) {
|
||||
var privateFrame = document.createElement("iframe");
|
||||
privateFrame.mozbrowser = true;
|
||||
privateFrame.mozprivatebrowsing = true;
|
||||
|
||||
var gotGrandchildResult = false;
|
||||
privateFrame.addEventListener("mozbrowsershowmodalprompt", t.step_func(e => {
|
||||
assert_equals(e.detail.message, 'private=active private=active');
|
||||
gotGrandchildResult = true;
|
||||
}));
|
||||
|
||||
privateFrame.onload = t.step_func(function() {
|
||||
assert_true(gotGrandchildResult);
|
||||
|
||||
var parent = privateFrame.parentNode;
|
||||
parent.removeChild(privateFrame);
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var promptDisplay = false;
|
||||
iframe.mozbrowser = true;
|
||||
iframe.onload = t.step_func(function() {
|
||||
assert_true(promptDisplay);
|
||||
t.done();
|
||||
});
|
||||
iframe.addEventListener("mozbrowsershowmodalprompt", t.step_func(e => {
|
||||
promptDisplay = true;
|
||||
assert_equals(e.detail.message, "");
|
||||
}));
|
||||
iframe.src = "iframe_contentDocument_inner.html";
|
||||
parent.appendChild(iframe);
|
||||
});
|
||||
privateFrame.src = "iframe_privateContent_inner.html";
|
||||
document.body.appendChild(privateFrame);
|
||||
});
|
||||
</script>
|
||||
</body>
|
Loading…
Add table
Add a link
Reference in a new issue