Merge pull request #65 from atom/custom-protocol

Support custom protocols
This commit is contained in:
Cheng Zhao 2013-09-04 03:33:09 -07:00
commit 3b7dd85d3f
25 changed files with 1184 additions and 24 deletions

View file

@ -19,6 +19,7 @@
'browser/api/lib/menu.coffee',
'browser/api/lib/menu-item.coffee',
'browser/api/lib/power-monitor.coffee',
'browser/api/lib/protocol.coffee',
'browser/atom/atom.coffee',
'browser/atom/objects-registry.coffee',
'browser/atom/rpc-server.coffee',
@ -54,6 +55,8 @@
'browser/api/atom_api_menu_win.h',
'browser/api/atom_api_power_monitor.cc',
'browser/api/atom_api_power_monitor.h',
'browser/api/atom_api_protocol.cc',
'browser/api/atom_api_protocol.h',
'browser/api/atom_api_window.cc',
'browser/api/atom_api_window.h',
'browser/api/atom_browser_bindings.cc',
@ -94,6 +97,12 @@
'browser/native_window_win.cc',
'browser/native_window_win.h',
'browser/native_window_observer.h',
'browser/net/adapter_request_job.cc',
'browser/net/adapter_request_job.h',
'browser/net/atom_url_request_job_factory.cc',
'browser/net/atom_url_request_job_factory.h',
'browser/net/url_request_string_job.cc',
'browser/net/url_request_string_job.h',
'browser/ui/accelerator_util.cc',
'browser/ui/accelerator_util.h',
'browser/ui/accelerator_util_mac.mm',

View file

