Merge pull request #3789 from atom/template-web-request

Cleanup the webRequest and cookies code
This commit is contained in:
Cheng Zhao 2015-12-12 16:34:10 +08:00
commit 84cea1b494
15 changed files with 956 additions and 852 deletions

View file

@ -7,7 +7,6 @@
#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/callback.h"
#include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h"
#include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/native_mate_converters/value_converter.h"
#include "base/bind.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/values.h" #include "base/values.h"
#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_context.h"
@ -20,139 +19,21 @@
#include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_context_getter.h"
using atom::api::Cookies;
using content::BrowserThread; using content::BrowserThread;
namespace {
bool GetCookieListFromStore(
net::CookieStore* cookie_store,
const std::string& url,
const net::CookieMonster::GetCookieListCallback& callback) {
DCHECK(cookie_store);
GURL gurl(url);
net::CookieMonster* monster = cookie_store->GetCookieMonster();
// Empty url will match all url cookies.
if (url.empty()) {
monster->GetAllCookiesAsync(callback);
return true;
}
if (!gurl.is_valid())
return false;
monster->GetAllCookiesForURLAsync(gurl, callback);
return true;
}
void RunGetCookiesCallbackOnUIThread(v8::Isolate* isolate,
const std::string& error_message,
const net::CookieList& cookie_list,
const Cookies::CookiesCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
v8::Locker locker(isolate);
v8::HandleScope handle_scope(isolate);
if (!error_message.empty()) {
v8::Local<v8::Value> error = mate::ConvertToV8(isolate, error_message);
callback.Run(error, v8::Null(isolate));
return;
}
callback.Run(v8::Null(isolate), mate::ConvertToV8(isolate, cookie_list));
}
void RunRemoveCookiesCallbackOnUIThread(
v8::Isolate* isolate,
const std::string& error_message,
const Cookies::CookiesCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
v8::Locker locker(isolate);
v8::HandleScope handle_scope(isolate);
if (!error_message.empty()) {
v8::Local<v8::Value> error = mate::ConvertToV8(isolate, error_message);
callback.Run(error, v8::Null(isolate));
return;
}
callback.Run(v8::Null(isolate), v8::Null(isolate));
}
void RunSetCookiesCallbackOnUIThread(v8::Isolate* isolate,
const std::string& error_message,
bool set_success,
const Cookies::CookiesCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
v8::Locker locker(isolate);
v8::HandleScope handle_scope(isolate);
if (!error_message.empty()) {
v8::Local<v8::Value> error = mate::ConvertToV8(isolate, error_message);
callback.Run(error, v8::Null(isolate));
return;
}
if (!set_success) {
v8::Local<v8::Value> error = mate::ConvertToV8(
isolate, "Failed to set cookies");
callback.Run(error, v8::Null(isolate));
}
callback.Run(v8::Null(isolate), v8::Null(isolate));
}
bool MatchesDomain(const base::DictionaryValue* filter,
const std::string& cookie_domain) {
std::string filter_domain;
if (!filter->GetString("domain", &filter_domain))
return true;
// Add a leading '.' character to the filter domain if it doesn't exist.
if (net::cookie_util::DomainIsHostOnly(filter_domain))
filter_domain.insert(0, ".");
std::string sub_domain(cookie_domain);
// Strip any leading '.' character from the input cookie domain.
if (!net::cookie_util::DomainIsHostOnly(sub_domain))
sub_domain = sub_domain.substr(1);
// Now check whether the domain argument is a subdomain of the filter domain.
for (sub_domain.insert(0, ".");
sub_domain.length() >= filter_domain.length();) {
if (sub_domain == filter_domain) {
return true;
}
const size_t next_dot = sub_domain.find('.', 1); // Skip over leading dot.
sub_domain.erase(0, next_dot);
}
return false;
}
bool MatchesCookie(const base::DictionaryValue* filter,
const net::CanonicalCookie& cookie) {
std::string name, domain, path;
bool is_secure, session;
if (filter->GetString("name", &name) && name != cookie.Name())
return false;
if (filter->GetString("path", &path) && path != cookie.Path())
return false;
if (!MatchesDomain(filter, cookie.Domain()))
return false;
if (filter->GetBoolean("secure", &is_secure) &&
is_secure != cookie.IsSecure())
return false;
if (filter->GetBoolean("session", &session) &&
session != cookie.IsPersistent())
return false;
return true;
}
} // namespace
namespace mate { namespace mate {
template<>
struct Converter<atom::api::Cookies::Error> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
atom::api::Cookies::Error val) {
if (val == atom::api::Cookies::SUCCESS)
return v8::Null(isolate);
else
return v8::Exception::Error(StringToV8(isolate, "failed"));
}
};
template<> template<>
struct Converter<net::CanonicalCookie> { struct Converter<net::CanonicalCookie> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
@ -161,11 +42,11 @@ struct Converter<net::CanonicalCookie> {
dict.Set("name", val.Name()); dict.Set("name", val.Name());
dict.Set("value", val.Value()); dict.Set("value", val.Value());
dict.Set("domain", val.Domain()); dict.Set("domain", val.Domain());
dict.Set("host_only", net::cookie_util::DomainIsHostOnly(val.Domain())); dict.Set("hostOnly", net::cookie_util::DomainIsHostOnly(val.Domain()));
dict.Set("path", val.Path()); dict.Set("path", val.Path());
dict.Set("secure", val.IsSecure()); dict.Set("secure", val.IsSecure());
dict.Set("http_only", val.IsHttpOnly()); dict.Set("httpOnly", val.IsHttpOnly());
dict.Set("session", val.IsPersistent()); dict.Set("session", !val.IsPersistent());
if (!val.IsPersistent()) if (!val.IsPersistent())
dict.Set("expirationDate", val.ExpiryDate().ToDoubleT()); dict.Set("expirationDate", val.ExpiryDate().ToDoubleT());
return dict.GetHandle(); return dict.GetHandle();
@ -178,121 +59,117 @@ namespace atom {
namespace api { namespace api {
Cookies::Cookies(content::BrowserContext* browser_context) namespace {
: request_context_getter_(browser_context->GetRequestContext()) {
}
Cookies::~Cookies() { // Returns whether |domain| matches |filter|.
} bool MatchesDomain(std::string filter, const std::string& domain) {
// Add a leading '.' character to the filter domain if it doesn't exist.
if (net::cookie_util::DomainIsHostOnly(filter))
filter.insert(0, ".");
void Cookies::Get(const base::DictionaryValue& options, std::string sub_domain(domain);
const CookiesCallback& callback) { // Strip any leading '.' character from the input cookie domain.
scoped_ptr<base::DictionaryValue> filter( if (!net::cookie_util::DomainIsHostOnly(sub_domain))
options.DeepCopyWithoutEmptyChildren()); sub_domain = sub_domain.substr(1);
content::BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, // Now check whether the domain argument is a subdomain of the filter domain.
base::Bind(&Cookies::GetCookiesOnIOThread, base::Unretained(this), for (sub_domain.insert(0, "."); sub_domain.length() >= filter.length();) {
Passed(&filter), callback)); if (sub_domain == filter)
} return true;
const size_t next_dot = sub_domain.find('.', 1); // Skip over leading dot.
void Cookies::GetCookiesOnIOThread(scoped_ptr<base::DictionaryValue> filter, sub_domain.erase(0, next_dot);
const CookiesCallback& callback) {
std::string url;
filter->GetString("url", &url);
if (!GetCookieListFromStore(GetCookieStore(), url,
base::Bind(&Cookies::OnGetCookies, base::Unretained(this),
Passed(&filter), callback))) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&RunGetCookiesCallbackOnUIThread, isolate(),
"URL is not valid", net::CookieList(), callback));
} }
return false;
} }
void Cookies::OnGetCookies(scoped_ptr<base::DictionaryValue> filter, // Returns whether |cookie| matches |filter|.
const CookiesCallback& callback, bool MatchesCookie(const base::DictionaryValue* filter,
const net::CookieList& cookie_list) { const net::CanonicalCookie& cookie) {
std::string str;
bool b;
if (filter->GetString("name", &str) && str != cookie.Name())
return false;
if (filter->GetString("path", &str) && str != cookie.Path())
return false;
if (filter->GetString("domain", &str) && !MatchesDomain(str, cookie.Domain()))
return false;
if (filter->GetBoolean("secure", &b) && b != cookie.IsSecure())
return false;
if (filter->GetBoolean("session", &b) && b != !cookie.IsPersistent())
return false;
return true;
}
// Helper to returns the CookieStore.
inline net::CookieStore* GetCookieStore(
scoped_refptr<net::URLRequestContextGetter> getter) {
return getter->GetURLRequestContext()->cookie_store();
}
// Run |callback| on UI thread.
void RunCallbackInUI(const base::Closure& callback) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback);
}
// Remove cookies from |list| not matching |filter|, and pass it to |callback|.
void FilterCookies(scoped_ptr<base::DictionaryValue> filter,
const Cookies::GetCallback& callback,
const net::CookieList& list) {
net::CookieList result; net::CookieList result;
for (const auto& cookie : cookie_list) { for (const auto& cookie : list) {
if (MatchesCookie(filter.get(), cookie)) if (MatchesCookie(filter.get(), cookie))
result.push_back(cookie); result.push_back(cookie);
} }
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( RunCallbackInUI(base::Bind(callback, Cookies::SUCCESS, result));
&RunGetCookiesCallbackOnUIThread, isolate(), "", result, callback));
} }
void Cookies::Remove(const mate::Dictionary& details, // Receives cookies matching |filter| in IO thread.
const CookiesCallback& callback) { void GetCookiesOnIO(scoped_refptr<net::URLRequestContextGetter> getter,
GURL url; scoped_ptr<base::DictionaryValue> filter,
std::string name; const Cookies::GetCallback& callback) {
std::string error_message;
if (!details.Get("url", &url) || !details.Get("name", &name)) {
error_message = "Details(url, name) of removing cookie are required.";
}
if (error_message.empty() && !url.is_valid()) {
error_message = "URL is not valid.";
}
if (!error_message.empty()) {
RunRemoveCookiesCallbackOnUIThread(isolate(), error_message, callback);
return;
}
content::BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&Cookies::RemoveCookiesOnIOThread, base::Unretained(this),
url, name, callback));
}
void Cookies::RemoveCookiesOnIOThread(const GURL& url, const std::string& name,
const CookiesCallback& callback) {
GetCookieStore()->DeleteCookieAsync(url, name,
base::Bind(&Cookies::OnRemoveCookies, base::Unretained(this), callback));
}
void Cookies::OnRemoveCookies(const CookiesCallback& callback) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&RunRemoveCookiesCallbackOnUIThread, isolate(), "", callback));
}
void Cookies::Set(const base::DictionaryValue& options,
const CookiesCallback& callback) {
std::string url; std::string url;
std::string error_message; filter->GetString("url", &url);
if (!options.GetString("url", &url)) {
error_message = "The url field is required.";
}
GURL gurl(url); auto filtered_callback =
if (error_message.empty() && !gurl.is_valid()) { base::Bind(FilterCookies, base::Passed(&filter), callback);
error_message = "URL is not valid.";
}
if (!error_message.empty()) { net::CookieMonster* monster = GetCookieStore(getter)->GetCookieMonster();
RunSetCookiesCallbackOnUIThread(isolate(), error_message, false, callback); // Empty url will match all url cookies.
return; if (url.empty())
} monster->GetAllCookiesAsync(filtered_callback);
else
scoped_ptr<base::DictionaryValue> details( monster->GetAllCookiesForURLAsync(GURL(url), filtered_callback);
options.DeepCopyWithoutEmptyChildren());
content::BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&Cookies::SetCookiesOnIOThread, base::Unretained(this),
Passed(&details), gurl, callback));
} }
void Cookies::SetCookiesOnIOThread(scoped_ptr<base::DictionaryValue> details, // Removes cookie with |url| and |name| in IO thread.
const GURL& url, void RemoveCookieOnIOThread(scoped_refptr<net::URLRequestContextGetter> getter,
const CookiesCallback& callback) { const GURL& url, const std::string& name,
DCHECK_CURRENTLY_ON(BrowserThread::IO); const base::Closure& callback) {
GetCookieStore(getter)->DeleteCookieAsync(
url, name, base::Bind(RunCallbackInUI, callback));
}
std::string name, value, domain, path; // Callback of SetCookie.
void OnSetCookie(const Cookies::SetCallback& callback, bool success) {
RunCallbackInUI(
base::Bind(callback, success ? Cookies::SUCCESS : Cookies::FAILED));
}
// Sets cookie with |details| in IO thread.
void SetCookieOnIO(scoped_refptr<net::URLRequestContextGetter> getter,
scoped_ptr<base::DictionaryValue> details,
const Cookies::SetCallback& callback) {
std::string url, name, value, domain, path;
bool secure = false; bool secure = false;
bool http_only = false; bool http_only = false;
double expiration_date; double expiration_date;
details->GetString("url", &url);
details->GetString("name", &name); details->GetString("name", &name);
details->GetString("value", &value); details->GetString("value", &value);
details->GetString("domain", &domain); details->GetString("domain", &domain);
details->GetString("path", &path); details->GetString("path", &path);
details->GetBoolean("secure", &secure); details->GetBoolean("secure", &secure);
details->GetBoolean("http_only", &http_only); details->GetBoolean("httpOnly", &http_only);
base::Time expiration_time; base::Time expiration_time;
if (details->GetDouble("expirationDate", &expiration_date)) { if (details->GetDouble("expirationDate", &expiration_date)) {
@ -301,29 +178,44 @@ void Cookies::SetCookiesOnIOThread(scoped_ptr<base::DictionaryValue> details,
base::Time::FromDoubleT(expiration_date); base::Time::FromDoubleT(expiration_date);
} }
GetCookieStore()->GetCookieMonster()->SetCookieWithDetailsAsync( GetCookieStore(getter)->GetCookieMonster()->SetCookieWithDetailsAsync(
url, GURL(url), name, value, domain, path, expiration_time, secure, http_only,
name, false, net::COOKIE_PRIORITY_DEFAULT, base::Bind(OnSetCookie, callback));
value,
domain,
path,
expiration_time,
secure,
http_only,
false,
net::COOKIE_PRIORITY_DEFAULT,
base::Bind(&Cookies::OnSetCookies, base::Unretained(this), callback));
} }
void Cookies::OnSetCookies(const CookiesCallback& callback, } // namespace
bool set_success) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, Cookies::Cookies(content::BrowserContext* browser_context)
base::Bind(&RunSetCookiesCallbackOnUIThread, isolate(), "", set_success, : request_context_getter_(browser_context->GetRequestContext()) {
callback));
} }
net::CookieStore* Cookies::GetCookieStore() { Cookies::~Cookies() {
return request_context_getter_->GetURLRequestContext()->cookie_store(); }
void Cookies::Get(const base::DictionaryValue& filter,
const GetCallback& callback) {
scoped_ptr<base::DictionaryValue> copied(filter.CreateDeepCopy());
auto getter = make_scoped_refptr(request_context_getter_);
content::BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(GetCookiesOnIO, getter, Passed(&copied), callback));
}
void Cookies::Remove(const GURL& url, const std::string& name,
const base::Closure& callback) {
auto getter = make_scoped_refptr(request_context_getter_);
content::BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(RemoveCookieOnIOThread, getter, url, name, callback));
}
void Cookies::Set(const base::DictionaryValue& details,
const SetCallback& callback) {
scoped_ptr<base::DictionaryValue> copied(details.CreateDeepCopy());
auto getter = make_scoped_refptr(request_context_getter_);
content::BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(SetCookieOnIO, getter, Passed(&copied), callback));
} }
// static // static

