diff --git a/atom/browser/api/atom_api_cookies.cc b/atom/browser/api/atom_api_cookies.cc index 3b1bd499be4..6323e5110c1 100644 --- a/atom/browser/api/atom_api_cookies.cc +++ b/atom/browser/api/atom_api_cookies.cc @@ -7,7 +7,6 @@ #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/value_converter.h" -#include "base/bind.h" #include "base/time/time.h" #include "base/values.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_getter.h" -using atom::api::Cookies; 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 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 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 error = mate::ConvertToV8(isolate, error_message); - callback.Run(error, v8::Null(isolate)); - return; - } - if (!set_success) { - v8::Local 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 { +template<> +struct Converter { + static v8::Local 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<> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, @@ -161,11 +42,11 @@ struct Converter { dict.Set("name", val.Name()); dict.Set("value", val.Value()); 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("secure", val.IsSecure()); - dict.Set("http_only", val.IsHttpOnly()); - dict.Set("session", val.IsPersistent()); + dict.Set("httpOnly", val.IsHttpOnly()); + dict.Set("session", !val.IsPersistent()); if (!val.IsPersistent()) dict.Set("expirationDate", val.ExpiryDate().ToDoubleT()); return dict.GetHandle(); @@ -178,121 +59,117 @@ namespace atom { namespace api { -Cookies::Cookies(content::BrowserContext* browser_context) - : request_context_getter_(browser_context->GetRequestContext()) { -} +namespace { -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, - const CookiesCallback& callback) { - scoped_ptr filter( - options.DeepCopyWithoutEmptyChildren()); + std::string sub_domain(domain); + // Strip any leading '.' character from the input cookie domain. + if (!net::cookie_util::DomainIsHostOnly(sub_domain)) + sub_domain = sub_domain.substr(1); - content::BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&Cookies::GetCookiesOnIOThread, base::Unretained(this), - Passed(&filter), callback)); -} - -void Cookies::GetCookiesOnIOThread(scoped_ptr filter, - 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)); + // Now check whether the domain argument is a subdomain of the filter domain. + for (sub_domain.insert(0, "."); sub_domain.length() >= filter.length();) { + if (sub_domain == filter) + return true; + const size_t next_dot = sub_domain.find('.', 1); // Skip over leading dot. + sub_domain.erase(0, next_dot); } + return false; } -void Cookies::OnGetCookies(scoped_ptr filter, - const CookiesCallback& callback, - const net::CookieList& cookie_list) { +// Returns whether |cookie| matches |filter|. +bool MatchesCookie(const base::DictionaryValue* filter, + 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 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 filter, + const Cookies::GetCallback& callback, + const net::CookieList& list) { net::CookieList result; - for (const auto& cookie : cookie_list) { + for (const auto& cookie : list) { if (MatchesCookie(filter.get(), cookie)) result.push_back(cookie); } - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( - &RunGetCookiesCallbackOnUIThread, isolate(), "", result, callback)); + RunCallbackInUI(base::Bind(callback, Cookies::SUCCESS, result)); } -void Cookies::Remove(const mate::Dictionary& details, - const CookiesCallback& callback) { - GURL url; - std::string name; - 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) { +// Receives cookies matching |filter| in IO thread. +void GetCookiesOnIO(scoped_refptr getter, + scoped_ptr filter, + const Cookies::GetCallback& callback) { std::string url; - std::string error_message; - if (!options.GetString("url", &url)) { - error_message = "The url field is required."; - } + filter->GetString("url", &url); - GURL gurl(url); - if (error_message.empty() && !gurl.is_valid()) { - error_message = "URL is not valid."; - } + auto filtered_callback = + base::Bind(FilterCookies, base::Passed(&filter), callback); - if (!error_message.empty()) { - RunSetCookiesCallbackOnUIThread(isolate(), error_message, false, callback); - return; - } - - scoped_ptr details( - options.DeepCopyWithoutEmptyChildren()); - - content::BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&Cookies::SetCookiesOnIOThread, base::Unretained(this), - Passed(&details), gurl, callback)); + net::CookieMonster* monster = GetCookieStore(getter)->GetCookieMonster(); + // Empty url will match all url cookies. + if (url.empty()) + monster->GetAllCookiesAsync(filtered_callback); + else + monster->GetAllCookiesForURLAsync(GURL(url), filtered_callback); } -void Cookies::SetCookiesOnIOThread(scoped_ptr details, - const GURL& url, - const CookiesCallback& callback) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); +// Removes cookie with |url| and |name| in IO thread. +void RemoveCookieOnIOThread(scoped_refptr getter, + const GURL& url, const std::string& name, + 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 getter, + scoped_ptr details, + const Cookies::SetCallback& callback) { + std::string url, name, value, domain, path; bool secure = false; bool http_only = false; double expiration_date; - + details->GetString("url", &url); details->GetString("name", &name); details->GetString("value", &value); details->GetString("domain", &domain); details->GetString("path", &path); details->GetBoolean("secure", &secure); - details->GetBoolean("http_only", &http_only); + details->GetBoolean("httpOnly", &http_only); base::Time expiration_time; if (details->GetDouble("expirationDate", &expiration_date)) { @@ -301,29 +178,44 @@ void Cookies::SetCookiesOnIOThread(scoped_ptr details, base::Time::FromDoubleT(expiration_date); } - GetCookieStore()->GetCookieMonster()->SetCookieWithDetailsAsync( - url, - name, - value, - domain, - path, - expiration_time, - secure, - http_only, - false, - net::COOKIE_PRIORITY_DEFAULT, - base::Bind(&Cookies::OnSetCookies, base::Unretained(this), callback)); + GetCookieStore(getter)->GetCookieMonster()->SetCookieWithDetailsAsync( + GURL(url), name, value, domain, path, expiration_time, secure, http_only, + false, net::COOKIE_PRIORITY_DEFAULT, base::Bind(OnSetCookie, callback)); } -void Cookies::OnSetCookies(const CookiesCallback& callback, - bool set_success) { - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(&RunSetCookiesCallbackOnUIThread, isolate(), "", set_success, - callback)); +} // namespace + +Cookies::Cookies(content::BrowserContext* browser_context) + : request_context_getter_(browser_context->GetRequestContext()) { } -net::CookieStore* Cookies::GetCookieStore() { - return request_context_getter_->GetURLRequestContext()->cookie_store(); +Cookies::~Cookies() { +} + +void Cookies::Get(const base::DictionaryValue& filter, + const GetCallback& callback) { + scoped_ptr 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 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 diff --git a/atom/browser/api/atom_api_cookies.h b/atom/browser/api/atom_api_cookies.h index 5afa1bd23ca..302fd1b2511 100644 --- a/atom/browser/api/atom_api_cookies.h +++ b/atom/browser/api/atom_api_cookies.h @@ -20,12 +20,7 @@ namespace content { class BrowserContext; } -namespace mate { -class Dictionary; -} - namespace net { -class CookieStore; class URLRequestContextGetter; } @@ -35,9 +30,13 @@ namespace api { class Cookies : public mate::TrackableObject { public: - // node.js style callback function(error, result) - typedef base::Callback, v8::Local)> - CookiesCallback; + enum Error { + SUCCESS, + FAILED, + }; + + using GetCallback = base::Callback; + using SetCallback = base::Callback; static mate::Handle Create(v8::Isolate* isolate, content::BrowserContext* browser_context); @@ -50,34 +49,12 @@ class Cookies : public mate::TrackableObject { explicit Cookies(content::BrowserContext* browser_context); ~Cookies(); - void Get(const base::DictionaryValue& options, - const CookiesCallback& callback); - void Remove(const mate::Dictionary& details, - const CookiesCallback& callback); - void Set(const base::DictionaryValue& details, - const CookiesCallback& callback); - - void GetCookiesOnIOThread(scoped_ptr filter, - const CookiesCallback& callback); - void OnGetCookies(scoped_ptr 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 details, - const GURL& url, - const CookiesCallback& callback); - void OnSetCookies(const CookiesCallback& callback, - bool set_success); + void Get(const base::DictionaryValue& filter, const GetCallback& callback); + void Remove(const GURL& url, const std::string& name, + const base::Closure& callback); + void Set(const base::DictionaryValue& details, const SetCallback& callback); private: - // Must be called on IO thread. - net::CookieStore* GetCookieStore(); - net::URLRequestContextGetter* request_context_getter_; DISALLOW_COPY_AND_ASSIGN(Cookies); diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 9cec7378b8e..8b73d61622e 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -10,6 +10,7 @@ #include "atom/browser/api/atom_api_cookies.h" #include "atom/browser/api/atom_api_download_item.h" #include "atom/browser/api/atom_api_web_contents.h" +#include "atom/browser/api/atom_api_web_request.h" #include "atom/browser/api/save_page_handler.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" @@ -368,6 +369,14 @@ v8::Local Session::Cookies(v8::Isolate* isolate) { return v8::Local::New(isolate, cookies_); } +v8::Local Session::WebRequest(v8::Isolate* isolate) { + if (web_request_.IsEmpty()) { + auto handle = atom::api::WebRequest::Create(isolate, browser_context()); + web_request_.Reset(isolate, handle.ToV8()); + } + return v8::Local::New(isolate, web_request_); +} + // static mate::Handle Session::CreateFrom( v8::Isolate* isolate, AtomBrowserContext* browser_context) { @@ -401,7 +410,8 @@ void Session::BuildPrototype(v8::Isolate* isolate, .SetMethod("enableNetworkEmulation", &Session::EnableNetworkEmulation) .SetMethod("disableNetworkEmulation", &Session::DisableNetworkEmulation) .SetMethod("setCertificateVerifyProc", &Session::SetCertVerifyProc) - .SetProperty("cookies", &Session::Cookies); + .SetProperty("cookies", &Session::Cookies) + .SetProperty("webRequest", &Session::WebRequest); } void ClearWrapSession() { diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index e800a992d4b..0034b148063 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -70,9 +70,11 @@ class Session: public mate::TrackableObject, void DisableNetworkEmulation(); void SetCertVerifyProc(v8::Local proc, mate::Arguments* args); v8::Local Cookies(v8::Isolate* isolate); + v8::Local WebRequest(v8::Isolate* isolate); - // Cached object for cookies API. + // Cached object. v8::Global cookies_; + v8::Global web_request_; scoped_refptr browser_context_; diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index a70b6cf4e0b..cb89db911f0 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -5,6 +5,7 @@ #include "atom/browser/api/atom_api_web_contents.h" #include +#include #include "atom/browser/api/atom_api_session.h" #include "atom/browser/api/atom_api_window.h" diff --git a/atom/browser/api/atom_api_web_request.cc b/atom/browser/api/atom_api_web_request.cc new file mode 100644 index 00000000000..a987369ed82 --- /dev/null +++ b/atom/browser/api/atom_api_web_request.cc @@ -0,0 +1,119 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_web_request.h" + +#include + +#include "atom/browser/atom_browser_context.h" +#include "atom/browser/net/atom_network_delegate.h" +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/net_converter.h" +#include "atom/common/native_mate_converters/value_converter.h" +#include "content/public/browser/browser_thread.h" +#include "native_mate/dictionary.h" +#include "native_mate/object_template_builder.h" + +using content::BrowserThread; + +namespace mate { + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local 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 api { + +WebRequest::WebRequest(AtomBrowserContext* browser_context) + : browser_context_(browser_context) { +} + +WebRequest::~WebRequest() { +} + +template +void WebRequest::SetSimpleListener(mate::Arguments* args) { + SetListener( + &AtomNetworkDelegate::SetSimpleListenerInIO, type, args); +} + +template +void WebRequest::SetResponseListener(mate::Arguments* args) { + SetListener( + &AtomNetworkDelegate::SetResponseListenerInIO, type, args); +} + +template +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 value; + Listener listener; + if (!args->GetNext(&listener) && + !(args->GetNext(&value) && value->IsNull())) { + args->ThrowError("Must pass null or a Function"); + return; + } + + auto delegate = browser_context_->network_delegate(); + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(method, base::Unretained(delegate), type, + patterns, listener)); +} + +// static +mate::Handle WebRequest::Create( + v8::Isolate* isolate, + AtomBrowserContext* browser_context) { + return mate::CreateHandle(isolate, new WebRequest(browser_context)); +} + +// static +void WebRequest::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("onBeforeRequest", + &WebRequest::SetResponseListener< + AtomNetworkDelegate::kOnBeforeRequest>) + .SetMethod("onBeforeSendHeaders", + &WebRequest::SetResponseListener< + AtomNetworkDelegate::kOnBeforeSendHeaders>) + .SetMethod("onHeadersReceived", + &WebRequest::SetResponseListener< + AtomNetworkDelegate::kOnHeadersReceived>) + .SetMethod("onSendHeaders", + &WebRequest::SetSimpleListener< + AtomNetworkDelegate::kOnSendHeaders>) + .SetMethod("onBeforeRedirect", + &WebRequest::SetSimpleListener< + AtomNetworkDelegate::kOnBeforeRedirect>) + .SetMethod("onResponseStarted", + &WebRequest::SetSimpleListener< + AtomNetworkDelegate::kOnResponseStarted>) + .SetMethod("onCompleted", + &WebRequest::SetSimpleListener< + AtomNetworkDelegate::kOnCompleted>) + .SetMethod("onErrorOccurred", + &WebRequest::SetSimpleListener< + AtomNetworkDelegate::kOnErrorOccurred>); +} + +} // namespace api + +} // namespace atom diff --git a/atom/browser/api/atom_api_web_request.h b/atom/browser/api/atom_api_web_request.h new file mode 100644 index 00000000000..9a6e17a0460 --- /dev/null +++ b/atom/browser/api/atom_api_web_request.h @@ -0,0 +1,50 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_API_ATOM_API_WEB_REQUEST_H_ +#define ATOM_BROWSER_API_ATOM_API_WEB_REQUEST_H_ + +#include "atom/browser/api/trackable_object.h" +#include "atom/browser/net/atom_network_delegate.h" +#include "native_mate/arguments.h" +#include "native_mate/handle.h" + +namespace atom { + +class AtomBrowserContext; + +namespace api { + +class WebRequest : public mate::TrackableObject { + public: + static mate::Handle Create(v8::Isolate* isolate, + AtomBrowserContext* browser_context); + + // mate::TrackableObject: + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + protected: + explicit WebRequest(AtomBrowserContext* browser_context); + ~WebRequest(); + + // C++ can not distinguish overloaded member function. + template + void SetSimpleListener(mate::Arguments* args); + template + void SetResponseListener(mate::Arguments* args); + template + void SetListener(Method method, Event type, mate::Arguments* args); + + private: + scoped_refptr browser_context_; + + DISALLOW_COPY_AND_ASSIGN(WebRequest); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_WEB_REQUEST_H_ diff --git a/atom/browser/api/lib/session.coffee b/atom/browser/api/lib/session.coffee index 6abfe7925e6..5c65aa29cf6 100644 --- a/atom/browser/api/lib/session.coffee +++ b/atom/browser/api/lib/session.coffee @@ -6,6 +6,7 @@ PERSIST_PERFIX = 'persist:' # Returns the Session from |partition| string. exports.fromPartition = (partition='') -> + return exports.defaultSession if partition is '' if partition.startsWith PERSIST_PERFIX bindings.fromPartition partition.substr(PERSIST_PERFIX.length), false else @@ -14,7 +15,7 @@ exports.fromPartition = (partition='') -> # Returns the default session. Object.defineProperty exports, 'defaultSession', enumerable: true - get: -> exports.fromPartition '' + get: -> bindings.fromPartition '', false wrapSession = (session) -> # session is an EventEmitter. diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index ec123825822..12e0125752e 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -8,6 +8,7 @@ #include "atom/browser/atom_download_manager_delegate.h" #include "atom/browser/browser.h" #include "atom/browser/net/atom_cert_verifier.h" +#include "atom/browser/net/atom_network_delegate.h" #include "atom/browser/net/atom_ssl_config_service.h" #include "atom/browser/net/atom_url_request_job_factory.h" #include "atom/browser/net/asar/asar_protocol_handler.h" @@ -63,12 +64,17 @@ AtomBrowserContext::AtomBrowserContext(const std::string& partition, : brightray::BrowserContext(partition, in_memory), cert_verifier_(nullptr), job_factory_(new AtomURLRequestJobFactory), + network_delegate_(new AtomNetworkDelegate), allow_ntlm_everywhere_(false) { } AtomBrowserContext::~AtomBrowserContext() { } +net::NetworkDelegate* AtomBrowserContext::CreateNetworkDelegate() { + return network_delegate_; +} + std::string AtomBrowserContext::GetUserAgent() { Browser* browser = Browser::Get(); std::string name = RemoveWhitespace(browser->GetName()); diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index 564c9955d91..9c94a60c305 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -13,6 +13,7 @@ namespace atom { class AtomDownloadManagerDelegate; class AtomCertVerifier; +class AtomNetworkDelegate; class AtomURLRequestJobFactory; class WebViewManager; @@ -22,6 +23,7 @@ class AtomBrowserContext : public brightray::BrowserContext { ~AtomBrowserContext() override; // brightray::URLRequestContextGetter::Delegate: + net::NetworkDelegate* CreateNetworkDelegate() override; std::string GetUserAgent() override; scoped_ptr CreateURLRequestJobFactory( content::ProtocolHandlerMap* handlers, @@ -45,6 +47,8 @@ class AtomBrowserContext : public brightray::BrowserContext { AtomURLRequestJobFactory* job_factory() const { return job_factory_; } + AtomNetworkDelegate* network_delegate() const { return network_delegate_; } + private: scoped_ptr download_manager_delegate_; scoped_ptr guest_manager_; @@ -52,6 +56,7 @@ class AtomBrowserContext : public brightray::BrowserContext { // Managed by brightray::BrowserContext. AtomCertVerifier* cert_verifier_; AtomURLRequestJobFactory* job_factory_; + AtomNetworkDelegate* network_delegate_; bool allow_ntlm_everywhere_; diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index 53bbb735b04..9ed75c225f5 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -73,15 +73,15 @@ ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', (event, guestId) -> ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestId, method, args...) -> BrowserWindow.fromId(guestId)?[method] args... -ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId, message, targetOrigin) -> +ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId, message, targetOrigin, sourceOrigin) -> guestContents = BrowserWindow.fromId(guestId)?.webContents if guestContents?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*' - guestContents.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, targetOrigin + guestContents?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, sourceOrigin ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', (event, guestId, message, targetOrigin, sourceOrigin) -> embedder = v8Util.getHiddenValue event.sender, 'embedder' if embedder?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*' - embedder.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, sourceOrigin + embedder?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, sourceOrigin ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestId, method, args...) -> BrowserWindow.fromId(guestId)?.webContents?[method] args... diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc new file mode 100644 index 00000000000..6bc25027db1 --- /dev/null +++ b/atom/browser/net/atom_network_delegate.cc @@ -0,0 +1,380 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/net/atom_network_delegate.h" + +#include + +#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/resource_request_info.h" +#include "net/url_request/url_request.h" + +using content::BrowserThread; + +namespace atom { + +namespace { + +const char* ResourceTypeToString(content::ResourceType type) { + switch (type) { + case content::RESOURCE_TYPE_MAIN_FRAME: + return "mainFrame"; + case content::RESOURCE_TYPE_SUB_FRAME: + return "subFrame"; + case content::RESOURCE_TYPE_STYLESHEET: + return "stylesheet"; + case content::RESOURCE_TYPE_SCRIPT: + return "script"; + case content::RESOURCE_TYPE_IMAGE: + return "image"; + case content::RESOURCE_TYPE_OBJECT: + return "object"; + case content::RESOURCE_TYPE_XHR: + return "xhr"; + default: + return "other"; + } +} + +void RunSimpleListener(const AtomNetworkDelegate::SimpleListener& listener, + scoped_ptr details) { + return listener.Run(*(details.get())); +} + +void RunResponseListener( + const AtomNetworkDelegate::ResponseListener& listener, + scoped_ptr details, + const AtomNetworkDelegate::ResponseCallback& callback) { + return listener.Run(*(details.get()), callback); +} + +// 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 false; +} + +// Overloaded by multiple types to fill the |details| object. +void ToDictionary(base::DictionaryValue* details, net::URLRequest* request) { + details->SetInteger("id", request->identifier()); + details->SetString("url", request->url().spec()); + details->SetString("method", request->method()); + details->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000); + auto info = content::ResourceRequestInfo::ForRequest(request); + details->SetString("resourceType", + info ? ResourceTypeToString(info->GetResourceType()) + : "other"); +} + +void ToDictionary(base::DictionaryValue* details, + const net::HttpRequestHeaders& headers) { + scoped_ptr dict(new base::DictionaryValue); + net::HttpRequestHeaders::Iterator it(headers); + while (it.GetNext()) + dict->SetString(it.name(), it.value()); + details->Set("requestHeaders", dict.Pass()); +} + +void ToDictionary(base::DictionaryValue* details, + const net::HttpResponseHeaders* headers) { + if (!headers) + return; + + scoped_ptr dict(new base::DictionaryValue); + void* iter = nullptr; + std::string key; + std::string value; + while (headers->EnumerateHeaderLines(&iter, &key, &value)) { + if (dict->HasKey(key)) { + base::ListValue* values = nullptr; + if (dict->GetList(key, &values)) + values->AppendString(value); + } else { + scoped_ptr values(new base::ListValue); + values->AppendString(value); + dict->Set(key, values.Pass()); + } + } + details->Set("responseHeaders", dict.Pass()); + details->SetString("statusLine", headers->GetStatusLine()); + details->SetInteger("statusCode", headers->response_code()); +} + +void ToDictionary(base::DictionaryValue* details, const GURL& location) { + details->SetString("redirectURL", location.spec()); +} + +void ToDictionary(base::DictionaryValue* details, + const net::HostPortPair& host_port) { + if (host_port.host().empty()) + details->SetString("ip", host_port.host()); +} + +void ToDictionary(base::DictionaryValue* details, bool from_cache) { + details->SetBoolean("fromCache", from_cache); +} + +void ToDictionary(base::DictionaryValue* details, + const net::URLRequestStatus& status) { + details->SetString("error", net::ErrorToString(status.error())); +} + +// Helper function to fill |details| with arbitrary |args|. +template +void FillDetailsObject(base::DictionaryValue* details, Arg arg) { + ToDictionary(details, arg); +} + +template +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; + if (it.value().GetAsString(&value)) + headers->SetHeader(it.key(), value); + } + } +} + +void ReadFromResponseObject(const base::DictionaryValue& response, + scoped_refptr* 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 +void OnListenerResultInIO(const net::CompletionCallback& callback, + T out, + scoped_ptr response) { + ReadFromResponseObject(*response.get(), out); + + bool cancel = false; + response->GetBoolean("cancel", &cancel); + callback.Run(cancel ? net::ERR_BLOCKED_BY_CLIENT : net::OK); +} + +template +void OnListenerResultInUI(const net::CompletionCallback& callback, + T out, + const base::DictionaryValue& response) { + scoped_ptr copy = response.CreateDeepCopy(); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(OnListenerResultInIO, callback, out, base::Passed(©))); +} + +} // namespace + +AtomNetworkDelegate::AtomNetworkDelegate() { +} + +AtomNetworkDelegate::~AtomNetworkDelegate() { +} + +void AtomNetworkDelegate::SetSimpleListenerInIO( + SimpleEvent type, + const URLPatterns& patterns, + const SimpleListener& callback) { + if (callback.is_null()) + simple_listeners_.erase(type); + else + simple_listeners_[type] = { patterns, callback }; +} + +void AtomNetworkDelegate::SetResponseListenerInIO( + ResponseEvent type, + const URLPatterns& patterns, + const ResponseListener& callback) { + if (callback.is_null()) + response_listeners_.erase(type); + else + response_listeners_[type] = { patterns, callback }; +} + +int AtomNetworkDelegate::OnBeforeURLRequest( + net::URLRequest* request, + const net::CompletionCallback& callback, + GURL* new_url) { + if (!ContainsKey(response_listeners_, kOnBeforeRequest)) + return brightray::NetworkDelegate::OnBeforeURLRequest( + request, callback, new_url); + + return HandleResponseEvent(kOnBeforeRequest, request, callback, new_url); +} + +int AtomNetworkDelegate::OnBeforeSendHeaders( + net::URLRequest* request, + const net::CompletionCallback& callback, + net::HttpRequestHeaders* headers) { + if (!ContainsKey(response_listeners_, kOnBeforeSendHeaders)) + return brightray::NetworkDelegate::OnBeforeSendHeaders( + request, callback, headers); + + return HandleResponseEvent( + kOnBeforeSendHeaders, request, callback, headers, *headers); +} + +void AtomNetworkDelegate::OnSendHeaders( + net::URLRequest* request, + const net::HttpRequestHeaders& headers) { + if (!ContainsKey(simple_listeners_, kOnSendHeaders)) { + brightray::NetworkDelegate::OnSendHeaders(request, headers); + return; + } + + HandleSimpleEvent(kOnSendHeaders, request, headers); +} + +int AtomNetworkDelegate::OnHeadersReceived( + net::URLRequest* request, + const net::CompletionCallback& callback, + const net::HttpResponseHeaders* original, + scoped_refptr* override, + GURL* allowed) { + if (!ContainsKey(response_listeners_, kOnHeadersReceived)) + return brightray::NetworkDelegate::OnHeadersReceived( + request, callback, original, override, allowed); + + return HandleResponseEvent( + kOnHeadersReceived, request, callback, override, original); +} + +void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, + const GURL& new_location) { + if (!ContainsKey(simple_listeners_, kOnBeforeRedirect)) { + 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) { + if (!ContainsKey(simple_listeners_, kOnResponseStarted)) { + brightray::NetworkDelegate::OnResponseStarted(request); + return; + } + + if (request->status().status() != net::URLRequestStatus::SUCCESS) + return; + + HandleSimpleEvent(kOnResponseStarted, request, request->response_headers(), + request->was_cached()); +} + +void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { + if (request->status().status() == net::URLRequestStatus::FAILED || + request->status().status() == net::URLRequestStatus::CANCELED) { + // Error event. + if (ContainsKey(simple_listeners_, kOnErrorOccurred)) + OnErrorOccurred(request); + else + 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) { + HandleSimpleEvent(kOnErrorOccurred, request, request->was_cached(), + request->status()); +} + +template +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 details(new base::DictionaryValue); + FillDetailsObject(details.get(), request, args...); + + ResponseCallback response = + base::Bind(OnListenerResultInUI, callback, out); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(RunResponseListener, info.listener, base::Passed(&details), + response)); + return net::ERR_IO_PENDING; +} + +template +void AtomNetworkDelegate::HandleSimpleEvent( + SimpleEvent type, net::URLRequest* request, Args... args) { + const auto& info = simple_listeners_[type]; + if (!MatchesFilterCondition(request, info.url_patterns)) + return; + + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request, args...); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(RunSimpleListener, info.listener, base::Passed(&details))); +} + +} // namespace atom diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h new file mode 100644 index 00000000000..8950a1b5110 --- /dev/null +++ b/atom/browser/net/atom_network_delegate.h @@ -0,0 +1,111 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_NET_ATOM_NETWORK_DELEGATE_H_ +#define ATOM_BROWSER_NET_ATOM_NETWORK_DELEGATE_H_ + +#include +#include + +#include "brightray/browser/network_delegate.h" +#include "base/callback.h" +#include "base/values.h" +#include "extensions/common/url_pattern.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" + +namespace extensions { +class URLPattern; +} + +namespace atom { + +using URLPatterns = std::set; + +class AtomNetworkDelegate : public brightray::NetworkDelegate { + public: + using ResponseCallback = base::Callback; + using SimpleListener = base::Callback; + using ResponseListener = base::Callback; + + enum SimpleEvent { + kOnSendHeaders, + kOnBeforeRedirect, + kOnResponseStarted, + kOnCompleted, + kOnErrorOccurred, + }; + + enum ResponseEvent { + kOnBeforeRequest, + kOnBeforeSendHeaders, + kOnHeadersReceived, + }; + + struct SimpleListenerInfo { + URLPatterns url_patterns; + SimpleListener listener; + }; + + struct ResponseListenerInfo { + URLPatterns url_patterns; + ResponseListener listener; + }; + + AtomNetworkDelegate(); + ~AtomNetworkDelegate() override; + + void SetSimpleListenerInIO(SimpleEvent type, + const URLPatterns& patterns, + const SimpleListener& callback); + void SetResponseListenerInIO(ResponseEvent type, + const URLPatterns& patterns, + const ResponseListener& callback); + + protected: + // net::NetworkDelegate: + int OnBeforeURLRequest(net::URLRequest* request, + const net::CompletionCallback& callback, + GURL* new_url) override; + int OnBeforeSendHeaders(net::URLRequest* request, + const net::CompletionCallback& callback, + net::HttpRequestHeaders* headers) override; + void OnSendHeaders(net::URLRequest* request, + const net::HttpRequestHeaders& headers) override; + int OnHeadersReceived( + net::URLRequest* request, + const net::CompletionCallback& callback, + const net::HttpResponseHeaders* original_response_headers, + scoped_refptr* override_response_headers, + GURL* allowed_unsafe_redirect_url) override; + void OnBeforeRedirect(net::URLRequest* request, + const GURL& new_location) override; + void OnResponseStarted(net::URLRequest* request) override; + void OnCompleted(net::URLRequest* request, bool started) override; + + void OnErrorOccurred(net::URLRequest* request); + + private: + template + void HandleSimpleEvent(SimpleEvent type, + net::URLRequest* request, + Args... args); + template + int HandleResponseEvent(ResponseEvent type, + net::URLRequest* request, + const net::CompletionCallback& callback, + Out out, + Args... args); + + std::map simple_listeners_; + std::map response_listeners_;; + + DISALLOW_COPY_AND_ASSIGN(AtomNetworkDelegate); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NET_ATOM_NETWORK_DELEGATE_H_ diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 179f26db03f..4c72c04cfad 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.35.4 + 0.36.0 CFBundleShortVersionString - 0.35.4 + 0.36.0 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 05fb3f336ff..9568629c422 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -56,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,35,4,0 - PRODUCTVERSION 0,35,4,0 + FILEVERSION 0,36,0,0 + PRODUCTVERSION 0,36,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.35.4" + VALUE "FileVersion", "0.36.0" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.35.4" + VALUE "ProductVersion", "0.36.0" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index a4930d0edb2..46293a0eb89 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -6,8 +6,8 @@ #define ATOM_VERSION_H #define ATOM_MAJOR_VERSION 0 -#define ATOM_MINOR_VERSION 35 -#define ATOM_PATCH_VERSION 4 +#define ATOM_MINOR_VERSION 36 +#define ATOM_PATCH_VERSION 0 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index 6089d715e37..7a1b48d9311 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -8,12 +8,15 @@ #include #include "atom/common/node_includes.h" +#include "atom/common/native_mate_converters/gurl_converter.h" +#include "atom/common/native_mate_converters/value_converter.h" #include "native_mate/dictionary.h" #include "net/base/upload_bytes_element_reader.h" #include "net/base/upload_data_stream.h" #include "net/base/upload_element_reader.h" #include "net/base/upload_file_element_reader.h" #include "net/cert/x509_certificate.h" +#include "net/http/http_response_headers.h" #include "net/url_request/url_request.h" namespace mate { diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index cb4fb8fbac2..333acc82739 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -8,9 +8,18 @@ resolveURL = (url) -> # Window object returned by "window.open". class BrowserWindowProxy + @proxies: {} + + @getOrCreate: (guestId) -> + @proxies[guestId] ?= new BrowserWindowProxy(guestId) + + @remove: (guestId) -> + delete @proxies[guestId] + constructor: (@guestId) -> @closed = false ipcRenderer.once "ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_#{@guestId}", => + BrowserWindowProxy.remove(@guestId) @closed = true close: -> @@ -23,7 +32,7 @@ class BrowserWindowProxy ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', @guestId, 'blur' postMessage: (message, targetOrigin='*') -> - ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', @guestId, message, targetOrigin + ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', @guestId, message, targetOrigin, location.origin eval: (args...) -> ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', @guestId, 'executeJavaScript', args... @@ -60,7 +69,7 @@ window.open = (url, frameName='', features='') -> guestId = ipcRenderer.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, options if guestId - new BrowserWindowProxy(guestId) + BrowserWindowProxy.getOrCreate(guestId) else null @@ -96,7 +105,7 @@ ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, guestId, message, event.initEvent 'message', false, false event.data = message event.origin = sourceOrigin - event.source = new BrowserWindowProxy(guestId) + event.source = BrowserWindowProxy.getOrCreate(guestId) window.dispatchEvent event # Forward history operations to browser. diff --git a/chromium_src/extensions/common/url_pattern.cc b/chromium_src/extensions/common/url_pattern.cc new file mode 100644 index 00000000000..4303689fe81 --- /dev/null +++ b/chromium_src/extensions/common/url_pattern.cc @@ -0,0 +1,619 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/common/url_pattern.h" + +#include + +#include "base/strings/pattern.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "content/public/common/url_constants.h" +#include "net/base/registry_controlled_domains/registry_controlled_domain.h" +#include "url/gurl.h" +#include "url/url_util.h" + +const char extensions::URLPattern::kAllUrlsPattern[] = ""; +const char kExtensionScheme[] = "chrome-extension"; + +namespace { + +// TODO(aa): What about more obscure schemes like data: and javascript: ? +// Note: keep this array in sync with kValidSchemeMasks. +const char* kValidSchemes[] = { + url::kHttpScheme, + url::kHttpsScheme, + url::kFileScheme, + url::kFtpScheme, + content::kChromeUIScheme, + kExtensionScheme, + url::kFileSystemScheme, +}; + +const int kValidSchemeMasks[] = { + extensions::URLPattern::SCHEME_HTTP, + extensions::URLPattern::SCHEME_HTTPS, + extensions::URLPattern::SCHEME_FILE, + extensions::URLPattern::SCHEME_FTP, + extensions::URLPattern::SCHEME_CHROMEUI, + extensions::URLPattern::SCHEME_EXTENSION, + extensions::URLPattern::SCHEME_FILESYSTEM, +}; + +static_assert(arraysize(kValidSchemes) == arraysize(kValidSchemeMasks), + "must keep these arrays in sync"); + +const char kParseSuccess[] = "Success."; +const char kParseErrorMissingSchemeSeparator[] = "Missing scheme separator."; +const char kParseErrorInvalidScheme[] = "Invalid scheme."; +const char kParseErrorWrongSchemeType[] = "Wrong scheme type."; +const char kParseErrorEmptyHost[] = "Host can not be empty."; +const char kParseErrorInvalidHostWildcard[] = "Invalid host wildcard."; +const char kParseErrorEmptyPath[] = "Empty path."; +const char kParseErrorInvalidPort[] = "Invalid port."; +const char kParseErrorInvalidHost[] = "Invalid host."; + +// Message explaining each URLPattern::ParseResult. +const char* const kParseResultMessages[] = { + kParseSuccess, + kParseErrorMissingSchemeSeparator, + kParseErrorInvalidScheme, + kParseErrorWrongSchemeType, + kParseErrorEmptyHost, + kParseErrorInvalidHostWildcard, + kParseErrorEmptyPath, + kParseErrorInvalidPort, + kParseErrorInvalidHost, +}; + +static_assert(extensions::URLPattern::NUM_PARSE_RESULTS == arraysize(kParseResultMessages), + "must add message for each parse result"); + +const char kPathSeparator[] = "/"; + +bool IsStandardScheme(const std::string& scheme) { + // "*" gets the same treatment as a standard scheme. + if (scheme == "*") + return true; + + return url::IsStandard(scheme.c_str(), + url::Component(0, static_cast(scheme.length()))); +} + +bool IsValidPortForScheme(const std::string& scheme, const std::string& port) { + if (port == "*") + return true; + + // Only accept non-wildcard ports if the scheme uses ports. + if (url::DefaultPortForScheme(scheme.c_str(), scheme.length()) == + url::PORT_UNSPECIFIED) { + return false; + } + + int parsed_port = url::PORT_UNSPECIFIED; + if (!base::StringToInt(port, &parsed_port)) + return false; + return (parsed_port >= 0) && (parsed_port < 65536); +} + +// Returns |path| with the trailing wildcard stripped if one existed. +// +// The functions that rely on this (OverlapsWith and Contains) are only +// called for the patterns inside URLPatternSet. In those cases, we know that +// the path will have only a single wildcard at the end. This makes figuring +// out overlap much easier. It seems like there is probably a computer-sciency +// way to solve the general case, but we don't need that yet. +std::string StripTrailingWildcard(const std::string& path) { + size_t wildcard_index = path.find('*'); + size_t path_last = path.size() - 1; + return wildcard_index == path_last ? path.substr(0, path_last) : path; +} + +} // namespace + +namespace extensions { +// static +bool URLPattern::IsValidSchemeForExtensions(const std::string& scheme) { + for (size_t i = 0; i < arraysize(kValidSchemes); ++i) { + if (scheme == kValidSchemes[i]) + return true; + } + return false; +} + +URLPattern::URLPattern() + : valid_schemes_(SCHEME_ALL), + match_all_urls_(false), + match_subdomains_(false), + port_("*") {} + +URLPattern::URLPattern(int valid_schemes) + : valid_schemes_(valid_schemes), + match_all_urls_(false), + match_subdomains_(false), + port_("*") {} + +URLPattern::URLPattern(int valid_schemes, const std::string& pattern) + // Strict error checking is used, because this constructor is only + // appropriate when we know |pattern| is valid. + : valid_schemes_(valid_schemes), + match_all_urls_(false), + match_subdomains_(false), + port_("*") { + ParseResult result = Parse(pattern); + if (PARSE_SUCCESS != result) + NOTREACHED() << "URLPattern invalid: " << pattern << " result " << result; +} + +URLPattern::~URLPattern() { +} + +bool URLPattern::operator<(const URLPattern& other) const { + return GetAsString() < other.GetAsString(); +} + +bool URLPattern::operator>(const URLPattern& other) const { + return GetAsString() > other.GetAsString(); +} + +bool URLPattern::operator==(const URLPattern& other) const { + return GetAsString() == other.GetAsString(); +} + +std::ostream& operator<<(std::ostream& out, const URLPattern& url_pattern) { + return out << '"' << url_pattern.GetAsString() << '"'; +} + +URLPattern::ParseResult URLPattern::Parse(const std::string& pattern) { + spec_.clear(); + SetMatchAllURLs(false); + SetMatchSubdomains(false); + SetPort("*"); + + // Special case pattern to match every valid URL. + if (pattern == kAllUrlsPattern) { + SetMatchAllURLs(true); + return PARSE_SUCCESS; + } + + // Parse out the scheme. + size_t scheme_end_pos = pattern.find(url::kStandardSchemeSeparator); + bool has_standard_scheme_separator = true; + + // Some urls also use ':' alone as the scheme separator. + if (scheme_end_pos == std::string::npos) { + scheme_end_pos = pattern.find(':'); + has_standard_scheme_separator = false; + } + + if (scheme_end_pos == std::string::npos) + return PARSE_ERROR_MISSING_SCHEME_SEPARATOR; + + if (!SetScheme(pattern.substr(0, scheme_end_pos))) + return PARSE_ERROR_INVALID_SCHEME; + + bool standard_scheme = IsStandardScheme(scheme_); + if (standard_scheme != has_standard_scheme_separator) + return PARSE_ERROR_WRONG_SCHEME_SEPARATOR; + + // Advance past the scheme separator. + scheme_end_pos += + (standard_scheme ? strlen(url::kStandardSchemeSeparator) : 1); + if (scheme_end_pos >= pattern.size()) + return PARSE_ERROR_EMPTY_HOST; + + // Parse out the host and path. + size_t host_start_pos = scheme_end_pos; + size_t path_start_pos = 0; + + if (!standard_scheme) { + path_start_pos = host_start_pos; + } else if (scheme_ == url::kFileScheme) { + size_t host_end_pos = pattern.find(kPathSeparator, host_start_pos); + if (host_end_pos == std::string::npos) { + // Allow hostname omission. + // e.g. file://* is interpreted as file:///*, + // file://foo* is interpreted as file:///foo*. + path_start_pos = host_start_pos - 1; + } else { + // Ignore hostname if scheme is file://. + // e.g. file://localhost/foo is equal to file:///foo. + path_start_pos = host_end_pos; + } + } else { + size_t host_end_pos = pattern.find(kPathSeparator, host_start_pos); + + // Host is required. + if (host_start_pos == host_end_pos) + return PARSE_ERROR_EMPTY_HOST; + + if (host_end_pos == std::string::npos) + return PARSE_ERROR_EMPTY_PATH; + + host_ = pattern.substr(host_start_pos, host_end_pos - host_start_pos); + + // The first component can optionally be '*' to match all subdomains. + std::vector host_components = base::SplitString( + host_, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + // Could be empty if the host only consists of whitespace characters. + if (host_components.empty() || + (host_components.size() == 1 && host_components[0].empty())) + return PARSE_ERROR_EMPTY_HOST; + + if (host_components[0] == "*") { + match_subdomains_ = true; + host_components.erase(host_components.begin(), + host_components.begin() + 1); + } + host_ = base::JoinString(host_components, "."); + + path_start_pos = host_end_pos; + } + + SetPath(pattern.substr(path_start_pos)); + + size_t port_pos = host_.find(':'); + if (port_pos != std::string::npos) { + if (!SetPort(host_.substr(port_pos + 1))) + return PARSE_ERROR_INVALID_PORT; + host_ = host_.substr(0, port_pos); + } + + // No other '*' can occur in the host, though. This isn't necessary, but is + // done as a convenience to developers who might otherwise be confused and + // think '*' works as a glob in the host. + if (host_.find('*') != std::string::npos) + return PARSE_ERROR_INVALID_HOST_WILDCARD; + + // Null characters are not allowed in hosts. + if (host_.find('\0') != std::string::npos) + return PARSE_ERROR_INVALID_HOST; + + return PARSE_SUCCESS; +} + +void URLPattern::SetValidSchemes(int valid_schemes) { + spec_.clear(); + valid_schemes_ = valid_schemes; +} + +void URLPattern::SetHost(const std::string& host) { + spec_.clear(); + host_ = host; +} + +void URLPattern::SetMatchAllURLs(bool val) { + spec_.clear(); + match_all_urls_ = val; + + if (val) { + match_subdomains_ = true; + scheme_ = "*"; + host_.clear(); + SetPath("/*"); + } +} + +void URLPattern::SetMatchSubdomains(bool val) { + spec_.clear(); + match_subdomains_ = val; +} + +bool URLPattern::SetScheme(const std::string& scheme) { + spec_.clear(); + scheme_ = scheme; + if (scheme_ == "*") { + valid_schemes_ &= (SCHEME_HTTP | SCHEME_HTTPS); + } else if (!IsValidScheme(scheme_)) { + return false; + } + return true; +} + +bool URLPattern::IsValidScheme(const std::string& scheme) const { + if (valid_schemes_ == SCHEME_ALL) + return true; + + for (size_t i = 0; i < arraysize(kValidSchemes); ++i) { + if (scheme == kValidSchemes[i] && (valid_schemes_ & kValidSchemeMasks[i])) + return true; + } + + return false; +} + +void URLPattern::SetPath(const std::string& path) { + spec_.clear(); + path_ = path; + path_escaped_ = path_; + base::ReplaceSubstringsAfterOffset(&path_escaped_, 0, "\\", "\\\\"); + base::ReplaceSubstringsAfterOffset(&path_escaped_, 0, "?", "\\?"); +} + +bool URLPattern::SetPort(const std::string& port) { + spec_.clear(); + if (IsValidPortForScheme(scheme_, port)) { + port_ = port; + return true; + } + return false; +} + +bool URLPattern::MatchesURL(const GURL& test) const { + const GURL* test_url = &test; + bool has_inner_url = test.inner_url() != NULL; + + if (has_inner_url) { + if (!test.SchemeIsFileSystem()) + return false; // The only nested URLs we handle are filesystem URLs. + test_url = test.inner_url(); + } + + if (!MatchesScheme(test_url->scheme())) + return false; + + if (match_all_urls_) + return true; + + std::string path_for_request = test.PathForRequest(); + if (has_inner_url) + path_for_request = test_url->path() + path_for_request; + + return MatchesSecurityOriginHelper(*test_url) && + MatchesPath(path_for_request); +} + +bool URLPattern::MatchesSecurityOrigin(const GURL& test) const { + const GURL* test_url = &test; + bool has_inner_url = test.inner_url() != NULL; + + if (has_inner_url) { + if (!test.SchemeIsFileSystem()) + return false; // The only nested URLs we handle are filesystem URLs. + test_url = test.inner_url(); + } + + if (!MatchesScheme(test_url->scheme())) + return false; + + if (match_all_urls_) + return true; + + return MatchesSecurityOriginHelper(*test_url); +} + +bool URLPattern::MatchesScheme(const std::string& test) const { + if (!IsValidScheme(test)) + return false; + + return scheme_ == "*" || test == scheme_; +} + +bool URLPattern::MatchesHost(const std::string& host) const { + std::string test(url::kHttpScheme); + test += url::kStandardSchemeSeparator; + test += host; + test += "/"; + return MatchesHost(GURL(test)); +} + +bool URLPattern::MatchesHost(const GURL& test) const { + // If the hosts are exactly equal, we have a match. + if (test.host() == host_) + return true; + + // If we're matching subdomains, and we have no host in the match pattern, + // that means that we're matching all hosts, which means we have a match no + // matter what the test host is. + if (match_subdomains_ && host_.empty()) + return true; + + // Otherwise, we can only match if our match pattern matches subdomains. + if (!match_subdomains_) + return false; + + // We don't do subdomain matching against IP addresses, so we can give up now + // if the test host is an IP address. + if (test.HostIsIPAddress()) + return false; + + // Check if the test host is a subdomain of our host. + if (test.host().length() <= (host_.length() + 1)) + return false; + + if (test.host().compare(test.host().length() - host_.length(), + host_.length(), host_) != 0) + return false; + + return test.host()[test.host().length() - host_.length() - 1] == '.'; +} + +bool URLPattern::ImpliesAllHosts() const { + // Check if it matches all urls or is a pattern like http://*/*. + if (match_all_urls_ || + (match_subdomains_ && host_.empty() && port_ == "*" && path_ == "/*")) { + return true; + } + + // If this doesn't even match subdomains, it can't possibly imply all hosts. + if (!match_subdomains_) + return false; + + // If |host_| is a recognized TLD, this will be 0. We don't include private + // TLDs, so that, e.g., *.appspot.com does not imply all hosts. + size_t registry_length = net::registry_controlled_domains::GetRegistryLength( + host_, + net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES, + net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); + // If there was more than just a TLD in the host (e.g., *.foobar.com), it + // doesn't imply all hosts. + if (registry_length > 0) + return false; + + // At this point the host could either be just a TLD ("com") or some unknown + // TLD-like string ("notatld"). To disambiguate between them construct a + // fake URL, and check the registry. This returns 0 if the TLD is + // unrecognized, or the length of the recognized TLD. + registry_length = net::registry_controlled_domains::GetRegistryLength( + base::StringPrintf("foo.%s", host_.c_str()), + net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES, + net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); + // If we recognized this TLD, then this is a pattern like *.com, and it + // should imply all hosts. Otherwise, this doesn't imply all hosts. + return registry_length > 0; +} + +bool URLPattern::MatchesSingleOrigin() const { + // Strictly speaking, the port is part of the origin, but in URLPattern it + // defaults to *. It's not very interesting anyway, so leave it out. + return !ImpliesAllHosts() && scheme_ != "*" && !match_subdomains_; +} + +bool URLPattern::MatchesPath(const std::string& test) const { + // Make the behaviour of OverlapsWith consistent with MatchesURL, which is + // need to match hosted apps on e.g. 'google.com' also run on 'google.com/'. + if (test + "/*" == path_escaped_) + return true; + + return base::MatchPattern(test, path_escaped_); +} + +const std::string& URLPattern::GetAsString() const { + if (!spec_.empty()) + return spec_; + + if (match_all_urls_) { + spec_ = kAllUrlsPattern; + return spec_; + } + + bool standard_scheme = IsStandardScheme(scheme_); + + std::string spec = scheme_ + + (standard_scheme ? url::kStandardSchemeSeparator : ":"); + + if (scheme_ != url::kFileScheme && standard_scheme) { + if (match_subdomains_) { + spec += "*"; + if (!host_.empty()) + spec += "."; + } + + if (!host_.empty()) + spec += host_; + + if (port_ != "*") { + spec += ":"; + spec += port_; + } + } + + if (!path_.empty()) + spec += path_; + + spec_ = spec; + return spec_; +} + +bool URLPattern::OverlapsWith(const URLPattern& other) const { + if (match_all_urls() || other.match_all_urls()) + return true; + return (MatchesAnyScheme(other.GetExplicitSchemes()) || + other.MatchesAnyScheme(GetExplicitSchemes())) + && (MatchesHost(other.host()) || other.MatchesHost(host())) + && (MatchesPortPattern(other.port()) || other.MatchesPortPattern(port())) + && (MatchesPath(StripTrailingWildcard(other.path())) || + other.MatchesPath(StripTrailingWildcard(path()))); +} + +bool URLPattern::Contains(const URLPattern& other) const { + if (match_all_urls()) + return true; + return MatchesAllSchemes(other.GetExplicitSchemes()) && + MatchesHost(other.host()) && + (!other.match_subdomains_ || match_subdomains_) && + MatchesPortPattern(other.port()) && + MatchesPath(StripTrailingWildcard(other.path())); +} + +bool URLPattern::MatchesAnyScheme( + const std::vector& schemes) const { + for (std::vector::const_iterator i = schemes.begin(); + i != schemes.end(); ++i) { + if (MatchesScheme(*i)) + return true; + } + + return false; +} + +bool URLPattern::MatchesAllSchemes( + const std::vector& schemes) const { + for (std::vector::const_iterator i = schemes.begin(); + i != schemes.end(); ++i) { + if (!MatchesScheme(*i)) + return false; + } + + return true; +} + +bool URLPattern::MatchesSecurityOriginHelper(const GURL& test) const { + // Ignore hostname if scheme is file://. + if (scheme_ != url::kFileScheme && !MatchesHost(test)) + return false; + + if (!MatchesPortPattern(base::IntToString(test.EffectiveIntPort()))) + return false; + + return true; +} + +bool URLPattern::MatchesPortPattern(const std::string& port) const { + return port_ == "*" || port_ == port; +} + +std::vector URLPattern::GetExplicitSchemes() const { + std::vector result; + + if (scheme_ != "*" && !match_all_urls_ && IsValidScheme(scheme_)) { + result.push_back(scheme_); + return result; + } + + for (size_t i = 0; i < arraysize(kValidSchemes); ++i) { + if (MatchesScheme(kValidSchemes[i])) { + result.push_back(kValidSchemes[i]); + } + } + + return result; +} + +std::vector URLPattern::ConvertToExplicitSchemes() const { + std::vector explicit_schemes = GetExplicitSchemes(); + std::vector result; + + for (std::vector::const_iterator i = explicit_schemes.begin(); + i != explicit_schemes.end(); ++i) { + URLPattern temp = *this; + temp.SetScheme(*i); + temp.SetMatchAllURLs(false); + result.push_back(temp); + } + + return result; +} + +// static +const char* URLPattern::GetParseResultString( + URLPattern::ParseResult parse_result) { + return kParseResultMessages[parse_result]; +} + +} // namespace extensions diff --git a/chromium_src/extensions/common/url_pattern.h b/chromium_src/extensions/common/url_pattern.h new file mode 100644 index 00000000000..fa9b6495e31 --- /dev/null +++ b/chromium_src/extensions/common/url_pattern.h @@ -0,0 +1,264 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef EXTENSIONS_COMMON_URL_PATTERN_H_ +#define EXTENSIONS_COMMON_URL_PATTERN_H_ + +#include +#include +#include +#include + +class GURL; + +namespace extensions { +// A pattern that can be used to match URLs. A URLPattern is a very restricted +// subset of URL syntax: +// +// := :// | '' +// := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome' | +// 'chrome-extension' | 'filesystem' +// := '*' | '*.' + +// := [':' ('*' | )] +// := '/' +// +// * Host is not used when the scheme is 'file'. +// * The path can have embedded '*' characters which act as glob wildcards. +// * '' is a special pattern that matches any URL that contains a +// valid scheme (as specified by valid_schemes_). +// * The '*' scheme pattern excludes file URLs. +// +// Examples of valid patterns: +// - http://*/* +// - http://*/foo* +// - https://*.google.com/foo*bar +// - file://monkey* +// - http://127.0.0.1/* +// +// Examples of invalid patterns: +// - http://* -- path not specified +// - http://*foo/bar -- * not allowed as substring of host component +// - http://foo.*.bar/baz -- * must be first component +// - http:/bar -- scheme separator not found +// - foo://* -- invalid scheme +// - chrome:// -- we don't support chrome internal URLs +class URLPattern { + public: + // A collection of scheme bitmasks for use with valid_schemes. + enum SchemeMasks { + SCHEME_NONE = 0, + SCHEME_HTTP = 1 << 0, + SCHEME_HTTPS = 1 << 1, + SCHEME_FILE = 1 << 2, + SCHEME_FTP = 1 << 3, + SCHEME_CHROMEUI = 1 << 4, + SCHEME_EXTENSION = 1 << 5, + SCHEME_FILESYSTEM = 1 << 6, + + // IMPORTANT! + // SCHEME_ALL will match every scheme, including chrome://, chrome- + // extension://, about:, etc. Because this has lots of security + // implications, third-party extensions should usually not be able to get + // access to URL patterns initialized this way. If there is a reason + // for violating this general rule, document why this it safe. + SCHEME_ALL = -1, + }; + + // Error codes returned from Parse(). + enum ParseResult { + PARSE_SUCCESS = 0, + PARSE_ERROR_MISSING_SCHEME_SEPARATOR, + PARSE_ERROR_INVALID_SCHEME, + PARSE_ERROR_WRONG_SCHEME_SEPARATOR, + PARSE_ERROR_EMPTY_HOST, + PARSE_ERROR_INVALID_HOST_WILDCARD, + PARSE_ERROR_EMPTY_PATH, + PARSE_ERROR_INVALID_PORT, + PARSE_ERROR_INVALID_HOST, + NUM_PARSE_RESULTS + }; + + // The string pattern. + static const char kAllUrlsPattern[]; + + // Returns true if the given |scheme| is considered valid for extensions. + static bool IsValidSchemeForExtensions(const std::string& scheme); + + explicit URLPattern(int valid_schemes); + + // Convenience to construct a URLPattern from a string. If the string is not + // known ahead of time, use Parse() instead, which returns success or failure. + URLPattern(int valid_schemes, const std::string& pattern); + + URLPattern(); + ~URLPattern(); + + bool operator<(const URLPattern& other) const; + bool operator>(const URLPattern& other) const; + bool operator==(const URLPattern& other) const; + + // Initializes this instance by parsing the provided string. Returns + // URLPattern::PARSE_SUCCESS on success, or an error code otherwise. On + // failure, this instance will have some intermediate values and is in an + // invalid state. + ParseResult Parse(const std::string& pattern_str); + + // Gets the bitmask of valid schemes. + int valid_schemes() const { return valid_schemes_; } + void SetValidSchemes(int valid_schemes); + + // Gets the host the pattern matches. This can be an empty string if the + // pattern matches all hosts (the input was ://*/). + const std::string& host() const { return host_; } + void SetHost(const std::string& host); + + // Gets whether to match subdomains of host(). + bool match_subdomains() const { return match_subdomains_; } + void SetMatchSubdomains(bool val); + + // Gets the path the pattern matches with the leading slash. This can have + // embedded asterisks which are interpreted using glob rules. + const std::string& path() const { return path_; } + void SetPath(const std::string& path); + + // Returns true if this pattern matches all urls. + bool match_all_urls() const { return match_all_urls_; } + void SetMatchAllURLs(bool val); + + // Sets the scheme for pattern matches. This can be a single '*' if the + // pattern matches all valid schemes (as defined by the valid_schemes_ + // property). Returns false on failure (if the scheme is not valid). + bool SetScheme(const std::string& scheme); + // Note: You should use MatchesScheme() instead of this getter unless you + // absolutely need the exact scheme. This is exposed for testing. + const std::string& scheme() const { return scheme_; } + + // Returns true if the specified scheme can be used in this URL pattern, and + // false otherwise. Uses valid_schemes_ to determine validity. + bool IsValidScheme(const std::string& scheme) const; + + // Returns true if this instance matches the specified URL. + bool MatchesURL(const GURL& test) const; + + // Returns true if this instance matches the specified security origin. + bool MatchesSecurityOrigin(const GURL& test) const; + + // Returns true if |test| matches our scheme. + // Note that if test is "filesystem", this may fail whereas MatchesURL + // may succeed. MatchesURL is smart enough to look at the inner_url instead + // of the outer "filesystem:" part. + bool MatchesScheme(const std::string& test) const; + + // Returns true if |test| matches our host. + bool MatchesHost(const std::string& test) const; + bool MatchesHost(const GURL& test) const; + + // Returns true if |test| matches our path. + bool MatchesPath(const std::string& test) const; + + // Returns true if the pattern is vague enough that it implies all hosts, + // such as *://*/*. + // This is an expensive method, and should be used sparingly! + // You should probably use URLPatternSet::ShouldWarnAllHosts(), which is + // cached. + bool ImpliesAllHosts() const; + + // Returns true if the pattern only matches a single origin. The pattern may + // include a path. + bool MatchesSingleOrigin() const; + + // Sets the port. Returns false if the port is invalid. + bool SetPort(const std::string& port); + const std::string& port() const { return port_; } + + // Returns a string representing this instance. + const std::string& GetAsString() const; + + // Determines whether there is a URL that would match this instance and + // another instance. This method is symmetrical: Calling + // other.OverlapsWith(this) would result in the same answer. + bool OverlapsWith(const URLPattern& other) const; + + // Returns true if this pattern matches all possible URLs that |other| can + // match. For example, http://*.google.com encompasses http://www.google.com. + bool Contains(const URLPattern& other) const; + + // Converts this URLPattern into an equivalent set of URLPatterns that don't + // use a wildcard in the scheme component. If this URLPattern doesn't use a + // wildcard scheme, then the returned set will contain one element that is + // equivalent to this instance. + std::vector ConvertToExplicitSchemes() const; + + static bool EffectiveHostCompare(const URLPattern& a, const URLPattern& b) { + if (a.match_all_urls_ && b.match_all_urls_) + return false; + return a.host_.compare(b.host_) < 0; + } + + // Used for origin comparisons in a std::set. + class EffectiveHostCompareFunctor { + public: + bool operator()(const URLPattern& a, const URLPattern& b) const { + return EffectiveHostCompare(a, b); + } + }; + + // Get an error string for a ParseResult. + static const char* GetParseResultString(URLPattern::ParseResult parse_result); + + private: + // Returns true if any of the |schemes| items matches our scheme. + bool MatchesAnyScheme(const std::vector& schemes) const; + + // Returns true if all of the |schemes| items matches our scheme. + bool MatchesAllSchemes(const std::vector& schemes) const; + + bool MatchesSecurityOriginHelper(const GURL& test) const; + + // Returns true if our port matches the |port| pattern (it may be "*"). + bool MatchesPortPattern(const std::string& port) const; + + // If the URLPattern contains a wildcard scheme, returns a list of + // equivalent literal schemes, otherwise returns the current scheme. + std::vector GetExplicitSchemes() const; + + // A bitmask containing the schemes which are considered valid for this + // pattern. Parse() uses this to decide whether a pattern contains a valid + // scheme. + int valid_schemes_; + + // True if this is a special-case "" pattern. + bool match_all_urls_; + + // The scheme for the pattern. + std::string scheme_; + + // The host without any leading "*" components. + std::string host_; + + // Whether we should match subdomains of the host. This is true if the first + // component of the pattern's host was "*". + bool match_subdomains_; + + // The port. + std::string port_; + + // The path to match. This is everything after the host of the URL, or + // everything after the scheme in the case of file:// URLs. + std::string path_; + + // The path with "?" and "\" characters escaped for use with the + // MatchPattern() function. + std::string path_escaped_; + + // A string representing this URLPattern. + mutable std::string spec_; +}; + +std::ostream& operator<<(std::ostream& out, const URLPattern& url_pattern); + +typedef std::vector URLPatternList; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_URL_PATTERN_H_ diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 2fbd1544c3e..44c4da5c65b 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -4,8 +4,12 @@ The `BrowserWindow` class gives you the ability to create a browser window. For example: ```javascript +// In the main process. const BrowserWindow = require('electron').BrowserWindow; +// Or in the renderer process. +const BrowserWindow = require('electron').remote.BrowserWindow; + var win = new BrowserWindow({ width: 800, height: 600, show: false }); win.on('closed', function() { win = null; diff --git a/docs/api/session.md b/docs/api/session.md index a69c5939ed6..21464bd481e 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -12,7 +12,7 @@ const BrowserWindow = require('electron').BrowserWindow; var win = new BrowserWindow({ width: 800, height: 600 }); win.loadURL("http://github.com"); -var ses = win.webContents.session +var ses = win.webContents.session; ``` ## Methods @@ -63,7 +63,7 @@ Emitted when Electron is about to download `item` in `webContents`. Calling `event.preventDefault()` will cancel the download. ```javascript -session.on('will-download', function(event, item, webContents) { +session.defaultSession.on('will-download', function(event, item, webContents) { event.preventDefault(); require('request')(item.getURL(), function(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: ```javascript -const BrowserWindow = require('electron').BrowserWindow; +// Query all cookies. +session.defaultSession.cookies.get({}, function(error, cookies) { + console.log(cookies); +}); -var win = new BrowserWindow({ width: 800, height: 600 }); +// Query all cookies associated with a specific url. +session.defaultSession.cookies.get({ url : "http://www.github.com" }, function(error, cookies) { + console.log(cookies); +}); -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); - }); - - // Query all cookies associated with a specific url. - win.webContents.session.cookies.get({ url : "http://www.github.com" }, - function(error, cookies) { - if (error) throw error; - console.log(cookies); - }); - - // Set a cookie with the given cookie data; - // may overwrite equivalent cookies if they exist. - win.webContents.session.cookies.set( - { url : "http://www.github.com", name : "dummy_name", value : "dummy"}, - function(error, cookies) { - if (error) throw error; - console.log(cookies); - }); +// Set a cookie with the given cookie data; +// may overwrite equivalent cookies if they exist. +var cookie = { url : "http://www.github.com", name : "dummy_name", value : "dummy" }; +session.defaultSession.cookies.set(cookie, function(error) { + if (error) + console.error(error); }); ``` -#### `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`. - Empty implies retrieving cookies of all urls. -* `name` String - Filters cookies by name -* `domain` String - Retrieves cookies whose domains match or are subdomains of - `domains` -* `path` String - Retrieves cookies whose path matches `path` -* `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: +Sends a request to get all cookies matching `details`, `callback` will be called +with `callback(error, cookies)` on complete. + +`cookies` is an Array of `cookie` objects. + +* `cookie` Object * `name` String - The name of the cookie. * `value` String - The value 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. - * `secure` Boolean - Whether the cookie is marked as Secure (typically HTTPS). - * `http_only` Boolean - Whether the cookie is marked as HttpOnly. + * `secure` Boolean - Whether the cookie is marked as secure. + * `httpOnly` Boolean - Whether the cookie is marked as HTTP only. * `session` Boolean - Whether the cookie is a session cookie or a persistent 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 cookies. #### `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 - * `url` String - The URL associated with the cookie - * `name` String - The name of cookie to remove -* `callback` Function - function(error) - * `error` Error + * `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 + +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)` @@ -286,3 +279,197 @@ myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, c callback(false); }); ``` + +#### `ses.webRequest` + +The `webRequest` API set allows to intercept and modify contents of a request at +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 +// Modify the user agent for all requests to the following urls. +var filter = { + urls: ["https://*.github.com/*", "*://electron.github.io"] +}; + +session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details, callback) { + details.requestHeaders['User-Agent'] = "MyAgent"; + callback({cancel: false, requestHeaders: details.requestHeaders}); +}); +``` + +#### `ses.webRequest.onBeforeRequest([filter, ]listener)` + +* `filter` Object +* `listener` Function + +The `listener` will be called with `listener(details, callback)` when a request +is about to occur. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + +The `callback` has to be called with an `response` object: + +* `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 +* `listener` Function + +The `listener` will be called with `listener(details, callback)` before sending +an HTTP request, once the request headers are available. This may occur after a +TCP connection is made to the server, but before any http data is sent. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object + +The `callback` has to be called with an `response` object: + +* `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)` + +* `filter` Object +* `listener` Function + +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 + * `method` String + * `resourceType` String + * `timestamp` Double + * `statusLine` String + * `statusCode` Integer + * `responseHeaders` Object + +The `callback` has to be called with an `response` object: + +* `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 +* `listener` Function + +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 + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean - Indicates whether the response was fetched from disk + cache. + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onBeforeRedirect([filter, ]listener)` + +* `filter` Object +* `listener` Function + +The `listener` will be called with `listener(details)` when a server initiated +redirect is about to occur. + +* `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `redirectURL` String + * `statusCode` Integer + * `ip` String __optional__ - The server IP address that the request was + actually sent to. + * `fromCache` Boolean + * `responseHeaders` Object + +#### `ses.webRequest.onCompleted([filter, ]listener)` + +* `filter` Object +* `listener` Function + +The `listener` will be called with `listener(details)` when a request is +completed. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onErrorOccurred([filter, ]listener)` + +* `filter` Object +* `listener` Function + +The `listener` will be called with `listener(details)` when an error occurs. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `fromCache` Boolean + * `error` String - The error description. diff --git a/filenames.gypi b/filenames.gypi index aefe493335f..f1209eeeb0e 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -110,6 +110,8 @@ 'atom/browser/api/atom_api_tray.h', 'atom/browser/api/atom_api_web_contents.cc', 'atom/browser/api/atom_api_web_contents.h', + 'atom/browser/api/atom_api_web_request.cc', + 'atom/browser/api/atom_api_web_request.h', 'atom/browser/api/atom_api_web_view_manager.cc', 'atom/browser/api/atom_api_window.cc', 'atom/browser/api/atom_api_window.h', @@ -178,6 +180,8 @@ 'atom/browser/net/asar/url_request_asar_job.h', 'atom/browser/net/atom_cert_verifier.cc', 'atom/browser/net/atom_cert_verifier.h', + 'atom/browser/net/atom_network_delegate.cc', + 'atom/browser/net/atom_network_delegate.h', 'atom/browser/net/atom_ssl_config_service.cc', 'atom/browser/net/atom_ssl_config_service.h', 'atom/browser/net/atom_url_request_job_factory.cc', @@ -472,6 +476,8 @@ 'chromium_src/chrome/utility/utility_message_handler.h', 'chromium_src/extensions/browser/app_window/size_constraints.cc', 'chromium_src/extensions/browser/app_window/size_constraints.h', + 'chromium_src/extensions/common/url_pattern.cc', + 'chromium_src/extensions/common/url_pattern.h', 'chromium_src/library_loaders/libspeechd_loader.cc', 'chromium_src/library_loaders/libspeechd.h', 'chromium_src/net/test/embedded_test_server/stream_listen_socket.cc', diff --git a/package.json b/package.json index a5d56e3a990..ea29419925e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "private": true, "scripts": { - "preinstall": "node -e 'process.exit(0)'" + "preinstall": "node -e 'process.exit(0)'", + "test": "python ./script/test.py" } } diff --git a/script/lib/config.py b/script/lib/config.py index 132f5b8d83e..1237a4be4aa 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = 'cfbe8ec7e14af4cabd1474386f54e197db1f7ac1' +LIBCHROMIUMCONTENT_COMMIT = '66bd8d1c705b7258f76c82436e4b16e82afbbd33' PLATFORM = { 'cygwin': 'win32', diff --git a/spec/api-browser-window-spec.coffee b/spec/api-browser-window-spec.coffee index 86beb1fdc60..38c8217041e 100644 --- a/spec/api-browser-window-spec.coffee +++ b/spec/api-browser-window-spec.coffee @@ -8,7 +8,7 @@ os = require 'os' {remote, screen} = require 'electron' {ipcMain, BrowserWindow} = remote.require 'electron' -isCI = remote.process.argv[2] == '--ci' +isCI = remote.getGlobal('isCi') describe 'browser-window module', -> fixtures = path.resolve __dirname, 'fixtures' diff --git a/spec/api-crash-reporter-spec.coffee b/spec/api-crash-reporter-spec.coffee index 676dbf9d692..789ba6e01f5 100644 --- a/spec/api-crash-reporter-spec.coffee +++ b/spec/api-crash-reporter-spec.coffee @@ -18,7 +18,7 @@ describe 'crash-reporter module', -> return if process.mas # The crash-reporter test is not reliable on CI machine. - isCI = remote.process.argv[2] == '--ci' + isCI = remote.getGlobal('isCi') return if isCI it 'should send minidump when renderer crashes', (done) -> diff --git a/spec/api-session-spec.coffee b/spec/api-session-spec.coffee index 98e47e357a5..03c62c1f1db 100644 --- a/spec/api-session-spec.coffee +++ b/spec/api-session-spec.coffee @@ -49,12 +49,11 @@ describe 'session module', -> it 'should remove cookies', (done) -> session.defaultSession.cookies.set {url: url, name: '2', value: '2'}, (error) -> return done(error) if error - session.defaultSession.cookies.remove {url: url, name: '2'}, (error) -> - return done(error) if error + session.defaultSession.cookies.remove url, '2', -> session.defaultSession.cookies.get {url: url}, (error, list) -> return done(error) if error for cookie in list when cookie.name is '2' - return done('Cookie not deleted') + return done('Cookie not deleted') done() describe 'session.clearStorageData(options)', -> diff --git a/spec/api-web-request-spec.coffee b/spec/api-web-request-spec.coffee new file mode 100644 index 00000000000..a10ff3d1277 --- /dev/null +++ b/spec/api-web-request-spec.coffee @@ -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') diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index 211de346846..2cc2237385b 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -45,7 +45,7 @@ describe 'chromium feature', -> done() w.loadURL url - describe 'navigator.webkitGetUserMedia', -> + xdescribe 'navigator.webkitGetUserMedia', -> it 'calls its callbacks', (done) -> @timeout 5000 navigator.webkitGetUserMedia audio: true, video: false, @@ -115,12 +115,25 @@ describe 'chromium feature', -> window.addEventListener 'message', listener b = window.open url, '', 'show=no' + describe 'window.postMessage', -> + it 'sets the origin correctly', (done) -> + listener = (event) -> + window.removeEventListener 'message', listener + b.close() + assert.equal event.data, 'file://testing' + assert.equal event.origin, 'file://' + done() + window.addEventListener 'message', listener + b = window.open "file://#{fixtures}/pages/window-open-postMessage.html", '', 'show=no' + BrowserWindow.fromId(b.guestId).webContents.once 'did-finish-load', -> + b.postMessage('testing', '*') + describe 'window.opener.postMessage', -> it 'sets source and origin correctly', (done) -> listener = (event) -> window.removeEventListener 'message', listener b.close() - assert.equal event.source.guestId, b.guestId + assert.equal event.source, b assert.equal event.origin, 'file://' done() window.addEventListener 'message', listener @@ -210,7 +223,7 @@ describe 'chromium feature', -> setImmediate -> called = false Promise.resolve().then -> - done(if called then undefined else new Error('wrong sequnce')) + done(if called then undefined else new Error('wrong sequence')) document.createElement 'x-element' called = true @@ -224,6 +237,6 @@ describe 'chromium feature', -> remote.getGlobal('setImmediate') -> called = false Promise.resolve().then -> - done(if called then undefined else new Error('wrong sequnce')) + done(if called then undefined else new Error('wrong sequence')) document.createElement 'y-element' called = true diff --git a/spec/fixtures/pages/window-open-postMessage.html b/spec/fixtures/pages/window-open-postMessage.html new file mode 100644 index 00000000000..e547fa2a609 --- /dev/null +++ b/spec/fixtures/pages/window-open-postMessage.html @@ -0,0 +1,9 @@ + + + + + diff --git a/spec/package.json b/spec/package.json index 38bd8379670..79e7d954eb8 100644 --- a/spec/package.json +++ b/spec/package.json @@ -5,13 +5,14 @@ "version": "0.1.0", "devDependencies": { "basic-auth": "^1.0.0", - "multiparty": "4.1.2", "graceful-fs": "3.0.5", "mocha": "2.1.0", + "multiparty": "4.1.2", "q": "0.9.7", "temp": "0.8.1", "walkdir": "0.0.7", - "ws": "0.7.2" + "ws": "0.7.2", + "yargs": "^3.31.0" }, "optionalDependencies": { "ffi": "2.0.0", diff --git a/spec/static/index.html b/spec/static/index.html index ea86f6ee302..f958e1b7ed0 100644 --- a/spec/static/index.html +++ b/spec/static/index.html @@ -18,8 +18,7 @@ var remote = electron.remote; var ipcRenderer = electron.ipcRenderer; - var argv = remote.process.argv; - var isCi = argv[2] == '--ci'; + var isCi = remote.getGlobal('isCi') if (!isCi) { var win = remote.getCurrentWindow(); diff --git a/spec/static/main.js b/spec/static/main.js index be3690cd9e7..4de123830ee 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -5,6 +5,13 @@ const dialog = electron.dialog; const BrowserWindow = electron.BrowserWindow; const path = require('path'); +const url = require('url'); + +var argv = require('yargs') + .boolean('ci') + .string('g').alias('g', 'grep') + .boolean('i').alias('i', 'invert') + .argv; var window = null; process.port = 0; // will be used by crash-reporter spec. @@ -42,7 +49,8 @@ ipcMain.on('echo', function(event, msg) { event.returnValue = msg; }); -if (process.argv[2] == '--ci') { +global.isCi = !!argv.ci; +if (global.isCi) { process.removeAllListeners('uncaughtException'); process.on('uncaughtException', function(error) { console.error(error, error.stack); @@ -67,7 +75,14 @@ app.on('ready', function() { javascript: true // Test whether web-preferences crashes. }, }); - window.loadURL('file://' + __dirname + '/index.html'); + window.loadURL(url.format({ + pathname: __dirname + '/index.html', + protocol: 'file', + query: { + grep: argv.grep, + invert: argv.invert ? 'true': '' + } + })); window.on('unresponsive', function() { var chosen = dialog.showMessageBox(window, { type: 'warning', diff --git a/vendor/native_mate b/vendor/native_mate index 5e70868fd0c..a3dcf8ced66 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit 5e70868fd0c005dc2c43bea15ca6e93da0b68741 +Subproject commit a3dcf8ced663e974ac94ad5e50a1d25a43995a9d