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:
bors-servo 2016-06-20 14:32:17 -05:00 committed by GitHub
commit 6166d8e74f
14 changed files with 294 additions and 138 deletions

View file

@ -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

View file

@ -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,
}
}

View file

@ -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,89 +168,149 @@ 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) {
loop {
match self.from_client.recv().unwrap() {
CoreResourceMsg::Load(load_data, consumer, id_sender) =>
self.resource_manager.load(load_data, consumer, id_sender, control_sender.clone()),
CoreResourceMsg::Fetch(init, sender) =>
self.resource_manager.fetch(init, sender),
CoreResourceMsg::WebsocketConnect(connect, connect_data) =>
self.resource_manager.websocket_connect(connect, connect_data),
CoreResourceMsg::SetCookiesForUrl(request, cookie_list, source) =>
self.resource_manager.set_cookies_for_url(request, cookie_list, source),
CoreResourceMsg::GetCookiesForUrl(url, consumer, source) => {
let cookie_jar = &self.resource_manager.cookie_jar;
let mut cookie_jar = cookie_jar.write().unwrap();
consumer.send(cookie_jar.cookies_for_url(&url, source)).unwrap();
}
CoreResourceMsg::Cancel(res_id) => {
if let Some(cancel_sender) = self.resource_manager.cancel_load_map.get(&res_id) {
let _ = cancel_sender.send(());
}
self.resource_manager.cancel_load_map.remove(&res_id);
}
CoreResourceMsg::Synchronize(sender) => {
let _ = sender.send(());
}
CoreResourceMsg::Exit(sender) => {
if let Some(ref config_dir) = opts::get().config_dir {
match self.resource_manager.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() {
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() {
Ok(hsts) => write_json_to_file(&*hsts, config_dir, "hsts_list.json"),
Err(_) => warn!("Error writing hsts list to disk"),
}
}
let _ = sender.send(());
break;
}
#[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 {
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(), group),
CoreResourceMsg::Fetch(init, sender) =>
self.resource_manager.fetch(init, sender, group),
CoreResourceMsg::WebsocketConnect(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, group),
CoreResourceMsg::GetCookiesForUrl(url, consumer, source) => {
let mut cookie_jar = group.cookie_jar.write().unwrap();
consumer.send(cookie_jar.cookies_for_url(&url, source)).unwrap();
}
CoreResourceMsg::Cancel(res_id) => {
if let Some(cancel_sender) = self.resource_manager.cancel_load_map.get(&res_id) {
let _ = cancel_sender.send(());
}
self.resource_manager.cancel_load_map.remove(&res_id);
}
CoreResourceMsg::Synchronize(sender) => {
let _ = sender.send(());
}
CoreResourceMsg::Exit(sender) => {
if let Some(ref config_dir) = opts::get().config_dir {
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 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 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(());
return false;
}
}
true
}
}
pub fn read_json_from_file<T: Decodable>(data: &mut T, config_dir: &str, filename: &str) {
@ -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());
}
}

View file

@ -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

View file

@ -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 {

View file

@ -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;

View file

@ -489,8 +489,8 @@ impl Runnable for ConnectionEstablishedTask {
for cookie in cookies.iter() {
if let Ok(cookie_value) = String::from_utf8(cookie.clone()) {
let _ = ws.global().r().core_resource_thread().send(SetCookiesForUrl(ws.url.clone(),
cookie_value,
HTTP));
cookie_value,
HTTP));
}
}
}

View file

@ -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(),
devtools_chan.clone(),
time_profiler_chan.clone());
let image_cache_thread = new_image_cache_thread(resource_threads.sender(),
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(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,

View file

@ -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();

View file

@ -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",

View file

@ -0,0 +1,3 @@
<html><body><div id="test">Normal iFrame</div></body>
<script>alert(document.cookie)</script>
</html>

View file

@ -0,0 +1,4 @@
<span id="cookie"></span>
<script>
document.querySelector('#cookie').innerHTML = document.cookie;
</script>

View file

@ -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>

View file

@ -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>