View file

@ -20,12 +20,7 @@ namespace content {
class BrowserContext; class BrowserContext;
} }
namespace mate {
class Dictionary;
}
namespace net { namespace net {
class CookieStore;
class URLRequestContextGetter; class URLRequestContextGetter;
} }
@ -35,9 +30,13 @@ namespace api {
class Cookies : public mate::TrackableObject<Cookies> { class Cookies : public mate::TrackableObject<Cookies> {
public: public:
// node.js style callback function(error, result) enum Error {
typedef base::Callback<void(v8::Local<v8::Value>, v8::Local<v8::Value>)> SUCCESS,
CookiesCallback; FAILED,
};
using GetCallback = base::Callback<void(Error, const net::CookieList&)>;
using SetCallback = base::Callback<void(Error)>;
static mate::Handle<Cookies> Create(v8::Isolate* isolate, static mate::Handle<Cookies> Create(v8::Isolate* isolate,
content::BrowserContext* browser_context); content::BrowserContext* browser_context);
@ -50,34 +49,12 @@ class Cookies : public mate::TrackableObject<Cookies> {
explicit Cookies(content::BrowserContext* browser_context); explicit Cookies(content::BrowserContext* browser_context);
~Cookies(); ~Cookies();
void Get(const base::DictionaryValue& options, void Get(const base::DictionaryValue& filter, const GetCallback& callback);
const CookiesCallback& callback); void Remove(const GURL& url, const std::string& name,
void Remove(const mate::Dictionary& details, const base::Closure& callback);
const CookiesCallback& callback); void Set(const base::DictionaryValue& details, const SetCallback& callback);
void Set(const base::DictionaryValue& details,
const CookiesCallback& callback);
void GetCookiesOnIOThread(scoped_ptr<base::DictionaryValue> filter,
const CookiesCallback& callback);
void OnGetCookies(scoped_ptr<base::DictionaryValue> filter,
const CookiesCallback& callback,
const net::CookieList& cookie_list);
void RemoveCookiesOnIOThread(const GURL& url,
const std::string& name,
const CookiesCallback& callback);
void OnRemoveCookies(const CookiesCallback& callback);
void SetCookiesOnIOThread(scoped_ptr<base::DictionaryValue> details,
const GURL& url,
const CookiesCallback& callback);
void OnSetCookies(const CookiesCallback& callback,
bool set_success);
private: private:
// Must be called on IO thread.
net::CookieStore* GetCookieStore();
net::URLRequestContextGetter* request_context_getter_; net::URLRequestContextGetter* request_context_getter_;
DISALLOW_COPY_AND_ASSIGN(Cookies); DISALLOW_COPY_AND_ASSIGN(Cookies);

View file

@ -5,6 +5,7 @@
#include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/api/atom_api_web_contents.h"
#include <set> #include <set>
#include <string>
#include "atom/browser/api/atom_api_session.h" #include "atom/browser/api/atom_api_session.h"
#include "atom/browser/api/atom_api_window.h" #include "atom/browser/api/atom_api_window.h"

View file

@ -4,6 +4,8 @@
#include "atom/browser/api/atom_api_web_request.h" #include "atom/browser/api/atom_api_web_request.h"
#include <string>
#include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_context.h"
#include "atom/browser/net/atom_network_delegate.h" #include "atom/browser/net/atom_network_delegate.h"
#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/callback.h"
@ -15,6 +17,21 @@
using content::BrowserThread; using content::BrowserThread;
namespace mate {
template<>
struct Converter<extensions::URLPattern> {
static bool FromV8(v8::Isolate* isolate, v8::Local<v8::Value> val,
extensions::URLPattern* out) {
std::string pattern;
if (!ConvertFromV8(isolate, val, &pattern))
return false;
return out->Parse(pattern) == extensions::URLPattern::PARSE_SUCCESS;
}
};
} // namespace mate
namespace atom { namespace atom {
namespace api { namespace api {
@ -26,23 +43,38 @@ WebRequest::WebRequest(AtomBrowserContext* browser_context)
WebRequest::~WebRequest() { WebRequest::~WebRequest() {
} }
template<AtomNetworkDelegate::EventTypes type> template<AtomNetworkDelegate::SimpleEvent type>
void WebRequest::SetListener(mate::Arguments* args) { void WebRequest::SetSimpleListener(mate::Arguments* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI); SetListener<AtomNetworkDelegate::SimpleListener>(
&AtomNetworkDelegate::SetSimpleListenerInIO, type, args);
}
scoped_ptr<base::DictionaryValue> filter(new base::DictionaryValue()); template<AtomNetworkDelegate::ResponseEvent type>
args->GetNext(filter.get()); void WebRequest::SetResponseListener(mate::Arguments* args) {
AtomNetworkDelegate::Listener callback; SetListener<AtomNetworkDelegate::ResponseListener>(
if (!args->GetNext(&callback)) { &AtomNetworkDelegate::SetResponseListenerInIO, type, args);
args->ThrowError("Must pass null or a function"); }
template<typename Listener, typename Method, typename Event>
void WebRequest::SetListener(Method method, Event type, mate::Arguments* args) {
// { urls }.
URLPatterns patterns;
mate::Dictionary dict;
args->GetNext(&dict) && dict.Get("urls", &patterns);
// Function or null.
v8::Local<v8::Value> value;
Listener listener;
if (!args->GetNext(&listener) &&
!(args->GetNext(&value) && value->IsNull())) {
args->ThrowError("Must pass null or a Function");
return; return;
} }
auto delegate = browser_context_->network_delegate(); auto delegate = browser_context_->network_delegate();
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&AtomNetworkDelegate::SetListenerInIO, base::Bind(method, base::Unretained(delegate), type,
base::Unretained(delegate), patterns, listener));
type, base::Passed(&filter), callback));
} }
// static // static
@ -57,28 +89,28 @@ void WebRequest::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> prototype) { v8::Local<v8::ObjectTemplate> prototype) {
mate::ObjectTemplateBuilder(isolate, prototype) mate::ObjectTemplateBuilder(isolate, prototype)
.SetMethod("onBeforeRequest", .SetMethod("onBeforeRequest",
&WebRequest::SetListener< &WebRequest::SetResponseListener<
AtomNetworkDelegate::kOnBeforeRequest>) AtomNetworkDelegate::kOnBeforeRequest>)
.SetMethod("onBeforeSendHeaders", .SetMethod("onBeforeSendHeaders",
&WebRequest::SetListener< &WebRequest::SetResponseListener<
AtomNetworkDelegate::kOnBeforeSendHeaders>) AtomNetworkDelegate::kOnBeforeSendHeaders>)
.SetMethod("onSendHeaders",
&WebRequest::SetListener<
AtomNetworkDelegate::kOnSendHeaders>)
.SetMethod("onHeadersReceived", .SetMethod("onHeadersReceived",
&WebRequest::SetListener< &WebRequest::SetResponseListener<
AtomNetworkDelegate::kOnHeadersReceived>) AtomNetworkDelegate::kOnHeadersReceived>)
.SetMethod("onSendHeaders",
&WebRequest::SetSimpleListener<
AtomNetworkDelegate::kOnSendHeaders>)
.SetMethod("onBeforeRedirect", .SetMethod("onBeforeRedirect",
&WebRequest::SetListener< &WebRequest::SetSimpleListener<
AtomNetworkDelegate::kOnBeforeRedirect>) AtomNetworkDelegate::kOnBeforeRedirect>)
.SetMethod("onResponseStarted", .SetMethod("onResponseStarted",
&WebRequest::SetListener< &WebRequest::SetSimpleListener<
AtomNetworkDelegate::kOnResponseStarted>) AtomNetworkDelegate::kOnResponseStarted>)
.SetMethod("onCompleted", .SetMethod("onCompleted",
&WebRequest::SetListener< &WebRequest::SetSimpleListener<
AtomNetworkDelegate::kOnCompleted>) AtomNetworkDelegate::kOnCompleted>)
.SetMethod("onErrorOccurred", .SetMethod("onErrorOccurred",
&WebRequest::SetListener< &WebRequest::SetSimpleListener<
AtomNetworkDelegate::kOnErrorOccurred>); AtomNetworkDelegate::kOnErrorOccurred>);
} }

View file

@ -5,8 +5,6 @@
#ifndef ATOM_BROWSER_API_ATOM_API_WEB_REQUEST_H_ #ifndef ATOM_BROWSER_API_ATOM_API_WEB_REQUEST_H_
#define ATOM_BROWSER_API_ATOM_API_WEB_REQUEST_H_ #define ATOM_BROWSER_API_ATOM_API_WEB_REQUEST_H_
#include <string>
#include "atom/browser/api/trackable_object.h" #include "atom/browser/api/trackable_object.h"
#include "atom/browser/net/atom_network_delegate.h" #include "atom/browser/net/atom_network_delegate.h"
#include "native_mate/arguments.h" #include "native_mate/arguments.h"
@ -31,8 +29,13 @@ class WebRequest : public mate::TrackableObject<WebRequest> {
explicit WebRequest(AtomBrowserContext* browser_context); explicit WebRequest(AtomBrowserContext* browser_context);
~WebRequest(); ~WebRequest();
template<AtomNetworkDelegate::EventTypes Event> // C++ can not distinguish overloaded member function.
void SetListener(mate::Arguments* args); template<AtomNetworkDelegate::SimpleEvent type>
void SetSimpleListener(mate::Arguments* args);
template<AtomNetworkDelegate::ResponseEvent type>
void SetResponseListener(mate::Arguments* args);
template<typename Listener, typename Method, typename Event>
void SetListener(Method method, Event type, mate::Arguments* args);
private: private:
scoped_refptr<AtomBrowserContext> browser_context_; scoped_refptr<AtomBrowserContext> browser_context_;

View file

@ -6,6 +6,7 @@ PERSIST_PERFIX = 'persist:'
# Returns the Session from |partition| string. # Returns the Session from |partition| string.
exports.fromPartition = (partition='') -> exports.fromPartition = (partition='') ->
return exports.defaultSession if partition is ''
if partition.startsWith PERSIST_PERFIX if partition.startsWith PERSIST_PERFIX
bindings.fromPartition partition.substr(PERSIST_PERFIX.length), false bindings.fromPartition partition.substr(PERSIST_PERFIX.length), false
else else
@ -14,7 +15,7 @@ exports.fromPartition = (partition='') ->
# Returns the default session. # Returns the default session.
Object.defineProperty exports, 'defaultSession', Object.defineProperty exports, 'defaultSession',
enumerable: true enumerable: true
get: -> exports.fromPartition 'persist:' get: -> bindings.fromPartition '', false
wrapSession = (session) -> wrapSession = (session) ->
# session is an EventEmitter. # session is an EventEmitter.

View file

@ -4,7 +4,11 @@
#include "atom/browser/net/atom_network_delegate.h" #include "atom/browser/net/atom_network_delegate.h"
#include <string>
#include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/native_mate_converters/net_converter.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_request_info.h" #include "content/public/browser/resource_request_info.h"
#include "net/url_request/url_request.h" #include "net/url_request/url_request.h"
@ -15,7 +19,7 @@ namespace atom {
namespace { namespace {
std::string ResourceTypeToString(content::ResourceType type) { const char* ResourceTypeToString(content::ResourceType type) {
switch (type) { switch (type) {
case content::RESOURCE_TYPE_MAIN_FRAME: case content::RESOURCE_TYPE_MAIN_FRAME:
return "mainFrame"; return "mainFrame";
@ -36,98 +40,171 @@ std::string ResourceTypeToString(content::ResourceType type) {
} }
} }
AtomNetworkDelegate::BlockingResponse RunListener( void RunSimpleListener(const AtomNetworkDelegate::SimpleListener& listener,
const AtomNetworkDelegate::Listener& callback,
scoped_ptr<base::DictionaryValue> details) { scoped_ptr<base::DictionaryValue> details) {
return callback.Run(*(details.get())); return listener.Run(*(details.get()));
} }
bool MatchesFilterCondition( void RunResponseListener(
net::URLRequest* request, const AtomNetworkDelegate::ResponseListener& listener,
const AtomNetworkDelegate::ListenerInfo& info) { scoped_ptr<base::DictionaryValue> details,
if (!info.url_patterns.empty()) { const AtomNetworkDelegate::ResponseCallback& callback) {
auto url = request->url(); return listener.Run(*(details.get()), callback);
for (auto& pattern : info.url_patterns) }
if (pattern.MatchesURL(url))
// Test whether the URL of |request| matches |patterns|.
bool MatchesFilterCondition(net::URLRequest* request,
const URLPatterns& patterns) {
if (patterns.empty())
return true;
for (const auto& pattern : patterns) {
if (pattern.MatchesURL(request->url()))
return true; return true;
return false;
} }
return false;
return true;
} }
scoped_ptr<base::DictionaryValue> ExtractRequestInfo(net::URLRequest* request) { // Overloaded by multiple types to fill the |details| object.
scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); void ToDictionary(base::DictionaryValue* details, net::URLRequest* request) {
dict->SetInteger("id", request->identifier()); details->SetInteger("id", request->identifier());
dict->SetString("url", request->url().spec()); details->SetString("url", request->url().spec());
dict->SetString("method", request->method()); details->SetString("method", request->method());
content::ResourceType resourceType = content::RESOURCE_TYPE_LAST_TYPE; details->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000);
auto info = content::ResourceRequestInfo::ForRequest(request); auto info = content::ResourceRequestInfo::ForRequest(request);
if (info) details->SetString("resourceType",
resourceType = info->GetResourceType(); info ? ResourceTypeToString(info->GetResourceType())
dict->SetString("resourceType", ResourceTypeToString(resourceType)); : "other");
dict->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000);
return dict.Pass();
} }
scoped_ptr<base::DictionaryValue> GetRequestHeadersDict( void ToDictionary(base::DictionaryValue* details,
const net::HttpRequestHeaders& headers) { const net::HttpRequestHeaders& headers) {
scoped_ptr<base::DictionaryValue> header_dict(new base::DictionaryValue()); scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
net::HttpRequestHeaders::Iterator it(headers); net::HttpRequestHeaders::Iterator it(headers);
while (it.GetNext()) while (it.GetNext())
header_dict->SetString(it.name(), it.value()); dict->SetString(it.name(), it.value());
return header_dict.Pass(); details->Set("requestHeaders", dict.Pass());
} }
scoped_ptr<base::DictionaryValue> GetResponseHeadersDict( void ToDictionary(base::DictionaryValue* details,
const net::HttpResponseHeaders* headers) { const net::HttpResponseHeaders* headers) {
scoped_ptr<base::DictionaryValue> header_dict(new base::DictionaryValue()); if (!headers)
if (headers) { return;
scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
void* iter = nullptr; void* iter = nullptr;
std::string key; std::string key;
std::string value; std::string value;
while (headers->EnumerateHeaderLines(&iter, &key, &value)) while (headers->EnumerateHeaderLines(&iter, &key, &value)) {
header_dict->SetString(key, value); if (dict->HasKey(key)) {
base::ListValue* values = nullptr;
if (dict->GetList(key, &values))
values->AppendString(value);
} else {
scoped_ptr<base::ListValue> values(new base::ListValue);
values->AppendString(value);
dict->Set(key, values.Pass());
} }
return header_dict.Pass(); }
details->Set("responseHeaders", dict.Pass());
details->SetString("statusLine", headers->GetStatusLine());
details->SetInteger("statusCode", headers->response_code());
} }
void OnBeforeURLRequestResponse( void ToDictionary(base::DictionaryValue* details, const GURL& location) {
const net::CompletionCallback& callback, details->SetString("redirectURL", location.spec());
GURL* new_url,
const AtomNetworkDelegate::BlockingResponse& result) {
if (!result.redirect_url.is_empty())
*new_url = result.redirect_url;
callback.Run(result.Code());
} }
void OnBeforeSendHeadersResponse( void ToDictionary(base::DictionaryValue* details,
const net::CompletionCallback& callback, const net::HostPortPair& host_port) {
net::HttpRequestHeaders* headers, if (host_port.host().empty())
const AtomNetworkDelegate::BlockingResponse& result) { details->SetString("ip", host_port.host());
if (!result.request_headers.IsEmpty())
*headers = result.request_headers;
callback.Run(result.Code());
} }
void OnHeadersReceivedResponse( void ToDictionary(base::DictionaryValue* details, bool from_cache) {
const net::CompletionCallback& callback, details->SetBoolean("fromCache", from_cache);
const net::HttpResponseHeaders* original_response_headers, }
scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
const AtomNetworkDelegate::BlockingResponse& result) { void ToDictionary(base::DictionaryValue* details,
if (result.response_headers.get()) { const net::URLRequestStatus& status) {
*override_response_headers = new net::HttpResponseHeaders( details->SetString("error", net::ErrorToString(status.error()));
original_response_headers->raw_headers()); }
void* iter = nullptr;
std::string key; // Helper function to fill |details| with arbitrary |args|.
template<typename Arg>
void FillDetailsObject(base::DictionaryValue* details, Arg arg) {
ToDictionary(details, arg);
}
template<typename Arg, typename... Args>
void FillDetailsObject(base::DictionaryValue* details, Arg arg, Args... args) {
ToDictionary(details, arg);
FillDetailsObject(details, args...);
}
// Fill the native types with the result from the response object.
void ReadFromResponseObject(const base::DictionaryValue& response,
GURL* new_location) {
std::string url;
if (response.GetString("redirectURL", &url))
*new_location = GURL(url);
}
void ReadFromResponseObject(const base::DictionaryValue& response,
net::HttpRequestHeaders* headers) {
const base::DictionaryValue* dict;
if (response.GetDictionary("requestHeaders", &dict)) {
for (base::DictionaryValue::Iterator it(*dict);
!it.IsAtEnd();
it.Advance()) {
std::string value; std::string value;
while (result.response_headers->EnumerateHeaderLines(&iter, &key, &value)) { if (it.value().GetAsString(&value))
(*override_response_headers)->RemoveHeader(key); headers->SetHeader(it.key(), value);
(*override_response_headers)->AddHeader(key + ": " + value);
} }
} }
callback.Run(result.Code()); }
void ReadFromResponseObject(const base::DictionaryValue& response,
scoped_refptr<net::HttpResponseHeaders>* headers) {
const base::DictionaryValue* dict;
if (response.GetDictionary("responseHeaders", &dict)) {
*headers = new net::HttpResponseHeaders("");
for (base::DictionaryValue::Iterator it(*dict);
!it.IsAtEnd();
it.Advance()) {
const base::ListValue* list;
if (it.value().GetAsList(&list)) {
(*headers)->RemoveHeader(it.key());
for (size_t i = 0; i < list->GetSize(); ++i) {
std::string value;
if (list->GetString(i, &value))
(*headers)->AddHeader(it.key() + " : " + value);
}
}
}
}
}
// Deal with the results of Listener.
template<typename T>
void OnListenerResultInIO(const net::CompletionCallback& callback,
T out,
scoped_ptr<base::DictionaryValue> response) {
ReadFromResponseObject(*response.get(), out);
bool cancel = false;
response->GetBoolean("cancel", &cancel);
callback.Run(cancel ? net::ERR_BLOCKED_BY_CLIENT : net::OK);
}
template<typename T>
void OnListenerResultInUI(const net::CompletionCallback& callback,
T out,
const base::DictionaryValue& response) {
scoped_ptr<base::DictionaryValue> copy = response.CreateDeepCopy();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(OnListenerResultInIO<T>, callback, out, base::Passed(&copy)));
} }
} // namespace } // namespace
@ -138,256 +215,166 @@ AtomNetworkDelegate::AtomNetworkDelegate() {
AtomNetworkDelegate::~AtomNetworkDelegate() { AtomNetworkDelegate::~AtomNetworkDelegate() {
} }
void AtomNetworkDelegate::SetListenerInIO( void AtomNetworkDelegate::SetSimpleListenerInIO(
EventTypes type, SimpleEvent type,
scoped_ptr<base::DictionaryValue> filter, const URLPatterns& patterns,
const Listener& callback) { const SimpleListener& callback) {
if (callback.is_null()) { if (callback.is_null())
event_listener_map_.erase(type); simple_listeners_.erase(type);
return; else
} simple_listeners_[type] = { patterns, callback };
}
ListenerInfo info; void AtomNetworkDelegate::SetResponseListenerInIO(
info.callback = callback; ResponseEvent type,
const URLPatterns& patterns,
const base::ListValue* url_list = nullptr; const ResponseListener& callback) {
if (filter->GetList("urls", &url_list)) { if (callback.is_null())
for (size_t i = 0; i < url_list->GetSize(); ++i) { response_listeners_.erase(type);
std::string url; else
extensions::URLPattern pattern; response_listeners_[type] = { patterns, callback };
if (url_list->GetString(i, &url) &&
pattern.Parse(url) == extensions::URLPattern::PARSE_SUCCESS) {
info.url_patterns.insert(pattern);
}
}
}
event_listener_map_[type] = info;
} }
int AtomNetworkDelegate::OnBeforeURLRequest( int AtomNetworkDelegate::OnBeforeURLRequest(
net::URLRequest* request, net::URLRequest* request,
const net::CompletionCallback& callback, const net::CompletionCallback& callback,
GURL* new_url) { GURL* new_url) {
auto listener_info = event_listener_map_.find(kOnBeforeRequest); if (!ContainsKey(response_listeners_, kOnBeforeRequest))
if (listener_info != event_listener_map_.end()) { return brightray::NetworkDelegate::OnBeforeURLRequest(
if (!MatchesFilterCondition(request, listener_info->second)) request, callback, new_url);
return net::OK;
auto wrapped_callback = listener_info->second.callback; return HandleResponseEvent(kOnBeforeRequest, request, callback, new_url);
auto details = ExtractRequestInfo(request);
BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE,
base::Bind(&RunListener, wrapped_callback, base::Passed(&details)),
base::Bind(&OnBeforeURLRequestResponse,
callback, new_url));
return net::ERR_IO_PENDING;
}
return brightray::NetworkDelegate::OnBeforeURLRequest(request,
callback,
new_url);
} }
int AtomNetworkDelegate::OnBeforeSendHeaders( int AtomNetworkDelegate::OnBeforeSendHeaders(
net::URLRequest* request, net::URLRequest* request,
const net::CompletionCallback& callback, const net::CompletionCallback& callback,
net::HttpRequestHeaders* headers) { net::HttpRequestHeaders* headers) {
auto listener_info = event_listener_map_.find(kOnBeforeSendHeaders); if (!ContainsKey(response_listeners_, kOnBeforeSendHeaders))
if (listener_info != event_listener_map_.end()) { return brightray::NetworkDelegate::OnBeforeSendHeaders(
if (!MatchesFilterCondition(request, listener_info->second)) request, callback, headers);
return net::OK;
auto wrapped_callback = listener_info->second.callback; return HandleResponseEvent(
auto details = ExtractRequestInfo(request); kOnBeforeSendHeaders, request, callback, headers, *headers);
details->Set("requestHeaders", GetRequestHeadersDict(*headers).release());
BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE,
base::Bind(&RunListener, wrapped_callback, base::Passed(&details)),
base::Bind(&OnBeforeSendHeadersResponse,
callback, headers));
return net::ERR_IO_PENDING;
}
return brightray::NetworkDelegate::OnBeforeSendHeaders(request,
callback,
headers);
} }
void AtomNetworkDelegate::OnSendHeaders( void AtomNetworkDelegate::OnSendHeaders(
net::URLRequest* request, net::URLRequest* request,
const net::HttpRequestHeaders& headers) { const net::HttpRequestHeaders& headers) {
auto listener_info = event_listener_map_.find(kOnSendHeaders); if (!ContainsKey(simple_listeners_, kOnSendHeaders)) {
if (listener_info != event_listener_map_.end()) {
if (!MatchesFilterCondition(request, listener_info->second))
return;
auto wrapped_callback = listener_info->second.callback;
auto details = ExtractRequestInfo(request);
details->Set("requestHeaders", GetRequestHeadersDict(headers).release());
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(base::IgnoreResult(&RunListener),
wrapped_callback,
base::Passed(&details)));
} else {
brightray::NetworkDelegate::OnSendHeaders(request, headers); brightray::NetworkDelegate::OnSendHeaders(request, headers);
return;
} }
HandleSimpleEvent(kOnSendHeaders, request, headers);
} }
int AtomNetworkDelegate::OnHeadersReceived( int AtomNetworkDelegate::OnHeadersReceived(
net::URLRequest* request, net::URLRequest* request,
const net::CompletionCallback& callback, const net::CompletionCallback& callback,
const net::HttpResponseHeaders* original_response_headers, const net::HttpResponseHeaders* original,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers, scoped_refptr<net::HttpResponseHeaders>* override,
GURL* allowed_unsafe_redirect_url) { GURL* allowed) {
auto listener_info = event_listener_map_.find(kOnHeadersReceived); if (!ContainsKey(response_listeners_, kOnHeadersReceived))
if (listener_info != event_listener_map_.end()) {
if (!MatchesFilterCondition(request, listener_info->second))
return net::OK;
auto wrapped_callback = listener_info->second.callback;
auto details = ExtractRequestInfo(request);
details->SetString("statusLine",
original_response_headers->GetStatusLine());
details->SetInteger("statusCode",
original_response_headers->response_code());
details->Set("responseHeaders",
GetResponseHeadersDict(original_response_headers).release());
BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE,
base::Bind(&RunListener, wrapped_callback, base::Passed(&details)),
base::Bind(&OnHeadersReceivedResponse,
callback,
original_response_headers,
override_response_headers));
return net::ERR_IO_PENDING;
}
return brightray::NetworkDelegate::OnHeadersReceived( return brightray::NetworkDelegate::OnHeadersReceived(
request, callback, original_response_headers, override_response_headers, request, callback, original, override, allowed);
allowed_unsafe_redirect_url);
return HandleResponseEvent(
kOnHeadersReceived, request, callback, override, original);
} }
void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request,
const GURL& new_location) { const GURL& new_location) {
auto listener_info = event_listener_map_.find(kOnBeforeRedirect); if (!ContainsKey(simple_listeners_, kOnBeforeRedirect)) {
if (listener_info != event_listener_map_.end()) {
if (!MatchesFilterCondition(request, listener_info->second))
return;
auto wrapped_callback = listener_info->second.callback;
auto details = ExtractRequestInfo(request);
details->SetString("redirectURL", new_location.spec());
details->SetInteger("statusCode", request->GetResponseCode());
auto ip = request->GetSocketAddress().host();
if (!ip.empty())
details->SetString("ip", ip);
details->SetBoolean("fromCache", request->was_cached());
details->Set("responseHeaders",
GetResponseHeadersDict(request->response_headers()).release());
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(base::IgnoreResult(&RunListener),
wrapped_callback,
base::Passed(&details)));
} else {
brightray::NetworkDelegate::OnBeforeRedirect(request, new_location); brightray::NetworkDelegate::OnBeforeRedirect(request, new_location);
return;
} }
HandleSimpleEvent(kOnBeforeRedirect, request, new_location,
request->response_headers(), request->GetSocketAddress(),
request->was_cached());
} }
void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) {
auto listener_info = event_listener_map_.find(kOnResponseStarted); if (!ContainsKey(simple_listeners_, kOnResponseStarted)) {
if (listener_info != event_listener_map_.end()) { brightray::NetworkDelegate::OnResponseStarted(request);
return;
}
if (request->status().status() != net::URLRequestStatus::SUCCESS) if (request->status().status() != net::URLRequestStatus::SUCCESS)
return; return;
if (!MatchesFilterCondition(request, listener_info->second)) HandleSimpleEvent(kOnResponseStarted, request, request->response_headers(),
return; request->was_cached());
auto wrapped_callback = listener_info->second.callback;
auto details = ExtractRequestInfo(request);
details->Set("responseHeaders",
GetResponseHeadersDict(request->response_headers()).release());
details->SetBoolean("fromCache", request->was_cached());
auto response_headers = request->response_headers();
details->SetInteger("statusCode",
response_headers ?
response_headers->response_code() : 200);
details->SetString("statusLine",
response_headers ?
response_headers->GetStatusLine() : std::string());
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(base::IgnoreResult(&RunListener),
wrapped_callback,
base::Passed(&details)));
} else {
brightray::NetworkDelegate::OnResponseStarted(request);
}
} }
void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) {
auto listener_info = event_listener_map_.find(kOnCompleted);
if (listener_info != event_listener_map_.end()) {
if (request->status().status() == net::URLRequestStatus::FAILED || if (request->status().status() == net::URLRequestStatus::FAILED ||
request->status().status() == net::URLRequestStatus::CANCELED) { request->status().status() == net::URLRequestStatus::CANCELED) {
// Error event.
if (ContainsKey(simple_listeners_, kOnErrorOccurred))
OnErrorOccurred(request); OnErrorOccurred(request);
return; else
} else {
bool is_redirect = request->response_headers() &&
net::HttpResponseHeaders::IsRedirectResponseCode(
request->response_headers()->response_code());
if (is_redirect)
return;
}
if (!MatchesFilterCondition(request, listener_info->second))
return;
auto wrapped_callback = listener_info->second.callback;
auto details = ExtractRequestInfo(request);
details->Set("responseHeaders",
GetResponseHeadersDict(request->response_headers()).release());
details->SetBoolean("fromCache", request->was_cached());
auto response_headers = request->response_headers();
details->SetInteger("statusCode",
response_headers ?
response_headers->response_code() : 200);
details->SetString("statusLine",
response_headers ?
response_headers->GetStatusLine() : std::string());
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(base::IgnoreResult(&RunListener),
wrapped_callback,
base::Passed(&details)));
} else {
brightray::NetworkDelegate::OnCompleted(request, started); brightray::NetworkDelegate::OnCompleted(request, started);
return;
} else if (request->response_headers() &&
net::HttpResponseHeaders::IsRedirectResponseCode(
request->response_headers()->response_code())) {
// Redirect event.
brightray::NetworkDelegate::OnCompleted(request, started);
return;
} }
if (!ContainsKey(simple_listeners_, kOnCompleted)) {
brightray::NetworkDelegate::OnCompleted(request, started);
return;
}
HandleSimpleEvent(kOnCompleted, request, request->response_headers(),
request->was_cached());
} }
void AtomNetworkDelegate::OnErrorOccurred(net::URLRequest* request) { void AtomNetworkDelegate::OnErrorOccurred(net::URLRequest* request) {
auto listener_info = event_listener_map_.find(kOnErrorOccurred); HandleSimpleEvent(kOnErrorOccurred, request, request->was_cached(),
if (listener_info != event_listener_map_.end()) { request->status());
if (!MatchesFilterCondition(request, listener_info->second)) }
template<typename Out, typename... Args>
int AtomNetworkDelegate::HandleResponseEvent(
ResponseEvent type,
net::URLRequest* request,
const net::CompletionCallback& callback,
Out out,
Args... args) {
const auto& info = response_listeners_[type];
if (!MatchesFilterCondition(request, info.url_patterns))
return net::OK;
scoped_ptr<base::DictionaryValue> details(new base::DictionaryValue);
FillDetailsObject(details.get(), request, args...);
ResponseCallback response =
base::Bind(OnListenerResultInUI<Out>, callback, out);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(RunResponseListener, info.listener, base::Passed(&details),
response));
return net::ERR_IO_PENDING;
}
template<typename...Args>
void AtomNetworkDelegate::HandleSimpleEvent(
SimpleEvent type, net::URLRequest* request, Args... args) {
const auto& info = simple_listeners_[type];
if (!MatchesFilterCondition(request, info.url_patterns))
return; return;
auto wrapped_callback = listener_info->second.callback; scoped_ptr<base::DictionaryValue> details(new base::DictionaryValue);
auto details = ExtractRequestInfo(request); FillDetailsObject(details.get(), request, args...);
details->SetBoolean("fromCache", request->was_cached());
details->SetString("error", net::ErrorToString(request->status().error()));
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, BrowserThread::PostTask(
base::Bind(base::IgnoreResult(&RunListener), BrowserThread::UI, FROM_HERE,
wrapped_callback, base::Bind(RunSimpleListener, info.listener, base::Passed(&details)));
base::Passed(&details)));
}
} }
} // namespace atom } // namespace atom