@ -0,0 +1,363 @@
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "browser/api/atom_api_protocol.h"
#include "base/stl_util.h"
#include "browser/atom_browser_context.h"
#include "browser/net/adapter_request_job.h"
#include "browser/net/atom_url_request_job_factory.h"
#include "content/public/browser/browser_thread.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "vendor/node/src/node.h"
#include "vendor/node/src/node_internals.h"
namespace atom {
namespace api {
typedef net::URLRequestJobFactory::ProtocolHandler ProtocolHandler;
namespace {
// The protocol module object.
v8::Persistent<v8::Object> g_protocol_object;
// Registered protocol handlers.
typedef std::map<std::string, v8::Persistent<v8::Function>> HandlersMap;
static HandlersMap g_handlers;
// Emit an event for the protocol module.
void EmitEventInUI(const std::string& event, const std::string& parameter) {
v8::HandleScope scope;
v8::Local<v8::Value> argv[] = {
v8::String::New(event.data(), event.size()),
v8::String::New(parameter.data(), parameter.size()),
};
node::MakeCallback(g_protocol_object, "emit", arraysize(argv), argv);
}
// Convert the URLRequest object to V8 object.
v8::Handle<v8::Object> ConvertURLRequestToV8Object(
const net::URLRequest* request) {
v8::Local<v8::Object> obj = v8::Object::New();
obj->Set(v8::String::New("method"),
v8::String::New(request->method().c_str()));
obj->Set(v8::String::New("url"),
v8::String::New(request->url().spec().c_str()));
obj->Set(v8::String::New("referrer"),
v8::String::New(request->referrer().c_str()));
return obj;
}
// Get the job factory.
AtomURLRequestJobFactory* GetRequestJobFactory() {
return static_cast<AtomURLRequestJobFactory*>(
const_cast<net::URLRequestJobFactory*>(
static_cast<content::BrowserContext*>(AtomBrowserContext::Get())->
GetRequestContext()->GetURLRequestContext()->job_factory()));
}
class CustomProtocolRequestJob : public AdapterRequestJob {
public:
CustomProtocolRequestJob(ProtocolHandler* protocol_handler,
net::URLRequest* request,
net::NetworkDelegate* network_delegate)
: AdapterRequestJob(protocol_handler, request, network_delegate) {
}
// AdapterRequestJob:
virtual void GetJobTypeInUI() OVERRIDE {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// Call the JS handler.
v8::HandleScope scope;
v8::Handle<v8::Value> argv[] = {
ConvertURLRequestToV8Object(request()),
};
v8::Handle<v8::Value> result = g_handlers[request()->url().scheme()]->Call(
v8::Context::GetCurrent()->Global(), arraysize(argv), argv);
// Determine the type of the job we are going to create.
if (result->IsString()) {
std::string data = *v8::String::Utf8Value(result);
content::BrowserThread::PostTask(
content::BrowserThread::IO,
FROM_HERE,
base::Bind(&AdapterRequestJob::CreateStringJobAndStart,
GetWeakPtr(),
"text/plain",
"UTF-8",
data));
return;
} else if (result->IsObject()) {
v8::Handle<v8::Object> obj = result->ToObject();
std::string name = *v8::String::Utf8Value(obj->GetConstructorName());
if (name == "RequestStringJob") {
std::string mime_type = *v8::String::Utf8Value(obj->Get(
v8::String::New("mimeType")));
std::string charset = *v8::String::Utf8Value(obj->Get(
v8::String::New("charset")));
std::string data = *v8::String::Utf8Value(obj->Get(
v8::String::New("data")));
content::BrowserThread::PostTask(
content::BrowserThread::IO,
FROM_HERE,
base::Bind(&AdapterRequestJob::CreateStringJobAndStart,
GetWeakPtr(),
mime_type,
charset,
data));
return;
} else if (name == "RequestFileJob") {
base::FilePath path = base::FilePath::FromUTF8Unsafe(
*v8::String::Utf8Value(obj->Get(v8::String::New("path"))));
content::BrowserThread::PostTask(
content::BrowserThread::IO,
FROM_HERE,
base::Bind(&AdapterRequestJob::CreateFileJobAndStart,
GetWeakPtr(),
path));
return;
}
}
// Try the default protocol handler if we have.
if (default_protocol_handler()) {
content::BrowserThread::PostTask(
content::BrowserThread::IO,
FROM_HERE,
base::Bind(&AdapterRequestJob::CreateJobFromProtocolHandlerAndStart,
GetWeakPtr()));
return;
}
// Fallback to the not implemented error.
content::BrowserThread::PostTask(
content::BrowserThread::IO,
FROM_HERE,
base::Bind(&AdapterRequestJob::CreateErrorJobAndStart,
GetWeakPtr(),
net::ERR_NOT_IMPLEMENTED));
}
};
// Always return the same CustomProtocolRequestJob for all requests, because
// the content API needs the ProtocolHandler to return a job immediately, and
// getting the real job from the JS requires asynchronous calls, so we have
// to create an adapter job first.
// Users can also pass an extra ProtocolHandler as the fallback one when
// registered handler doesn't want to deal with the request.
class CustomProtocolHandler : public ProtocolHandler {
public:
explicit CustomProtocolHandler(ProtocolHandler* protocol_handler = NULL)
: protocol_handler_(protocol_handler) {
}
virtual net::URLRequestJob* MaybeCreateJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const OVERRIDE {
return new CustomProtocolRequestJob(protocol_handler_.get(),
request,
network_delegate);
}
ProtocolHandler* ReleaseDefaultProtocolHandler() {
return protocol_handler_.release();
}
ProtocolHandler* original_handler() { return protocol_handler_.get(); }
private:
scoped_ptr<ProtocolHandler> protocol_handler_;
DISALLOW_COPY_AND_ASSIGN(CustomProtocolHandler);
};
} // namespace
// static
v8::Handle<v8::Value> Protocol::RegisterProtocol(const v8::Arguments& args) {
std::string scheme(*v8::String::Utf8Value(args[0]));
if (g_handlers.find(scheme) != g_handlers.end() ||
net::URLRequest::IsHandledProtocol(scheme))
return node::ThrowError("The scheme is already registered");
// Store the handler in a map.
if (!args[1]->IsFunction())
return node::ThrowError("Handler must be a function");
g_handlers[scheme] = v8::Persistent<v8::Function>::New(
node::node_isolate, v8::Handle<v8::Function>::Cast(args[1]));
content::BrowserThread::PostTask(content::BrowserThread::IO,
FROM_HERE,
base::Bind(&RegisterProtocolInIO, scheme));
return v8::Undefined();
}
// static
v8::Handle<v8::Value> Protocol::UnregisterProtocol(const v8::Arguments& args) {
std::string scheme(*v8::String::Utf8Value(args[0]));
// Erase the handler from map.
HandlersMap::iterator it(g_handlers.find(scheme));
if (it == g_handlers.end())
return node::ThrowError("The scheme has not been registered");
g_handlers.erase(it);
content::BrowserThread::PostTask(content::BrowserThread::IO,
FROM_HERE,
base::Bind(&UnregisterProtocolInIO, scheme));
return v8::Undefined();
}
// static
v8::Handle<v8::Value> Protocol::IsHandledProtocol(const v8::Arguments& args) {
return v8::Boolean::New(net::URLRequest::IsHandledProtocol(
*v8::String::Utf8Value(args[0])));
}
// static
v8::Handle<v8::Value> Protocol::InterceptProtocol(const v8::Arguments& args) {
std::string scheme(*v8::String::Utf8Value(args[0]));
if (!GetRequestJobFactory()->HasProtocolHandler(scheme))
return node::ThrowError("Cannot intercept procotol");
if (ContainsKey(g_handlers, scheme))
return node::ThrowError("Cannot intercept custom procotols");
// Store the handler in a map.
if (!args[1]->IsFunction())
return node::ThrowError("Handler must be a function");
g_handlers[scheme] = v8::Persistent<v8::Function>::New(
node::node_isolate, v8::Handle<v8::Function>::Cast(args[1]));
content::BrowserThread::PostTask(content::BrowserThread::IO,
FROM_HERE,
base::Bind(&InterceptProtocolInIO, scheme));
return v8::Undefined();
}
// static
v8::Handle<v8::Value> Protocol::UninterceptProtocol(const v8::Arguments& args) {
std::string scheme(*v8::String::Utf8Value(args[0]));
// Erase the handler from map.
HandlersMap::iterator it(g_handlers.find(scheme));
if (it == g_handlers.end())
return node::ThrowError("The scheme has not been registered");
g_handlers.erase(it);
content::BrowserThread::PostTask(content::BrowserThread::IO,
FROM_HERE,
base::Bind(&UninterceptProtocolInIO,
scheme));
return v8::Undefined();
}
// static
void Protocol::RegisterProtocolInIO(const std::string& scheme) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
AtomURLRequestJobFactory* job_factory(GetRequestJobFactory());
job_factory->SetProtocolHandler(scheme, new CustomProtocolHandler);
content::BrowserThread::PostTask(content::BrowserThread::UI,
FROM_HERE,
base::Bind(&EmitEventInUI,
"registered",
scheme));
}
// static
void Protocol::UnregisterProtocolInIO(const std::string& scheme) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
AtomURLRequestJobFactory* job_factory(GetRequestJobFactory());
job_factory->SetProtocolHandler(scheme, NULL);
content::BrowserThread::PostTask(content::BrowserThread::UI,
FROM_HERE,
base::Bind(&EmitEventInUI,
"unregistered",
scheme));
}
// static
void Protocol::InterceptProtocolInIO(const std::string& scheme) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
AtomURLRequestJobFactory* job_factory(GetRequestJobFactory());
ProtocolHandler* original_handler = job_factory->GetProtocolHandler(scheme);
if (original_handler == NULL) {
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(&EmitEventInUI,
"error",
"There is no protocol handler to intercpet"));
return;
}
job_factory->ReplaceProtocol(scheme,
new CustomProtocolHandler(original_handler));
content::BrowserThread::PostTask(content::BrowserThread::UI,
FROM_HERE,
base::Bind(&EmitEventInUI,
"intercepted",
scheme));
}
// static
void Protocol::UninterceptProtocolInIO(const std::string& scheme) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
AtomURLRequestJobFactory* job_factory(GetRequestJobFactory());
// Check if the protocol handler is intercepted.
CustomProtocolHandler* handler = static_cast<CustomProtocolHandler*>(
job_factory->GetProtocolHandler(scheme));
if (handler->original_handler() == NULL) {
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(&EmitEventInUI,
"error",
"The protocol is not intercpeted"));
return;
}
// Reset the protocol handler to the orignal one and delete current
// protocol handler.
ProtocolHandler* original_handler = handler->ReleaseDefaultProtocolHandler();
delete job_factory->ReplaceProtocol(scheme, original_handler);
content::BrowserThread::PostTask(content::BrowserThread::UI,
FROM_HERE,
base::Bind(&EmitEventInUI,
"unintercepted",
scheme));
}
// static
void Protocol::Initialize(v8::Handle<v8::Object> target) {
// Remember the protocol object, used for emitting event later.
g_protocol_object = v8::Persistent<v8::Object>::New(
node::node_isolate, target);
node::SetMethod(target, "registerProtocol", RegisterProtocol);
node::SetMethod(target, "unregisterProtocol", UnregisterProtocol);
node::SetMethod(target, "isHandledProtocol", IsHandledProtocol);
node::SetMethod(target, "interceptProtocol", InterceptProtocol);
node::SetMethod(target, "uninterceptProtocol", UninterceptProtocol);
}
} // namespace api
} // namespace atom
NODE_MODULE(atom_browser_protocol, atom::api::Protocol::Initialize)