View file

@ -7,7 +7,6 @@
#include <map> #include <map>
#include <set> #include <set>
#include <string>
#include "brightray/browser/network_delegate.h" #include "brightray/browser/network_delegate.h"
#include "base/callback.h" #include "base/callback.h"
@ -23,53 +22,50 @@ class URLPattern;
namespace atom { namespace atom {
using URLPatterns = std::set<extensions::URLPattern>;
class AtomNetworkDelegate : public brightray::NetworkDelegate { class AtomNetworkDelegate : public brightray::NetworkDelegate {
public: public:
struct BlockingResponse; using ResponseCallback = base::Callback<void(const base::DictionaryValue&)>;
using Listener = using SimpleListener = base::Callback<void(const base::DictionaryValue&)>;
base::Callback<BlockingResponse(const base::DictionaryValue&)>; using ResponseListener = base::Callback<void(const base::DictionaryValue&,
const ResponseCallback&)>;
enum EventTypes { enum SimpleEvent {
kInvalidEvent = 0, kOnSendHeaders,
kOnBeforeRequest = 1 << 0, kOnBeforeRedirect,
kOnBeforeSendHeaders = 1 << 1, kOnResponseStarted,
kOnSendHeaders = 1 << 2, kOnCompleted,
kOnHeadersReceived = 1 << 3, kOnErrorOccurred,
kOnBeforeRedirect = 1 << 4,
kOnResponseStarted = 1 << 5,
kOnCompleted = 1 << 6,
kOnErrorOccurred = 1 << 7,
}; };
struct ListenerInfo { enum ResponseEvent {
std::set<extensions::URLPattern> url_patterns; kOnBeforeRequest,
AtomNetworkDelegate::Listener callback; kOnBeforeSendHeaders,
kOnHeadersReceived,
}; };
struct BlockingResponse { struct SimpleListenerInfo {
BlockingResponse() : cancel(false) {} URLPatterns url_patterns;
~BlockingResponse() {} SimpleListener listener;
};
int Code() const { struct ResponseListenerInfo {
return cancel ? net::ERR_BLOCKED_BY_CLIENT : net::OK; URLPatterns url_patterns;
} ResponseListener listener;
bool cancel;
GURL redirect_url;
net::HttpRequestHeaders request_headers;
scoped_refptr<net::HttpResponseHeaders> response_headers;
}; };
AtomNetworkDelegate(); AtomNetworkDelegate();
~AtomNetworkDelegate() override; ~AtomNetworkDelegate() override;
void SetListenerInIO(EventTypes type, void SetSimpleListenerInIO(SimpleEvent type,
scoped_ptr<base::DictionaryValue> filter, const URLPatterns& patterns,
const Listener& callback); const SimpleListener& callback);
void SetResponseListenerInIO(ResponseEvent type,
const URLPatterns& patterns,
const ResponseListener& callback);
protected: protected:
void OnErrorOccurred(net::URLRequest* request);
// net::NetworkDelegate: // net::NetworkDelegate:
int OnBeforeURLRequest(net::URLRequest* request, int OnBeforeURLRequest(net::URLRequest* request,
const net::CompletionCallback& callback, const net::CompletionCallback& callback,
@ -90,8 +86,22 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate {
void OnResponseStarted(net::URLRequest* request) override; void OnResponseStarted(net::URLRequest* request) override;
void OnCompleted(net::URLRequest* request, bool started) override; void OnCompleted(net::URLRequest* request, bool started) override;
void OnErrorOccurred(net::URLRequest* request);
private: private:
std::map<EventTypes, ListenerInfo> event_listener_map_; template<typename...Args>
void HandleSimpleEvent(SimpleEvent type,
net::URLRequest* request,
Args... args);
template<typename Out, typename... Args>
int HandleResponseEvent(ResponseEvent type,
net::URLRequest* request,
const net::CompletionCallback& callback,
Out out,
Args... args);
std::map<SimpleEvent, SimpleListenerInfo> simple_listeners_;
std::map<ResponseEvent, ResponseListenerInfo> response_listeners_;;
DISALLOW_COPY_AND_ASSIGN(AtomNetworkDelegate); DISALLOW_COPY_AND_ASSIGN(AtomNetworkDelegate);
}; };

View file

@ -82,38 +82,4 @@ v8::Local<v8::Value> Converter<scoped_refptr<net::X509Certificate>>::ToV8(
return dict.GetHandle(); return dict.GetHandle();
} }
// static
bool Converter<atom::AtomNetworkDelegate::BlockingResponse>::FromV8(
v8::Isolate* isolate, v8::Local<v8::Value> val,
atom::AtomNetworkDelegate::BlockingResponse* out) {
mate::Dictionary dict;
if (!ConvertFromV8(isolate, val, &dict))
return false;
if (!dict.Get("cancel", &(out->cancel)))
return false;
dict.Get("redirectURL", &(out->redirect_url));
base::DictionaryValue request_headers;
if (dict.Get("requestHeaders", &request_headers)) {
for (base::DictionaryValue::Iterator it(request_headers);
!it.IsAtEnd();
it.Advance()) {
std::string value;
CHECK(it.value().GetAsString(&value));
out->request_headers.SetHeader(it.key(), value);
}
}
base::DictionaryValue response_headers;
if (dict.Get("responseHeaders", &response_headers)) {
out->response_headers = new net::HttpResponseHeaders("");
for (base::DictionaryValue::Iterator it(response_headers);
!it.IsAtEnd();
it.Advance()) {
std::string value;
CHECK(it.value().GetAsString(&value));
out->response_headers->AddHeader(it.key() + " : " + value);
}
}
return true;
}
} // namespace mate } // namespace mate

View file

@ -5,7 +5,6 @@
#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ #ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ #define ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_
#include "atom/browser/net/atom_network_delegate.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "native_mate/converter.h" #include "native_mate/converter.h"
@ -35,13 +34,6 @@ struct Converter<scoped_refptr<net::X509Certificate>> {
const scoped_refptr<net::X509Certificate>& val); const scoped_refptr<net::X509Certificate>& val);
}; };
template<>
struct Converter<atom::AtomNetworkDelegate::BlockingResponse> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
atom::AtomNetworkDelegate::BlockingResponse* out);
};
} // namespace mate } // namespace mate
#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_

View file

@ -12,7 +12,7 @@ const BrowserWindow = require('electron').BrowserWindow;
var win = new BrowserWindow({ width: 800, height: 600 }); var win = new BrowserWindow({ width: 800, height: 600 });
win.loadURL("http://github.com"); win.loadURL("http://github.com");
var ses = win.webContents.session var ses = win.webContents.session;
``` ```
## Methods ## Methods
@ -63,7 +63,7 @@ Emitted when Electron is about to download `item` in `webContents`.
Calling `event.preventDefault()` will cancel the download. Calling `event.preventDefault()` will cancel the download.
```javascript ```javascript
session.on('will-download', function(event, item, webContents) { session.defaultSession.on('will-download', function(event, item, webContents) {
event.preventDefault(); event.preventDefault();
require('request')(item.getURL(), function(data) { require('request')(item.getURL(), function(data) {
require('fs').writeFileSync('/somewhere', data); require('fs').writeFileSync('/somewhere', data);
@ -80,91 +80,84 @@ The following methods are available on instances of `Session`:
The `cookies` gives you ability to query and modify cookies. For example: The `cookies` gives you ability to query and modify cookies. For example:
```javascript ```javascript
const BrowserWindow = require('electron').BrowserWindow; // Query all cookies.
session.defaultSession.cookies.get({}, function(error, cookies) {
var win = new BrowserWindow({ width: 800, height: 600 });
win.loadURL('https://github.com');
win.webContents.on('did-finish-load', function() {
// Query all cookies.
win.webContents.session.cookies.get({}, function(error, cookies) {
if (error) throw error;
console.log(cookies); console.log(cookies);
}); });
// Query all cookies associated with a specific url. // Query all cookies associated with a specific url.
win.webContents.session.cookies.get({ url : "http://www.github.com" }, session.defaultSession.cookies.get({ url : "http://www.github.com" }, function(error, cookies) {
function(error, cookies) {
if (error) throw error;
console.log(cookies); console.log(cookies);
}); });
// Set a cookie with the given cookie data; // Set a cookie with the given cookie data;
// may overwrite equivalent cookies if they exist. // may overwrite equivalent cookies if they exist.
win.webContents.session.cookies.set( var cookie = { url : "http://www.github.com", name : "dummy_name", value : "dummy" };
{ url : "http://www.github.com", name : "dummy_name", value : "dummy"}, session.defaultSession.cookies.set(cookie, function(error) {
function(error, cookies) { if (error)
if (error) throw error; console.error(error);
console.log(cookies);
});
}); });
``` ```
#### `ses.cookies.get(details, callback)` #### `ses.cookies.get(filter, callback)`
`details` Object, properties: * `filter` Object
* `url` String __optional__ - Retrieves cookies which are associated with
`url`. Empty implies retrieving cookies of all urls.
* `name` String __optional__ - Filters cookies by name.
* `domain` String __optional__ - Retrieves cookies whose domains match or are
subdomains of `domains`
* `path` String __optional__ - Retrieves cookies whose path matches `path`.
* `secure` Boolean __optional__ - Filters cookies by their Secure property.
* `session` Boolean __optional__ - Filters out session or persistent cookies.
* `callback` Function
* `url` String - Retrieves cookies which are associated with `url`. Sends a request to get all cookies matching `details`, `callback` will be called
Empty implies retrieving cookies of all urls. with `callback(error, cookies)` on complete.
* `name` String - Filters cookies by name
* `domain` String - Retrieves cookies whose domains match or are subdomains of `cookies` is an Array of `cookie` objects.
`domains`
* `path` String - Retrieves cookies whose path matches `path` * `cookie` Object
* `secure` Boolean - Filters cookies by their Secure property
* `session` Boolean - Filters out session or persistent cookies.
* `callback` Function - function(error, cookies)
* `error` Error
* `cookies` Array - array of `cookie` objects, properties:
* `name` String - The name of the cookie. * `name` String - The name of the cookie.
* `value` String - The value of the cookie. * `value` String - The value of the cookie.
* `domain` String - The domain of the cookie. * `domain` String - The domain of the cookie.
* `host_only` String - Whether the cookie is a host-only cookie. * `hostOnly` String - Whether the cookie is a host-only cookie.
* `path` String - The path of the cookie. * `path` String - The path of the cookie.
* `secure` Boolean - Whether the cookie is marked as Secure (typically HTTPS). * `secure` Boolean - Whether the cookie is marked as secure.
* `http_only` Boolean - Whether the cookie is marked as HttpOnly. * `httpOnly` Boolean - Whether the cookie is marked as HTTP only.
* `session` Boolean - Whether the cookie is a session cookie or a persistent * `session` Boolean - Whether the cookie is a session cookie or a persistent
cookie with an expiration date. cookie with an expiration date.
* `expirationDate` Double - (Option) The expiration date of the cookie as * `expirationDate` Double __optional__ - The expiration date of the cookie as
the number of seconds since the UNIX epoch. Not provided for session the number of seconds since the UNIX epoch. Not provided for session
cookies. cookies.
#### `ses.cookies.set(details, callback)` #### `ses.cookies.set(details, callback)`
`details` Object, properties:
* `url` String - Retrieves cookies which are associated with `url`
* `name` String - The name of the cookie. Empty by default if omitted.
* `value` String - The value of the cookie. Empty by default if omitted.
* `domain` String - The domain of the cookie. Empty by default if omitted.
* `path` String - The path of the cookie. Empty by default if omitted.
* `secure` Boolean - Whether the cookie should be marked as Secure. Defaults to
false.
* `session` Boolean - Whether the cookie should be marked as HttpOnly. Defaults
to false.
* `expirationDate` Double - The expiration date of the cookie as the number of
seconds since the UNIX epoch. If omitted, the cookie becomes a session cookie.
* `callback` Function - function(error)
* `error` Error
#### `ses.cookies.remove(details, callback)`
* `details` Object * `details` Object
* `url` String - The URL associated with the cookie * `url` String - Retrieves cookies which are associated with `url`
* `name` String - The name of cookie to remove * `name` String - The name of the cookie. Empty by default if omitted.
* `callback` Function - function(error) * `value` String - The value of the cookie. Empty by default if omitted.
* `error` Error * `domain` String - The domain of the cookie. Empty by default if omitted.
* `path` String - The path of the cookie. Empty by default if omitted.
* `secure` Boolean - Whether the cookie should be marked as Secure. Defaults to
false.
* `session` Boolean - Whether the cookie should be marked as HttpOnly. Defaults
to false.
* `expirationDate` Double - The expiration date of the cookie as the number of
seconds since the UNIX epoch. If omitted, the cookie becomes a session cookie.
* `callback` Function
Sets the cookie with `details`, `callback` will be called with `callback(error)`
on complete.
#### `ses.cookies.remove(url, name, callback)`
* `url` String - The URL associated with the cookie.
* `name` String - The name of cookie to remove.
* `callback` Function
Removes the cookies matching `url` and `name`, `callback` will called with
`callback()` on complete.
#### `ses.clearCache(callback)` #### `ses.clearCache(callback)`
@ -289,88 +282,106 @@ myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, c
#### `ses.webRequest` #### `ses.webRequest`
The `webRequest` api allows to intercept and modify contents of a request at various The `webRequest` API set allows to intercept and modify contents of a request at
stages of its lifetime. various stages of its lifetime.
Each API accepts an optional `filter` and a `listener`, the `listener` will be
called with `listener(details)` when the API's event has happened, the `details`
is an object that describes the request. Passing `null` as `listener` will
unsubscribe from the event.
The `filter` is an object that has an `urls` property, which is an Array of URL
patterns that will be used to filter out the requests that do not match the URL
patterns. If the `filter` is omitted then all requests will be matched.
For certain events the `listener` is passed with a `callback`, which should be
called with an `response` object when `listener` has done its work.
```javascript ```javascript
// Modify the user agent for all requests to the following urls. // Modify the user agent for all requests to the following urls.
var filter = { var filter = {
urls: ["https://*.github.com/*", "*://electron.github.io"] urls: ["https://*.github.com/*", "*://electron.github.io"]
} };
myWindow.webContents.session.webRequest.onBeforeSendHeaders(filter, function(details) { session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details, callback) {
details.requestHeaders['User-Agent'] = "MyAgent"; details.requestHeaders['User-Agent'] = "MyAgent";
return {cancel: false, requestHeaders: details.requestHeaders}; callback({cancel: false, requestHeaders: details.requestHeaders});
}) });
``` ```
#### `ses.webRequest.onBeforeRequest([filter,] listener)` #### `ses.webRequest.onBeforeRequest([filter, ]listener)`
* `filter` Object * `filter` Object
* `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs
will be filtered out.
* `listener` Function * `listener` Function
* `details` Object
* `id` String - Request id. The `listener` will be called with `listener(details, callback)` when a request
is about to occur.
* `details` Object
* `id` Integer
* `url` String * `url` String
* `method` String * `method` String
* `resourceType` String * `resourceType` String
* `timestamp` Double * `timestamp` Double
* `blockingResponse` Object
* `cancel` Boolean - Whether to continue or block the request.
* `redirectURL` String **optional** - The original request is prevented from being sent or
completed, and is instead redirected to the given URL.
Fired when a request is about to occur. Should return a `blockingResponse`. The `callback` has to be called with an `response` object:
#### `ses.webRequest.onBeforeSendHeaders([filter,] listener)` * `response` Object
* `cancel` Boolean __optional__
* `redirectURL` String __optional__ - The original request is prevented from
being sent or completed, and is instead redirected to the given URL.
#### `ses.webRequest.onBeforeSendHeaders([filter, ]listener)`
* `filter` Object * `filter` Object
* `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs
will be filtered out.
* `listener` Function * `listener` Function
* `details` Object
* `id` String - Request id.
* `url` String
* `method` String
* `resourceType` String
* `timestamp` Double
* `requestHeaders` Object
* `blockingResponse` Object
* `cancel` Boolean - Whether to continue or block the request.
* `requestHeaders` Object **optional** - When provided, request will be made with these
headers.
Fired before sending an HTTP request, once the request headers are available. This may The `listener` will be called with `listener(details, callback)` before sending
occur after a TCP connection is made to the server, but before any http data is sent. an HTTP request, once the request headers are available. This may occur after a
Should return a `blockingResponse`. TCP connection is made to the server, but before any http data is sent.
#### `ses.webRequest.onSendHeaders([filter,] listener)` * `details` Object
* `id` Integer
* `filter` Object
* `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs
will be filtered out.
* `listener` Function
* `details` Object
* `id` String - Request id.
* `url` String * `url` String
* `method` String * `method` String
* `resourceType` String * `resourceType` String
* `timestamp` Double * `timestamp` Double
* `requestHeaders` Object * `requestHeaders` Object
Fired just before a request is going to be sent to the server, modifications of previous The `callback` has to be called with an `response` object:
`onBeforeSendHeaders` response are visible by the time this listener is fired.
* `response` Object
* `cancel` Boolean __optional__
* `requestHeaders` Object __optional__ - When provided, request will be made
with these headers.
#### `ses.webRequest.onSendHeaders([filter, ]listener)`
* `filter` Object
* `listener` Function
The `listener` will be called with `listener(details)` just before a request is
going to be sent to the server, modifications of previous `onBeforeSendHeaders`
response are visible by the time this listener is fired.
* `details` Object
* `id` Integer
* `url` String
* `method` String
* `resourceType` String
* `timestamp` Double
* `requestHeaders` Object
#### `ses.webRequest.onHeadersReceived([filter,] listener)` #### `ses.webRequest.onHeadersReceived([filter,] listener)`
* `filter` Object * `filter` Object
* `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs
will be filtered out.
* `listener` Function * `listener` Function
* `details` Object
* `id` String - Request id. The `listener` will be called with `listener(details, callback)` when HTTP
response headers of a request have been received.
* `details` Object
* `id` String
* `url` String * `url` String
* `method` String * `method` String
* `resourceType` String * `resourceType` String
@ -378,86 +389,87 @@ Fired just before a request is going to be sent to the server, modifications of
* `statusLine` String * `statusLine` String
* `statusCode` Integer * `statusCode` Integer
* `responseHeaders` Object * `responseHeaders` Object
* `blockingResponse` Object
* `cancel` Boolean - Whether to continue or block the request.
* `responseHeaders` Object **optional** - When provided, the server is assumed to have
responded with these headers.
Fired when HTTP response headers of a request have been received. Should return a The `callback` has to be called with an `response` object:
`blockingResponse`.
#### `ses.webRequest.onResponseStarted([filter,] listener)` * `response` Object
* `cancel` Boolean
* `responseHeaders` Object __optional__ - When provided, the server is assumed
to have responded with these headers.
#### `ses.webRequest.onResponseStarted([filter, ]listener)`
* `filter` Object * `filter` Object
* `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs
will be filtered out.
* `listener` Function * `listener` Function
* `details` Object
* `id` String - Request id. The `listener` will be called with `listener(details)` when first byte of the
response body is received. For HTTP requests, this means that the status line
and response headers are available.
* `details` Object
* `id` Integer
* `url` String * `url` String
* `method` String * `method` String
* `resourceType` String * `resourceType` String
* `timestamp` Double * `timestamp` Double
* `responseHeaders` Object * `responseHeaders` Object
* `fromCache` Boolean * `fromCache` Boolean - Indicates whether the response was fetched from disk
cache.
* `statusCode` Integer * `statusCode` Integer
* `statusLine` String * `statusLine` String
Fired when first byte of the response body is received. For HTTP requests, this means that the #### `ses.webRequest.onBeforeRedirect([filter, ]listener)`
status line and response headers are available.
#### `ses.webRequest.onBeforeRedirect([filter,] listener)`
* `filter` Object * `filter` Object
* `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs
will be filtered out.
* `listener` Function * `listener` Function
* `details` Object
* `id` String - Request id. The `listener` will be called with `listener(details)` when a server initiated
redirect is about to occur.
* `details` Object
* `id` String
* `url` String * `url` String
* `method` String * `method` String
* `resourceType` String * `resourceType` String
* `timestamp` Double * `timestamp` Double
* `redirectURL` String * `redirectURL` String
* `statusCode` Integer * `statusCode` Integer
* `ip` String **optional** - The server IP address that the request was actually sent to. * `ip` String __optional__ - The server IP address that the request was
actually sent to.
* `fromCache` Boolean * `fromCache` Boolean
* `responseHeaders` Object * `responseHeaders` Object
Fired when a server initiated redirect is about to occur. #### `ses.webRequest.onCompleted([filter, ]listener)`
#### `ses.webRequest.onCompleted([filter,] listener)`
* `filter` Object * `filter` Object
* `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs
will be filtered out.
* `listener` Function * `listener` Function
* `details` Object
* `id` String - Request id. The `listener` will be called with `listener(details)` when a request is
completed.
* `details` Object
* `id` Integer
* `url` String * `url` String
* `method` String * `method` String
* `resourceType` String * `resourceType` String
* `timestamp` Double * `timestamp` Double
* `responseHeaders` Object * `responseHeaders` Object
* `fromCache` Boolean - Indicates whether the response was fetched from disk cache. * `fromCache` Boolean
* `statusCode` Integer * `statusCode` Integer
* `statusLine` String * `statusLine` String
Fired when a request is completed. #### `ses.webRequest.onErrorOccurred([filter, ]listener)`
#### `ses.webRequest.onErrorOccurred([filter,] listener)`
* `filter` Object * `filter` Object
* `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs
will be filtered out.
* `listener` Function * `listener` Function
* `details` Object
* `id` String - Request id. The `listener` will be called with `listener(details)` when an error occurs.
* `details` Object
* `id` Integer
* `url` String * `url` String
* `method` String * `method` String
* `resourceType` String * `resourceType` String
* `timestamp` Double * `timestamp` Double
* `fromCache` Boolean - Indicates whether the response was fetched from disk cache. * `fromCache` Boolean
* `error` String - The error description. * `error` String - The error description.
Fired when an error occurs.

View file

@ -49,8 +49,7 @@ describe 'session module', ->
it 'should remove cookies', (done) -> it 'should remove cookies', (done) ->
session.defaultSession.cookies.set {url: url, name: '2', value: '2'}, (error) -> session.defaultSession.cookies.set {url: url, name: '2', value: '2'}, (error) ->
return done(error) if error return done(error) if error
session.defaultSession.cookies.remove {url: url, name: '2'}, (error) -> session.defaultSession.cookies.remove url, '2', ->
return done(error) if error
session.defaultSession.cookies.get {url: url}, (error, list) -> session.defaultSession.cookies.get {url: url}, (error, list) ->
return done(error) if error return done(error) if error
for cookie in list when cookie.name is '2' for cookie in list when cookie.name is '2'

View file

@ -0,0 +1,232 @@
assert = require 'assert'
http = require 'http'
{remote} = require 'electron'
{session} = remote
describe 'webRequest module', ->
ses = session.defaultSession
server = http.createServer (req, res) ->
res.setHeader('Custom', ['Header'])
content = req.url
if req.headers.accept is '*/*;test/header'
content += 'header/received'
res.end content
defaultURL = null
before (done) ->
server.listen 0, '127.0.0.1', ->
{port} = server.address()
defaultURL = "http://127.0.0.1:#{port}/"
done()
after ->
server.close()
describe 'webRequest.onBeforeRequest', ->
afterEach ->
ses.webRequest.onBeforeRequest null
it 'can cancel the request', (done) ->
ses.webRequest.onBeforeRequest (details, callback) ->
callback(cancel: true)
$.ajax
url: defaultURL
success: (data) -> done('unexpected success')
error: (xhr, errorType, error) -> done()
it 'can filter URLs', (done) ->
filter = urls: ["#{defaultURL}filter/*"]
ses.webRequest.onBeforeRequest filter, (details, callback) ->
callback(cancel: true)
$.ajax
url: "#{defaultURL}nofilter/test"
success: (data) ->
assert.equal data, '/nofilter/test'
$.ajax
url: "#{defaultURL}filter/test"
success: (data) -> done('unexpected success')
error: (xhr, errorType, error) -> done()
error: (xhr, errorType, error) -> done(errorType)
it 'receives details object', (done) ->
ses.webRequest.onBeforeRequest (details, callback) ->
assert.equal typeof details.id, 'number'
assert.equal typeof details.timestamp, 'number'
assert.equal details.url, defaultURL
assert.equal details.method, 'GET'
assert.equal details.resourceType, 'xhr'
callback({})
$.ajax
url: defaultURL
success: (data) ->
assert.equal data, '/'
done()
error: (xhr, errorType, error) -> done(errorType)
it 'can redirect the request', (done) ->
ses.webRequest.onBeforeRequest (details, callback) ->
if details.url is defaultURL
callback(redirectURL: "#{defaultURL}redirect")
else
callback({})
$.ajax
url: defaultURL
success: (data) ->
assert.equal data, '/redirect'
done()
error: (xhr, errorType, error) -> done(errorType)
describe 'webRequest.onBeforeSendHeaders', ->
afterEach ->
ses.webRequest.onBeforeSendHeaders null
it 'receives details object', (done) ->
ses.webRequest.onBeforeSendHeaders (details, callback) ->
assert.equal typeof details.requestHeaders, 'object'
callback({})
$.ajax
url: defaultURL
success: (data) ->
assert.equal data, '/'
done()
error: (xhr, errorType, error) -> done(errorType)
it 'can change the request headers', (done) ->
ses.webRequest.onBeforeSendHeaders (details, callback) ->
{requestHeaders} = details
requestHeaders.Accept = '*/*;test/header'
callback({requestHeaders})
$.ajax
url: defaultURL
success: (data, textStatus, request) ->
assert.equal data, '/header/received'
done()
error: (xhr, errorType, error) -> done(errorType)
describe 'webRequest.onSendHeaders', ->
afterEach ->
ses.webRequest.onSendHeaders null
it 'receives details object', (done) ->
ses.webRequest.onSendHeaders (details, callback) ->
assert.equal typeof details.requestHeaders, 'object'
$.ajax
url: defaultURL
success: (data) ->
assert.equal data, '/'
done()
error: (xhr, errorType, error) -> done(errorType)
describe 'webRequest.onHeadersReceived', ->
afterEach ->
ses.webRequest.onHeadersReceived null
it 'receives details object', (done) ->
ses.webRequest.onHeadersReceived (details, callback) ->
assert.equal details.statusLine, 'HTTP/1.1 200 OK'
assert.equal details.statusCode, 200
assert.equal details.responseHeaders['Custom'], 'Header'
callback({})
$.ajax
url: defaultURL
success: (data) ->
assert.equal data, '/'
done()
error: (xhr, errorType, error) -> done(errorType)
it 'can change the response header', (done) ->
ses.webRequest.onHeadersReceived (details, callback) ->
{responseHeaders} = details
responseHeaders['Custom'] = ['Changed']
callback({responseHeaders})
$.ajax
url: defaultURL
success: (data, status, xhr) ->
assert.equal xhr.getResponseHeader('Custom'), 'Changed'
assert.equal data, '/'
done()
error: (xhr, errorType, error) -> done(errorType)
it 'does not change header by default', (done) ->
ses.webRequest.onHeadersReceived (details, callback) ->
callback({})
$.ajax
url: defaultURL
success: (data, status, xhr) ->
assert.equal xhr.getResponseHeader('Custom'), 'Header'
assert.equal data, '/'
done()
error: (xhr, errorType, error) -> done(errorType)
describe 'webRequest.onResponseStarted', ->
afterEach ->
ses.webRequest.onResponseStarted null
it 'receives details object', (done) ->
ses.webRequest.onResponseStarted (details) ->
assert.equal typeof details.fromCache, 'boolean'
assert.equal details.statusLine, 'HTTP/1.1 200 OK'
assert.equal details.statusCode, 200
assert.equal details.responseHeaders['Custom'], 'Header'
$.ajax
url: defaultURL
success: (data, status, xhr) ->
assert.equal xhr.getResponseHeader('Custom'), 'Header'
assert.equal data, '/'
done()
error: (xhr, errorType, error) -> done(errorType)
describe 'webRequest.onBeforeRedirect', ->
afterEach ->
ses.webRequest.onBeforeRedirect null
ses.webRequest.onBeforeRequest null
it 'receives details object', (done) ->
redirectURL = "#{defaultURL}redirect"
ses.webRequest.onBeforeRequest (details, callback) ->
if details.url is defaultURL
callback({redirectURL})
else
callback({})
ses.webRequest.onBeforeRedirect (details) ->
assert.equal typeof details.fromCache, 'boolean'
assert.equal details.statusLine, 'HTTP/1.1 307 Internal Redirect'
assert.equal details.statusCode, 307
assert.equal details.redirectURL, redirectURL
$.ajax
url: defaultURL
success: (data, status, xhr) ->
assert.equal data, '/redirect'
done()
error: (xhr, errorType, error) -> done(errorType)
describe 'webRequest.onCompleted', ->
afterEach ->
ses.webRequest.onCompleted null
it 'receives details object', (done) ->
ses.webRequest.onCompleted (details) ->
assert.equal typeof details.fromCache, 'boolean'
assert.equal details.statusLine, 'HTTP/1.1 200 OK'
assert.equal details.statusCode, 200
$.ajax
url: defaultURL
success: (data, status, xhr) ->
assert.equal data, '/'
done()
error: (xhr, errorType, error) -> done(errorType)
describe 'webRequest.onErrorOccurred', ->
afterEach ->
ses.webRequest.onErrorOccurred null
ses.webRequest.onBeforeRequest null
it 'receives details object', (done) ->
ses.webRequest.onBeforeRequest (details, callback) ->
callback(cancel: true)
ses.webRequest.onErrorOccurred (details) ->
assert.equal details.error, 'net::ERR_BLOCKED_BY_CLIENT'
done()
$.ajax
url: defaultURL
success: (data) -> done('unexpected success')

View file

@ -45,7 +45,7 @@ describe 'chromium feature', ->
done() done()
w.loadURL url w.loadURL url
describe 'navigator.webkitGetUserMedia', -> xdescribe 'navigator.webkitGetUserMedia', ->
it 'calls its callbacks', (done) -> it 'calls its callbacks', (done) ->
@timeout 5000 @timeout 5000
navigator.webkitGetUserMedia audio: true, video: false, navigator.webkitGetUserMedia audio: true, video: false,

2
vendor/native_mate vendored

@ -1 +1 @@
Subproject commit 5e70868fd0c005dc2c43bea15ca6e93da0b68741 Subproject commit a3dcf8ced663e974ac94ad5e50a1d25a43995a9d