View file

@ -0,0 +1,43 @@
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_API_ATOM_API_PROTOCOL_H_
#define ATOM_BROWSER_API_ATOM_API_PROTOCOL_H_
#include <string>
#include <map>
#include "base/basictypes.h"
#include "v8/include/v8.h"
namespace atom {
namespace api {
class Protocol {
public:
static void Initialize(v8::Handle<v8::Object> target);
private:
static v8::Handle<v8::Value> RegisterProtocol(const v8::Arguments& args);
static v8::Handle<v8::Value> UnregisterProtocol(const v8::Arguments& args);
static v8::Handle<v8::Value> IsHandledProtocol(const v8::Arguments& args);
static v8::Handle<v8::Value> InterceptProtocol(const v8::Arguments& args);
static v8::Handle<v8::Value> UninterceptProtocol(const v8::Arguments& args);
static void RegisterProtocolInIO(const std::string& scheme);
static void UnregisterProtocolInIO(const std::string& scheme);
static void InterceptProtocolInIO(const std::string& scheme);
static void UninterceptProtocolInIO(const std::string& scheme);
DISALLOW_IMPLICIT_CONSTRUCTORS(Protocol);
};
} // namespace api
} // namespace atom
#endif // ATOM_BROWSER_API_ATOM_API_PROTOCOL_H_

View file

@ -0,0 +1,20 @@
protocol = process.atomBinding 'protocol'
EventEmitter = require('events').EventEmitter
protocol[key] = value for key, value of EventEmitter.prototype
protocol.RequestStringJob =
class RequestStringJob
constructor: ({mimeType, charset, data}) ->
if typeof data isnt 'string' and not data instanceof Buffer
throw new TypeError('Data should be string or Buffer')
@mimeType = mimeType ? 'text/plain'
@charset = charset ? 'UTF-8'
@data = String data
protocol.RequestFileJob =
class RequestFileJob
constructor: (@path) ->
module.exports = protocol

View file

@ -44,10 +44,13 @@ unwrapArgs = (processId, routingId, args) ->
when 'remote-object' then objectsRegistry.get meta.id
when 'array' then unwrapArgs processId, routingId, meta.value
when 'object'
ret = {}
ret = v8Util.createObjectWithName meta.name
for member in meta.members
ret[member.name] = metaToValue(member.value)
ret
when 'function-with-return-value'
returnValue = metaToValue meta.value
-> returnValue
when 'function'
ret = ->
ipc.sendChannel processId, routingId, 'ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(processId, routingId, arguments)
@ -101,6 +104,16 @@ ipc.on 'ATOM_BROWSER_FUNCTION_CALL', (event, processId, routingId, id, args) ->
catch e
event.result = errorToMeta e
ipc.on 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', (event, processId, routingId, id, method, args) ->
try
args = unwrapArgs processId, routingId, args
constructor = objectsRegistry.get(id)[method]
# Call new with array of arguments.
obj = new (Function::bind.apply(constructor, [null].concat(args)))
event.result = valueToMeta processId, routingId, obj
catch e
event.result = errorToMeta e
ipc.on 'ATOM_BROWSER_MEMBER_CALL', (event, processId, routingId, id, method, args) ->
try
args = unwrapArgs processId, routingId, args

View file

@ -5,6 +5,13 @@
#include "browser/atom_browser_client.h"
#include "browser/atom_browser_main_parts.h"
#include "browser/net/atom_url_request_job_factory.h"
#include "content/public/common/url_constants.h"
#include "net/url_request/data_protocol_handler.h"
#include "net/url_request/file_protocol_handler.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_storage.h"
#include "vendor/brightray/browser/url_request_context_getter.h"
#include "webkit/glue/webpreferences.h"
namespace atom {
@ -15,6 +22,36 @@ AtomBrowserClient::AtomBrowserClient() {
AtomBrowserClient::~AtomBrowserClient() {
}
net::URLRequestContextGetter* AtomBrowserClient::CreateRequestContext(
content::BrowserContext* browser_context,
content::ProtocolHandlerMap* protocol_handlers) {
content::ProtocolHandlerMap preset_handlers;
std::swap(preset_handlers, *protocol_handlers);
// Create our implementaton of job factory.
AtomURLRequestJobFactory* job_factory = new AtomURLRequestJobFactory;
content::ProtocolHandlerMap::iterator it;
for (it = preset_handlers.begin(); it != preset_handlers.end(); ++it)
job_factory->SetProtocolHandler(it->first, it->second.release());
job_factory->SetProtocolHandler(chrome::kDataScheme,
new net::DataProtocolHandler);
job_factory->SetProtocolHandler(chrome::kFileScheme,
new net::FileProtocolHandler);
// Go through default procedure.
net::URLRequestContextGetter* request_context_getter =
brightray::BrowserClient::CreateRequestContext(browser_context,
protocol_handlers);
net::URLRequestContext* request_context =
request_context_getter->GetURLRequestContext();
// Replace default job factory.
storage_.reset(new net::URLRequestContextStorage(request_context));
storage_->set_job_factory(job_factory);
return request_context_getter;
}
void AtomBrowserClient::OverrideWebkitPrefs(
content::RenderViewHost* render_view_host,
const GURL& url,

View file

@ -7,6 +7,10 @@
#include "brightray/browser/browser_client.h"
namespace net {
class URLRequestContextStorage;
}
namespace atom {
class AtomBrowserClient : public brightray::BrowserClient {
@ -15,6 +19,9 @@ class AtomBrowserClient : public brightray::BrowserClient {
virtual ~AtomBrowserClient();
protected:
net::URLRequestContextGetter* CreateRequestContext(
content::BrowserContext* browser_context,
content::ProtocolHandlerMap* protocol_handlers) OVERRIDE;
virtual void OverrideWebkitPrefs(content::RenderViewHost* render_view_host,
const GURL& url,
WebPreferences* prefs) OVERRIDE;
@ -27,6 +34,8 @@ class AtomBrowserClient : public brightray::BrowserClient {
virtual brightray::BrowserMainParts* OverrideCreateBrowserMainParts(
const content::MainFunctionParams&) OVERRIDE;
scoped_ptr<net::URLRequestContextStorage> storage_;
DISALLOW_COPY_AND_ASSIGN(AtomBrowserClient);
};

View file

@ -0,0 +1,105 @@
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "browser/net/adapter_request_job.h"
#include "browser/net/url_request_string_job.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_request_error_job.h"
#include "net/url_request/url_request_file_job.h"
namespace atom {
AdapterRequestJob::AdapterRequestJob(ProtocolHandler* protocol_handler,
net::URLRequest* request,
net::NetworkDelegate* network_delegate)
: URLRequestJob(request, network_delegate),
protocol_handler_(protocol_handler),
weak_factory_(this) {
}
void AdapterRequestJob::Start() {
DCHECK(!real_job_);
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(&AdapterRequestJob::GetJobTypeInUI,
weak_factory_.GetWeakPtr()));
}
void AdapterRequestJob::Kill() {
DCHECK(real_job_);
real_job_->Kill();
}
bool AdapterRequestJob::ReadRawData(net::IOBuffer* buf,
int buf_size,
int *bytes_read) {
DCHECK(real_job_);
return real_job_->ReadRawData(buf, buf_size, bytes_read);
}
bool AdapterRequestJob::IsRedirectResponse(GURL* location,
int* http_status_code) {
DCHECK(real_job_);
return real_job_->IsRedirectResponse(location, http_status_code);
}
net::Filter* AdapterRequestJob::SetupFilter() const {
DCHECK(real_job_);
return real_job_->SetupFilter();
}
bool AdapterRequestJob::GetMimeType(std::string* mime_type) const {
DCHECK(real_job_);
return real_job_->GetMimeType(mime_type);
}
bool AdapterRequestJob::GetCharset(std::string* charset) {
DCHECK(real_job_);
return real_job_->GetCharset(charset);
}
base::WeakPtr<AdapterRequestJob> AdapterRequestJob::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void AdapterRequestJob::CreateErrorJobAndStart(int error_code) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
real_job_ = new net::URLRequestErrorJob(
request(), network_delegate(), error_code);
real_job_->Start();
}
void AdapterRequestJob::CreateStringJobAndStart(const std::string& mime_type,
const std::string& charset,
const std::string& data) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
real_job_ = new URLRequestStringJob(
request(), network_delegate(), mime_type, charset, data);
real_job_->Start();
}
void AdapterRequestJob::CreateFileJobAndStart(const base::FilePath& path) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
real_job_ = new net::URLRequestFileJob(request(), network_delegate(), path);
real_job_->Start();
}
void AdapterRequestJob::CreateJobFromProtocolHandlerAndStart() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
DCHECK(protocol_handler_);
real_job_ = protocol_handler_->MaybeCreateJob(request(),
network_delegate());
if (!real_job_.get())
CreateErrorJobAndStart(net::ERR_NOT_IMPLEMENTED);
else
real_job_->Start();
}
} // namespace atom

View file

@ -0,0 +1,68 @@
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NET_ADAPTER_REQUEST_JOB_H_
#define ATOM_BROWSER_NET_ADAPTER_REQUEST_JOB_H_
#include "base/memory/weak_ptr.h"
#include "net/url_request/url_request_job.h"
#include "net/url_request/url_request_job_factory.h"
namespace base {
class FilePath;
}
namespace atom {
// Ask JS which type of job it wants, and then delegate corresponding methods.
class AdapterRequestJob : public net::URLRequestJob {
public:
typedef net::URLRequestJobFactory::ProtocolHandler ProtocolHandler;
AdapterRequestJob(ProtocolHandler* protocol_handler,
net::URLRequest* request,
net::NetworkDelegate* network_delegate);
public:
// net::URLRequestJob:
virtual void Start() OVERRIDE;
virtual void Kill() OVERRIDE;
virtual bool ReadRawData(net::IOBuffer* buf,
int buf_size,
int *bytes_read) OVERRIDE;
virtual bool IsRedirectResponse(GURL* location,
int* http_status_code) OVERRIDE;
virtual net::Filter* SetupFilter() const OVERRIDE;
virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
virtual bool GetCharset(std::string* charset) OVERRIDE;
base::WeakPtr<AdapterRequestJob> GetWeakPtr();
ProtocolHandler* default_protocol_handler() { return protocol_handler_; }
// Override this function to determine which job should be started.
virtual void GetJobTypeInUI() = 0;
void CreateErrorJobAndStart(int error_code);
void CreateStringJobAndStart(const std::string& mime_type,
const std::string& charset,
const std::string& data);
void CreateFileJobAndStart(const base::FilePath& path);
void CreateJobFromProtocolHandlerAndStart();
private:
// The delegated request job.
scoped_refptr<net::URLRequestJob> real_job_;
// Default protocol handler.
ProtocolHandler* protocol_handler_;
base::WeakPtrFactory<AdapterRequestJob> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(AdapterRequestJob);
};
} // namespace atom
#endif // ATOM_BROWSER_NET_ADAPTER_REQUEST_JOB_H_

View file

@ -0,0 +1,105 @@
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
// Copyright (c) 2011 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 "browser/net/atom_url_request_job_factory.h"
#include "base/stl_util.h"
#include "googleurl/src/gurl.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_request.h"
namespace atom {
typedef net::URLRequestJobFactory::ProtocolHandler ProtocolHandler;
AtomURLRequestJobFactory::AtomURLRequestJobFactory() {}
AtomURLRequestJobFactory::~AtomURLRequestJobFactory() {
STLDeleteValues(&protocol_handler_map_);
}
bool AtomURLRequestJobFactory::SetProtocolHandler(
const std::string& scheme,
ProtocolHandler* protocol_handler) {
DCHECK(CalledOnValidThread());
base::AutoLock locked(lock_);
if (!protocol_handler) {
ProtocolHandlerMap::iterator it = protocol_handler_map_.find(scheme);
if (it == protocol_handler_map_.end())
return false;
delete it->second;
protocol_handler_map_.erase(it);
return true;
}
if (ContainsKey(protocol_handler_map_, scheme))
return false;
protocol_handler_map_[scheme] = protocol_handler;
return true;
}
ProtocolHandler* AtomURLRequestJobFactory::ReplaceProtocol(
const std::string& scheme,
ProtocolHandler* protocol_handler) {
DCHECK(CalledOnValidThread());
DCHECK(protocol_handler);
base::AutoLock locked(lock_);
if (!ContainsKey(protocol_handler_map_, scheme))
return NULL;
ProtocolHandler* original_protocol_handler = protocol_handler_map_[scheme];
protocol_handler_map_[scheme] = protocol_handler;
return original_protocol_handler;
}
ProtocolHandler* AtomURLRequestJobFactory::GetProtocolHandler(
const std::string& scheme) const {
DCHECK(CalledOnValidThread());
base::AutoLock locked(lock_);
ProtocolHandlerMap::const_iterator it = protocol_handler_map_.find(scheme);
if (it == protocol_handler_map_.end())
return NULL;
return it->second;
}
bool AtomURLRequestJobFactory::HasProtocolHandler(
const std::string& scheme) const {
base::AutoLock locked(lock_);
return ContainsKey(protocol_handler_map_, scheme);
}
net::URLRequestJob* AtomURLRequestJobFactory::MaybeCreateJobWithProtocolHandler(
const std::string& scheme,
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const {
DCHECK(CalledOnValidThread());
base::AutoLock locked(lock_);
ProtocolHandlerMap::const_iterator it = protocol_handler_map_.find(scheme);
if (it == protocol_handler_map_.end())
return NULL;
return it->second->MaybeCreateJob(request, network_delegate);
}
bool AtomURLRequestJobFactory::IsHandledProtocol(
const std::string& scheme) const {
DCHECK(CalledOnValidThread());
return HasProtocolHandler(scheme) ||
net::URLRequest::IsHandledProtocol(scheme);
}
bool AtomURLRequestJobFactory::IsHandledURL(const GURL& url) const {
if (!url.is_valid()) {
// We handle error cases.
return true;
}
return IsHandledProtocol(url.scheme());
}
} // namespace atom

View file

@ -0,0 +1,61 @@
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
// Copyright (c) 2011 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 ATOM_BROWSER_NET_ATOM_URL_REQUEST_URL_REQUEST_JOB_FACTORY_H_
#define ATOM_BROWSER_NET_ATOM_URL_REQUEST_URL_REQUEST_JOB_FACTORY_H_
#include <map>
#include <vector>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/synchronization/lock.h"
#include "net/url_request/url_request_job_factory.h"
namespace atom {
class AtomURLRequestJobFactory : public net::URLRequestJobFactory {
public:
AtomURLRequestJobFactory();
virtual ~AtomURLRequestJobFactory();
// Sets the ProtocolHandler for a scheme. Returns true on success, false on
// failure (a ProtocolHandler already exists for |scheme|). On success,
// URLRequestJobFactory takes ownership of |protocol_handler|.
bool SetProtocolHandler(const std::string& scheme,
ProtocolHandler* protocol_handler);
// Intercepts the ProtocolHandler for a scheme. Returns the original protocol
// handler on success, otherwise returns NULL.
ProtocolHandler* ReplaceProtocol(const std::string& scheme,
ProtocolHandler* protocol_handler);
// Returns the protocol handler registered with scheme.
ProtocolHandler* GetProtocolHandler(const std::string& scheme) const;
// Whether the protocol handler is registered by the job factory.
bool HasProtocolHandler(const std::string& scheme) const;
// URLRequestJobFactory implementation
virtual net::URLRequestJob* MaybeCreateJobWithProtocolHandler(
const std::string& scheme,
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const OVERRIDE;
virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE;
virtual bool IsHandledURL(const GURL& url) const OVERRIDE;
private:
typedef std::map<std::string, ProtocolHandler*> ProtocolHandlerMap;
ProtocolHandlerMap protocol_handler_map_;
mutable base::Lock lock_;
DISALLOW_COPY_AND_ASSIGN(AtomURLRequestJobFactory);
};
} // namespace atom
#endif // ATOM_BROWSER_NET_ATOM_URL_REQUEST_URL_REQUEST_JOB_FACTORY_H_

View file

@ -0,0 +1,33 @@
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "browser/net/url_request_string_job.h"
#include "net/base/net_errors.h"
namespace atom {
URLRequestStringJob::URLRequestStringJob(net::URLRequest* request,
net::NetworkDelegate* network_delegate,
const std::string& mime_type,
const std::string& charset,
const std::string& data)
: net::URLRequestSimpleJob(request, network_delegate),
mime_type_(mime_type),
charset_(charset),
data_(data) {
}
int URLRequestStringJob::GetData(
std::string* mime_type,
std::string* charset,
std::string* data,
const net::CompletionCallback& callback) const {
*mime_type = mime_type_;
*charset = charset_;
*data = data_;
return net::OK;
}
} // namespace atom

View file

@ -0,0 +1,36 @@
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NET_URL_REQUEST_STRING_JOB_H_
#define ATOM_BROWSER_NET_URL_REQUEST_STRING_JOB_H_
#include "net/url_request/url_request_simple_job.h"
namespace atom {
class URLRequestStringJob : public net::URLRequestSimpleJob {
public:
URLRequestStringJob(net::URLRequest* request,
net::NetworkDelegate* network_delegate,
const std::string& mime_type,
const std::string& charset,
const std::string& data);
// URLRequestSimpleJob:
virtual int GetData(std::string* mime_type,
std::string* charset,
std::string* data,
const net::CompletionCallback& callback) const OVERRIDE;
private:
std::string mime_type_;
std::string charset_;
std::string data_;
DISALLOW_COPY_AND_ASSIGN(URLRequestStringJob);
};
} // namespace atom
#endif // ATOM_BROWSER_NET_URL_REQUEST_STRING_JOB_H_

View file

@ -16,6 +16,7 @@ NODE_EXT_LIST_ITEM(atom_browser_dialog)
NODE_EXT_LIST_ITEM(atom_browser_ipc)
NODE_EXT_LIST_ITEM(atom_browser_menu)
NODE_EXT_LIST_ITEM(atom_browser_power_monitor)
NODE_EXT_LIST_ITEM(atom_browser_protocol)
NODE_EXT_LIST_ITEM(atom_browser_window)
// Module names start with `atom_renderer_` can only be used by renderer

View file

@ -31,8 +31,9 @@ Browser side modules:
* [menu](menu.md)
* [menu-item](menu-item.md)
* [power-monitor](power-monitor.md)
* [protocol](protocol.md)
Common modules:
* [clipboard](clipboard.md)
* [shell](shell.md)
* [shell](shell.md)

68
docs/protocol.md Normal file
View file

@ -0,0 +1,68 @@
## Synopsis
The `protocol` module can register a new protocol or intercept an existing
protocol, so you can custom the response to the requests for vairous protocols.
An example of implementing a protocol that has the same effect with the
`file://` protocol:
```javascript
var protocol = require('protocol');
protocol.registerProtocol('atom', function(request) {
var path = request.url.substr(7)
return new protocol.RequestFileJob(path);
});
```
## protocol.registerProtocol(scheme, handler)
* `scheme` String
* `handler` Function
Registers a custom protocol of `scheme`, the `handler` would be called with
`handler(request)` when the a request with registered `scheme` is made.
You need to return a request job in the `handler` to specify which type of
response you would like to send.
## protocol.unregisterProtocol(scheme)
* `scheme` String
Unregisters the custom protocol of `scheme`.
## protocol.isHandledProtocol(scheme)
* `scheme` String
Returns whether the `scheme` can be handled already.
## protocol.interceptProtocol(scheme, handler)
* `scheme` String
* `handler` Function
Intercepts an existing protocol with `scheme`, returning `null` or `undefined`
in `handler` would use the original protocol handler to handle the request.
## protocol.uninterceptProtocol(scheme)
* `scheme` String
Unintercepts a protocol.
## Class: protocol.RequestFileJob(path)
* `path` String
Create a request job which would query a file of `path` and set corresponding
mime types.
## Class: protocol.RequestStringJob(options)
* `options` Object
* `mimeType` String - Default is `text/plain`
* `charset` String - Default is `UTF-8`
* `data` String
Create a request job which sends a string as response.

View file

@ -13,9 +13,11 @@ wrapArgs = (args) ->
else if value? and typeof value is 'object' and v8Util.getHiddenValue value, 'atomId'
type: 'remote-object', id: v8Util.getHiddenValue value, 'atomId'
else if value? and typeof value is 'object'
ret = type: 'object', members: []
ret = type: 'object', name: value.constructor.name, members: []
ret.members.push(name: prop, value: valueToMeta(field)) for prop, field of value
ret
else if typeof value is 'function' and v8Util.getHiddenValue value, 'returnValue'
type: 'function-with-return-value', value: valueToMeta(value())
else if typeof value is 'function'
type: 'function', id: callbacksRegistry.add(value)
else
@ -29,8 +31,7 @@ metaToValue = (meta) ->
when 'value' then meta.value
when 'array' then (metaToValue(el) for el in meta.members)
when 'error'
console.log meta.stack
throw new Error(meta.message)
throw new Error("#{meta.message}\n#{meta.stack}")
else
if meta.type is 'function'
# A shadow class to represent the remote function object.
@ -56,10 +57,17 @@ metaToValue = (meta) ->
for member in meta.members
do (member) ->
if member.type is 'function'
ret[member.name] = ->
# Call member function.
ret = ipc.sendChannelSync 'ATOM_BROWSER_MEMBER_CALL', meta.id, member.name, wrapArgs(arguments)
metaToValue ret
ret[member.name] =
class RemoteMemberFunction
constructor: ->
if @constructor is RemoteMemberFunction
# Constructor call.
obj = ipc.sendChannelSync 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', meta.id, member.name, wrapArgs(arguments)
return metaToValue obj
else
# Call member function.
ret = ipc.sendChannelSync 'ATOM_BROWSER_MEMBER_CALL', meta.id, member.name, wrapArgs(arguments)
return metaToValue ret
else
ret.__defineSetter__ member.name, (value) ->
# Set member data.
@ -121,3 +129,9 @@ processCache = null
exports.__defineGetter__ 'process', ->
processCache = exports.getGlobal('process') unless processCache?
processCache
# Create a funtion that will return the specifed value when called in browser.
exports.createFunctionWithReturnValue = (returnValue) ->
func = -> returnValue
v8Util.setHiddenValue func, 'returnValue', true
func

View file

@ -16,7 +16,14 @@ describe 'ipc', ->
a = remote.require path.join(fixtures, 'module', 'id.js')
assert.equal a.id, 1127
describe 'remote object', ->
describe 'remote.createFunctionWithReturnValue', ->
it 'should be called in browser synchronously', ->
buf = new Buffer('test')
call = remote.require path.join(fixtures, 'module', 'call.js')
result = call.call remote.createFunctionWithReturnValue(buf)
assert.equal result.constructor.name, 'Buffer'
describe 'remote object in renderer', ->
it 'can change its properties', ->
property = remote.require path.join(fixtures, 'module', 'property.js')
assert.equal property.property, 1127
@ -28,6 +35,17 @@ describe 'ipc', ->
# Restore.
property.property = 1127
it 'can construct an object from its member', ->
call = remote.require path.join(fixtures, 'module', 'call.js')
obj = new call.constructor
assert.equal obj.test, 'test'
describe 'remote value in browser', ->
it 'keeps its constructor name for objects', ->
buf = new Buffer('test')
print_name = remote.require path.join(fixtures, 'module', 'print_name.js')
assert.equal print_name.print(buf), 'Buffer'
describe 'ipc.send', ->
it 'should work when sending an object containing id property', (done) ->
obj = id: 1, name: 'ly'

132
spec/api/protocol.coffee Normal file
View file

@ -0,0 +1,132 @@
assert = require 'assert'
ipc = require 'ipc'
remote = require 'remote'
protocol = remote.require 'protocol'
describe 'protocol API', ->
describe 'protocol.registerProtocol', ->
it 'throws error when scheme is already registered', (done) ->
register = -> protocol.registerProtocol('test1', ->)
protocol.once 'registered', (scheme) ->
assert.equal scheme, 'test1'
assert.throws register, /The scheme is already registered/
protocol.unregisterProtocol 'test1'
done()
register()
it 'calls the callback when scheme is visited', (done) ->
protocol.registerProtocol 'test2', (request) ->
assert.equal request.url, 'test2://test2'
assert.equal request.referrer, window.location.toString()
protocol.unregisterProtocol 'test2'
done()
$.get 'test2://test2', ->
describe 'protocol.unregisterProtocol', ->
it 'throws error when scheme does not exist', ->
unregister = -> protocol.unregisterProtocol 'test3'
assert.throws unregister, /The scheme has not been registered/
describe 'registered protocol callback', ->
it 'returns string should send the string as request content', (done) ->
handler = remote.createFunctionWithReturnValue 'valar morghulis'
protocol.registerProtocol 'atom-string', handler
$.ajax
url: 'atom-string://fake-host'
success: (data) ->
assert.equal data, handler()
protocol.unregisterProtocol 'atom-string'
done()
error: (xhr, errorType, error) ->
assert false, 'Got error: ' + errorType + ' ' + error
protocol.unregisterProtocol 'atom-string'
it 'returns RequestStringJob should send string', (done) ->
data = 'valar morghulis'
job = new protocol.RequestStringJob(mimeType: 'text/html', data: data)
handler = remote.createFunctionWithReturnValue job
protocol.registerProtocol 'atom-string-job', handler
$.ajax
url: 'atom-string-job://fake-host'
success: (response) ->
assert.equal response, data
protocol.unregisterProtocol 'atom-string-job'
done()
error: (xhr, errorType, error) ->
assert false, 'Got error: ' + errorType + ' ' + error
protocol.unregisterProtocol 'atom-string-job'
it 'returns RequestFileJob should send file', (done) ->
job = new protocol.RequestFileJob(__filename)
handler = remote.createFunctionWithReturnValue job
protocol.registerProtocol 'atom-file-job', handler
$.ajax
url: 'atom-file-job://' + __filename
success: (data) ->
content = require('fs').readFileSync __filename
assert.equal data, String(content)
protocol.unregisterProtocol 'atom-file-job'
done()
error: (xhr, errorType, error) ->
assert false, 'Got error: ' + errorType + ' ' + error
protocol.unregisterProtocol 'atom-file-job'
describe 'protocol.isHandledProtocol', ->
it 'returns true if the scheme can be handled', ->
assert.equal protocol.isHandledProtocol('file'), true
assert.equal protocol.isHandledProtocol('http'), true
assert.equal protocol.isHandledProtocol('https'), true
assert.equal protocol.isHandledProtocol('atom'), false
describe 'protocol.interceptProtocol', ->
it 'throws error when scheme is not a registered one', ->
register = -> protocol.interceptProtocol('test-intercept', ->)
assert.throws register, /Cannot intercept procotol/
it 'throws error when scheme is a custom protocol', (done) ->
protocol.once 'unregistered', (scheme) ->
assert.equal scheme, 'atom'
done()
protocol.once 'registered', (scheme) ->
assert.equal scheme, 'atom'
register = -> protocol.interceptProtocol('test-intercept', ->)
assert.throws register, /Cannot intercept procotol/
protocol.unregisterProtocol scheme
protocol.registerProtocol('atom', ->)
it 'returns original job when callback returns nothing', (done) ->
targetScheme = 'file'
protocol.once 'intercepted', (scheme) ->
assert.equal scheme, targetScheme
free = -> protocol.uninterceptProtocol targetScheme
$.ajax
url: "#{targetScheme}://#{__filename}",
success: ->
protocol.once 'unintercepted', (scheme) ->
assert.equal scheme, targetScheme
done()
free()
error: (xhr, errorType, error) ->
free()
assert false, 'Got error: ' + errorType + ' ' + error
protocol.interceptProtocol targetScheme, (request) ->
assert.equal request.url, "#{targetScheme}://#{__filename}"
it 'can override original protocol handler', (done) ->
handler = remote.createFunctionWithReturnValue 'valar morghulis'
protocol.once 'intercepted', ->
free = -> protocol.uninterceptProtocol 'file'
$.ajax
url: 'file://fake-host'
success: (data) ->
protocol.once 'unintercepted', ->
assert.equal data, handler()
done()
free()
error: (xhr, errorType, error) ->
assert false, 'Got error: ' + errorType + ' ' + error
free()
protocol.interceptProtocol 'file', handler

7
spec/fixtures/module/call.js vendored Normal file
View file

@ -0,0 +1,7 @@
exports.call = function(func) {
return func();
}
exports.constructor = function() {
this.test = 'test';
}

3
spec/fixtures/module/print_name.js vendored Normal file
View file

@ -0,0 +1,3 @@
exports.print = function(obj) {
return obj.constructor.name;
}

View file

@ -1,6 +1,8 @@
<html>
<head>
<meta name="referrer" content="always">
<link href="../node_modules/mocha/mocha.css" rel="stylesheet">
<script src="jquery-2.0.3.min.js"></script>
</head>
<body>
@ -24,21 +26,23 @@
}
require('coffee-script'); // Supports .coffee tests.
var ipc = require('ipc');
// Rediret all output to browser.
var ipc = require('ipc');
global.__defineGetter__('console', function() {
return {
log: function() {
args = Array.prototype.slice.call(arguments);
ipc.sendChannel('console.log', args);
},
error: function() {
args = Array.prototype.slice.call(arguments);
ipc.sendChannel('console.error', args);
},
}
});
if (isCi) {
global.__defineGetter__('console', function() {
return {
log: function() {
args = Array.prototype.slice.call(arguments);
ipc.sendChannel('console.log', args);
},
error: function() {
args = Array.prototype.slice.call(arguments);
ipc.sendChannel('console.error', args);
},
}
});
}
var Mocha = require('mocha');

6
spec/jquery-2.0.3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -22,6 +22,10 @@ ipc.on('process.exit', function(pid, rid, code) {
process.exit(code);
});
ipc.on('eval', function(ev, pid, rid, script) {
ev.result = eval(script);
});
process.on('uncaughtException', function() {
window.openDevTools();
});

2
vendor/brightray vendored

@ -1 +1 @@
Subproject commit c62f91a82e5426996d149dcb63259f967a7e78fb
Subproject commit 3cb2782cff77081df2788c68948163dea53530d5